From bb36015553ee3fe70da0c5fb21e1c322612c0c0b Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Fri, 25 Oct 2024 18:11:22 +0300 Subject: [PATCH 01/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/npm/login.go | 69 +++++++ artifactory/commands/npm/login_test.go | 89 +++++++++ artifactory/commands/utils/npmcmdutils.go | 150 +++++++++++++-- .../commands/utils/npmcmdutils_test.go | 176 +++++++++++++++++- artifactory/commands/yarn/login.go | 74 ++++++++ artifactory/commands/yarn/login_test.go | 93 +++++++++ artifactory/utils/npm/config-delete.go | 25 +++ artifactory/utils/npm/config-set.go | 25 +++ artifactory/utils/repositoryutils.go | 27 +++ artifactory/utils/yarn/configdelete.go | 21 +++ 10 files changed, 732 insertions(+), 17 deletions(-) create mode 100644 artifactory/commands/npm/login.go create mode 100644 artifactory/commands/npm/login_test.go create mode 100644 artifactory/commands/yarn/login.go create mode 100644 artifactory/commands/yarn/login_test.go create mode 100644 artifactory/utils/npm/config-delete.go create mode 100644 artifactory/utils/npm/config-set.go create mode 100644 artifactory/utils/yarn/configdelete.go diff --git a/artifactory/commands/npm/login.go b/artifactory/commands/npm/login.go new file mode 100644 index 000000000..5e4c101b7 --- /dev/null +++ b/artifactory/commands/npm/login.go @@ -0,0 +1,69 @@ +package npm + +import ( + "github.com/jfrog/gofrog/version" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" + cmdutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + "github.com/jfrog/jfrog-cli-core/v2/common/project" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/artifactory/services" + "github.com/jfrog/jfrog-client-go/utils/log" +) + +type NpmLoginCommand struct { + commandName string + npmVersion *version.Version + repoUrl string + executablePath string + CommonArgs +} + +func NewNpmLoginCommand() *NpmLoginCommand { + return &NpmLoginCommand{commandName: "rt_npm_login"} +} + +// Run configures npm to use the specified or selected JFrog Artifactory repository +// for package management, setting up registry and authentication. +func (nlc *NpmLoginCommand) Run() (err error) { + // If no repository is specified, prompt the user to select an npm-compatible repository. + if nlc.repo == "" { + // Define filter parameters to select virtual repositories of npm package type. + repoFilterParams := services.RepositoriesFilterParams{ + RepoType: utils.Virtual.String(), + PackageType: repository.Npm, + } + + // Select repository interactively based on filter parameters and server details. + nlc.repo, err = utils.SelectRepositoryInteractively(nlc.serverDetails, repoFilterParams) + if err != nil { + return err + } + } + + // Initialize NpmrcYarnrcManager for npm to manage registry and authentication configurations. + npmrcManager := cmdutils.NewNpmrcYarnrcManager(project.Npm, nlc.repo, nlc.serverDetails) + + // Configure the registry URL for npm in the npm configuration. + if err = npmrcManager.ConfigureRegistry(); err != nil { + return err + } + + // Configure authentication settings, handling token or basic auth as needed. + if err = npmrcManager.ConfigureAuth(); err != nil { + return err + } + + // Output success message indicating successful npm configuration. + log.Output(coreutils.PrintTitle("Successfully configured npm client to work with your JFrog Artifactory repository: " + nlc.repo)) + return nil +} + +func (nlc *NpmLoginCommand) CommandName() string { + return nlc.commandName +} + +func (nlc *NpmLoginCommand) ServerDetails() (*config.ServerDetails, error) { + return nlc.serverDetails, nil +} diff --git a/artifactory/commands/npm/login_test.go b/artifactory/commands/npm/login_test.go new file mode 100644 index 000000000..d0a8cadf8 --- /dev/null +++ b/artifactory/commands/npm/login_test.go @@ -0,0 +1,89 @@ +package npm + +import ( + "fmt" + cmdutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" +) + +func TestNpmLoginCommand(t *testing.T) { + // Create a temporary directory to act as the environment's npmrc file location. + tempDir := t.TempDir() + npmrcFilePath := filepath.Join(tempDir, ".npmrc") + + // Set NPM_CONFIG_USERCONFIG to point to the temporary npmrc file path. + t.Setenv("NPM_CONFIG_USERCONFIG", npmrcFilePath) + + // Initialize a new NpmLoginCommand instance. + loginCmd := NewNpmLoginCommand() + loginCmd.SetServerDetails(&config.ServerDetails{ArtifactoryUrl: "https://acme.jfrog.io/artifactory"}) + loginCmd.repo = "npm-virtual" + + // Define test cases for different authentication types. + testCases := []struct { + name string + user string + password string + accessToken string + }{ + { + name: "Token Authentication", + accessToken: "test-token", + }, + { + name: "Basic Authentication", + user: "myUser", + password: "myPassword", + }, + { + name: "Anonymous Access", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + // Set up server details for the current test case's authentication type. + loginCmd.serverDetails.SetUser(testCase.user) + loginCmd.serverDetails.SetPassword(testCase.password) + loginCmd.serverDetails.SetAccessToken(testCase.accessToken) + + // Run the login command and ensure no errors occur. + assert.NoError(t, loginCmd.Run()) + + // Read the contents of the temporary npmrc file. + npmrcContentBytes, err := os.ReadFile(npmrcFilePath) + assert.NoError(t, err) + npmrcContent := string(npmrcContentBytes) + + // Validate that the registry URL was set correctly in .npmrc. + assert.Contains(t, npmrcContent, fmt.Sprintf("%s=%s", cmdutils.NpmConfigRegistryKey, "https://acme.jfrog.io/artifactory/api/npm/npm-virtual")) + + // Define expected keys for basic and token auth in the .npmrc. + basicAuthKey := fmt.Sprintf("//acme.jfrog.io/artifactory/api/npm/npm-virtual:%s=", cmdutils.NpmConfigAuthKey) + tokenAuthKey := fmt.Sprintf("//acme.jfrog.io/artifactory/api/npm/npm-virtual:%s=", cmdutils.NpmConfigAuthTokenKey) + + switch { + // Validate token-based authentication. + case testCase.accessToken != "": + assert.Contains(t, npmrcContent, tokenAuthKey+"test-token") + assert.NotContains(t, npmrcContent, basicAuthKey) + + // Validate basic authentication with encoded credentials. + case testCase.user != "" && testCase.password != "": + // Base64 encoding of "myUser:myPassword" + expectedBasicAuth := basicAuthKey + "\"bXlVc2VyOm15UGFzc3dvcmQ=\"" + assert.Contains(t, npmrcContent, expectedBasicAuth) + assert.NotContains(t, npmrcContent, tokenAuthKey) + + // Validate anonymous access, where neither auth method is configured. + default: + assert.NotContains(t, npmrcContent, basicAuthKey) + assert.NotContains(t, npmrcContent, tokenAuthKey) + } + }) + } +} diff --git a/artifactory/commands/utils/npmcmdutils.go b/artifactory/commands/utils/npmcmdutils.go index d7c5c760e..9f98d930e 100644 --- a/artifactory/commands/utils/npmcmdutils.go +++ b/artifactory/commands/utils/npmcmdutils.go @@ -1,26 +1,33 @@ package utils import ( + "encoding/base64" "fmt" - outFormat "github.com/jfrog/jfrog-cli-core/v2/common/format" - "net/http" - "strings" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/npm" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/yarn" "github.com/jfrog/jfrog-cli-core/v2/common/build" + outFormat "github.com/jfrog/jfrog-cli-core/v2/common/format" + "github.com/jfrog/jfrog-cli-core/v2/common/project" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/httpclient" clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" + "net/http" + "strings" ) const ( minSupportedArtifactoryVersionForNpmCmds = "5.5.2" - NpmConfigAuthKey = "_auth" - NpmConfigAuthTokenKey = "_authToken" - npmAuthRestApi = "api/npm/auth" + + NpmConfigAuthKey = "_auth" + // Supported only in npm version 9 and above. + NpmConfigAuthTokenKey = "_authToken" + NpmConfigRegistryKey = "registry" + npmAuthRestApi = "api/npm/auth" ) // Constructs npm auth config and registry, manually or by requesting the Artifactory /npm/auth endpoint. @@ -37,7 +44,7 @@ func GetArtifactoryNpmRepoDetails(repo string, authArtDetails auth.ServiceDetail return "", "", err } - registry = getNpmRepositoryUrl(repo, authArtDetails.GetUrl()) + registry = GetNpmRepositoryUrl(repo, authArtDetails.GetUrl()) return } @@ -59,7 +66,7 @@ func getNpmAuth(authArtDetails auth.ServiceDetails, isNpmAuthLegacyVersion bool) // Manually constructs the npm authToken config data. func constructNpmAuthToken(token string) string { - return fmt.Sprintf("%s = %s\nalways-auth = true", NpmConfigAuthTokenKey, token) + return fmt.Sprintf("%s = %s", NpmConfigAuthTokenKey, token) } func validateArtifactoryVersionForNpmCmds(artDetails auth.ServiceDetails) error { @@ -93,12 +100,8 @@ func getNpmAuthFromArtifactory(artDetails auth.ServiceDetails) (npmAuth string, return string(body), nil } -func getNpmRepositoryUrl(repo, url string) string { - if !strings.HasSuffix(url, "/") { - url += "/" - } - url += "api/npm/" + repo - return url +func GetNpmRepositoryUrl(repositoryName, artifactoryUrl string) string { + return strings.TrimSuffix(artifactoryUrl, "/") + "/api/npm/" + repositoryName } // Remove all the none npm CLI flags from args. @@ -125,3 +128,120 @@ func ExtractNpmOptionsFromArgs(args []string) (detailedSummary, xrayScan bool, s cleanArgs, buildConfig, err = build.ExtractBuildDetailsFromArgs(cleanArgs) return } + +// NpmrcYarnrcManager is responsible for configuring npm and Yarn registries +// and authentication settings based on the specified project type. +type NpmrcYarnrcManager struct { + // buildTool represents the project type, either NPM or Yarn. + buildTool project.ProjectType + // repoUrl holds the URL to the npm or Yarn repository. + repoUrl string + // serverDetails contains configuration details for the Artifactory server. + serverDetails *config.ServerDetails +} + +// NewNpmrcYarnrcManager initializes a new NpmrcYarnrcManager with the given project type, +// repository name, and Artifactory server details. +func NewNpmrcYarnrcManager(buildTool project.ProjectType, repoName string, serverDetails *config.ServerDetails) *NpmrcYarnrcManager { + repoUrl := GetNpmRepositoryUrl(repoName, serverDetails.ArtifactoryUrl) + return &NpmrcYarnrcManager{ + buildTool: buildTool, + repoUrl: repoUrl, + serverDetails: serverDetails, + } +} + +// ConfigureRegistry sets the registry URL in the npmrc or yarnrc file. +func (nm *NpmrcYarnrcManager) ConfigureRegistry() error { + return nm.configSet(NpmConfigRegistryKey, nm.repoUrl) +} + +// ConfigureAuth configures authentication in npmrc or yarnrc using token or basic auth, +// or clears authentication for anonymous access. +func (nm *NpmrcYarnrcManager) ConfigureAuth() error { + authArtDetails, err := nm.serverDetails.CreateArtAuthConfig() + if err != nil { + return err + } + + // Configure authentication based on available credentials. + switch { + case authArtDetails.GetAccessToken() != "": + return nm.handleNpmrcTokenAuth(authArtDetails.GetAccessToken()) + case authArtDetails.GetUser() != "" && authArtDetails.GetPassword() != "": + return nm.handleNpmrcBasicAuth(authArtDetails.GetUser(), authArtDetails.GetPassword()) + default: + return nm.handleNpmAnonymousAccess() + } +} + +// handleNpmrcTokenAuth sets the token in the npmrc or yarnrc file and clears basic auth if it exists. +func (nm *NpmrcYarnrcManager) handleNpmrcTokenAuth(token string) error { + authKey := nm.createAuthKey(NpmConfigAuthTokenKey) + if err := nm.configSet(authKey, token); err != nil { + return err + } + return nm.removeNpmrcBasicAuthIfExists() +} + +// handleNpmrcBasicAuth sets basic auth credentials and clears any token-based auth. +func (nm *NpmrcYarnrcManager) handleNpmrcBasicAuth(user, password string) error { + authKey := nm.createAuthKey(NpmConfigAuthKey) + authValue := basicAuthBase64Encode(user, password) + if err := nm.configSet(authKey, authValue); err != nil { + return err + } + return nm.removeNpmrcTokenAuthIfExists() +} + +// handleNpmAnonymousAccess removes any existing authentication settings for anonymous access. +func (nm *NpmrcYarnrcManager) handleNpmAnonymousAccess() error { + if err := nm.removeNpmrcBasicAuthIfExists(); err != nil { + return err + } + return nm.removeNpmrcTokenAuthIfExists() +} + +// removeNpmrcBasicAuthIfExists deletes basic auth credentials if present. +func (nm *NpmrcYarnrcManager) removeNpmrcBasicAuthIfExists() error { + return nm.configDelete(nm.createAuthKey(NpmConfigAuthKey)) +} + +// removeNpmrcTokenAuthIfExists deletes token auth credentials if present. +func (nm *NpmrcYarnrcManager) removeNpmrcTokenAuthIfExists() error { + return nm.configDelete(nm.createAuthKey(NpmConfigAuthTokenKey)) +} + +// configSet applies a configuration setting in npmrc or yarnrc, based on the build tool type. +func (nm *NpmrcYarnrcManager) configSet(key, value string) error { + switch nm.buildTool { + case project.Npm: + return npm.ConfigSet(key, value, nm.buildTool.String()) + case project.Yarn: + return yarn.ConfigSet(key, value, nm.buildTool.String(), false) + default: + return errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", nm.buildTool)) + } +} + +// configDelete removes a configuration setting from npmrc or yarnrc, based on the build tool type. +func (nm *NpmrcYarnrcManager) configDelete(key string) error { + switch nm.buildTool { + case project.Npm: + return npm.ConfigDelete(key, nm.buildTool.String()) + case project.Yarn: + return yarn.ConfigDelete(key, nm.buildTool.String()) + default: + return errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", nm.buildTool)) + } +} + +// createAuthKey generates the correct authentication key for npm or Yarn, based on the repo URL. +func (nm *NpmrcYarnrcManager) createAuthKey(keySuffix string) string { + return fmt.Sprintf("//%s:%s", strings.TrimPrefix(nm.repoUrl, "https://"), keySuffix) +} + +// basicAuthBase64Encode encodes user credentials in Base64 for basic authentication. +func basicAuthBase64Encode(user, password string) string { + return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, password))) +} diff --git a/artifactory/commands/utils/npmcmdutils_test.go b/artifactory/commands/utils/npmcmdutils_test.go index 30469b071..72115012f 100644 --- a/artifactory/commands/utils/npmcmdutils_test.go +++ b/artifactory/commands/utils/npmcmdutils_test.go @@ -1,13 +1,22 @@ package utils import ( + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/common/project" commonTests "github.com/jfrog/jfrog-cli-core/v2/common/tests" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" "github.com/jfrog/jfrog-client-go/auth" "github.com/stretchr/testify/assert" "net/http" + "os" + "path/filepath" + "strings" "testing" ) +var testArtifactoryUrl = "https://acme.jfrog.io/artifactory" + func TestGetRegistry(t *testing.T) { var getRegistryTest = []struct { repo string @@ -21,8 +30,8 @@ func TestGetRegistry(t *testing.T) { } for _, testCase := range getRegistryTest { - if getNpmRepositoryUrl(testCase.repo, testCase.url) != testCase.expected { - t.Errorf("The expected output of getRegistry(\"%s\", \"%s\") is %s. But the actual result is:%s", testCase.repo, testCase.url, testCase.expected, getNpmRepositoryUrl(testCase.repo, testCase.url)) + if GetNpmRepositoryUrl(testCase.repo, testCase.url) != testCase.expected { + t.Errorf("The expected output of getRegistry(\"%s\", \"%s\") is %s. But the actual result is:%s", testCase.repo, testCase.url, testCase.expected, GetNpmRepositoryUrl(testCase.repo, testCase.url)) } } } @@ -71,3 +80,166 @@ func TestGetNpmAuth(t *testing.T) { }) } } + +// Helper function to set up the NpmrcYarnrcManager. +func setupNpmrcManager(buildTool project.ProjectType) *NpmrcYarnrcManager { + serverDetails := &config.ServerDetails{ + ArtifactoryUrl: testArtifactoryUrl, + } + return NewNpmrcYarnrcManager(buildTool, "my-repo-virtual", serverDetails) +} + +// Helper function to create a temporary .npmrc file for isolated tests. +func createTempNpmrc(t *testing.T) string { + // Create a temporary directory for npmrc + tempDir := t.TempDir() + + // Set the NPM_CONFIG_USERCONFIG environment variable + tempNpmrcPath := filepath.Join(tempDir, ".npmrc") + t.Setenv("NPM_CONFIG_USERCONFIG", tempNpmrcPath) + + return tempNpmrcPath +} + +// Helper function to create a temporary .npmrc file for isolated tests. +func createTempYarnrc(t *testing.T) (string, func() error) { + homeDir, err := os.UserHomeDir() + assert.NoError(t, err) + yarnrcPath := filepath.Join(homeDir, ".yarnrc") + restoreYarnrcFunc, err := ioutils.BackupFile(yarnrcPath, ".yarnrc.backup") + assert.NoError(t, err) + return yarnrcPath, restoreYarnrcFunc +} + +// Test for configuring registry in npm. +func TestConfigureRegistry_Npm(t *testing.T) { + tempNpmrcPath := createTempNpmrc(t) + + nm := setupNpmrcManager(project.Npm) + + err := nm.ConfigureRegistry() + assert.NoError(t, err) + + // Verify that the correct .npmrc entry was added + fileContent, err := os.ReadFile(tempNpmrcPath) + assert.NoError(t, err) + assert.Contains(t, string(fileContent), "registry="+testArtifactoryUrl) +} + +// Test for configuring registry in yarn. +func TestConfigureRegistry_Yarn(t *testing.T) { + yarnrcPath, restoreYarnrcFunc := createTempYarnrc(t) + defer func() { + assert.NoError(t, restoreYarnrcFunc()) + }() + nm := setupNpmrcManager(project.Yarn) + + err := nm.ConfigureRegistry() + assert.NoError(t, err) + + // Verify that the correct .yarnrc entry was added + fileContent, err := os.ReadFile(yarnrcPath) + assert.NoError(t, err) + expectedRegistryLine := fmt.Sprintf("registry \"%sapi/npm/my-repo-virtual\"", testArtifactoryUrl) + assert.Contains(t, string(fileContent), expectedRegistryLine) +} + +// Test for setting token auth in npm. +func TestConfigureAuth_Token_Npm(t *testing.T) { + tempNpmrcPath := createTempNpmrc(t) + + nm := setupNpmrcManager(project.Npm) + + err := nm.handleNpmrcTokenAuth("my-access-token") + assert.NoError(t, err) + + // Verify that the correct auth token entry was added to .npmrc + fileContent, err := os.ReadFile(tempNpmrcPath) + assert.NoError(t, err) + expectedAuthLine := fmt.Sprintf("//%s/api/npm/my-repo-virtual:_authToken=my-access-token", strings.TrimPrefix(testArtifactoryUrl, "https://")) + assert.Contains(t, string(fileContent), expectedAuthLine) +} + +// Test for setting token auth in yarn. +func TestConfigureAuth_Token_Yarn(t *testing.T) { + yarnrcPath, restoreYarnrcFunc := createTempYarnrc(t) + defer func() { + assert.NoError(t, restoreYarnrcFunc()) + }() + + nm := setupNpmrcManager(project.Yarn) + + err := nm.handleNpmrcTokenAuth("my-access-token") + assert.NoError(t, err) + + // Verify that the correct auth token entry was added to .yarnrc + fileContent, err := os.ReadFile(yarnrcPath) + assert.NoError(t, err) + expectedAuthLine := fmt.Sprintf("\"//%s/api/npm/my-repo-virtual:_authToken\" my-access-token", strings.TrimPrefix(testArtifactoryUrl, "https://")) + assert.Contains(t, string(fileContent), expectedAuthLine) +} + +func TestHandleNpmrcBasicAuth(t *testing.T) { + // Set up a temporary .npmrc configuration. + tempDir := t.TempDir() + t.Setenv("NPM_CONFIG_USERCONFIG", filepath.Join(tempDir, ".npmrc")) + + // Set up the NpmrcYarnrcManager for npm build tool. + nm := setupNpmrcManager(project.Npm) + + // Actual username and password for testing. + username := "myUser" + password := "myPassword" + + // Expected base64 encoded value. (Base64 encoded "myUser:myPassword") + expectedAuthValue := "bXlVc2VyOm15UGFzc3dvcmQ=" + + // Run the method to handle Basic Auth. + err := nm.handleNpmrcBasicAuth(username, password) + assert.NoError(t, err) + + // Read the resulting .npmrc file and verify the auth value. + npmrcContent, err := os.ReadFile(filepath.Join(tempDir, ".npmrc")) + assert.NoError(t, err) + + // Verify that the auth key and value are correctly set. + expectedAuthLine := fmt.Sprintf("//%s/api/npm/my-repo-virtual:_auth=\"%s\"", strings.TrimPrefix(testArtifactoryUrl, "https://"), expectedAuthValue) + assert.Contains(t, string(npmrcContent), expectedAuthLine) +} + +// Test for handling anonymous access in npm. +func TestHandleAnonymousAccess_Npm(t *testing.T) { + tempNpmrcPath := createTempNpmrc(t) + + nm := setupNpmrcManager(project.Npm) + // Set basic auth credentials to be removed on the by the anonymous access method. + assert.NoError(t, nm.handleNpmrcBasicAuth("user", "password")) + + err := nm.handleNpmAnonymousAccess() + assert.NoError(t, err) + + // Verify that the auth entries were removed from .npmrc + fileContent, err := os.ReadFile(tempNpmrcPath) + assert.NoError(t, err) + assert.NotContains(t, string(fileContent), "_auth") + assert.NotContains(t, string(fileContent), "_authToken") +} + +// Test for handling anonymous access in yarn. +func TestHandleAnonymousAccess_Yarn(t *testing.T) { + yarnrcPath, restoreYarnrcFunc := createTempYarnrc(t) + defer func() { + assert.NoError(t, restoreYarnrcFunc()) + }() + + nm := setupNpmrcManager(project.Yarn) + + err := nm.handleNpmAnonymousAccess() + assert.NoError(t, err) + + // Verify that the auth entries were removed from .yarnrc + fileContent, err := os.ReadFile(yarnrcPath) + assert.NoError(t, err) + assert.NotContains(t, string(fileContent), "_auth") + assert.NotContains(t, string(fileContent), "_authToken") +} diff --git a/artifactory/commands/yarn/login.go b/artifactory/commands/yarn/login.go new file mode 100644 index 000000000..5788d1016 --- /dev/null +++ b/artifactory/commands/yarn/login.go @@ -0,0 +1,74 @@ +package yarn + +import ( + "github.com/jfrog/gofrog/version" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" + cmdutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + "github.com/jfrog/jfrog-cli-core/v2/common/project" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/artifactory/services" + "github.com/jfrog/jfrog-client-go/utils/log" +) + +type YarnLoginCommand struct { + commandName string + npmVersion *version.Version + executablePath string + repo string + serverDetails *config.ServerDetails +} + +func NewYarnLoginCommand() *YarnLoginCommand { + return &YarnLoginCommand{commandName: "rt_yarn_login"} +} + +// Run configures Yarn to use the specified or selected JFrog Artifactory repository +// for package management, setting up registry and authentication. +func (ylc *YarnLoginCommand) Run() (err error) { + // If no repository is specified, prompt the user to select an npm-compatible repository. + if ylc.repo == "" { + // Define filter parameters to select virtual repositories of npm package type. + repoFilterParams := services.RepositoriesFilterParams{ + RepoType: utils.Virtual.String(), + PackageType: repository.Npm, + } + + // Select repository interactively based on filter parameters and server details. + ylc.repo, err = utils.SelectRepositoryInteractively(ylc.serverDetails, repoFilterParams) + if err != nil { + return err + } + } + + // Initialize NpmrcYarnrcManager for Yarn to manage registry and authentication configurations. + npmrcManager := cmdutils.NewNpmrcYarnrcManager(project.Yarn, ylc.repo, ylc.serverDetails) + + // Configure the registry URL for Yarn in the Yarn configuration. + if err = npmrcManager.ConfigureRegistry(); err != nil { + return err + } + + // Configure authentication settings, handling token or basic auth as needed. + if err = npmrcManager.ConfigureAuth(); err != nil { + return err + } + + // Output success message indicating successful Yarn configuration. + log.Output(coreutils.PrintTitle("Successfully configured yarn client to work with your JFrog Artifactory repository: " + ylc.repo)) + return nil +} + +func (ylc *YarnLoginCommand) SetServerDetails(serverDetails *config.ServerDetails) *YarnLoginCommand { + ylc.serverDetails = serverDetails + return ylc +} + +func (ylc *YarnLoginCommand) ServerDetails() (*config.ServerDetails, error) { + return ylc.serverDetails, nil +} + +func (ylc *YarnLoginCommand) CommandName() string { + return ylc.commandName +} diff --git a/artifactory/commands/yarn/login_test.go b/artifactory/commands/yarn/login_test.go new file mode 100644 index 000000000..5822f1a94 --- /dev/null +++ b/artifactory/commands/yarn/login_test.go @@ -0,0 +1,93 @@ +package yarn + +import ( + "fmt" + cmdutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" +) + +func TestYarnLoginCommand(t *testing.T) { + // Retrieve the home directory and construct the .yarnrc file path. + homeDir, err := os.UserHomeDir() + assert.NoError(t, err) + yarnrcPath := filepath.Join(homeDir, ".yarnrc") + + // Back up the existing .yarnrc file and ensure restoration after the test. + restoreYarnrcFunc, err := ioutils.BackupFile(yarnrcPath, ".yarnrc.backup") + assert.NoError(t, err) + defer func() { + assert.NoError(t, restoreYarnrcFunc()) + }() + + // Create a new YarnLoginCommand instance. + loginCmd := NewYarnLoginCommand() + loginCmd.SetServerDetails(&config.ServerDetails{ArtifactoryUrl: "https://acme.jfrog.io/artifactory"}) + loginCmd.repo = "npm-virtual" + + // Define test cases with different authentication methods. + testCases := []struct { + name string + user string + password string + accessToken string + }{ + { + name: "Token Authentication", + accessToken: "test-token", + }, + { + name: "Basic Authentication", + user: "myUser", + password: "myPassword", + }, + { + name: "Anonymous Access", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + // Configure the command's server details for each test case. + loginCmd.serverDetails.SetUser(testCase.user) + loginCmd.serverDetails.SetPassword(testCase.password) + loginCmd.serverDetails.SetAccessToken(testCase.accessToken) + + // Run the login command and check for errors. + assert.NoError(t, loginCmd.Run()) + + // Read the content of .yarnrc to verify configuration. + yarnrcContentBytes, err := os.ReadFile(yarnrcPath) + assert.NoError(t, err) + yarnrcContent := string(yarnrcContentBytes) + + // Check that the registry URL is correctly set in .yarnrc. + assert.Contains(t, yarnrcContent, fmt.Sprintf("%s \"%s\"", cmdutils.NpmConfigRegistryKey, "https://acme.jfrog.io/artifactory/api/npm/npm-virtual")) + + // Define expected keys for basic auth and token-based auth in .yarnrc. + basicAuthKey := fmt.Sprintf("\"//acme.jfrog.io/artifactory/api/npm/npm-virtual:%s\"", cmdutils.NpmConfigAuthKey) + tokenAuthKey := fmt.Sprintf("\"//acme.jfrog.io/artifactory/api/npm/npm-virtual:%s\"", cmdutils.NpmConfigAuthTokenKey) + + // Verify the .yarnrc file contents according to the authentication method. + switch { + case testCase.accessToken != "": + assert.Contains(t, yarnrcContent, tokenAuthKey+" test-token") + assert.NotContains(t, yarnrcContent, basicAuthKey) + + case testCase.user != "" && testCase.password != "": + // Base64 encoding of "myUser:myPassword" + expectedBasicAuth := basicAuthKey + " bXlVc2VyOm15UGFzc3dvcmQ=" + assert.Contains(t, yarnrcContent, expectedBasicAuth) + assert.NotContains(t, yarnrcContent, tokenAuthKey) + + default: + assert.NotContains(t, yarnrcContent, basicAuthKey) + assert.NotContains(t, yarnrcContent, tokenAuthKey) + } + }) + } +} diff --git a/artifactory/utils/npm/config-delete.go b/artifactory/utils/npm/config-delete.go new file mode 100644 index 000000000..9c4c1f289 --- /dev/null +++ b/artifactory/utils/npm/config-delete.go @@ -0,0 +1,25 @@ +package npm + +import ( + gofrogcmd "github.com/jfrog/gofrog/io" + npmutils "github.com/jfrog/jfrog-cli-core/v2/utils/npm" + "github.com/jfrog/jfrog-client-go/utils/errorutils" +) + +func ConfigDelete(key, executablePath string) error { + configGetCmdConfig := createConfigDeleteCmdConfig(executablePath, key) + _, err := gofrogcmd.RunCmdOutput(configGetCmdConfig) + if err != nil { + return errorutils.CheckError(err) + } + return nil +} + +func createConfigDeleteCmdConfig(executablePath, key string) *npmutils.NpmConfig { + return &npmutils.NpmConfig{ + Npm: executablePath, + Command: []string{"config", "delete", key}, + StrWriter: nil, + ErrWriter: nil, + } +} diff --git a/artifactory/utils/npm/config-set.go b/artifactory/utils/npm/config-set.go new file mode 100644 index 000000000..1df8431d8 --- /dev/null +++ b/artifactory/utils/npm/config-set.go @@ -0,0 +1,25 @@ +package npm + +import ( + gofrogcmd "github.com/jfrog/gofrog/io" + npmutils "github.com/jfrog/jfrog-cli-core/v2/utils/npm" + "github.com/jfrog/jfrog-client-go/utils/errorutils" +) + +func ConfigSet(key, value, executablePath string) error { + configGetCmdConfig := createConfigSetCmdConfig(executablePath, key, value) + _, err := gofrogcmd.RunCmdOutput(configGetCmdConfig) + if err != nil { + return errorutils.CheckError(err) + } + return nil +} + +func createConfigSetCmdConfig(executablePath, key, value string) *npmutils.NpmConfig { + return &npmutils.NpmConfig{ + Npm: executablePath, + Command: []string{"config", "set", key, value}, + StrWriter: nil, + ErrWriter: nil, + } +} diff --git a/artifactory/utils/repositoryutils.go b/artifactory/utils/repositoryutils.go index 40acbb86d..40e11b30f 100644 --- a/artifactory/utils/repositoryutils.go +++ b/artifactory/utils/repositoryutils.go @@ -2,6 +2,8 @@ package utils import ( "github.com/jfrog/gofrog/datastructures" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" "golang.org/x/exp/slices" "path" "strings" @@ -77,6 +79,31 @@ func IsRemoteRepo(repoName string, serviceManager artifactory.ArtifactoryService return repoDetails.GetRepoType() == "remote", nil } +// SelectRepositoryInteractively prompts the user to select a repository from a list of repositories that match the given filter parameters. +func SelectRepositoryInteractively(serverDetails *config.ServerDetails, repoFilterParams services.RepositoriesFilterParams) (string, error) { + sm, err := CreateServiceManager(serverDetails, 3, 0, false) + if err != nil { + return "", err + } + + filteredRepos, err := GetFilteredRepositoriesWithFilterParams(sm, nil, nil, + repoFilterParams) + if err != nil { + return "", err + } + + if len(filteredRepos) == 0 { + return "", errorutils.CheckErrorf("no repositories were found that match the following criteria: %v", repoFilterParams) + } + + if len(filteredRepos) == 1 { + // Automatically select the repository if only one exists. + return filteredRepos[0], nil + } + // Prompt the user to select a repository. + return ioutils.AskFromListWithMismatchConfirmation("Please select a repository to login to:", "Repository not found.", ioutils.ConvertToSuggests(filteredRepos)), nil +} + // GetFilteredRepositoriesWithFilterParams returns the names of local, remote, virtual, and federated repositories filtered by their names and type. // servicesManager - The Artifactory services manager used to interact with the Artifactory server. // includePatterns - Patterns of repository names (can contain wildcards) to include in the results. A repository's name diff --git a/artifactory/utils/yarn/configdelete.go b/artifactory/utils/yarn/configdelete.go new file mode 100644 index 000000000..63058c6c7 --- /dev/null +++ b/artifactory/utils/yarn/configdelete.go @@ -0,0 +1,21 @@ +package yarn + +import ( + gofrogcmd "github.com/jfrog/gofrog/io" + "github.com/jfrog/jfrog-client-go/utils/errorutils" +) + +func ConfigDelete(key, executablePath string) error { + configGetCmdConfig := createConfigDeleteCmdConfig(executablePath, key) + _, err := gofrogcmd.RunCmdOutput(configGetCmdConfig) + return errorutils.CheckError(err) +} + +func createConfigDeleteCmdConfig(executablePath, key string) *YarnConfig { + return &YarnConfig{ + Executable: executablePath, + Command: []string{"config", "delete", key}, + StrWriter: nil, + ErrWriter: nil, + } +} From 6583684c54a6947b9971741e512022d5c7d1f290 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Fri, 25 Oct 2024 18:33:09 +0300 Subject: [PATCH 02/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/npm/login.go | 6 +----- artifactory/commands/utils/npmcmdutils_test.go | 11 ++++------- artifactory/commands/yarn/login.go | 9 +++------ 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/artifactory/commands/npm/login.go b/artifactory/commands/npm/login.go index 5e4c101b7..c44befb64 100644 --- a/artifactory/commands/npm/login.go +++ b/artifactory/commands/npm/login.go @@ -1,7 +1,6 @@ package npm import ( - "github.com/jfrog/gofrog/version" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" cmdutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" @@ -13,10 +12,7 @@ import ( ) type NpmLoginCommand struct { - commandName string - npmVersion *version.Version - repoUrl string - executablePath string + commandName string CommonArgs } diff --git a/artifactory/commands/utils/npmcmdutils_test.go b/artifactory/commands/utils/npmcmdutils_test.go index 72115012f..2dc46efb9 100644 --- a/artifactory/commands/utils/npmcmdutils_test.go +++ b/artifactory/commands/utils/npmcmdutils_test.go @@ -140,7 +140,7 @@ func TestConfigureRegistry_Yarn(t *testing.T) { // Verify that the correct .yarnrc entry was added fileContent, err := os.ReadFile(yarnrcPath) assert.NoError(t, err) - expectedRegistryLine := fmt.Sprintf("registry \"%sapi/npm/my-repo-virtual\"", testArtifactoryUrl) + expectedRegistryLine := fmt.Sprintf("registry \"%s/api/npm/my-repo-virtual\"", testArtifactoryUrl) assert.Contains(t, string(fileContent), expectedRegistryLine) } @@ -214,15 +214,12 @@ func TestHandleAnonymousAccess_Npm(t *testing.T) { nm := setupNpmrcManager(project.Npm) // Set basic auth credentials to be removed on the by the anonymous access method. assert.NoError(t, nm.handleNpmrcBasicAuth("user", "password")) + assert.FileExists(t, tempNpmrcPath) err := nm.handleNpmAnonymousAccess() assert.NoError(t, err) - - // Verify that the auth entries were removed from .npmrc - fileContent, err := os.ReadFile(tempNpmrcPath) - assert.NoError(t, err) - assert.NotContains(t, string(fileContent), "_auth") - assert.NotContains(t, string(fileContent), "_authToken") + // Verify that .npmrc was deleted because it should be empty. + assert.NoFileExists(t, tempNpmrcPath) } // Test for handling anonymous access in yarn. diff --git a/artifactory/commands/yarn/login.go b/artifactory/commands/yarn/login.go index 5788d1016..37f57bec0 100644 --- a/artifactory/commands/yarn/login.go +++ b/artifactory/commands/yarn/login.go @@ -1,7 +1,6 @@ package yarn import ( - "github.com/jfrog/gofrog/version" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" cmdutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" @@ -13,11 +12,9 @@ import ( ) type YarnLoginCommand struct { - commandName string - npmVersion *version.Version - executablePath string - repo string - serverDetails *config.ServerDetails + commandName string + repo string + serverDetails *config.ServerDetails } func NewYarnLoginCommand() *YarnLoginCommand { From fb20e2030c6f5c4b305ed18973d2bc9a280f208c Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Sun, 27 Oct 2024 16:57:57 +0200 Subject: [PATCH 03/41] Improve repositores code Signed-off-by: Michael Sverdlov --- common/cliutils/utils.go | 1 + 1 file changed, 1 insertion(+) diff --git a/common/cliutils/utils.go b/common/cliutils/utils.go index ce4d0d3a1..6bc1423c3 100644 --- a/common/cliutils/utils.go +++ b/common/cliutils/utils.go @@ -217,6 +217,7 @@ func CreateServerDetailsWithConfigOffer(createServerDetails func() (*config.Serv if err != nil { return nil, err } + log.Debug(fmt.Sprintf("Using <%s> server-id configuration", confDetails.ServerId)) // Take insecureTls value from options since it is not saved in config. confDetails.InsecureTls = details.InsecureTls From ed7b39d724462eb8e7e3ec729a5130cdf78bf6b0 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 30 Oct 2024 15:31:58 +0200 Subject: [PATCH 04/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/python/login.go | 64 ++++++++ artifactory/commands/utils/buildtoolconfig.go | 142 ++++++++++++++++++ artifactory/commands/utils/npmcmdutils.go | 122 --------------- 3 files changed, 206 insertions(+), 122 deletions(-) create mode 100644 artifactory/commands/python/login.go create mode 100644 artifactory/commands/utils/buildtoolconfig.go diff --git a/artifactory/commands/python/login.go b/artifactory/commands/python/login.go new file mode 100644 index 000000000..2f7487364 --- /dev/null +++ b/artifactory/commands/python/login.go @@ -0,0 +1,64 @@ +package python + +import ( + "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" + cmdutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + "github.com/jfrog/jfrog-cli-core/v2/common/project" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/artifactory/services" + "github.com/jfrog/jfrog-client-go/utils/log" +) + +type PythonLoginCommand struct { + ר +} + +func NewPythonLoginCommand() *PythonLoginCommand { + return &PythonLoginCommand{commandName: "rt_python_login"} +} + +// Run configures python to use the specified or selected JFrog Artifactory repository +// for package management, setting up registry and authentication. +func (plc *PythonLoginCommand) Run() (err error) { + // If no repository is specified, prompt the user to select a pypi-compatible repository. + if plc.repo == "" { + // Define filter parameters to select virtual repositories of npm package type. + repoFilterParams := services.RepositoriesFilterParams{ + RepoType: utils.Virtual.String(), + PackageType: repository.Npm, + } + + // Select repository interactively based on filter parameters and server details. + plc.repo, err = utils.SelectRepositoryInteractively(plc.serverDetails, repoFilterParams) + if err != nil { + return err + } + } + + // Initialize NpmrcYarnrcManager for npm to manage registry and authentication configurations. + npmrcManager := cmdutils.NewNpmrcYarnrcManager(project.Npm, plc.repo, plc.serverDetails) + + // Configure the registry URL for npm in the npm configuration. + if err = npmrcManager.ConfigureRegistry(); err != nil { + return err + } + + // Configure authentication settings, handling token or basic auth as needed. + if err = npmrcManager.ConfigureAuth(); err != nil { + return err + } + + // Output success message indicating successful npm configuration. + log.Output(coreutils.PrintTitle("Successfully configured npm client to work with your JFrog Artifactory repository: " + plc.repo)) + return nil +} + +func (plc *PythonLoginCommand) CommandName() string { + return plc.commandName +} + +func (plc *PythonLoginCommand) ServerDetails() (*config.ServerDetails, error) { + return plc.serverDetails, nil +} diff --git a/artifactory/commands/utils/buildtoolconfig.go b/artifactory/commands/utils/buildtoolconfig.go new file mode 100644 index 000000000..8c7d626fb --- /dev/null +++ b/artifactory/commands/utils/buildtoolconfig.go @@ -0,0 +1,142 @@ +package utils + +import ( + "encoding/base64" + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/npm" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/yarn" + "github.com/jfrog/jfrog-cli-core/v2/common/project" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "strings" +) + +// NpmrcYarnrcManager is responsible for configuring npm and Yarn registries +// and authentication settings based on the specified project type. +type NpmrcYarnrcManager struct { + // buildTool represents the project type, either NPM or Yarn. + buildTool project.ProjectType + // repoUrl holds the URL to the npm or Yarn repository. + repoUrl string + // serverDetails contains configuration details for the Artifactory server. + serverDetails *config.ServerDetails +} + +// NewNpmrcYarnrcManager initializes a new NpmrcYarnrcManager with the given project type, +// repository name, and Artifactory server details. +func NewNpmrcYarnrcManager(buildTool project.ProjectType, repoName string, serverDetails *config.ServerDetails) *NpmrcYarnrcManager { + repoUrl := GetNpmRepositoryUrl(repoName, serverDetails.ArtifactoryUrl) + return &NpmrcYarnrcManager{ + buildTool: buildTool, + repoUrl: repoUrl, + serverDetails: serverDetails, + } +} + +// ConfigureRegistry sets the registry URL in the npmrc or yarnrc file. +func (nm *NpmrcYarnrcManager) Run() error { + switch nm.buildTool { + case project.Npm, project.Yarn: + if err := nm.ConfigureRegistry(); err != nil { + return err + } + return nm.ConfigureAuth() + default: + return errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", nm.buildTool)) + } +} + +// ConfigureRegistry sets the registry URL in the npmrc or yarnrc file. +func (nm *NpmrcYarnrcManager) ConfigureRegistry() error { + return nm.configSet(NpmConfigRegistryKey, nm.repoUrl) +} + +// ConfigureAuth configures authentication in npmrc or yarnrc using token or basic auth, +// or clears authentication for anonymous access. +func (nm *NpmrcYarnrcManager) ConfigureAuth() error { + authArtDetails, err := nm.serverDetails.CreateArtAuthConfig() + if err != nil { + return err + } + + // Configure authentication based on available credentials. + switch { + case authArtDetails.GetAccessToken() != "": + return nm.handleNpmrcTokenAuth(authArtDetails.GetAccessToken()) + case authArtDetails.GetUser() != "" && authArtDetails.GetPassword() != "": + return nm.handleNpmrcBasicAuth(authArtDetails.GetUser(), authArtDetails.GetPassword()) + default: + return nm.handleNpmAnonymousAccess() + } +} + +// handleNpmrcTokenAuth sets the token in the npmrc or yarnrc file and clears basic auth if it exists. +func (nm *NpmrcYarnrcManager) handleNpmrcTokenAuth(token string) error { + authKey := nm.createAuthKey(NpmConfigAuthTokenKey) + if err := nm.configSet(authKey, token); err != nil { + return err + } + return nm.removeNpmrcBasicAuthIfExists() +} + +// handleNpmrcBasicAuth sets basic auth credentials and clears any token-based auth. +func (nm *NpmrcYarnrcManager) handleNpmrcBasicAuth(user, password string) error { + authKey := nm.createAuthKey(NpmConfigAuthKey) + authValue := basicAuthBase64Encode(user, password) + if err := nm.configSet(authKey, authValue); err != nil { + return err + } + return nm.removeNpmrcTokenAuthIfExists() +} + +// handleNpmAnonymousAccess removes any existing authentication settings for anonymous access. +func (nm *NpmrcYarnrcManager) handleNpmAnonymousAccess() error { + if err := nm.removeNpmrcBasicAuthIfExists(); err != nil { + return err + } + return nm.removeNpmrcTokenAuthIfExists() +} + +// removeNpmrcBasicAuthIfExists deletes basic auth credentials if present. +func (nm *NpmrcYarnrcManager) removeNpmrcBasicAuthIfExists() error { + return nm.configDelete(nm.createAuthKey(NpmConfigAuthKey)) +} + +// removeNpmrcTokenAuthIfExists deletes token auth credentials if present. +func (nm *NpmrcYarnrcManager) removeNpmrcTokenAuthIfExists() error { + return nm.configDelete(nm.createAuthKey(NpmConfigAuthTokenKey)) +} + +// configSet applies a configuration setting in npmrc or yarnrc, based on the build tool type. +func (nm *NpmrcYarnrcManager) configSet(key, value string) error { + switch nm.buildTool { + case project.Npm: + return npm.ConfigSet(key, value, nm.buildTool.String()) + case project.Yarn: + return yarn.ConfigSet(key, value, nm.buildTool.String(), false) + default: + return errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", nm.buildTool)) + } +} + +// configDelete removes a configuration setting from npmrc or yarnrc, based on the build tool type. +func (nm *NpmrcYarnrcManager) configDelete(key string) error { + switch nm.buildTool { + case project.Npm: + return npm.ConfigDelete(key, nm.buildTool.String()) + case project.Yarn: + return yarn.ConfigDelete(key, nm.buildTool.String()) + default: + return errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", nm.buildTool)) + } +} + +// createAuthKey generates the correct authentication key for npm or Yarn, based on the repo URL. +func (nm *NpmrcYarnrcManager) createAuthKey(keySuffix string) string { + return fmt.Sprintf("//%s:%s", strings.TrimPrefix(nm.repoUrl, "https://"), keySuffix) +} + +// basicAuthBase64Encode encodes user credentials in Base64 for basic authentication. +func basicAuthBase64Encode(user, password string) string { + return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, password))) +} diff --git a/artifactory/commands/utils/npmcmdutils.go b/artifactory/commands/utils/npmcmdutils.go index 9f98d930e..d07fed025 100644 --- a/artifactory/commands/utils/npmcmdutils.go +++ b/artifactory/commands/utils/npmcmdutils.go @@ -1,15 +1,10 @@ package utils import ( - "encoding/base64" "fmt" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/npm" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/yarn" "github.com/jfrog/jfrog-cli-core/v2/common/build" outFormat "github.com/jfrog/jfrog-cli-core/v2/common/format" - "github.com/jfrog/jfrog-cli-core/v2/common/project" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/httpclient" @@ -128,120 +123,3 @@ func ExtractNpmOptionsFromArgs(args []string) (detailedSummary, xrayScan bool, s cleanArgs, buildConfig, err = build.ExtractBuildDetailsFromArgs(cleanArgs) return } - -// NpmrcYarnrcManager is responsible for configuring npm and Yarn registries -// and authentication settings based on the specified project type. -type NpmrcYarnrcManager struct { - // buildTool represents the project type, either NPM or Yarn. - buildTool project.ProjectType - // repoUrl holds the URL to the npm or Yarn repository. - repoUrl string - // serverDetails contains configuration details for the Artifactory server. - serverDetails *config.ServerDetails -} - -// NewNpmrcYarnrcManager initializes a new NpmrcYarnrcManager with the given project type, -// repository name, and Artifactory server details. -func NewNpmrcYarnrcManager(buildTool project.ProjectType, repoName string, serverDetails *config.ServerDetails) *NpmrcYarnrcManager { - repoUrl := GetNpmRepositoryUrl(repoName, serverDetails.ArtifactoryUrl) - return &NpmrcYarnrcManager{ - buildTool: buildTool, - repoUrl: repoUrl, - serverDetails: serverDetails, - } -} - -// ConfigureRegistry sets the registry URL in the npmrc or yarnrc file. -func (nm *NpmrcYarnrcManager) ConfigureRegistry() error { - return nm.configSet(NpmConfigRegistryKey, nm.repoUrl) -} - -// ConfigureAuth configures authentication in npmrc or yarnrc using token or basic auth, -// or clears authentication for anonymous access. -func (nm *NpmrcYarnrcManager) ConfigureAuth() error { - authArtDetails, err := nm.serverDetails.CreateArtAuthConfig() - if err != nil { - return err - } - - // Configure authentication based on available credentials. - switch { - case authArtDetails.GetAccessToken() != "": - return nm.handleNpmrcTokenAuth(authArtDetails.GetAccessToken()) - case authArtDetails.GetUser() != "" && authArtDetails.GetPassword() != "": - return nm.handleNpmrcBasicAuth(authArtDetails.GetUser(), authArtDetails.GetPassword()) - default: - return nm.handleNpmAnonymousAccess() - } -} - -// handleNpmrcTokenAuth sets the token in the npmrc or yarnrc file and clears basic auth if it exists. -func (nm *NpmrcYarnrcManager) handleNpmrcTokenAuth(token string) error { - authKey := nm.createAuthKey(NpmConfigAuthTokenKey) - if err := nm.configSet(authKey, token); err != nil { - return err - } - return nm.removeNpmrcBasicAuthIfExists() -} - -// handleNpmrcBasicAuth sets basic auth credentials and clears any token-based auth. -func (nm *NpmrcYarnrcManager) handleNpmrcBasicAuth(user, password string) error { - authKey := nm.createAuthKey(NpmConfigAuthKey) - authValue := basicAuthBase64Encode(user, password) - if err := nm.configSet(authKey, authValue); err != nil { - return err - } - return nm.removeNpmrcTokenAuthIfExists() -} - -// handleNpmAnonymousAccess removes any existing authentication settings for anonymous access. -func (nm *NpmrcYarnrcManager) handleNpmAnonymousAccess() error { - if err := nm.removeNpmrcBasicAuthIfExists(); err != nil { - return err - } - return nm.removeNpmrcTokenAuthIfExists() -} - -// removeNpmrcBasicAuthIfExists deletes basic auth credentials if present. -func (nm *NpmrcYarnrcManager) removeNpmrcBasicAuthIfExists() error { - return nm.configDelete(nm.createAuthKey(NpmConfigAuthKey)) -} - -// removeNpmrcTokenAuthIfExists deletes token auth credentials if present. -func (nm *NpmrcYarnrcManager) removeNpmrcTokenAuthIfExists() error { - return nm.configDelete(nm.createAuthKey(NpmConfigAuthTokenKey)) -} - -// configSet applies a configuration setting in npmrc or yarnrc, based on the build tool type. -func (nm *NpmrcYarnrcManager) configSet(key, value string) error { - switch nm.buildTool { - case project.Npm: - return npm.ConfigSet(key, value, nm.buildTool.String()) - case project.Yarn: - return yarn.ConfigSet(key, value, nm.buildTool.String(), false) - default: - return errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", nm.buildTool)) - } -} - -// configDelete removes a configuration setting from npmrc or yarnrc, based on the build tool type. -func (nm *NpmrcYarnrcManager) configDelete(key string) error { - switch nm.buildTool { - case project.Npm: - return npm.ConfigDelete(key, nm.buildTool.String()) - case project.Yarn: - return yarn.ConfigDelete(key, nm.buildTool.String()) - default: - return errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", nm.buildTool)) - } -} - -// createAuthKey generates the correct authentication key for npm or Yarn, based on the repo URL. -func (nm *NpmrcYarnrcManager) createAuthKey(keySuffix string) string { - return fmt.Sprintf("//%s:%s", strings.TrimPrefix(nm.repoUrl, "https://"), keySuffix) -} - -// basicAuthBase64Encode encodes user credentials in Base64 for basic authentication. -func basicAuthBase64Encode(user, password string) string { - return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, password))) -} From 633d8a069ffee2987588dcc9e3288e8cf958426e Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Sun, 3 Nov 2024 17:44:54 +0200 Subject: [PATCH 05/41] Improve repositores code Signed-off-by: Michael Sverdlov --- .../commands/buildtoollogin/buildtoollogin.go | 200 ++++++++++++++ .../buildtoollogin/buildtoollogin_test.go | 256 ++++++++++++++++++ artifactory/commands/npm/login.go | 65 ----- artifactory/commands/npm/login_test.go | 89 ------ artifactory/commands/python/login.go | 64 ----- artifactory/commands/python/poetry.go | 76 +++++- artifactory/commands/python/python.go | 5 +- artifactory/commands/python/utils.go | 75 +++++ .../commands}/python/utils_test.go | 2 +- artifactory/commands/utils/buildtoolconfig.go | 142 ---------- artifactory/commands/utils/npmcmdutils.go | 23 ++ .../commands/utils/npmcmdutils_test.go | 6 +- artifactory/commands/yarn/login.go | 71 ----- artifactory/commands/yarn/login_test.go | 93 ------- utils/python/utils.go | 135 --------- 15 files changed, 625 insertions(+), 677 deletions(-) create mode 100644 artifactory/commands/buildtoollogin/buildtoollogin.go create mode 100644 artifactory/commands/buildtoollogin/buildtoollogin_test.go delete mode 100644 artifactory/commands/npm/login.go delete mode 100644 artifactory/commands/npm/login_test.go delete mode 100644 artifactory/commands/python/login.go create mode 100644 artifactory/commands/python/utils.go rename {utils => artifactory/commands}/python/utils_test.go (99%) delete mode 100644 artifactory/commands/utils/buildtoolconfig.go delete mode 100644 artifactory/commands/yarn/login.go delete mode 100644 artifactory/commands/yarn/login_test.go delete mode 100644 utils/python/utils.go diff --git a/artifactory/commands/buildtoollogin/buildtoollogin.go b/artifactory/commands/buildtoollogin/buildtoollogin.go new file mode 100644 index 000000000..54cbce8b8 --- /dev/null +++ b/artifactory/commands/buildtoollogin/buildtoollogin.go @@ -0,0 +1,200 @@ +package buildtoollogin + +import ( + "fmt" + pythoncommands "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/python" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" + commandsutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/npm" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/yarn" + "github.com/jfrog/jfrog-cli-core/v2/common/project" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-client-go/artifactory/services" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/log" +) + +// BuildToolLoginCommand configures npm, Yarn, Pip, Pipenv, and Poetry registries and authentication +// based on the specified project type. +type BuildToolLoginCommand struct { + // buildTool represents the project type, either NPM or Yarn. + buildTool project.ProjectType + // repoName holds the name of the repository. + repoName string + // serverDetails contains configuration details for the Artifactory server. + serverDetails *config.ServerDetails + // commandName holds the name of the command. + commandName string +} + +// NewBuildToolLogin initializes a new BuildToolLogin with the given project type, +// repository name, and Artifactory server details. +func NewBuildToolLoginCommand(buildTool project.ProjectType) *BuildToolLoginCommand { + return &BuildToolLoginCommand{ + buildTool: buildTool, + commandName: buildTool.String() + "_login", + } +} + +// Run executes the appropriate configuration method based on the project type. +func (btlc *BuildToolLoginCommand) Run() (err error) { + // If no repository is specified, prompt the user to select a compatible repository. + if btlc.repoName == "" { + if err = btlc.SetVirtualRepoNameInteractively(); err != nil { + return err + } + } + + switch btlc.buildTool { + case project.Npm: + err = btlc.configureNpm() + case project.Yarn: + err = btlc.configureYarn() + case project.Pip: + err = btlc.configurePip() + case project.Pipenv: + err = btlc.configurePipenv() + case project.Poetry: + err = btlc.configurePoetry() + default: + err = errorutils.CheckErrorf("unsupported build tool: %s", btlc.buildTool) + } + if err != nil { + return err + } + + log.Info(fmt.Sprintf("Successfully configured %s to use JFrog Artifactory repository '%s'.", btlc.buildTool.String(), btlc.repoName)) + return nil +} + +// SetVirtualRepoNameInteractively prompts the user to select a virtual repository +func (btlc *BuildToolLoginCommand) SetVirtualRepoNameInteractively() error { + // Get the package type that corresponds to the build tool. + packageType, err := buildToolToPackageType(btlc.buildTool) + if err != nil { + return err + } + // Define filter parameters to select virtual repositories of npm package type. + repoFilterParams := services.RepositoriesFilterParams{ + RepoType: utils.Virtual.String(), + PackageType: packageType, + } + + // Select repository interactively based on filter parameters and server details. + btlc.repoName, err = utils.SelectRepositoryInteractively(btlc.serverDetails, repoFilterParams) + return err +} + +// configurePip sets the global index-url for pip to use Artifactory. +// Running the following commands: +// +// pip config set global index-url https://:@/artifactory/api/pypi//simple +func (btlc *BuildToolLoginCommand) configurePip() error { + repoWithCredsUrl, err := pythoncommands.GetPypiRepoUrl(btlc.serverDetails, btlc.repoName, false) + if err != nil { + return err + } + return pythoncommands.RunConfigCommand(btlc.buildTool, []string{"set", "global.index-url", repoWithCredsUrl}) +} + +// configurePipenv sets the PyPI URL for pipenv to use Artifactory. +// Running the following commands: +// +// pipenv config set pypi.url https://:@/artifactory/api/pypi//simple +func (btlc *BuildToolLoginCommand) configurePipenv() error { + repoWithCredsUrl, err := pythoncommands.GetPypiRepoUrl(btlc.serverDetails, btlc.repoName, false) + if err != nil { + return err + } + return pythoncommands.RunConfigCommand(btlc.buildTool, []string{"set", "pypi.url", repoWithCredsUrl}) +} + +// configurePoetry configures a Poetry repository and basic auth credentials. +// Running the following commands: +// +// poetry config repositories. https:///artifactory/api/pypi//simple +// poetry config http-basic. +func (btlc *BuildToolLoginCommand) configurePoetry() error { + repoUrl, username, password, err := pythoncommands.GetPypiRepoUrlWithCredentials(btlc.serverDetails, btlc.repoName, false) + if err != nil { + return err + } + return pythoncommands.RunPoetryConfig(repoUrl.String(), username, password, btlc.repoName) +} + +// configureNpm sets the registry URL and auth for npm to use Artifactory. +// Running the following commands: +// +// npm config set registry https:///artifactory/api/npm/ +// +// For token-based auth: +// +// npm config set //your-artifactory-url/artifactory/api/npm//:_authToken "" +// +// For basic auth (username:password): +// +// npm config set //your-artifactory-url/artifactory/api/npm//:_auth "" +func (btlc *BuildToolLoginCommand) configureNpm() error { + repoUrl := commandsutils.GetNpmRepositoryUrl(btlc.repoName, btlc.serverDetails.ArtifactoryUrl) + + if err := npm.ConfigSet(commandsutils.NpmConfigRegistryKey, repoUrl, "npm"); err != nil { + return err + } + + authKey, authValue := commandsutils.GetNpmAuthKeyValue(btlc.serverDetails, repoUrl) + if authKey != "" && authValue != "" { + return npm.ConfigSet(authKey, authValue, "npm") + } + return nil +} + +// configureYarn sets the registry URL and auth for Yarn to use Artifactory. +// Running the following commands: +// +// yarn config set registry https:///artifactory/api/npm/ +// +// For token-based auth: +// +// yarn config set //your-artifactory-url/artifactory/api/npm//:_authToken "" +// +// For basic auth (username:password): +// +// yarn config set //your-artifactory-url/artifactory/api/npm//:_auth "" +func (btlc *BuildToolLoginCommand) configureYarn() error { + repoUrl := commandsutils.GetNpmRepositoryUrl(btlc.repoName, btlc.serverDetails.ArtifactoryUrl) + + if err := yarn.ConfigSet(commandsutils.NpmConfigRegistryKey, repoUrl, "yarn", false); err != nil { + return err + } + + authKey, authValue := commandsutils.GetNpmAuthKeyValue(btlc.serverDetails, repoUrl) + if authKey != "" && authValue != "" { + return yarn.ConfigSet(authKey, authValue, "yarn", false) + } + return nil +} + +// buildToolToPackageType maps the project type to the corresponding package type. +func buildToolToPackageType(buildTool project.ProjectType) (string, error) { + switch buildTool { + case project.Npm, project.Yarn: + return repository.Npm, nil + case project.Pip, project.Pipenv, project.Poetry: + return repository.Pypi, nil + default: + return "", errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", buildTool)) + } +} + +func (btlc *BuildToolLoginCommand) CommandName() string { + return btlc.commandName +} + +func (btlc *BuildToolLoginCommand) SetServerDetails(serverDetails *config.ServerDetails) *BuildToolLoginCommand { + btlc.serverDetails = serverDetails + return btlc +} +func (btlc *BuildToolLoginCommand) ServerDetails() (*config.ServerDetails, error) { + return btlc.serverDetails, nil +} diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/buildtoollogin_test.go new file mode 100644 index 000000000..8cf8228de --- /dev/null +++ b/artifactory/commands/buildtoollogin/buildtoollogin_test.go @@ -0,0 +1,256 @@ +package buildtoollogin + +import ( + "fmt" + cmdutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" + "github.com/jfrog/jfrog-cli-core/v2/common/project" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" +) + +// #nosec G101 -- Dummy token for tests +var dummyToken = "eyJ2ZXIiOiIyIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYiLCJraWQiOiJIcnU2VHctZk1yOTV3dy12TDNjV3ZBVjJ3Qm9FSHpHdGlwUEFwOE1JdDljIn0.eyJzdWIiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3XC91c2Vyc1wvYWRtaW4iLCJzY3AiOiJtZW1iZXItb2YtZ3JvdXBzOnJlYWRlcnMgYXBpOioiLCJhdWQiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3IiwiaXNzIjoiamZydEAwMWMzZ2ZmaGcyZTh3NjE0OWUzYTJxMHc5NyIsImV4cCI6MTU1NjAzNzc2NSwiaWF0IjoxNTU2MDM0MTY1LCJqdGkiOiI1M2FlMzgyMy05NGM3LTQ0OGItOGExOC1iZGVhNDBiZjFlMjAifQ.Bp3sdvppvRxysMlLgqT48nRIHXISj9sJUCXrm7pp8evJGZW1S9hFuK1olPmcSybk2HNzdzoMcwhUmdUzAssiQkQvqd_HanRcfFbrHeg5l1fUQ397ECES-r5xK18SYtG1VR7LNTVzhJqkmRd3jzqfmIK2hKWpEgPfm8DRz3j4GGtDRxhb3oaVsT2tSSi_VfT3Ry74tzmO0GcCvmBE2oh58kUZ4QfEsalgZ8IpYHTxovsgDx_M7ujOSZx_hzpz-iy268-OkrU22PQPCfBmlbEKeEUStUO9n0pj4l1ODL31AGARyJRy46w4yzhw7Fk5P336WmDMXYs5LAX2XxPFNLvNzA" + +func createTestBuildToolLoginCommand(buildTool project.ProjectType) *BuildToolLoginCommand { + cmd := NewBuildToolLoginCommand(buildTool) + cmd.repoName = "test-repo" + cmd.serverDetails = &config.ServerDetails{ArtifactoryUrl: "https://acme.jfrog.io/artifactory"} + + return cmd +} + +func TestBuildToolLoginCommand_Npm(t *testing.T) { + // Create a temporary directory to act as the environment's npmrc file location. + tempDir := t.TempDir() + npmrcFilePath := filepath.Join(tempDir, ".npmrc") + + // Set NPM_CONFIG_USERCONFIG to point to the temporary npmrc file path. + t.Setenv("NPM_CONFIG_USERCONFIG", npmrcFilePath) + + npmLoginCmd := createTestBuildToolLoginCommand(project.Npm) + + // Define test cases for different authentication types. + testCases := []struct { + name string + user string + password string + accessToken string + }{ + { + name: "Token Authentication", + accessToken: dummyToken, + }, + { + name: "Basic Authentication", + user: "myUser", + password: "myPassword", + }, + { + name: "Anonymous Access", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + // Set up server details for the current test case's authentication type. + npmLoginCmd.serverDetails.SetUser(testCase.user) + npmLoginCmd.serverDetails.SetPassword(testCase.password) + npmLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) + + // Run the login command and ensure no errors occur. + if npmLoginCmd.Run() != nil { + t.FailNow() + } + + // Read the contents of the temporary npmrc file. + npmrcContentBytes, err := os.ReadFile(npmrcFilePath) + assert.NoError(t, err) + npmrcContent := string(npmrcContentBytes) + + // Validate that the registry URL was set correctly in .npmrc. + assert.Contains(t, npmrcContent, fmt.Sprintf("%s=%s", cmdutils.NpmConfigRegistryKey, "https://acme.jfrog.io/artifactory/api/npm/test-repo")) + + // Define expected keys for basic and token auth in the .npmrc. + basicAuthKey := fmt.Sprintf("//acme.jfrog.io/artifactory/api/npm/test-repo:%s=", cmdutils.NpmConfigAuthKey) + tokenAuthKey := fmt.Sprintf("//acme.jfrog.io/artifactory/api/npm/test-repo:%s=", cmdutils.NpmConfigAuthTokenKey) + + // Validate token-based authentication. + if testCase.accessToken != "" { + assert.Contains(t, npmrcContent, tokenAuthKey+"test-token") + } else if testCase.user != "" && testCase.password != "" { + // Validate basic authentication with encoded credentials. + // Base64 encoding of "myUser:myPassword" + expectedBasicAuth := basicAuthKey + "\"bXlVc2VyOm15UGFzc3dvcmQ=\"" + assert.Contains(t, npmrcContent, expectedBasicAuth) + } + + // Clean up the temporary npmrc file. + assert.NoError(t, os.Remove(npmrcFilePath)) + }) + } +} + +func TestBuildToolLoginCommand_Yarn(t *testing.T) { + // Retrieve the home directory and construct the .yarnrc file path. + homeDir, err := os.UserHomeDir() + assert.NoError(t, err) + yarnrcFilePath := filepath.Join(homeDir, ".yarnrc") + + // Back up the existing .yarnrc file and ensure restoration after the test. + restoreYarnrcFunc, err := ioutils.BackupFile(yarnrcFilePath, ".yarnrc.backup") + assert.NoError(t, err) + defer func() { + assert.NoError(t, restoreYarnrcFunc()) + }() + + yarnLoginCmd := createTestBuildToolLoginCommand(project.Yarn) + + // Define test cases for different authentication types. + testCases := []struct { + name string + user string + password string + accessToken string + }{ + { + name: "Token Authentication", + accessToken: "test-token", + }, + { + name: "Basic Authentication", + user: "myUser", + password: "myPassword", + }, + { + name: "Anonymous Access", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + // Set up server details for the current test case's authentication type. + yarnLoginCmd.serverDetails.SetUser(testCase.user) + yarnLoginCmd.serverDetails.SetPassword(testCase.password) + yarnLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) + + // Run the login command and ensure no errors occur. + if yarnLoginCmd.Run() != nil { + t.FailNow() + } + + // Read the contents of the temporary npmrc file. + yarnrcContentBytes, err := os.ReadFile(yarnrcFilePath) + assert.NoError(t, err) + yarnrcContent := string(yarnrcContentBytes) + + // Check that the registry URL is correctly set in .yarnrc. + assert.Contains(t, yarnrcContent, fmt.Sprintf("%s \"%s\"", cmdutils.NpmConfigRegistryKey, "https://acme.jfrog.io/artifactory/api/npm/test-repo")) + + // Define expected keys for basic auth and token-based auth in .yarnrc. + basicAuthKey := fmt.Sprintf("\"//acme.jfrog.io/artifactory/api/npm/test-repo:%s\" ", cmdutils.NpmConfigAuthKey) + tokenAuthKey := fmt.Sprintf("\"//acme.jfrog.io/artifactory/api/npm/test-repo:%s\" ", cmdutils.NpmConfigAuthTokenKey) + + // Validate token-based authentication. + if testCase.accessToken != "" { + assert.Contains(t, yarnrcContent, tokenAuthKey+"test-token") + + } else if testCase.user != "" && testCase.password != "" { + // Validate basic authentication with encoded credentials. + // Base64 encoding of "myUser:myPassword" + expectedBasicAuth := basicAuthKey + "bXlVc2VyOm15UGFzc3dvcmQ=" + assert.Contains(t, yarnrcContent, expectedBasicAuth) + } + + // Clean up the temporary npmrc file. + assert.NoError(t, os.Remove(yarnrcFilePath)) + }) + } +} + +func TestBuildToolLoginCommand_Pip(t *testing.T) { + // Retrieve the home directory and construct the pip.conf file path. + homeDir, err := os.UserHomeDir() + assert.NoError(t, err) + var pipConfFilePath string + if coreutils.IsWindows() { + pipConfFilePath = filepath.Join(homeDir, "pip", "pip.ini") + } else { + pipConfFilePath = filepath.Join(homeDir, ".config", "pip", "pip.conf") + } + + // Back up the existing .pip.conf file and ensure restoration after the test. + restorePipConfFunc, err := ioutils.BackupFile(pipConfFilePath, ".pipconf.backup") + assert.NoError(t, err) + defer func() { + assert.NoError(t, restorePipConfFunc()) + }() + + pipLoginCmd := createTestBuildToolLoginCommand(project.Pip) + + // Define test cases for different authentication types. + testCases := []struct { + name string + user string + password string + accessToken string + }{ + { + name: "Token Authentication", + accessToken: "test-token", + }, + { + name: "Basic Authentication", + user: "myUser", + password: "myPassword", + }, + { + name: "Anonymous Access", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + // Clean up the temporary pip config file. + assert.NoError(t, os.Remove(pipConfFilePath)) + + // Set up server details for the current test case's authentication type. + pipLoginCmd.serverDetails.SetUser(testCase.user) + pipLoginCmd.serverDetails.SetPassword(testCase.password) + pipLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) + + // Run the login command and ensure no errors occur. + if pipLoginCmd.Run() != nil { + t.FailNow() + } + + // Read the contents of the temporary pip config file. + pipConfigContentBytes, err := os.ReadFile(pipConfFilePath) + assert.NoError(t, err) + pipConfigContent := string(pipConfigContentBytes) + + // Validate that the index URL was set correctly in pip.conf. + assert.Contains(t, pipConfigContent, fmt.Sprintf("index-url = https://%s:%s@acme.jfrog.io/artifactory/api/pypi/test-repo/simple", testCase.user, testCase.password)) + + // Validate token-based authentication. + if testCase.accessToken != "" { + assert.Contains(t, pipConfigContent, fmt.Sprintf("index-url = https://%s@acme.jfrog.io/artifactory/api/pypi/test-repo/simple", "test-token")) + } else if testCase.user != "" && testCase.password != "" { + // Validate basic authentication with user and password. + assert.Contains(t, pipConfigContent, fmt.Sprintf("index-url = https://%s:%s@acme.jfrog.io/artifactory/api/pypi/test-repo/simple", "myUser", "myPassword")) + } + }) + } +} + +func TestBuildToolLoginCommand_configurePipenv(t *testing.T) { + // todo +} + +func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { + // todo +} diff --git a/artifactory/commands/npm/login.go b/artifactory/commands/npm/login.go deleted file mode 100644 index c44befb64..000000000 --- a/artifactory/commands/npm/login.go +++ /dev/null @@ -1,65 +0,0 @@ -package npm - -import ( - "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" - cmdutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" - "github.com/jfrog/jfrog-cli-core/v2/common/project" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-client-go/artifactory/services" - "github.com/jfrog/jfrog-client-go/utils/log" -) - -type NpmLoginCommand struct { - commandName string - CommonArgs -} - -func NewNpmLoginCommand() *NpmLoginCommand { - return &NpmLoginCommand{commandName: "rt_npm_login"} -} - -// Run configures npm to use the specified or selected JFrog Artifactory repository -// for package management, setting up registry and authentication. -func (nlc *NpmLoginCommand) Run() (err error) { - // If no repository is specified, prompt the user to select an npm-compatible repository. - if nlc.repo == "" { - // Define filter parameters to select virtual repositories of npm package type. - repoFilterParams := services.RepositoriesFilterParams{ - RepoType: utils.Virtual.String(), - PackageType: repository.Npm, - } - - // Select repository interactively based on filter parameters and server details. - nlc.repo, err = utils.SelectRepositoryInteractively(nlc.serverDetails, repoFilterParams) - if err != nil { - return err - } - } - - // Initialize NpmrcYarnrcManager for npm to manage registry and authentication configurations. - npmrcManager := cmdutils.NewNpmrcYarnrcManager(project.Npm, nlc.repo, nlc.serverDetails) - - // Configure the registry URL for npm in the npm configuration. - if err = npmrcManager.ConfigureRegistry(); err != nil { - return err - } - - // Configure authentication settings, handling token or basic auth as needed. - if err = npmrcManager.ConfigureAuth(); err != nil { - return err - } - - // Output success message indicating successful npm configuration. - log.Output(coreutils.PrintTitle("Successfully configured npm client to work with your JFrog Artifactory repository: " + nlc.repo)) - return nil -} - -func (nlc *NpmLoginCommand) CommandName() string { - return nlc.commandName -} - -func (nlc *NpmLoginCommand) ServerDetails() (*config.ServerDetails, error) { - return nlc.serverDetails, nil -} diff --git a/artifactory/commands/npm/login_test.go b/artifactory/commands/npm/login_test.go deleted file mode 100644 index d0a8cadf8..000000000 --- a/artifactory/commands/npm/login_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package npm - -import ( - "fmt" - cmdutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/stretchr/testify/assert" - "os" - "path/filepath" - "testing" -) - -func TestNpmLoginCommand(t *testing.T) { - // Create a temporary directory to act as the environment's npmrc file location. - tempDir := t.TempDir() - npmrcFilePath := filepath.Join(tempDir, ".npmrc") - - // Set NPM_CONFIG_USERCONFIG to point to the temporary npmrc file path. - t.Setenv("NPM_CONFIG_USERCONFIG", npmrcFilePath) - - // Initialize a new NpmLoginCommand instance. - loginCmd := NewNpmLoginCommand() - loginCmd.SetServerDetails(&config.ServerDetails{ArtifactoryUrl: "https://acme.jfrog.io/artifactory"}) - loginCmd.repo = "npm-virtual" - - // Define test cases for different authentication types. - testCases := []struct { - name string - user string - password string - accessToken string - }{ - { - name: "Token Authentication", - accessToken: "test-token", - }, - { - name: "Basic Authentication", - user: "myUser", - password: "myPassword", - }, - { - name: "Anonymous Access", - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - // Set up server details for the current test case's authentication type. - loginCmd.serverDetails.SetUser(testCase.user) - loginCmd.serverDetails.SetPassword(testCase.password) - loginCmd.serverDetails.SetAccessToken(testCase.accessToken) - - // Run the login command and ensure no errors occur. - assert.NoError(t, loginCmd.Run()) - - // Read the contents of the temporary npmrc file. - npmrcContentBytes, err := os.ReadFile(npmrcFilePath) - assert.NoError(t, err) - npmrcContent := string(npmrcContentBytes) - - // Validate that the registry URL was set correctly in .npmrc. - assert.Contains(t, npmrcContent, fmt.Sprintf("%s=%s", cmdutils.NpmConfigRegistryKey, "https://acme.jfrog.io/artifactory/api/npm/npm-virtual")) - - // Define expected keys for basic and token auth in the .npmrc. - basicAuthKey := fmt.Sprintf("//acme.jfrog.io/artifactory/api/npm/npm-virtual:%s=", cmdutils.NpmConfigAuthKey) - tokenAuthKey := fmt.Sprintf("//acme.jfrog.io/artifactory/api/npm/npm-virtual:%s=", cmdutils.NpmConfigAuthTokenKey) - - switch { - // Validate token-based authentication. - case testCase.accessToken != "": - assert.Contains(t, npmrcContent, tokenAuthKey+"test-token") - assert.NotContains(t, npmrcContent, basicAuthKey) - - // Validate basic authentication with encoded credentials. - case testCase.user != "" && testCase.password != "": - // Base64 encoding of "myUser:myPassword" - expectedBasicAuth := basicAuthKey + "\"bXlVc2VyOm15UGFzc3dvcmQ=\"" - assert.Contains(t, npmrcContent, expectedBasicAuth) - assert.NotContains(t, npmrcContent, tokenAuthKey) - - // Validate anonymous access, where neither auth method is configured. - default: - assert.NotContains(t, npmrcContent, basicAuthKey) - assert.NotContains(t, npmrcContent, tokenAuthKey) - } - }) - } -} diff --git a/artifactory/commands/python/login.go b/artifactory/commands/python/login.go deleted file mode 100644 index 2f7487364..000000000 --- a/artifactory/commands/python/login.go +++ /dev/null @@ -1,64 +0,0 @@ -package python - -import ( - "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" - cmdutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" - "github.com/jfrog/jfrog-cli-core/v2/common/project" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-client-go/artifactory/services" - "github.com/jfrog/jfrog-client-go/utils/log" -) - -type PythonLoginCommand struct { - ר -} - -func NewPythonLoginCommand() *PythonLoginCommand { - return &PythonLoginCommand{commandName: "rt_python_login"} -} - -// Run configures python to use the specified or selected JFrog Artifactory repository -// for package management, setting up registry and authentication. -func (plc *PythonLoginCommand) Run() (err error) { - // If no repository is specified, prompt the user to select a pypi-compatible repository. - if plc.repo == "" { - // Define filter parameters to select virtual repositories of npm package type. - repoFilterParams := services.RepositoriesFilterParams{ - RepoType: utils.Virtual.String(), - PackageType: repository.Npm, - } - - // Select repository interactively based on filter parameters and server details. - plc.repo, err = utils.SelectRepositoryInteractively(plc.serverDetails, repoFilterParams) - if err != nil { - return err - } - } - - // Initialize NpmrcYarnrcManager for npm to manage registry and authentication configurations. - npmrcManager := cmdutils.NewNpmrcYarnrcManager(project.Npm, plc.repo, plc.serverDetails) - - // Configure the registry URL for npm in the npm configuration. - if err = npmrcManager.ConfigureRegistry(); err != nil { - return err - } - - // Configure authentication settings, handling token or basic auth as needed. - if err = npmrcManager.ConfigureAuth(); err != nil { - return err - } - - // Output success message indicating successful npm configuration. - log.Output(coreutils.PrintTitle("Successfully configured npm client to work with your JFrog Artifactory repository: " + plc.repo)) - return nil -} - -func (plc *PythonLoginCommand) CommandName() string { - return plc.commandName -} - -func (plc *PythonLoginCommand) ServerDetails() (*config.ServerDetails, error) { - return plc.serverDetails, nil -} diff --git a/artifactory/commands/python/poetry.go b/artifactory/commands/python/poetry.go index 33152c9cf..4e081af97 100644 --- a/artifactory/commands/python/poetry.go +++ b/artifactory/commands/python/poetry.go @@ -10,27 +10,25 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/python/dependencies" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" buildUtils "github.com/jfrog/jfrog-cli-core/v2/common/build" + "github.com/jfrog/jfrog-cli-core/v2/common/project" "github.com/jfrog/jfrog-cli-core/v2/utils/config" - python "github.com/jfrog/jfrog-cli-core/v2/utils/python" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" + "github.com/spf13/viper" "golang.org/x/exp/slices" "io" + "os" "os/exec" + "path/filepath" ) type PoetryCommand struct { PythonCommand - // The uniq Artifactory repository name for poetry config file. - poetryConfigRepoName string } -const baseConfigRepoName = "jfrog-server" - func NewPoetryCommand() *PoetryCommand { return &PoetryCommand{ - PythonCommand: *NewPythonCommand(pythonutils.Poetry), - poetryConfigRepoName: baseConfigRepoName, + PythonCommand: *NewPythonCommand(pythonutils.Poetry), } } @@ -90,7 +88,7 @@ func (pc *PoetryCommand) install(buildConfiguration *buildUtils.BuildConfigurati } func (pc *PoetryCommand) publish(buildConfiguration *buildUtils.BuildConfiguration, pythonBuildInfo *build.Build) error { - publishCmdArgs := append(slices.Clone(pc.args), "-r "+pc.poetryConfigRepoName) + publishCmdArgs := append(slices.Clone(pc.args), "-r "+pc.repository) // Collect build info by running the jf poetry install cmd pc.args = []string{} err := pc.install(buildConfiguration, pythonBuildInfo) @@ -126,20 +124,76 @@ func (pc *PoetryCommand) SetCommandName(commandName string) *PoetryCommand { } func (pc *PoetryCommand) SetPypiRepoUrlWithCredentials() error { - rtUrl, username, password, err := python.GetPypiRepoUrlWithCredentials(pc.serverDetails, pc.repository, false) + rtUrl, username, password, err := GetPypiRepoUrlWithCredentials(pc.serverDetails, pc.repository, false) if err != nil { return err } if password != "" { - return python.ConfigPoetryRepo( + return ConfigPoetryRepo( rtUrl.Scheme+"://"+rtUrl.Host+rtUrl.Path, username, password, - pc.poetryConfigRepoName) + pc.repository) } return nil } +func ConfigPoetryRepo(url, username, password, configRepoName string) error { + err := RunPoetryConfig(url, username, password, configRepoName) + if err != nil { + return err + } + + // Add the repository config to the pyproject.toml + currentDir, err := os.Getwd() + if err != nil { + return errorutils.CheckError(err) + } + if err = addRepoToPyprojectFile(filepath.Join(currentDir, pyproject), configRepoName, url); err != nil { + return err + } + return poetryUpdate() +} + +func RunPoetryConfig(url, username, password, configRepoName string) error { + // Add the poetry repository config + // poetry config repositories. https:///artifactory/api/pypi//simple + err := RunConfigCommand(project.Poetry, []string{poetryConfigRepoPrefix + configRepoName, url}) + if err != nil { + return err + } + + // Set the poetry repository credentials + // poetry config http-basic. + return RunConfigCommand(project.Poetry, []string{poetryConfigAuthPrefix + configRepoName, username, password}) +} + +func poetryUpdate() (err error) { + log.Info("Running Poetry update") + cmd := gofrogcmd.NewCommand("poetry", "update", []string{}) + err = gofrogcmd.RunCmd(cmd) + if err != nil { + return errorutils.CheckErrorf("Poetry config command failed with: %s", err.Error()) + } + return +} + +func addRepoToPyprojectFile(filepath, poetryRepoName, repoUrl string) error { + viper.SetConfigType("toml") + viper.SetConfigFile(filepath) + err := viper.ReadInConfig() + if err != nil { + return errorutils.CheckErrorf("Failed to read pyproject.toml: %s", err.Error()) + } + viper.Set("tool.poetry.source", []map[string]string{{"name": poetryRepoName, "url": repoUrl}}) + err = viper.WriteConfig() + if err != nil { + return errorutils.CheckErrorf("Failed to add tool.poetry.source to pyproject.toml: %s", err.Error()) + } + log.Info(fmt.Sprintf("Added tool.poetry.source name:%q url:%q", poetryRepoName, repoUrl)) + return err +} + func (pc *PoetryCommand) CommandName() string { return "rt_python_poetry" } diff --git a/artifactory/commands/python/python.go b/artifactory/commands/python/python.go index 5e193040d..194b8010d 100644 --- a/artifactory/commands/python/python.go +++ b/artifactory/commands/python/python.go @@ -11,7 +11,6 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" buildUtils "github.com/jfrog/jfrog-cli-core/v2/common/build" "github.com/jfrog/jfrog-cli-core/v2/utils/config" - python "github.com/jfrog/jfrog-cli-core/v2/utils/python" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" "io" @@ -118,11 +117,11 @@ func (pc *PythonCommand) SetCommandName(commandName string) *PythonCommand { } func (pc *PythonCommand) SetPypiRepoUrlWithCredentials() error { - rtUrl, err := python.GetPypiRepoUrl(pc.serverDetails, pc.repository, false) + rtUrl, err := GetPypiRepoUrl(pc.serverDetails, pc.repository, false) if err != nil { return err } - pc.args = append(pc.args, python.GetPypiRemoteRegistryFlag(pc.pythonTool), rtUrl) + pc.args = append(pc.args, GetPypiRemoteRegistryFlag(pc.pythonTool), rtUrl) return nil } diff --git a/artifactory/commands/python/utils.go b/artifactory/commands/python/utils.go new file mode 100644 index 000000000..9731387ab --- /dev/null +++ b/artifactory/commands/python/utils.go @@ -0,0 +1,75 @@ +package python + +import ( + "github.com/jfrog/build-info-go/utils/pythonutils" + "github.com/jfrog/gofrog/io" + gofrogcmd "github.com/jfrog/gofrog/io" + "github.com/jfrog/jfrog-cli-core/v2/common/project" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/auth" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/log" + "net/url" +) + +const ( + pipenvRemoteRegistryFlag = "--pypi-mirror" + pipRemoteRegistryFlag = "-i" + poetryConfigAuthPrefix = "http-basic." + poetryConfigRepoPrefix = "repositories." + pyproject = "pyproject.toml" +) + +// Get the pypi repository url and the credentials. +func GetPypiRepoUrlWithCredentials(serverDetails *config.ServerDetails, repository string, isCurationCmd bool) (*url.URL, string, string, error) { + rtUrl, err := url.Parse(serverDetails.GetArtifactoryUrl()) + if err != nil { + return nil, "", "", errorutils.CheckError(err) + } + + username := serverDetails.GetUser() + password := serverDetails.GetPassword() + + // Get credentials from access-token if exists. + if serverDetails.GetAccessToken() != "" { + if username == "" { + username = auth.ExtractUsernameFromAccessToken(serverDetails.GetAccessToken()) + } + password = serverDetails.GetAccessToken() + } + if isCurationCmd { + rtUrl = rtUrl.JoinPath(coreutils.CurationPassThroughApi) + } + rtUrl = rtUrl.JoinPath("api/pypi", repository, "simple") + return rtUrl, username, password, err +} + +func GetPypiRemoteRegistryFlag(tool pythonutils.PythonTool) string { + if tool == pythonutils.Pip { + return pipRemoteRegistryFlag + } + return pipenvRemoteRegistryFlag +} + +// Get the pypi repository embedded credentials URL (https://:@/artifactory/api/pypi//simple) +func GetPypiRepoUrl(serverDetails *config.ServerDetails, repository string, isCurationCmd bool) (string, error) { + rtUrl, username, password, err := GetPypiRepoUrlWithCredentials(serverDetails, repository, isCurationCmd) + if err != nil { + return "", err + } + if password != "" { + rtUrl.User = url.UserPassword(username, password) + } + return rtUrl.String(), err +} + +func RunConfigCommand(buildTool project.ProjectType, args []string) error { + log.Debug("Running", buildTool.String(), "config command...") + configCmd := io.NewCommand(buildTool.String(), "config", args) + err := gofrogcmd.RunCmd(configCmd) + if err != nil { + return errorutils.CheckErrorf(buildTool.String()+" config command failed with: %s", err.Error()) + } + return nil +} diff --git a/utils/python/utils_test.go b/artifactory/commands/python/utils_test.go similarity index 99% rename from utils/python/utils_test.go rename to artifactory/commands/python/utils_test.go index 3ef8785c8..2c564b3b4 100644 --- a/utils/python/utils_test.go +++ b/artifactory/commands/python/utils_test.go @@ -1,4 +1,4 @@ -package utils +package python import ( "github.com/jfrog/jfrog-cli-core/v2/utils/config" diff --git a/artifactory/commands/utils/buildtoolconfig.go b/artifactory/commands/utils/buildtoolconfig.go deleted file mode 100644 index 8c7d626fb..000000000 --- a/artifactory/commands/utils/buildtoolconfig.go +++ /dev/null @@ -1,142 +0,0 @@ -package utils - -import ( - "encoding/base64" - "fmt" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/npm" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/yarn" - "github.com/jfrog/jfrog-cli-core/v2/common/project" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-client-go/utils/errorutils" - "strings" -) - -// NpmrcYarnrcManager is responsible for configuring npm and Yarn registries -// and authentication settings based on the specified project type. -type NpmrcYarnrcManager struct { - // buildTool represents the project type, either NPM or Yarn. - buildTool project.ProjectType - // repoUrl holds the URL to the npm or Yarn repository. - repoUrl string - // serverDetails contains configuration details for the Artifactory server. - serverDetails *config.ServerDetails -} - -// NewNpmrcYarnrcManager initializes a new NpmrcYarnrcManager with the given project type, -// repository name, and Artifactory server details. -func NewNpmrcYarnrcManager(buildTool project.ProjectType, repoName string, serverDetails *config.ServerDetails) *NpmrcYarnrcManager { - repoUrl := GetNpmRepositoryUrl(repoName, serverDetails.ArtifactoryUrl) - return &NpmrcYarnrcManager{ - buildTool: buildTool, - repoUrl: repoUrl, - serverDetails: serverDetails, - } -} - -// ConfigureRegistry sets the registry URL in the npmrc or yarnrc file. -func (nm *NpmrcYarnrcManager) Run() error { - switch nm.buildTool { - case project.Npm, project.Yarn: - if err := nm.ConfigureRegistry(); err != nil { - return err - } - return nm.ConfigureAuth() - default: - return errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", nm.buildTool)) - } -} - -// ConfigureRegistry sets the registry URL in the npmrc or yarnrc file. -func (nm *NpmrcYarnrcManager) ConfigureRegistry() error { - return nm.configSet(NpmConfigRegistryKey, nm.repoUrl) -} - -// ConfigureAuth configures authentication in npmrc or yarnrc using token or basic auth, -// or clears authentication for anonymous access. -func (nm *NpmrcYarnrcManager) ConfigureAuth() error { - authArtDetails, err := nm.serverDetails.CreateArtAuthConfig() - if err != nil { - return err - } - - // Configure authentication based on available credentials. - switch { - case authArtDetails.GetAccessToken() != "": - return nm.handleNpmrcTokenAuth(authArtDetails.GetAccessToken()) - case authArtDetails.GetUser() != "" && authArtDetails.GetPassword() != "": - return nm.handleNpmrcBasicAuth(authArtDetails.GetUser(), authArtDetails.GetPassword()) - default: - return nm.handleNpmAnonymousAccess() - } -} - -// handleNpmrcTokenAuth sets the token in the npmrc or yarnrc file and clears basic auth if it exists. -func (nm *NpmrcYarnrcManager) handleNpmrcTokenAuth(token string) error { - authKey := nm.createAuthKey(NpmConfigAuthTokenKey) - if err := nm.configSet(authKey, token); err != nil { - return err - } - return nm.removeNpmrcBasicAuthIfExists() -} - -// handleNpmrcBasicAuth sets basic auth credentials and clears any token-based auth. -func (nm *NpmrcYarnrcManager) handleNpmrcBasicAuth(user, password string) error { - authKey := nm.createAuthKey(NpmConfigAuthKey) - authValue := basicAuthBase64Encode(user, password) - if err := nm.configSet(authKey, authValue); err != nil { - return err - } - return nm.removeNpmrcTokenAuthIfExists() -} - -// handleNpmAnonymousAccess removes any existing authentication settings for anonymous access. -func (nm *NpmrcYarnrcManager) handleNpmAnonymousAccess() error { - if err := nm.removeNpmrcBasicAuthIfExists(); err != nil { - return err - } - return nm.removeNpmrcTokenAuthIfExists() -} - -// removeNpmrcBasicAuthIfExists deletes basic auth credentials if present. -func (nm *NpmrcYarnrcManager) removeNpmrcBasicAuthIfExists() error { - return nm.configDelete(nm.createAuthKey(NpmConfigAuthKey)) -} - -// removeNpmrcTokenAuthIfExists deletes token auth credentials if present. -func (nm *NpmrcYarnrcManager) removeNpmrcTokenAuthIfExists() error { - return nm.configDelete(nm.createAuthKey(NpmConfigAuthTokenKey)) -} - -// configSet applies a configuration setting in npmrc or yarnrc, based on the build tool type. -func (nm *NpmrcYarnrcManager) configSet(key, value string) error { - switch nm.buildTool { - case project.Npm: - return npm.ConfigSet(key, value, nm.buildTool.String()) - case project.Yarn: - return yarn.ConfigSet(key, value, nm.buildTool.String(), false) - default: - return errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", nm.buildTool)) - } -} - -// configDelete removes a configuration setting from npmrc or yarnrc, based on the build tool type. -func (nm *NpmrcYarnrcManager) configDelete(key string) error { - switch nm.buildTool { - case project.Npm: - return npm.ConfigDelete(key, nm.buildTool.String()) - case project.Yarn: - return yarn.ConfigDelete(key, nm.buildTool.String()) - default: - return errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", nm.buildTool)) - } -} - -// createAuthKey generates the correct authentication key for npm or Yarn, based on the repo URL. -func (nm *NpmrcYarnrcManager) createAuthKey(keySuffix string) string { - return fmt.Sprintf("//%s:%s", strings.TrimPrefix(nm.repoUrl, "https://"), keySuffix) -} - -// basicAuthBase64Encode encodes user credentials in Base64 for basic authentication. -func basicAuthBase64Encode(user, password string) string { - return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, password))) -} diff --git a/artifactory/commands/utils/npmcmdutils.go b/artifactory/commands/utils/npmcmdutils.go index d07fed025..d21cdea8e 100644 --- a/artifactory/commands/utils/npmcmdutils.go +++ b/artifactory/commands/utils/npmcmdutils.go @@ -1,10 +1,12 @@ package utils import ( + "encoding/base64" "fmt" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/common/build" outFormat "github.com/jfrog/jfrog-cli-core/v2/common/format" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/http/httpclient" @@ -99,6 +101,27 @@ func GetNpmRepositoryUrl(repositoryName, artifactoryUrl string) string { return strings.TrimSuffix(artifactoryUrl, "/") + "/api/npm/" + repositoryName } +// GetNpmAuthKeyValue generates the correct authentication key and value for npm or Yarn, based on the repo URL. +func GetNpmAuthKeyValue(serverDetails *config.ServerDetails, repoUrl string) (key, value string) { + keySuffix := "" + if serverDetails.GetAccessToken() != "" { + keySuffix = NpmConfigAuthTokenKey + value = serverDetails.GetAccessToken() + } else if serverDetails.GetUser() != "" && serverDetails.GetPassword() != "" { + keySuffix = NpmConfigAuthKey + value = basicAuthBase64Encode(serverDetails.GetUser(), serverDetails.GetPassword()) + } else { + return "", "" + } + + return fmt.Sprintf("//%s:%s", strings.TrimPrefix(repoUrl, "https://"), keySuffix), value +} + +// basicAuthBase64Encode encodes user credentials in Base64 for basic authentication. +func basicAuthBase64Encode(user, password string) string { + return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, password))) +} + // Remove all the none npm CLI flags from args. func ExtractNpmOptionsFromArgs(args []string) (detailedSummary, xrayScan bool, scanOutputFormat outFormat.OutputFormat, cleanArgs []string, buildConfig *build.BuildConfiguration, err error) { cleanArgs = append([]string(nil), args...) diff --git a/artifactory/commands/utils/npmcmdutils_test.go b/artifactory/commands/utils/npmcmdutils_test.go index 2dc46efb9..e16e3d7ad 100644 --- a/artifactory/commands/utils/npmcmdutils_test.go +++ b/artifactory/commands/utils/npmcmdutils_test.go @@ -81,8 +81,8 @@ func TestGetNpmAuth(t *testing.T) { } } -// Helper function to set up the NpmrcYarnrcManager. -func setupNpmrcManager(buildTool project.ProjectType) *NpmrcYarnrcManager { +// Helper function to set up the BuildToolLogin. +func setupNpmrcManager(buildTool project.ProjectType) *BuildToolLogin { serverDetails := &config.ServerDetails{ ArtifactoryUrl: testArtifactoryUrl, } @@ -184,7 +184,7 @@ func TestHandleNpmrcBasicAuth(t *testing.T) { tempDir := t.TempDir() t.Setenv("NPM_CONFIG_USERCONFIG", filepath.Join(tempDir, ".npmrc")) - // Set up the NpmrcYarnrcManager for npm build tool. + // Set up the BuildToolLogin for npm build tool. nm := setupNpmrcManager(project.Npm) // Actual username and password for testing. diff --git a/artifactory/commands/yarn/login.go b/artifactory/commands/yarn/login.go deleted file mode 100644 index 37f57bec0..000000000 --- a/artifactory/commands/yarn/login.go +++ /dev/null @@ -1,71 +0,0 @@ -package yarn - -import ( - "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" - cmdutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" - "github.com/jfrog/jfrog-cli-core/v2/common/project" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-client-go/artifactory/services" - "github.com/jfrog/jfrog-client-go/utils/log" -) - -type YarnLoginCommand struct { - commandName string - repo string - serverDetails *config.ServerDetails -} - -func NewYarnLoginCommand() *YarnLoginCommand { - return &YarnLoginCommand{commandName: "rt_yarn_login"} -} - -// Run configures Yarn to use the specified or selected JFrog Artifactory repository -// for package management, setting up registry and authentication. -func (ylc *YarnLoginCommand) Run() (err error) { - // If no repository is specified, prompt the user to select an npm-compatible repository. - if ylc.repo == "" { - // Define filter parameters to select virtual repositories of npm package type. - repoFilterParams := services.RepositoriesFilterParams{ - RepoType: utils.Virtual.String(), - PackageType: repository.Npm, - } - - // Select repository interactively based on filter parameters and server details. - ylc.repo, err = utils.SelectRepositoryInteractively(ylc.serverDetails, repoFilterParams) - if err != nil { - return err - } - } - - // Initialize NpmrcYarnrcManager for Yarn to manage registry and authentication configurations. - npmrcManager := cmdutils.NewNpmrcYarnrcManager(project.Yarn, ylc.repo, ylc.serverDetails) - - // Configure the registry URL for Yarn in the Yarn configuration. - if err = npmrcManager.ConfigureRegistry(); err != nil { - return err - } - - // Configure authentication settings, handling token or basic auth as needed. - if err = npmrcManager.ConfigureAuth(); err != nil { - return err - } - - // Output success message indicating successful Yarn configuration. - log.Output(coreutils.PrintTitle("Successfully configured yarn client to work with your JFrog Artifactory repository: " + ylc.repo)) - return nil -} - -func (ylc *YarnLoginCommand) SetServerDetails(serverDetails *config.ServerDetails) *YarnLoginCommand { - ylc.serverDetails = serverDetails - return ylc -} - -func (ylc *YarnLoginCommand) ServerDetails() (*config.ServerDetails, error) { - return ylc.serverDetails, nil -} - -func (ylc *YarnLoginCommand) CommandName() string { - return ylc.commandName -} diff --git a/artifactory/commands/yarn/login_test.go b/artifactory/commands/yarn/login_test.go deleted file mode 100644 index 5822f1a94..000000000 --- a/artifactory/commands/yarn/login_test.go +++ /dev/null @@ -1,93 +0,0 @@ -package yarn - -import ( - "fmt" - cmdutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" - "github.com/stretchr/testify/assert" - "os" - "path/filepath" - "testing" -) - -func TestYarnLoginCommand(t *testing.T) { - // Retrieve the home directory and construct the .yarnrc file path. - homeDir, err := os.UserHomeDir() - assert.NoError(t, err) - yarnrcPath := filepath.Join(homeDir, ".yarnrc") - - // Back up the existing .yarnrc file and ensure restoration after the test. - restoreYarnrcFunc, err := ioutils.BackupFile(yarnrcPath, ".yarnrc.backup") - assert.NoError(t, err) - defer func() { - assert.NoError(t, restoreYarnrcFunc()) - }() - - // Create a new YarnLoginCommand instance. - loginCmd := NewYarnLoginCommand() - loginCmd.SetServerDetails(&config.ServerDetails{ArtifactoryUrl: "https://acme.jfrog.io/artifactory"}) - loginCmd.repo = "npm-virtual" - - // Define test cases with different authentication methods. - testCases := []struct { - name string - user string - password string - accessToken string - }{ - { - name: "Token Authentication", - accessToken: "test-token", - }, - { - name: "Basic Authentication", - user: "myUser", - password: "myPassword", - }, - { - name: "Anonymous Access", - }, - } - - for _, testCase := range testCases { - t.Run(testCase.name, func(t *testing.T) { - // Configure the command's server details for each test case. - loginCmd.serverDetails.SetUser(testCase.user) - loginCmd.serverDetails.SetPassword(testCase.password) - loginCmd.serverDetails.SetAccessToken(testCase.accessToken) - - // Run the login command and check for errors. - assert.NoError(t, loginCmd.Run()) - - // Read the content of .yarnrc to verify configuration. - yarnrcContentBytes, err := os.ReadFile(yarnrcPath) - assert.NoError(t, err) - yarnrcContent := string(yarnrcContentBytes) - - // Check that the registry URL is correctly set in .yarnrc. - assert.Contains(t, yarnrcContent, fmt.Sprintf("%s \"%s\"", cmdutils.NpmConfigRegistryKey, "https://acme.jfrog.io/artifactory/api/npm/npm-virtual")) - - // Define expected keys for basic auth and token-based auth in .yarnrc. - basicAuthKey := fmt.Sprintf("\"//acme.jfrog.io/artifactory/api/npm/npm-virtual:%s\"", cmdutils.NpmConfigAuthKey) - tokenAuthKey := fmt.Sprintf("\"//acme.jfrog.io/artifactory/api/npm/npm-virtual:%s\"", cmdutils.NpmConfigAuthTokenKey) - - // Verify the .yarnrc file contents according to the authentication method. - switch { - case testCase.accessToken != "": - assert.Contains(t, yarnrcContent, tokenAuthKey+" test-token") - assert.NotContains(t, yarnrcContent, basicAuthKey) - - case testCase.user != "" && testCase.password != "": - // Base64 encoding of "myUser:myPassword" - expectedBasicAuth := basicAuthKey + " bXlVc2VyOm15UGFzc3dvcmQ=" - assert.Contains(t, yarnrcContent, expectedBasicAuth) - assert.NotContains(t, yarnrcContent, tokenAuthKey) - - default: - assert.NotContains(t, yarnrcContent, basicAuthKey) - assert.NotContains(t, yarnrcContent, tokenAuthKey) - } - }) - } -} diff --git a/utils/python/utils.go b/utils/python/utils.go deleted file mode 100644 index 7a599b28a..000000000 --- a/utils/python/utils.go +++ /dev/null @@ -1,135 +0,0 @@ -package utils - -import ( - "fmt" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "net/url" - "os" - "path/filepath" - "strings" - - "github.com/jfrog/build-info-go/utils/pythonutils" - "github.com/jfrog/gofrog/io" - gofrogcmd "github.com/jfrog/gofrog/io" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-client-go/auth" - "github.com/jfrog/jfrog-client-go/utils/errorutils" - "github.com/jfrog/jfrog-client-go/utils/log" - "github.com/spf13/viper" -) - -const ( - pipenvRemoteRegistryFlag = "--pypi-mirror" - pipRemoteRegistryFlag = "-i" - poetryConfigAuthPrefix = "http-basic." - poetryConfigRepoPrefix = "repositories." - pyproject = "pyproject.toml" -) - -func GetPypiRepoUrlWithCredentials(serverDetails *config.ServerDetails, repository string, isCurationCmd bool) (*url.URL, string, string, error) { - rtUrl, err := url.Parse(serverDetails.GetArtifactoryUrl()) - if err != nil { - return nil, "", "", errorutils.CheckError(err) - } - - username := serverDetails.GetUser() - password := serverDetails.GetPassword() - - // Get credentials from access-token if exists. - if serverDetails.GetAccessToken() != "" { - if username == "" { - username = auth.ExtractUsernameFromAccessToken(serverDetails.GetAccessToken()) - } - password = serverDetails.GetAccessToken() - } - // In case of curation command, the download urls should be routed through a dedicated api. - if isCurationCmd { - rtUrl.Path += coreutils.CurationPassThroughApi - } - rtUrl.Path += "api/pypi/" + repository + "/simple" - return rtUrl, username, password, err -} - -func GetPypiRemoteRegistryFlag(tool pythonutils.PythonTool) string { - if tool == pythonutils.Pip { - return pipRemoteRegistryFlag - } - return pipenvRemoteRegistryFlag -} - -func GetPypiRepoUrl(serverDetails *config.ServerDetails, repository string, isCurationCmd bool) (string, error) { - rtUrl, username, password, err := GetPypiRepoUrlWithCredentials(serverDetails, repository, isCurationCmd) - if err != nil { - return "", err - } - if password != "" { - rtUrl.User = url.UserPassword(username, password) - } - return rtUrl.String(), err -} - -func ConfigPoetryRepo(url, username, password, configRepoName string) error { - // Add the poetry repository config - err := runPoetryConfigCommand([]string{poetryConfigRepoPrefix + configRepoName, url}, false) - if err != nil { - return err - } - - // Set the poetry repository credentials - err = runPoetryConfigCommand([]string{poetryConfigAuthPrefix + configRepoName, username, password}, true) - if err != nil { - return err - } - - // Add the repository config to the pyproject.toml - currentDir, err := os.Getwd() - if err != nil { - return errorutils.CheckError(err) - } - if err = addRepoToPyprojectFile(filepath.Join(currentDir, pyproject), configRepoName, url); err != nil { - return err - } - return poetryUpdate() -} - -func poetryUpdate() (err error) { - log.Info("Running Poetry update") - cmd := io.NewCommand("poetry", "update", []string{}) - err = gofrogcmd.RunCmd(cmd) - if err != nil { - return errorutils.CheckErrorf("Poetry config command failed with: %s", err.Error()) - } - return -} - -func runPoetryConfigCommand(args []string, maskArgs bool) error { - logMessage := "config " - if maskArgs { - logMessage += "***" - } else { - logMessage += strings.Join(args, " ") - } - log.Info(fmt.Sprintf("Running Poetry %s", logMessage)) - cmd := io.NewCommand("poetry", "config", args) - err := gofrogcmd.RunCmd(cmd) - if err != nil { - return errorutils.CheckErrorf("Poetry config command failed with: %s", err.Error()) - } - return nil -} - -func addRepoToPyprojectFile(filepath, poetryRepoName, repoUrl string) error { - viper.SetConfigType("toml") - viper.SetConfigFile(filepath) - err := viper.ReadInConfig() - if err != nil { - return errorutils.CheckErrorf("Failed to read pyproject.toml: %s", err.Error()) - } - viper.Set("tool.poetry.source", []map[string]string{{"name": poetryRepoName, "url": repoUrl}}) - err = viper.WriteConfig() - if err != nil { - return errorutils.CheckErrorf("Failed to add tool.poetry.source to pyproject.toml: %s", err.Error()) - } - log.Info(fmt.Sprintf("Added tool.poetry.source name:%q url:%q", poetryRepoName, repoUrl)) - return err -} From 74c9a477bc953202da66675b4f26106a7250384d Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Sun, 3 Nov 2024 18:07:19 +0200 Subject: [PATCH 06/41] Improve repositores code Signed-off-by: Michael Sverdlov --- .../buildtoollogin/buildtoollogin_test.go | 20 ++++++++++--------- artifactory/commands/python/utils.go | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/buildtoollogin_test.go index 8cf8228de..34cbf97b1 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin_test.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin_test.go @@ -7,6 +7,7 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" + "github.com/jfrog/jfrog-client-go/auth" "github.com/stretchr/testify/assert" "os" "path/filepath" @@ -119,7 +120,7 @@ func TestBuildToolLoginCommand_Yarn(t *testing.T) { }{ { name: "Token Authentication", - accessToken: "test-token", + accessToken: dummyToken, }, { name: "Basic Authentication", @@ -201,7 +202,7 @@ func TestBuildToolLoginCommand_Pip(t *testing.T) { }{ { name: "Token Authentication", - accessToken: "test-token", + accessToken: dummyToken, }, { name: "Basic Authentication", @@ -233,15 +234,16 @@ func TestBuildToolLoginCommand_Pip(t *testing.T) { assert.NoError(t, err) pipConfigContent := string(pipConfigContentBytes) - // Validate that the index URL was set correctly in pip.conf. - assert.Contains(t, pipConfigContent, fmt.Sprintf("index-url = https://%s:%s@acme.jfrog.io/artifactory/api/pypi/test-repo/simple", testCase.user, testCase.password)) - - // Validate token-based authentication. - if testCase.accessToken != "" { - assert.Contains(t, pipConfigContent, fmt.Sprintf("index-url = https://%s@acme.jfrog.io/artifactory/api/pypi/test-repo/simple", "test-token")) - } else if testCase.user != "" && testCase.password != "" { + switch { + case testCase.accessToken != "": + // Validate token-based authentication. + assert.Contains(t, pipConfigContent, fmt.Sprintf("index-url = https://%s:%s@acme.jfrog.io/artifactory/api/pypi/test-repo/simple", auth.ExtractUsernameFromAccessToken(testCase.accessToken), testCase.accessToken)) + case testCase.user != "" && testCase.password != "": // Validate basic authentication with user and password. assert.Contains(t, pipConfigContent, fmt.Sprintf("index-url = https://%s:%s@acme.jfrog.io/artifactory/api/pypi/test-repo/simple", "myUser", "myPassword")) + default: + // Validate anonymous access. + assert.Contains(t, pipConfigContent, "index-url = https://acme.jfrog.io/artifactory/api/pypi/test-repo/simple") } }) } diff --git a/artifactory/commands/python/utils.go b/artifactory/commands/python/utils.go index 9731387ab..416a913f6 100644 --- a/artifactory/commands/python/utils.go +++ b/artifactory/commands/python/utils.go @@ -52,7 +52,7 @@ func GetPypiRemoteRegistryFlag(tool pythonutils.PythonTool) string { return pipenvRemoteRegistryFlag } -// Get the pypi repository embedded credentials URL (https://:@/artifactory/api/pypi//simple) +// Get the pypi repository embedded credentials URL (https://:@/artifactory/api/pypi//simple) func GetPypiRepoUrl(serverDetails *config.ServerDetails, repository string, isCurationCmd bool) (string, error) { rtUrl, username, password, err := GetPypiRepoUrlWithCredentials(serverDetails, repository, isCurationCmd) if err != nil { From e0e502498408696ef95fddabff584b694bba0aaa Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 4 Nov 2024 16:37:33 +0200 Subject: [PATCH 07/41] Improve repositores code Signed-off-by: Michael Sverdlov --- .../commands/buildtoollogin/buildtoollogin.go | 20 +-- .../buildtoollogin/buildtoollogin_test.go | 169 ++++++++++-------- 2 files changed, 100 insertions(+), 89 deletions(-) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin.go b/artifactory/commands/buildtoollogin/buildtoollogin.go index 54cbce8b8..7e0e33dd5 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin.go @@ -51,17 +51,15 @@ func (btlc *BuildToolLoginCommand) Run() (err error) { err = btlc.configureNpm() case project.Yarn: err = btlc.configureYarn() - case project.Pip: + case project.Pip, project.Pipenv: err = btlc.configurePip() - case project.Pipenv: - err = btlc.configurePipenv() case project.Poetry: err = btlc.configurePoetry() default: err = errorutils.CheckErrorf("unsupported build tool: %s", btlc.buildTool) } if err != nil { - return err + return fmt.Errorf("failed to configure %s: %w", btlc.buildTool.String(), err) } log.Info(fmt.Sprintf("Successfully configured %s to use JFrog Artifactory repository '%s'.", btlc.buildTool.String(), btlc.repoName)) @@ -86,7 +84,7 @@ func (btlc *BuildToolLoginCommand) SetVirtualRepoNameInteractively() error { return err } -// configurePip sets the global index-url for pip to use Artifactory. +// configurePip sets the global index-url for pip/pipenv to use Artifactory. // Running the following commands: // // pip config set global index-url https://:@/artifactory/api/pypi//simple @@ -98,18 +96,6 @@ func (btlc *BuildToolLoginCommand) configurePip() error { return pythoncommands.RunConfigCommand(btlc.buildTool, []string{"set", "global.index-url", repoWithCredsUrl}) } -// configurePipenv sets the PyPI URL for pipenv to use Artifactory. -// Running the following commands: -// -// pipenv config set pypi.url https://:@/artifactory/api/pypi//simple -func (btlc *BuildToolLoginCommand) configurePipenv() error { - repoWithCredsUrl, err := pythoncommands.GetPypiRepoUrl(btlc.serverDetails, btlc.repoName, false) - if err != nil { - return err - } - return pythoncommands.RunConfigCommand(btlc.buildTool, []string{"set", "pypi.url", repoWithCredsUrl}) -} - // configurePoetry configures a Poetry repository and basic auth credentials. // Running the following commands: // diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/buildtoollogin_test.go index 34cbf97b1..89da73da4 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin_test.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin_test.go @@ -8,6 +8,7 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" "github.com/jfrog/jfrog-client-go/auth" + "github.com/jfrog/jfrog-client-go/utils/io" "github.com/stretchr/testify/assert" "os" "path/filepath" @@ -17,6 +18,26 @@ import ( // #nosec G101 -- Dummy token for tests var dummyToken = "eyJ2ZXIiOiIyIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYiLCJraWQiOiJIcnU2VHctZk1yOTV3dy12TDNjV3ZBVjJ3Qm9FSHpHdGlwUEFwOE1JdDljIn0.eyJzdWIiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3XC91c2Vyc1wvYWRtaW4iLCJzY3AiOiJtZW1iZXItb2YtZ3JvdXBzOnJlYWRlcnMgYXBpOioiLCJhdWQiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3IiwiaXNzIjoiamZydEAwMWMzZ2ZmaGcyZTh3NjE0OWUzYTJxMHc5NyIsImV4cCI6MTU1NjAzNzc2NSwiaWF0IjoxNTU2MDM0MTY1LCJqdGkiOiI1M2FlMzgyMy05NGM3LTQ0OGItOGExOC1iZGVhNDBiZjFlMjAifQ.Bp3sdvppvRxysMlLgqT48nRIHXISj9sJUCXrm7pp8evJGZW1S9hFuK1olPmcSybk2HNzdzoMcwhUmdUzAssiQkQvqd_HanRcfFbrHeg5l1fUQ397ECES-r5xK18SYtG1VR7LNTVzhJqkmRd3jzqfmIK2hKWpEgPfm8DRz3j4GGtDRxhb3oaVsT2tSSi_VfT3Ry74tzmO0GcCvmBE2oh58kUZ4QfEsalgZ8IpYHTxovsgDx_M7ujOSZx_hzpz-iy268-OkrU22PQPCfBmlbEKeEUStUO9n0pj4l1ODL31AGARyJRy46w4yzhw7Fk5P336WmDMXYs5LAX2XxPFNLvNzA" +var testCases = []struct { + name string + user string + password string + accessToken string +}{ + { + name: "Token Authentication", + accessToken: dummyToken, + }, + { + name: "Basic Authentication", + user: "myUser", + password: "myPassword", + }, + { + name: "Anonymous Access", + }, +} + func createTestBuildToolLoginCommand(buildTool project.ProjectType) *BuildToolLoginCommand { cmd := NewBuildToolLoginCommand(buildTool) cmd.repoName = "test-repo" @@ -35,27 +56,6 @@ func TestBuildToolLoginCommand_Npm(t *testing.T) { npmLoginCmd := createTestBuildToolLoginCommand(project.Npm) - // Define test cases for different authentication types. - testCases := []struct { - name string - user string - password string - accessToken string - }{ - { - name: "Token Authentication", - accessToken: dummyToken, - }, - { - name: "Basic Authentication", - user: "myUser", - password: "myPassword", - }, - { - name: "Anonymous Access", - }, - } - for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { // Set up server details for the current test case's authentication type. @@ -64,7 +64,7 @@ func TestBuildToolLoginCommand_Npm(t *testing.T) { npmLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) // Run the login command and ensure no errors occur. - if npmLoginCmd.Run() != nil { + if !assert.NoError(t, npmLoginCmd.Run()) { t.FailNow() } @@ -111,27 +111,6 @@ func TestBuildToolLoginCommand_Yarn(t *testing.T) { yarnLoginCmd := createTestBuildToolLoginCommand(project.Yarn) - // Define test cases for different authentication types. - testCases := []struct { - name string - user string - password string - accessToken string - }{ - { - name: "Token Authentication", - accessToken: dummyToken, - }, - { - name: "Basic Authentication", - user: "myUser", - password: "myPassword", - }, - { - name: "Anonymous Access", - }, - } - for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { // Set up server details for the current test case's authentication type. @@ -140,7 +119,7 @@ func TestBuildToolLoginCommand_Yarn(t *testing.T) { yarnLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) // Run the login command and ensure no errors occur. - if yarnLoginCmd.Run() != nil { + if !assert.NoError(t, yarnLoginCmd.Run()) { t.FailNow() } @@ -174,6 +153,16 @@ func TestBuildToolLoginCommand_Yarn(t *testing.T) { } func TestBuildToolLoginCommand_Pip(t *testing.T) { + // pip and pipenv share the same configuration file. + testBuildToolLoginCommandPip(t, project.Pip) +} + +func TestBuildToolLoginCommand_Pipenv(t *testing.T) { + // pip and pipenv share the same configuration file. + testBuildToolLoginCommandPip(t, project.Pipenv) +} + +func testBuildToolLoginCommandPip(t *testing.T, buildTool project.ProjectType) { // Retrieve the home directory and construct the pip.conf file path. homeDir, err := os.UserHomeDir() assert.NoError(t, err) @@ -191,28 +180,7 @@ func TestBuildToolLoginCommand_Pip(t *testing.T) { assert.NoError(t, restorePipConfFunc()) }() - pipLoginCmd := createTestBuildToolLoginCommand(project.Pip) - - // Define test cases for different authentication types. - testCases := []struct { - name string - user string - password string - accessToken string - }{ - { - name: "Token Authentication", - accessToken: dummyToken, - }, - { - name: "Basic Authentication", - user: "myUser", - password: "myPassword", - }, - { - name: "Anonymous Access", - }, - } + pipLoginCmd := createTestBuildToolLoginCommand(buildTool) for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { @@ -225,7 +193,7 @@ func TestBuildToolLoginCommand_Pip(t *testing.T) { pipLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) // Run the login command and ensure no errors occur. - if pipLoginCmd.Run() != nil { + if !assert.NoError(t, pipLoginCmd.Run()) { t.FailNow() } @@ -248,11 +216,68 @@ func TestBuildToolLoginCommand_Pip(t *testing.T) { }) } } +func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { + // Retrieve the home directory and construct the .yarnrc file path. + homeDir, err := os.UserHomeDir() + assert.NoError(t, err) + var poetryConfigDir string + switch { + case io.IsWindows(): + poetryConfigDir = filepath.Join(homeDir, "AppData", "Roaming") + case io.IsMacOS(): + poetryConfigDir = filepath.Join(homeDir, "Library", "Application Support") + default: + poetryConfigDir = filepath.Join(homeDir, ".config") + } + // Poetry uses keyring by default, we need to disable it so the password/token will be stored in the config file and could be tested. + t.Setenv("POETRY_NO_KEYRING", "1") -func TestBuildToolLoginCommand_configurePipenv(t *testing.T) { - // todo -} + poetryConfigFilePath := filepath.Join(poetryConfigDir, "pypoetry", "config.toml") + poetryAuthFilePath := filepath.Join(poetryConfigDir, "pypoetry", "auth.toml") -func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { - // todo + // Back up the existing config.toml and auth.toml files and ensure restoration after the test. + restorePoetryConfigFunc, err := ioutils.BackupFile(poetryConfigFilePath, ".poetry.config.backup") + assert.NoError(t, err) + restorePoetryAuthFunc, err := ioutils.BackupFile(poetryAuthFilePath, ".poetry-auth.backup") + assert.NoError(t, err) + defer func() { + assert.NoError(t, restorePoetryConfigFunc()) + assert.NoError(t, restorePoetryAuthFunc()) + }() + + poetryLoginCmd := createTestBuildToolLoginCommand(project.Poetry) + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + // Clean up the temporary Poetry config file. + assert.NoError(t, os.Remove(poetryConfigFilePath)) + + // Set up server details for the current test case's authentication type. + poetryLoginCmd.serverDetails.SetUser(testCase.user) + poetryLoginCmd.serverDetails.SetPassword(testCase.password) + poetryLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) + + // Run the login command and ensure no errors occur. + if !assert.NoError(t, poetryLoginCmd.Run()) { + t.FailNow() + } + + // Read the contents of the temporary Poetry config file. + poetryConfigContentBytes, err := os.ReadFile(poetryConfigFilePath) + assert.NoError(t, err) + poetryConfigContent := string(poetryConfigContentBytes) + + switch { + case testCase.accessToken != "": + // Validate token-based authentication. + assert.Contains(t, poetryConfigContent, fmt.Sprintf("[repositories.test-repo]\nurl = \"https://%s:%s@acme.jfrog.io/artifactory/api/pypi/test-repo/simple\"", auth.ExtractUsernameFromAccessToken(testCase.accessToken), testCase.accessToken)) + case testCase.user != "" && testCase.password != "": + // Validate basic authentication with user and password. + assert.Contains(t, poetryConfigContent, fmt.Sprintf("[repositories.test-repo]\nurl = \"https://%s:%s@acme.jfrog.io/artifactory/api/pypi/test-repo/simple\"", "myUser", "myPassword")) + default: + // Validate anonymous access. + assert.Contains(t, poetryConfigContent, "[repositories.test-repo]\nurl = \"https://acme.jfrog.io/artifactory/api/pypi/test-repo/simple\"") + } + }) + } } From f51ae6d56c47797a96d4d7a0b5bf73932dc2c4aa Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 4 Nov 2024 16:59:59 +0200 Subject: [PATCH 08/41] Improve repositores code Signed-off-by: Michael Sverdlov --- .../buildtoollogin/buildtoollogin_test.go | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/buildtoollogin_test.go index 89da73da4..25d0936b3 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin_test.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin_test.go @@ -229,10 +229,9 @@ func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { default: poetryConfigDir = filepath.Join(homeDir, ".config") } - // Poetry uses keyring by default, we need to disable it so the password/token will be stored in the config file and could be tested. - t.Setenv("POETRY_NO_KEYRING", "1") poetryConfigFilePath := filepath.Join(poetryConfigDir, "pypoetry", "config.toml") + // Poetry stores the auth in a separate file poetryAuthFilePath := filepath.Join(poetryConfigDir, "pypoetry", "auth.toml") // Back up the existing config.toml and auth.toml files and ensure restoration after the test. @@ -249,9 +248,6 @@ func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - // Clean up the temporary Poetry config file. - assert.NoError(t, os.Remove(poetryConfigFilePath)) - // Set up server details for the current test case's authentication type. poetryLoginCmd.serverDetails.SetUser(testCase.user) poetryLoginCmd.serverDetails.SetPassword(testCase.password) @@ -262,22 +258,29 @@ func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { t.FailNow() } + // Validate that the repository URL was set correctly in config.toml. // Read the contents of the temporary Poetry config file. poetryConfigContentBytes, err := os.ReadFile(poetryConfigFilePath) assert.NoError(t, err) poetryConfigContent := string(poetryConfigContentBytes) + assert.Contains(t, poetryConfigContent, "[repositories.test-repo]\nurl = \"https://acme.jfrog.io/artifactory/api/pypi/test-repo/simple\"") - switch { - case testCase.accessToken != "": - // Validate token-based authentication. - assert.Contains(t, poetryConfigContent, fmt.Sprintf("[repositories.test-repo]\nurl = \"https://%s:%s@acme.jfrog.io/artifactory/api/pypi/test-repo/simple\"", auth.ExtractUsernameFromAccessToken(testCase.accessToken), testCase.accessToken)) - case testCase.user != "" && testCase.password != "": - // Validate basic authentication with user and password. - assert.Contains(t, poetryConfigContent, fmt.Sprintf("[repositories.test-repo]\nurl = \"https://%s:%s@acme.jfrog.io/artifactory/api/pypi/test-repo/simple\"", "myUser", "myPassword")) - default: - // Validate anonymous access. - assert.Contains(t, poetryConfigContent, "[repositories.test-repo]\nurl = \"https://acme.jfrog.io/artifactory/api/pypi/test-repo/simple\"") + // Validate that the auth details were set correctly in auth.toml. + // Read the contents of the temporary Poetry config file. + poetryAuthContentBytes, err := os.ReadFile(poetryAuthFilePath) + assert.NoError(t, err) + poetryAuthContent := string(poetryAuthContentBytes) + if testCase.accessToken != "" { + // Validate token-based authentication (The token is stored in the keyring so we can't test it) + assert.Contains(t, poetryAuthContent, fmt.Sprintf("[http-basic.test-repo]\nusername = \"%s\"", auth.ExtractUsernameFromAccessToken(testCase.accessToken))) + } else if testCase.user != "" && testCase.password != "" { + // Validate basic authentication with user and password. (The password is stored in the keyring so we can't test it) + assert.Contains(t, poetryAuthContent, fmt.Sprintf("[http-basic.test-repo]\nusername = \"%s\"", "myUser")) } + + // Clean up the temporary Poetry config files. + assert.NoError(t, os.Remove(poetryConfigFilePath)) + assert.NoError(t, os.Remove(poetryAuthFilePath)) }) } } From f8e793a4fdcff2550485eafd9ef44a6a9d4e6d53 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 6 Nov 2024 11:53:17 +0200 Subject: [PATCH 09/41] Improve repositores code Signed-off-by: Michael Sverdlov --- .../commands/buildtoollogin/buildtoollogin.go | 110 +++++++++++------- .../buildtoollogin/buildtoollogin_test.go | 51 +++++--- 2 files changed, 103 insertions(+), 58 deletions(-) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin.go b/artifactory/commands/buildtoollogin/buildtoollogin.go index 7e0e33dd5..6a70704ad 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin.go @@ -2,6 +2,7 @@ package buildtoollogin import ( "fmt" + biutils "github.com/jfrog/build-info-go/utils" pythoncommands "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/python" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" commandsutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" @@ -10,26 +11,27 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/yarn" "github.com/jfrog/jfrog-cli-core/v2/common/project" "github.com/jfrog/jfrog-cli-core/v2/utils/config" + goutils "github.com/jfrog/jfrog-cli-core/v2/utils/golang" "github.com/jfrog/jfrog-client-go/artifactory/services" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" ) -// BuildToolLoginCommand configures npm, Yarn, Pip, Pipenv, and Poetry registries and authentication +// BuildToolLoginCommand configures registries and authentication for various build tools (npm, Yarn, Pip, Pipenv, Poetry, Go) // based on the specified project type. type BuildToolLoginCommand struct { - // buildTool represents the project type, either NPM or Yarn. + // buildTool represents the type of project (e.g., NPM, Yarn). buildTool project.ProjectType - // repoName holds the name of the repository. + // repoName is the name of the repository used for configuration. repoName string - // serverDetails contains configuration details for the Artifactory server. + // serverDetails contains Artifactory server configuration. serverDetails *config.ServerDetails - // commandName holds the name of the command. + // commandName specifies the command for this instance. commandName string } -// NewBuildToolLogin initializes a new BuildToolLogin with the given project type, -// repository name, and Artifactory server details. +// NewBuildToolLoginCommand initializes a new BuildToolLoginCommand for the specified project type +// and automatically sets a command name for the login operation. func NewBuildToolLoginCommand(buildTool project.ProjectType) *BuildToolLoginCommand { return &BuildToolLoginCommand{ buildTool: buildTool, @@ -37,15 +39,44 @@ func NewBuildToolLoginCommand(buildTool project.ProjectType) *BuildToolLoginComm } } -// Run executes the appropriate configuration method based on the project type. +// buildToolToPackageType maps project types to corresponding Artifactory package types (e.g., npm, pypi). +func buildToolToPackageType(buildTool project.ProjectType) (string, error) { + switch buildTool { + case project.Npm, project.Yarn: + return repository.Npm, nil + case project.Pip, project.Pipenv, project.Poetry: + return repository.Pypi, nil + default: + return "", errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", buildTool)) + } +} + +// CommandName returns the name of the login command. +func (btlc *BuildToolLoginCommand) CommandName() string { + return btlc.commandName +} + +// SetServerDetails assigns the server configuration details to the command. +func (btlc *BuildToolLoginCommand) SetServerDetails(serverDetails *config.ServerDetails) *BuildToolLoginCommand { + btlc.serverDetails = serverDetails + return btlc +} + +// ServerDetails returns the stored server configuration details. +func (btlc *BuildToolLoginCommand) ServerDetails() (*config.ServerDetails, error) { + return btlc.serverDetails, nil +} + +// Run executes the configuration method corresponding to the project type specified for the command. func (btlc *BuildToolLoginCommand) Run() (err error) { - // If no repository is specified, prompt the user to select a compatible repository. + // Prompt the user to select a repository if none has been specified. if btlc.repoName == "" { if err = btlc.SetVirtualRepoNameInteractively(); err != nil { return err } } + // Configure the appropriate tool based on the project type. switch btlc.buildTool { case project.Npm: err = btlc.configureNpm() @@ -55,6 +86,8 @@ func (btlc *BuildToolLoginCommand) Run() (err error) { err = btlc.configurePip() case project.Poetry: err = btlc.configurePoetry() + case project.Go: + err = btlc.configureGo() default: err = errorutils.CheckErrorf("unsupported build tool: %s", btlc.buildTool) } @@ -66,28 +99,27 @@ func (btlc *BuildToolLoginCommand) Run() (err error) { return nil } -// SetVirtualRepoNameInteractively prompts the user to select a virtual repository +// SetVirtualRepoNameInteractively prompts the user to select a compatible virtual repository. func (btlc *BuildToolLoginCommand) SetVirtualRepoNameInteractively() error { - // Get the package type that corresponds to the build tool. + // Map the build tool to its corresponding package type. packageType, err := buildToolToPackageType(btlc.buildTool) if err != nil { return err } - // Define filter parameters to select virtual repositories of npm package type. repoFilterParams := services.RepositoriesFilterParams{ RepoType: utils.Virtual.String(), PackageType: packageType, } - // Select repository interactively based on filter parameters and server details. + // Prompt for repository selection based on filter parameters. btlc.repoName, err = utils.SelectRepositoryInteractively(btlc.serverDetails, repoFilterParams) return err } -// configurePip sets the global index-url for pip/pipenv to use Artifactory. -// Running the following commands: +// configurePip sets the global index-url for pip and pipenv to use the Artifactory PyPI repository. +// Runs the following command: // -// pip config set global index-url https://:@/artifactory/api/pypi//simple +// pip config set global.index-url https://:@/artifactory/api/pypi//simple func (btlc *BuildToolLoginCommand) configurePip() error { repoWithCredsUrl, err := pythoncommands.GetPypiRepoUrl(btlc.serverDetails, btlc.repoName, false) if err != nil { @@ -96,8 +128,8 @@ func (btlc *BuildToolLoginCommand) configurePip() error { return pythoncommands.RunConfigCommand(btlc.buildTool, []string{"set", "global.index-url", repoWithCredsUrl}) } -// configurePoetry configures a Poetry repository and basic auth credentials. -// Running the following commands: +// configurePoetry configures Poetry to use the specified repository and authentication credentials. +// Runs the following commands: // // poetry config repositories. https:///artifactory/api/pypi//simple // poetry config http-basic. @@ -109,8 +141,8 @@ func (btlc *BuildToolLoginCommand) configurePoetry() error { return pythoncommands.RunPoetryConfig(repoUrl.String(), username, password, btlc.repoName) } -// configureNpm sets the registry URL and auth for npm to use Artifactory. -// Running the following commands: +// configureNpm configures npm to use the Artifactory repository URL and sets authentication. +// Runs the following commands: // // npm config set registry https:///artifactory/api/npm/ // @@ -118,7 +150,7 @@ func (btlc *BuildToolLoginCommand) configurePoetry() error { // // npm config set //your-artifactory-url/artifactory/api/npm//:_authToken "" // -// For basic auth (username:password): +// For basic auth: // // npm config set //your-artifactory-url/artifactory/api/npm//:_auth "" func (btlc *BuildToolLoginCommand) configureNpm() error { @@ -135,8 +167,8 @@ func (btlc *BuildToolLoginCommand) configureNpm() error { return nil } -// configureYarn sets the registry URL and auth for Yarn to use Artifactory. -// Running the following commands: +// configureYarn configures Yarn to use the specified Artifactory repository and sets authentication. +// Runs the following commands: // // yarn config set registry https:///artifactory/api/npm/ // @@ -144,7 +176,7 @@ func (btlc *BuildToolLoginCommand) configureNpm() error { // // yarn config set //your-artifactory-url/artifactory/api/npm//:_authToken "" // -// For basic auth (username:password): +// For basic auth: // // yarn config set //your-artifactory-url/artifactory/api/npm//:_auth "" func (btlc *BuildToolLoginCommand) configureYarn() error { @@ -161,26 +193,14 @@ func (btlc *BuildToolLoginCommand) configureYarn() error { return nil } -// buildToolToPackageType maps the project type to the corresponding package type. -func buildToolToPackageType(buildTool project.ProjectType) (string, error) { - switch buildTool { - case project.Npm, project.Yarn: - return repository.Npm, nil - case project.Pip, project.Pipenv, project.Poetry: - return repository.Pypi, nil - default: - return "", errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", buildTool)) +// configureGo configures Go to use the Artifactory repository for GOPROXY. +// Runs the following command: +// +// go env -w GOPROXY=https://:@/artifactory/go/,direct +func (btlc *BuildToolLoginCommand) configureGo() error { + repoWithCredsUrl, err := goutils.GetArtifactoryRemoteRepoUrl(btlc.serverDetails, btlc.repoName, goutils.GoProxyUrlParams{Direct: true}) + if err != nil { + return err } -} - -func (btlc *BuildToolLoginCommand) CommandName() string { - return btlc.commandName -} - -func (btlc *BuildToolLoginCommand) SetServerDetails(serverDetails *config.ServerDetails) *BuildToolLoginCommand { - btlc.serverDetails = serverDetails - return btlc -} -func (btlc *BuildToolLoginCommand) ServerDetails() (*config.ServerDetails, error) { - return btlc.serverDetails, nil + return biutils.RunGo([]string{"env", "-w", "GOPROXY=" + repoWithCredsUrl}, "") } diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/buildtoollogin_test.go index 25d0936b3..86b6af2e8 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin_test.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin_test.go @@ -76,17 +76,13 @@ func TestBuildToolLoginCommand_Npm(t *testing.T) { // Validate that the registry URL was set correctly in .npmrc. assert.Contains(t, npmrcContent, fmt.Sprintf("%s=%s", cmdutils.NpmConfigRegistryKey, "https://acme.jfrog.io/artifactory/api/npm/test-repo")) - // Define expected keys for basic and token auth in the .npmrc. - basicAuthKey := fmt.Sprintf("//acme.jfrog.io/artifactory/api/npm/test-repo:%s=", cmdutils.NpmConfigAuthKey) - tokenAuthKey := fmt.Sprintf("//acme.jfrog.io/artifactory/api/npm/test-repo:%s=", cmdutils.NpmConfigAuthTokenKey) - // Validate token-based authentication. if testCase.accessToken != "" { - assert.Contains(t, npmrcContent, tokenAuthKey+"test-token") + assert.Contains(t, npmrcContent, fmt.Sprintf("//acme.jfrog.io/artifactory/api/npm/test-repo:%s=%s", cmdutils.NpmConfigAuthTokenKey, dummyToken)) } else if testCase.user != "" && testCase.password != "" { // Validate basic authentication with encoded credentials. // Base64 encoding of "myUser:myPassword" - expectedBasicAuth := basicAuthKey + "\"bXlVc2VyOm15UGFzc3dvcmQ=\"" + expectedBasicAuth := fmt.Sprintf("//acme.jfrog.io/artifactory/api/npm/test-repo:%s=\"bXlVc2VyOm15UGFzc3dvcmQ=\"", cmdutils.NpmConfigAuthKey) assert.Contains(t, npmrcContent, expectedBasicAuth) } @@ -131,19 +127,14 @@ func TestBuildToolLoginCommand_Yarn(t *testing.T) { // Check that the registry URL is correctly set in .yarnrc. assert.Contains(t, yarnrcContent, fmt.Sprintf("%s \"%s\"", cmdutils.NpmConfigRegistryKey, "https://acme.jfrog.io/artifactory/api/npm/test-repo")) - // Define expected keys for basic auth and token-based auth in .yarnrc. - basicAuthKey := fmt.Sprintf("\"//acme.jfrog.io/artifactory/api/npm/test-repo:%s\" ", cmdutils.NpmConfigAuthKey) - tokenAuthKey := fmt.Sprintf("\"//acme.jfrog.io/artifactory/api/npm/test-repo:%s\" ", cmdutils.NpmConfigAuthTokenKey) - // Validate token-based authentication. if testCase.accessToken != "" { - assert.Contains(t, yarnrcContent, tokenAuthKey+"test-token") + assert.Contains(t, yarnrcContent, fmt.Sprintf("\"//acme.jfrog.io/artifactory/api/npm/test-repo:%s\" %s", cmdutils.NpmConfigAuthTokenKey, dummyToken)) } else if testCase.user != "" && testCase.password != "" { // Validate basic authentication with encoded credentials. // Base64 encoding of "myUser:myPassword" - expectedBasicAuth := basicAuthKey + "bXlVc2VyOm15UGFzc3dvcmQ=" - assert.Contains(t, yarnrcContent, expectedBasicAuth) + assert.Contains(t, yarnrcContent, fmt.Sprintf("\"//acme.jfrog.io/artifactory/api/npm/test-repo:%s\"bXlVc2VyOm15UGFzc3dvcmQ=", cmdutils.NpmConfigAuthKey)) } // Clean up the temporary npmrc file. @@ -216,6 +207,7 @@ func testBuildToolLoginCommandPip(t *testing.T, buildTool project.ProjectType) { }) } } + func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { // Retrieve the home directory and construct the .yarnrc file path. homeDir, err := os.UserHomeDir() @@ -284,3 +276,36 @@ func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { }) } } + +func TestBuildToolLoginCommand_Go(t *testing.T) { + // Assuming createTestBuildToolLoginCommand initializes your Go login command + goLoginCmd := createTestBuildToolLoginCommand(project.Go) + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + // Set up server details for the current test case's authentication type. + goLoginCmd.serverDetails.SetUser(testCase.user) + goLoginCmd.serverDetails.SetPassword(testCase.password) + goLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) + + // Run the login command and ensure no errors occur. + if !assert.NoError(t, goLoginCmd.Run()) { + t.FailNow() + } + + goProxy := os.Getenv("GOPROXY") + + switch { + case testCase.accessToken != "": + // Validate token-based authentication. + assert.Contains(t, goProxy, fmt.Sprintf("https://%s:%s@acme.jfrog.io/artifactory/api/go/test-repo", auth.ExtractUsernameFromAccessToken(testCase.accessToken), testCase.accessToken)) + case testCase.user != "" && testCase.password != "": + // Validate basic authentication with user and password. + assert.Contains(t, goProxy, fmt.Sprintf("https://%s:%s@acme.jfrog.io/artifactory/api/go/test-repo", "myUser", "myPassword")) + default: + // Validate anonymous access. + assert.Contains(t, goProxy, "https://acme.jfrog.io/artifactory/api/go/test-repo") + } + }) + } +} From c3e0486e10642f69dfa914786efda28137fd4a3e Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 6 Nov 2024 12:03:51 +0200 Subject: [PATCH 10/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/buildtoollogin/buildtoollogin.go | 2 +- artifactory/commands/buildtoollogin/buildtoollogin_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin.go b/artifactory/commands/buildtoollogin/buildtoollogin.go index 6a70704ad..1cb35ac31 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin.go @@ -125,7 +125,7 @@ func (btlc *BuildToolLoginCommand) configurePip() error { if err != nil { return err } - return pythoncommands.RunConfigCommand(btlc.buildTool, []string{"set", "global.index-url", repoWithCredsUrl}) + return pythoncommands.RunConfigCommand(project.Pip, []string{"set", "global.index-url", repoWithCredsUrl}) } // configurePoetry configures Poetry to use the specified repository and authentication credentials. diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/buildtoollogin_test.go index 86b6af2e8..def625de2 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin_test.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin_test.go @@ -134,7 +134,7 @@ func TestBuildToolLoginCommand_Yarn(t *testing.T) { } else if testCase.user != "" && testCase.password != "" { // Validate basic authentication with encoded credentials. // Base64 encoding of "myUser:myPassword" - assert.Contains(t, yarnrcContent, fmt.Sprintf("\"//acme.jfrog.io/artifactory/api/npm/test-repo:%s\"bXlVc2VyOm15UGFzc3dvcmQ=", cmdutils.NpmConfigAuthKey)) + assert.Contains(t, yarnrcContent, fmt.Sprintf("\"//acme.jfrog.io/artifactory/api/npm/test-repo:%s\" bXlVc2VyOm15UGFzc3dvcmQ=", cmdutils.NpmConfigAuthKey)) } // Clean up the temporary npmrc file. From e8cc9f845deb9465f5684b54bcc478dbbb3eedb3 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 6 Nov 2024 12:12:48 +0200 Subject: [PATCH 11/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/buildtoollogin/buildtoollogin_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/buildtoollogin_test.go index def625de2..19149ded7 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin_test.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin_test.go @@ -175,9 +175,6 @@ func testBuildToolLoginCommandPip(t *testing.T, buildTool project.ProjectType) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - // Clean up the temporary pip config file. - assert.NoError(t, os.Remove(pipConfFilePath)) - // Set up server details for the current test case's authentication type. pipLoginCmd.serverDetails.SetUser(testCase.user) pipLoginCmd.serverDetails.SetPassword(testCase.password) @@ -204,6 +201,9 @@ func testBuildToolLoginCommandPip(t *testing.T, buildTool project.ProjectType) { // Validate anonymous access. assert.Contains(t, pipConfigContent, "index-url = https://acme.jfrog.io/artifactory/api/pypi/test-repo/simple") } + + // Clean up the temporary pip config file. + assert.NoError(t, os.Remove(pipConfFilePath)) }) } } From 95ba696187afa485d438a80213bcffdf97b3e22f Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 6 Nov 2024 12:22:22 +0200 Subject: [PATCH 12/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/python/utils_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artifactory/commands/python/utils_test.go b/artifactory/commands/python/utils_test.go index 2c564b3b4..3960e0076 100644 --- a/artifactory/commands/python/utils_test.go +++ b/artifactory/commands/python/utils_test.go @@ -30,7 +30,7 @@ func TestAddRepoToPyprojectFile(t *testing.T) { func initPoetryTest(t *testing.T) (string, func()) { // Create and change directory to test workspace - testAbs, err := filepath.Abs(filepath.Join("..", "..", "tests", "testdata", "poetry-project")) + testAbs, err := filepath.Abs(filepath.Join("..", "..", "..", "tests", "testdata", "poetry-project")) assert.NoError(t, err) poetryProjectPath, cleanUp := tests.CreateTestWorkspace(t, testAbs) return poetryProjectPath, cleanUp From 3dff2bb45753b4c44915bda2a1282a1ae4e90528 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 6 Nov 2024 12:23:37 +0200 Subject: [PATCH 13/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/buildtoollogin/buildtoollogin_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/buildtoollogin_test.go index 19149ded7..382817907 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin_test.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin_test.go @@ -278,6 +278,8 @@ func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { } func TestBuildToolLoginCommand_Go(t *testing.T) { + // todo: Implement this test + t.Skip() // Assuming createTestBuildToolLoginCommand initializes your Go login command goLoginCmd := createTestBuildToolLoginCommand(project.Go) From 0f0856ee92ea9fb5d6fa662811464293fc92da6e Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 6 Nov 2024 12:29:36 +0200 Subject: [PATCH 14/41] Improve repositores code Signed-off-by: Michael Sverdlov --- .../commands/utils/npmcmdutils_test.go | 169 ------------------ 1 file changed, 169 deletions(-) diff --git a/artifactory/commands/utils/npmcmdutils_test.go b/artifactory/commands/utils/npmcmdutils_test.go index e16e3d7ad..3dc7cf8a7 100644 --- a/artifactory/commands/utils/npmcmdutils_test.go +++ b/artifactory/commands/utils/npmcmdutils_test.go @@ -1,22 +1,13 @@ package utils import ( - "fmt" - "github.com/jfrog/jfrog-cli-core/v2/common/project" commonTests "github.com/jfrog/jfrog-cli-core/v2/common/tests" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" "github.com/jfrog/jfrog-client-go/auth" "github.com/stretchr/testify/assert" "net/http" - "os" - "path/filepath" - "strings" "testing" ) -var testArtifactoryUrl = "https://acme.jfrog.io/artifactory" - func TestGetRegistry(t *testing.T) { var getRegistryTest = []struct { repo string @@ -80,163 +71,3 @@ func TestGetNpmAuth(t *testing.T) { }) } } - -// Helper function to set up the BuildToolLogin. -func setupNpmrcManager(buildTool project.ProjectType) *BuildToolLogin { - serverDetails := &config.ServerDetails{ - ArtifactoryUrl: testArtifactoryUrl, - } - return NewNpmrcYarnrcManager(buildTool, "my-repo-virtual", serverDetails) -} - -// Helper function to create a temporary .npmrc file for isolated tests. -func createTempNpmrc(t *testing.T) string { - // Create a temporary directory for npmrc - tempDir := t.TempDir() - - // Set the NPM_CONFIG_USERCONFIG environment variable - tempNpmrcPath := filepath.Join(tempDir, ".npmrc") - t.Setenv("NPM_CONFIG_USERCONFIG", tempNpmrcPath) - - return tempNpmrcPath -} - -// Helper function to create a temporary .npmrc file for isolated tests. -func createTempYarnrc(t *testing.T) (string, func() error) { - homeDir, err := os.UserHomeDir() - assert.NoError(t, err) - yarnrcPath := filepath.Join(homeDir, ".yarnrc") - restoreYarnrcFunc, err := ioutils.BackupFile(yarnrcPath, ".yarnrc.backup") - assert.NoError(t, err) - return yarnrcPath, restoreYarnrcFunc -} - -// Test for configuring registry in npm. -func TestConfigureRegistry_Npm(t *testing.T) { - tempNpmrcPath := createTempNpmrc(t) - - nm := setupNpmrcManager(project.Npm) - - err := nm.ConfigureRegistry() - assert.NoError(t, err) - - // Verify that the correct .npmrc entry was added - fileContent, err := os.ReadFile(tempNpmrcPath) - assert.NoError(t, err) - assert.Contains(t, string(fileContent), "registry="+testArtifactoryUrl) -} - -// Test for configuring registry in yarn. -func TestConfigureRegistry_Yarn(t *testing.T) { - yarnrcPath, restoreYarnrcFunc := createTempYarnrc(t) - defer func() { - assert.NoError(t, restoreYarnrcFunc()) - }() - nm := setupNpmrcManager(project.Yarn) - - err := nm.ConfigureRegistry() - assert.NoError(t, err) - - // Verify that the correct .yarnrc entry was added - fileContent, err := os.ReadFile(yarnrcPath) - assert.NoError(t, err) - expectedRegistryLine := fmt.Sprintf("registry \"%s/api/npm/my-repo-virtual\"", testArtifactoryUrl) - assert.Contains(t, string(fileContent), expectedRegistryLine) -} - -// Test for setting token auth in npm. -func TestConfigureAuth_Token_Npm(t *testing.T) { - tempNpmrcPath := createTempNpmrc(t) - - nm := setupNpmrcManager(project.Npm) - - err := nm.handleNpmrcTokenAuth("my-access-token") - assert.NoError(t, err) - - // Verify that the correct auth token entry was added to .npmrc - fileContent, err := os.ReadFile(tempNpmrcPath) - assert.NoError(t, err) - expectedAuthLine := fmt.Sprintf("//%s/api/npm/my-repo-virtual:_authToken=my-access-token", strings.TrimPrefix(testArtifactoryUrl, "https://")) - assert.Contains(t, string(fileContent), expectedAuthLine) -} - -// Test for setting token auth in yarn. -func TestConfigureAuth_Token_Yarn(t *testing.T) { - yarnrcPath, restoreYarnrcFunc := createTempYarnrc(t) - defer func() { - assert.NoError(t, restoreYarnrcFunc()) - }() - - nm := setupNpmrcManager(project.Yarn) - - err := nm.handleNpmrcTokenAuth("my-access-token") - assert.NoError(t, err) - - // Verify that the correct auth token entry was added to .yarnrc - fileContent, err := os.ReadFile(yarnrcPath) - assert.NoError(t, err) - expectedAuthLine := fmt.Sprintf("\"//%s/api/npm/my-repo-virtual:_authToken\" my-access-token", strings.TrimPrefix(testArtifactoryUrl, "https://")) - assert.Contains(t, string(fileContent), expectedAuthLine) -} - -func TestHandleNpmrcBasicAuth(t *testing.T) { - // Set up a temporary .npmrc configuration. - tempDir := t.TempDir() - t.Setenv("NPM_CONFIG_USERCONFIG", filepath.Join(tempDir, ".npmrc")) - - // Set up the BuildToolLogin for npm build tool. - nm := setupNpmrcManager(project.Npm) - - // Actual username and password for testing. - username := "myUser" - password := "myPassword" - - // Expected base64 encoded value. (Base64 encoded "myUser:myPassword") - expectedAuthValue := "bXlVc2VyOm15UGFzc3dvcmQ=" - - // Run the method to handle Basic Auth. - err := nm.handleNpmrcBasicAuth(username, password) - assert.NoError(t, err) - - // Read the resulting .npmrc file and verify the auth value. - npmrcContent, err := os.ReadFile(filepath.Join(tempDir, ".npmrc")) - assert.NoError(t, err) - - // Verify that the auth key and value are correctly set. - expectedAuthLine := fmt.Sprintf("//%s/api/npm/my-repo-virtual:_auth=\"%s\"", strings.TrimPrefix(testArtifactoryUrl, "https://"), expectedAuthValue) - assert.Contains(t, string(npmrcContent), expectedAuthLine) -} - -// Test for handling anonymous access in npm. -func TestHandleAnonymousAccess_Npm(t *testing.T) { - tempNpmrcPath := createTempNpmrc(t) - - nm := setupNpmrcManager(project.Npm) - // Set basic auth credentials to be removed on the by the anonymous access method. - assert.NoError(t, nm.handleNpmrcBasicAuth("user", "password")) - assert.FileExists(t, tempNpmrcPath) - - err := nm.handleNpmAnonymousAccess() - assert.NoError(t, err) - // Verify that .npmrc was deleted because it should be empty. - assert.NoFileExists(t, tempNpmrcPath) -} - -// Test for handling anonymous access in yarn. -func TestHandleAnonymousAccess_Yarn(t *testing.T) { - yarnrcPath, restoreYarnrcFunc := createTempYarnrc(t) - defer func() { - assert.NoError(t, restoreYarnrcFunc()) - }() - - nm := setupNpmrcManager(project.Yarn) - - err := nm.handleNpmAnonymousAccess() - assert.NoError(t, err) - - // Verify that the auth entries were removed from .yarnrc - fileContent, err := os.ReadFile(yarnrcPath) - assert.NoError(t, err) - assert.NotContains(t, string(fileContent), "_auth") - assert.NotContains(t, string(fileContent), "_authToken") -} From f280aa55bedead4ae43350a6bfdc5650cd9b790e Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 6 Nov 2024 12:41:15 +0200 Subject: [PATCH 15/41] Improve repositores code Signed-off-by: Michael Sverdlov --- .../commands/buildtoollogin/buildtoollogin_test.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/buildtoollogin_test.go index 382817907..eb95e5160 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin_test.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin_test.go @@ -9,8 +9,10 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/utils/io" + clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/stretchr/testify/assert" "os" + "os/exec" "path/filepath" "testing" ) @@ -278,8 +280,11 @@ func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { } func TestBuildToolLoginCommand_Go(t *testing.T) { - // todo: Implement this test - t.Skip() + goProxyEnv := "GOPROXY" + // Restore the original value of the GOPROXY environment variable after the test. + restoreGoProxy := clientTestUtils.SetEnvWithCallbackAndAssert(t, goProxyEnv, "") + defer restoreGoProxy() + // Assuming createTestBuildToolLoginCommand initializes your Go login command goLoginCmd := createTestBuildToolLoginCommand(project.Go) @@ -295,7 +300,10 @@ func TestBuildToolLoginCommand_Go(t *testing.T) { t.FailNow() } - goProxy := os.Getenv("GOPROXY") + // Get the value of the GOPROXY environment variable. + outputBytes, err := exec.Command("go", "env", "GOPROXY").Output() + assert.NoError(t, err) + goProxy := string(outputBytes) switch { case testCase.accessToken != "": From ecb74bba3688ce43106a757d7f60e1a18b08c997 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 6 Nov 2024 13:45:51 +0200 Subject: [PATCH 16/41] Improve repositores code Signed-off-by: Michael Sverdlov --- .../commands/buildtoollogin/buildtoollogin_test.go | 9 +++++---- artifactory/commands/utils/npmcmdutils.go | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/buildtoollogin_test.go index eb95e5160..85e0097c9 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin_test.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin_test.go @@ -156,13 +156,14 @@ func TestBuildToolLoginCommand_Pipenv(t *testing.T) { } func testBuildToolLoginCommandPip(t *testing.T, buildTool project.ProjectType) { - // Retrieve the home directory and construct the pip.conf file path. - homeDir, err := os.UserHomeDir() - assert.NoError(t, err) + var pipConfFilePath string if coreutils.IsWindows() { - pipConfFilePath = filepath.Join(homeDir, "pip", "pip.ini") + pipConfFilePath = filepath.Join(os.Getenv("APPDATA"), "pip", "pip.ini") } else { + // Retrieve the home directory and construct the pip.conf file path. + homeDir, err := os.UserHomeDir() + assert.NoError(t, err) pipConfFilePath = filepath.Join(homeDir, ".config", "pip", "pip.conf") } diff --git a/artifactory/commands/utils/npmcmdutils.go b/artifactory/commands/utils/npmcmdutils.go index d21cdea8e..2c4fe43ac 100644 --- a/artifactory/commands/utils/npmcmdutils.go +++ b/artifactory/commands/utils/npmcmdutils.go @@ -103,14 +103,15 @@ func GetNpmRepositoryUrl(repositoryName, artifactoryUrl string) string { // GetNpmAuthKeyValue generates the correct authentication key and value for npm or Yarn, based on the repo URL. func GetNpmAuthKeyValue(serverDetails *config.ServerDetails, repoUrl string) (key, value string) { - keySuffix := "" - if serverDetails.GetAccessToken() != "" { + var keySuffix string + switch { + case serverDetails.GetAccessToken() != "": keySuffix = NpmConfigAuthTokenKey value = serverDetails.GetAccessToken() - } else if serverDetails.GetUser() != "" && serverDetails.GetPassword() != "" { + case serverDetails.GetUser() != "" && serverDetails.GetPassword() != "": keySuffix = NpmConfigAuthKey value = basicAuthBase64Encode(serverDetails.GetUser(), serverDetails.GetPassword()) - } else { + default: return "", "" } From cd0fda379a5870211d34c6b9b57e02f7ef798f93 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 6 Nov 2024 13:55:45 +0200 Subject: [PATCH 17/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/buildtoollogin/buildtoollogin_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/buildtoollogin_test.go index 85e0097c9..e44667b82 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin_test.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin_test.go @@ -14,6 +14,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "testing" ) @@ -156,7 +157,6 @@ func TestBuildToolLoginCommand_Pipenv(t *testing.T) { } func testBuildToolLoginCommandPip(t *testing.T, buildTool project.ProjectType) { - var pipConfFilePath string if coreutils.IsWindows() { pipConfFilePath = filepath.Join(os.Getenv("APPDATA"), "pip", "pip.ini") @@ -166,7 +166,6 @@ func testBuildToolLoginCommandPip(t *testing.T, buildTool project.ProjectType) { assert.NoError(t, err) pipConfFilePath = filepath.Join(homeDir, ".config", "pip", "pip.conf") } - // Back up the existing .pip.conf file and ensure restoration after the test. restorePipConfFunc, err := ioutils.BackupFile(pipConfFilePath, ".pipconf.backup") assert.NoError(t, err) @@ -258,6 +257,9 @@ func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { poetryConfigContentBytes, err := os.ReadFile(poetryConfigFilePath) assert.NoError(t, err) poetryConfigContent := string(poetryConfigContentBytes) + // Normalize line endings for comparison.(For Windows) + poetryConfigContent = strings.ReplaceAll(poetryConfigContent, "\r\n", "\n") + assert.Contains(t, poetryConfigContent, "[repositories.test-repo]\nurl = \"https://acme.jfrog.io/artifactory/api/pypi/test-repo/simple\"") // Validate that the auth details were set correctly in auth.toml. From 547fa0214bc11d3a9e0b94c80c277cb751da6afa Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 6 Nov 2024 14:21:32 +0200 Subject: [PATCH 18/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/buildtoollogin/buildtoollogin_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/buildtoollogin_test.go index e44667b82..7da9c6364 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin_test.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin_test.go @@ -267,6 +267,9 @@ func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { poetryAuthContentBytes, err := os.ReadFile(poetryAuthFilePath) assert.NoError(t, err) poetryAuthContent := string(poetryAuthContentBytes) + // Normalize line endings for comparison.(For Windows) + poetryConfigContent = strings.ReplaceAll(poetryConfigContent, "\r\n", "\n") + if testCase.accessToken != "" { // Validate token-based authentication (The token is stored in the keyring so we can't test it) assert.Contains(t, poetryAuthContent, fmt.Sprintf("[http-basic.test-repo]\nusername = \"%s\"", auth.ExtractUsernameFromAccessToken(testCase.accessToken))) From 265fea7d94dea867ade15513ab1488c5e35f1a74 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 6 Nov 2024 14:25:11 +0200 Subject: [PATCH 19/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/buildtoollogin/buildtoollogin_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/buildtoollogin_test.go index 7da9c6364..f52e604f9 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin_test.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin_test.go @@ -268,7 +268,7 @@ func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { assert.NoError(t, err) poetryAuthContent := string(poetryAuthContentBytes) // Normalize line endings for comparison.(For Windows) - poetryConfigContent = strings.ReplaceAll(poetryConfigContent, "\r\n", "\n") + poetryAuthContent = strings.ReplaceAll(poetryAuthContent, "\r\n", "\n") if testCase.accessToken != "" { // Validate token-based authentication (The token is stored in the keyring so we can't test it) From e8f884c57d39ad6be37a305908f62bc37ad7b853 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Thu, 7 Nov 2024 14:23:36 +0200 Subject: [PATCH 20/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/buildtoollogin/buildtoollogin.go | 10 ++++++---- .../commands/buildtoollogin/buildtoollogin_test.go | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin.go b/artifactory/commands/buildtoollogin/buildtoollogin.go index 1cb35ac31..24cb45212 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin.go @@ -46,6 +46,8 @@ func buildToolToPackageType(buildTool project.ProjectType) (string, error) { return repository.Npm, nil case project.Pip, project.Pipenv, project.Poetry: return repository.Pypi, nil + case project.Go: + return repository.Go, nil default: return "", errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", buildTool)) } @@ -69,9 +71,9 @@ func (btlc *BuildToolLoginCommand) ServerDetails() (*config.ServerDetails, error // Run executes the configuration method corresponding to the project type specified for the command. func (btlc *BuildToolLoginCommand) Run() (err error) { - // Prompt the user to select a repository if none has been specified. if btlc.repoName == "" { - if err = btlc.SetVirtualRepoNameInteractively(); err != nil { + // Prompt the user to select a virtual repository that matches the project type. + if err = btlc.GetRepositoryNameFromUserInteractively(); err != nil { return err } } @@ -99,8 +101,8 @@ func (btlc *BuildToolLoginCommand) Run() (err error) { return nil } -// SetVirtualRepoNameInteractively prompts the user to select a compatible virtual repository. -func (btlc *BuildToolLoginCommand) SetVirtualRepoNameInteractively() error { +// GetRepositoryNameFromUserInteractively prompts the user to select a compatible virtual repository. +func (btlc *BuildToolLoginCommand) GetRepositoryNameFromUserInteractively() error { // Map the build tool to its corresponding package type. packageType, err := buildToolToPackageType(btlc.buildTool) if err != nil { diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/buildtoollogin_test.go index f52e604f9..8e68dcf91 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin_test.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin_test.go @@ -44,7 +44,8 @@ var testCases = []struct { func createTestBuildToolLoginCommand(buildTool project.ProjectType) *BuildToolLoginCommand { cmd := NewBuildToolLoginCommand(buildTool) cmd.repoName = "test-repo" - cmd.serverDetails = &config.ServerDetails{ArtifactoryUrl: "https://acme.jfrog.io/artifactory"} + dummyUrl := "https://acme.jfrog.io" + cmd.serverDetails = &config.ServerDetails{Url: dummyUrl, ArtifactoryUrl: dummyUrl + "/artifactory"} return cmd } From aec982a2b038f2250b89a5b37ff62335958410f5 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Thu, 7 Nov 2024 15:11:11 +0200 Subject: [PATCH 21/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/buildtoollogin/buildtoollogin.go | 4 ++-- artifactory/commands/golang/go.go | 6 +++--- artifactory/commands/golang/go_test.go | 2 +- artifactory/utils/npm/config-delete.go | 5 ++--- artifactory/utils/npm/config-set.go | 5 ++--- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin.go b/artifactory/commands/buildtoollogin/buildtoollogin.go index 24cb45212..babdab348 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin.go @@ -3,6 +3,7 @@ package buildtoollogin import ( "fmt" biutils "github.com/jfrog/build-info-go/utils" + gocommands "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/golang" pythoncommands "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/python" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" commandsutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" @@ -11,7 +12,6 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/yarn" "github.com/jfrog/jfrog-cli-core/v2/common/project" "github.com/jfrog/jfrog-cli-core/v2/utils/config" - goutils "github.com/jfrog/jfrog-cli-core/v2/utils/golang" "github.com/jfrog/jfrog-client-go/artifactory/services" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" @@ -200,7 +200,7 @@ func (btlc *BuildToolLoginCommand) configureYarn() error { // // go env -w GOPROXY=https://:@/artifactory/go/,direct func (btlc *BuildToolLoginCommand) configureGo() error { - repoWithCredsUrl, err := goutils.GetArtifactoryRemoteRepoUrl(btlc.serverDetails, btlc.repoName, goutils.GoProxyUrlParams{Direct: true}) + repoWithCredsUrl, err := gocommands.GetArtifactoryRemoteRepoUrl(btlc.serverDetails, btlc.repoName, gocommands.GoProxyUrlParams{Direct: true}) if err != nil { return err } diff --git a/artifactory/commands/golang/go.go b/artifactory/commands/golang/go.go index 6671589c7..39ac40ae1 100644 --- a/artifactory/commands/golang/go.go +++ b/artifactory/commands/golang/go.go @@ -154,7 +154,7 @@ func (gc *GoCommand) run() (err error) { return } // If noFallback=false, missing packages will be fetched directly from VCS - repoUrl, err := getArtifactoryRemoteRepoUrl(resolverDetails, gc.resolverParams.TargetRepo(), GoProxyUrlParams{Direct: !gc.noFallback}) + repoUrl, err := GetArtifactoryRemoteRepoUrl(resolverDetails, gc.resolverParams.TargetRepo(), GoProxyUrlParams{Direct: !gc.noFallback}) if err != nil { return } @@ -336,7 +336,7 @@ func SetArtifactoryAsResolutionServer(serverDetails *config.ServerDetails, depsR } func setGoProxy(server *config.ServerDetails, remoteGoRepo string, goProxyParams GoProxyUrlParams) error { - repoUrl, err := getArtifactoryRemoteRepoUrl(server, remoteGoRepo, goProxyParams) + repoUrl, err := GetArtifactoryRemoteRepoUrl(server, remoteGoRepo, goProxyParams) if err != nil { return err } @@ -380,7 +380,7 @@ func (gdu *GoProxyUrlParams) addDirect(url string) string { return url } -func getArtifactoryRemoteRepoUrl(serverDetails *config.ServerDetails, repo string, goProxyParams GoProxyUrlParams) (string, error) { +func GetArtifactoryRemoteRepoUrl(serverDetails *config.ServerDetails, repo string, goProxyParams GoProxyUrlParams) (string, error) { authServerDetails, err := serverDetails.CreateArtAuthConfig() if err != nil { return "", err diff --git a/artifactory/commands/golang/go_test.go b/artifactory/commands/golang/go_test.go index 7949b3062..7b334a3fe 100644 --- a/artifactory/commands/golang/go_test.go +++ b/artifactory/commands/golang/go_test.go @@ -86,7 +86,7 @@ func TestGetArtifactoryRemoteRepoUrl(t *testing.T) { AccessToken: "eyJ0eXAiOiJKV1QifQ.eyJzdWIiOiJmYWtlXC91c2Vyc1wvdGVzdCJ9.MTIzNDU2Nzg5MA", } repoName := "test-repo" - repoUrl, err := getArtifactoryRemoteRepoUrl(server, repoName, GoProxyUrlParams{}) + repoUrl, err := GetArtifactoryRemoteRepoUrl(server, repoName, GoProxyUrlParams{}) assert.NoError(t, err) assert.Equal(t, "https://test:eyJ0eXAiOiJKV1QifQ.eyJzdWIiOiJmYWtlXC91c2Vyc1wvdGVzdCJ9.MTIzNDU2Nzg5MA@server.com/artifactory/api/go/test-repo", repoUrl) } diff --git a/artifactory/utils/npm/config-delete.go b/artifactory/utils/npm/config-delete.go index 9c4c1f289..bcd8467e7 100644 --- a/artifactory/utils/npm/config-delete.go +++ b/artifactory/utils/npm/config-delete.go @@ -2,7 +2,6 @@ package npm import ( gofrogcmd "github.com/jfrog/gofrog/io" - npmutils "github.com/jfrog/jfrog-cli-core/v2/utils/npm" "github.com/jfrog/jfrog-client-go/utils/errorutils" ) @@ -15,8 +14,8 @@ func ConfigDelete(key, executablePath string) error { return nil } -func createConfigDeleteCmdConfig(executablePath, key string) *npmutils.NpmConfig { - return &npmutils.NpmConfig{ +func createConfigDeleteCmdConfig(executablePath, key string) *NpmConfig { + return &NpmConfig{ Npm: executablePath, Command: []string{"config", "delete", key}, StrWriter: nil, diff --git a/artifactory/utils/npm/config-set.go b/artifactory/utils/npm/config-set.go index 1df8431d8..c80c9c53c 100644 --- a/artifactory/utils/npm/config-set.go +++ b/artifactory/utils/npm/config-set.go @@ -2,7 +2,6 @@ package npm import ( gofrogcmd "github.com/jfrog/gofrog/io" - npmutils "github.com/jfrog/jfrog-cli-core/v2/utils/npm" "github.com/jfrog/jfrog-client-go/utils/errorutils" ) @@ -15,8 +14,8 @@ func ConfigSet(key, value, executablePath string) error { return nil } -func createConfigSetCmdConfig(executablePath, key, value string) *npmutils.NpmConfig { - return &npmutils.NpmConfig{ +func createConfigSetCmdConfig(executablePath, key, value string) *NpmConfig { + return &NpmConfig{ Npm: executablePath, Command: []string{"config", "set", key, value}, StrWriter: nil, From 57df2968bbd1fed1480e09282ea51e654377c415 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Thu, 7 Nov 2024 15:17:55 +0200 Subject: [PATCH 22/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/buildtoollogin/buildtoollogin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin.go b/artifactory/commands/buildtoollogin/buildtoollogin.go index babdab348..a8f6d5c5a 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin.go @@ -49,7 +49,7 @@ func buildToolToPackageType(buildTool project.ProjectType) (string, error) { case project.Go: return repository.Go, nil default: - return "", errorutils.CheckError(fmt.Errorf("unsupported build tool: %s", buildTool)) + return "", errorutils.CheckErrorf("unsupported build tool: %s", buildTool) } } From e8d58428ec54065c0ef4b999d1206d1664b34125 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Thu, 7 Nov 2024 15:18:51 +0200 Subject: [PATCH 23/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/python/poetry.go | 6 ++ .../python/{utils_test.go => poetry_test.go} | 33 +------- artifactory/commands/python/python.go | 63 ++++++++++++++++ artifactory/commands/python/python_test.go | 34 +++++++++ artifactory/commands/python/utils.go | 75 ------------------- common/cliutils/utils.go | 1 - 6 files changed, 105 insertions(+), 107 deletions(-) rename artifactory/commands/python/{utils_test.go => poetry_test.go} (59%) create mode 100644 artifactory/commands/python/python_test.go delete mode 100644 artifactory/commands/python/utils.go diff --git a/artifactory/commands/python/poetry.go b/artifactory/commands/python/poetry.go index 4e081af97..6ef05dfd4 100644 --- a/artifactory/commands/python/poetry.go +++ b/artifactory/commands/python/poetry.go @@ -22,6 +22,12 @@ import ( "path/filepath" ) +const ( + poetryConfigAuthPrefix = "http-basic." + poetryConfigRepoPrefix = "repositories." + pyproject = "pyproject.toml" +) + type PoetryCommand struct { PythonCommand } diff --git a/artifactory/commands/python/utils_test.go b/artifactory/commands/python/poetry_test.go similarity index 59% rename from artifactory/commands/python/utils_test.go rename to artifactory/commands/python/poetry_test.go index 3960e0076..48cd16cd8 100644 --- a/artifactory/commands/python/utils_test.go +++ b/artifactory/commands/python/poetry_test.go @@ -1,16 +1,11 @@ package python import ( - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/stretchr/testify/require" - "path/filepath" - "strings" - "testing" - "github.com/jfrog/jfrog-cli-core/v2/utils/tests" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "github.com/stretchr/testify/assert" + "path/filepath" + "testing" ) func TestAddRepoToPyprojectFile(t *testing.T) { @@ -35,27 +30,3 @@ func initPoetryTest(t *testing.T) (string, func()) { poetryProjectPath, cleanUp := tests.CreateTestWorkspace(t, testAbs) return poetryProjectPath, cleanUp } - -func TestGetPypiRepoUrlWithCredentials(t *testing.T) { - tests := []struct { - name string - curationCmd bool - }{ - { - name: "test curation command true", - curationCmd: true, - }, - { - name: "test curation command false", - curationCmd: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - url, _, _, err := GetPypiRepoUrlWithCredentials(&config.ServerDetails{}, "test", tt.curationCmd) - require.NoError(t, err) - assert.Equal(t, tt.curationCmd, strings.Contains(url.Path, coreutils.CurationPassThroughApi)) - }) - } -} diff --git a/artifactory/commands/python/python.go b/artifactory/commands/python/python.go index 194b8010d..9d6a8b86a 100644 --- a/artifactory/commands/python/python.go +++ b/artifactory/commands/python/python.go @@ -7,17 +7,27 @@ import ( "github.com/jfrog/build-info-go/entities" buildInfoUtils "github.com/jfrog/build-info-go/utils" "github.com/jfrog/build-info-go/utils/pythonutils" + gofrogcmd "github.com/jfrog/gofrog/io" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/python/dependencies" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" buildUtils "github.com/jfrog/jfrog-cli-core/v2/common/build" + "github.com/jfrog/jfrog-cli-core/v2/common/project" "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-client-go/auth" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" "io" + "net/url" "os" "os/exec" ) +const ( + pipenvRemoteRegistryFlag = "--pypi-mirror" + pipRemoteRegistryFlag = "-i" +) + type PythonCommand struct { serverDetails *config.ServerDetails pythonTool pythonutils.PythonTool @@ -125,6 +135,59 @@ func (pc *PythonCommand) SetPypiRepoUrlWithCredentials() error { return nil } +// Get the pypi repository url and the credentials. +func GetPypiRepoUrlWithCredentials(serverDetails *config.ServerDetails, repository string, isCurationCmd bool) (*url.URL, string, string, error) { + rtUrl, err := url.Parse(serverDetails.GetArtifactoryUrl()) + if err != nil { + return nil, "", "", errorutils.CheckError(err) + } + + username := serverDetails.GetUser() + password := serverDetails.GetPassword() + + // Get credentials from access-token if exists. + if serverDetails.GetAccessToken() != "" { + if username == "" { + username = auth.ExtractUsernameFromAccessToken(serverDetails.GetAccessToken()) + } + password = serverDetails.GetAccessToken() + } + if isCurationCmd { + rtUrl = rtUrl.JoinPath(coreutils.CurationPassThroughApi) + } + rtUrl = rtUrl.JoinPath("api/pypi", repository, "simple") + return rtUrl, username, password, err +} + +func GetPypiRemoteRegistryFlag(tool pythonutils.PythonTool) string { + if tool == pythonutils.Pip { + return pipRemoteRegistryFlag + } + return pipenvRemoteRegistryFlag +} + +// Get the pypi repository embedded credentials URL (https://:@/artifactory/api/pypi//simple) +func GetPypiRepoUrl(serverDetails *config.ServerDetails, repository string, isCurationCmd bool) (string, error) { + rtUrl, username, password, err := GetPypiRepoUrlWithCredentials(serverDetails, repository, isCurationCmd) + if err != nil { + return "", err + } + if password != "" { + rtUrl.User = url.UserPassword(username, password) + } + return rtUrl.String(), err +} + +func RunConfigCommand(buildTool project.ProjectType, args []string) error { + log.Debug("Running", buildTool.String(), "config command...") + configCmd := gofrogcmd.NewCommand(buildTool.String(), "config", args) + err := gofrogcmd.RunCmd(configCmd) + if err != nil { + return errorutils.CheckErrorf(buildTool.String()+" config command failed with: %s", err.Error()) + } + return nil +} + func (pc *PythonCommand) SetServerDetails(serverDetails *config.ServerDetails) *PythonCommand { pc.serverDetails = serverDetails return pc diff --git a/artifactory/commands/python/python_test.go b/artifactory/commands/python/python_test.go new file mode 100644 index 000000000..2b25468c9 --- /dev/null +++ b/artifactory/commands/python/python_test.go @@ -0,0 +1,34 @@ +package python + +import ( + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "strings" + "testing" +) + +func TestGetPypiRepoUrlWithCredentials(t *testing.T) { + tests := []struct { + name string + curationCmd bool + }{ + { + name: "test curation command true", + curationCmd: true, + }, + { + name: "test curation command false", + curationCmd: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + url, _, _, err := GetPypiRepoUrlWithCredentials(&config.ServerDetails{}, "test", tt.curationCmd) + require.NoError(t, err) + assert.Equal(t, tt.curationCmd, strings.Contains(url.Path, coreutils.CurationPassThroughApi)) + }) + } +} diff --git a/artifactory/commands/python/utils.go b/artifactory/commands/python/utils.go deleted file mode 100644 index 416a913f6..000000000 --- a/artifactory/commands/python/utils.go +++ /dev/null @@ -1,75 +0,0 @@ -package python - -import ( - "github.com/jfrog/build-info-go/utils/pythonutils" - "github.com/jfrog/gofrog/io" - gofrogcmd "github.com/jfrog/gofrog/io" - "github.com/jfrog/jfrog-cli-core/v2/common/project" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-client-go/auth" - "github.com/jfrog/jfrog-client-go/utils/errorutils" - "github.com/jfrog/jfrog-client-go/utils/log" - "net/url" -) - -const ( - pipenvRemoteRegistryFlag = "--pypi-mirror" - pipRemoteRegistryFlag = "-i" - poetryConfigAuthPrefix = "http-basic." - poetryConfigRepoPrefix = "repositories." - pyproject = "pyproject.toml" -) - -// Get the pypi repository url and the credentials. -func GetPypiRepoUrlWithCredentials(serverDetails *config.ServerDetails, repository string, isCurationCmd bool) (*url.URL, string, string, error) { - rtUrl, err := url.Parse(serverDetails.GetArtifactoryUrl()) - if err != nil { - return nil, "", "", errorutils.CheckError(err) - } - - username := serverDetails.GetUser() - password := serverDetails.GetPassword() - - // Get credentials from access-token if exists. - if serverDetails.GetAccessToken() != "" { - if username == "" { - username = auth.ExtractUsernameFromAccessToken(serverDetails.GetAccessToken()) - } - password = serverDetails.GetAccessToken() - } - if isCurationCmd { - rtUrl = rtUrl.JoinPath(coreutils.CurationPassThroughApi) - } - rtUrl = rtUrl.JoinPath("api/pypi", repository, "simple") - return rtUrl, username, password, err -} - -func GetPypiRemoteRegistryFlag(tool pythonutils.PythonTool) string { - if tool == pythonutils.Pip { - return pipRemoteRegistryFlag - } - return pipenvRemoteRegistryFlag -} - -// Get the pypi repository embedded credentials URL (https://:@/artifactory/api/pypi//simple) -func GetPypiRepoUrl(serverDetails *config.ServerDetails, repository string, isCurationCmd bool) (string, error) { - rtUrl, username, password, err := GetPypiRepoUrlWithCredentials(serverDetails, repository, isCurationCmd) - if err != nil { - return "", err - } - if password != "" { - rtUrl.User = url.UserPassword(username, password) - } - return rtUrl.String(), err -} - -func RunConfigCommand(buildTool project.ProjectType, args []string) error { - log.Debug("Running", buildTool.String(), "config command...") - configCmd := io.NewCommand(buildTool.String(), "config", args) - err := gofrogcmd.RunCmd(configCmd) - if err != nil { - return errorutils.CheckErrorf(buildTool.String()+" config command failed with: %s", err.Error()) - } - return nil -} diff --git a/common/cliutils/utils.go b/common/cliutils/utils.go index 6bc1423c3..186c34628 100644 --- a/common/cliutils/utils.go +++ b/common/cliutils/utils.go @@ -218,7 +218,6 @@ func CreateServerDetailsWithConfigOffer(createServerDetails func() (*config.Serv return nil, err } log.Debug(fmt.Sprintf("Using <%s> server-id configuration", confDetails.ServerId)) - // Take insecureTls value from options since it is not saved in config. confDetails.InsecureTls = details.InsecureTls confDetails.Url = clientUtils.AddTrailingSlashIfNeeded(confDetails.Url) From f84e6f8a42a8039c51329bd72d4bef6fc7007141 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Thu, 7 Nov 2024 16:25:03 +0200 Subject: [PATCH 24/41] Improve repositores code Signed-off-by: Michael Sverdlov --- artifactory/commands/python/python.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artifactory/commands/python/python.go b/artifactory/commands/python/python.go index 9d6a8b86a..b4f707fea 100644 --- a/artifactory/commands/python/python.go +++ b/artifactory/commands/python/python.go @@ -183,7 +183,7 @@ func RunConfigCommand(buildTool project.ProjectType, args []string) error { configCmd := gofrogcmd.NewCommand(buildTool.String(), "config", args) err := gofrogcmd.RunCmd(configCmd) if err != nil { - return errorutils.CheckErrorf(buildTool.String()+" config command failed with: %s", err.Error()) + return errorutils.CheckErrorf("%s config command failed with: %q", buildTool.String(), err) } return nil } From be5cdcbac1ff7f480420aaf6ca0c8b5c607e006e Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 11 Nov 2024 15:09:27 +0200 Subject: [PATCH 25/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../commands/buildtoollogin/buildtoollogin.go | 102 +++++++++--------- artifactory/commands/python/poetry.go | 9 +- artifactory/commands/python/python.go | 3 +- artifactory/utils/npm/config-delete.go | 24 ----- .../utils/npm/{config-get.go => configget.go} | 1 + .../npm/{config-list.go => configlist.go} | 0 .../utils/npm/{config-set.go => configset.go} | 1 + artifactory/utils/yarn/configdelete.go | 21 ---- artifactory/utils/yarn/configget.go | 1 + artifactory/utils/yarn/configset.go | 1 + go.mod | 22 ++-- go.sum | 79 ++++---------- 12 files changed, 91 insertions(+), 173 deletions(-) delete mode 100644 artifactory/utils/npm/config-delete.go rename artifactory/utils/npm/{config-get.go => configget.go} (88%) rename artifactory/utils/npm/{config-list.go => configlist.go} (100%) rename artifactory/utils/npm/{config-set.go => configset.go} (88%) delete mode 100644 artifactory/utils/yarn/configdelete.go diff --git a/artifactory/commands/buildtoollogin/buildtoollogin.go b/artifactory/commands/buildtoollogin/buildtoollogin.go index a8f6d5c5a..dc6360f2c 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin.go +++ b/artifactory/commands/buildtoollogin/buildtoollogin.go @@ -17,11 +17,11 @@ import ( "github.com/jfrog/jfrog-client-go/utils/log" ) -// BuildToolLoginCommand configures registries and authentication for various build tools (npm, Yarn, Pip, Pipenv, Poetry, Go) +// PackageManagerLoginCommand configures registries and authentication for various package managers (npm, Yarn, Pip, Pipenv, Poetry, Go) // based on the specified project type. -type BuildToolLoginCommand struct { - // buildTool represents the type of project (e.g., NPM, Yarn). - buildTool project.ProjectType +type PackageManagerLoginCommand struct { + // packageManager represents the type of project (e.g., NPM, Yarn). + packageManager project.ProjectType // repoName is the name of the repository used for configuration. repoName string // serverDetails contains Artifactory server configuration. @@ -30,18 +30,18 @@ type BuildToolLoginCommand struct { commandName string } -// NewBuildToolLoginCommand initializes a new BuildToolLoginCommand for the specified project type +// NewPackageManagerLoginCommand initializes a new PackageManagerLoginCommand for the specified project type // and automatically sets a command name for the login operation. -func NewBuildToolLoginCommand(buildTool project.ProjectType) *BuildToolLoginCommand { - return &BuildToolLoginCommand{ - buildTool: buildTool, - commandName: buildTool.String() + "_login", +func NewPackageManagerLoginCommand(packageManager project.ProjectType) *PackageManagerLoginCommand { + return &PackageManagerLoginCommand{ + packageManager: packageManager, + commandName: packageManager.String() + "_login", } } -// buildToolToPackageType maps project types to corresponding Artifactory package types (e.g., npm, pypi). -func buildToolToPackageType(buildTool project.ProjectType) (string, error) { - switch buildTool { +// packageManagerToPackageType maps project types to corresponding Artifactory package types (e.g., npm, pypi). +func packageManagerToPackageType(packageManager project.ProjectType) (string, error) { + switch packageManager { case project.Npm, project.Yarn: return repository.Npm, nil case project.Pip, project.Pipenv, project.Poetry: @@ -49,62 +49,62 @@ func buildToolToPackageType(buildTool project.ProjectType) (string, error) { case project.Go: return repository.Go, nil default: - return "", errorutils.CheckErrorf("unsupported build tool: %s", buildTool) + return "", errorutils.CheckErrorf("unsupported package manager: %s", packageManager) } } // CommandName returns the name of the login command. -func (btlc *BuildToolLoginCommand) CommandName() string { - return btlc.commandName +func (pmlc *PackageManagerLoginCommand) CommandName() string { + return pmlc.commandName } // SetServerDetails assigns the server configuration details to the command. -func (btlc *BuildToolLoginCommand) SetServerDetails(serverDetails *config.ServerDetails) *BuildToolLoginCommand { - btlc.serverDetails = serverDetails - return btlc +func (pmlc *PackageManagerLoginCommand) SetServerDetails(serverDetails *config.ServerDetails) *PackageManagerLoginCommand { + pmlc.serverDetails = serverDetails + return pmlc } // ServerDetails returns the stored server configuration details. -func (btlc *BuildToolLoginCommand) ServerDetails() (*config.ServerDetails, error) { - return btlc.serverDetails, nil +func (pmlc *PackageManagerLoginCommand) ServerDetails() (*config.ServerDetails, error) { + return pmlc.serverDetails, nil } // Run executes the configuration method corresponding to the project type specified for the command. -func (btlc *BuildToolLoginCommand) Run() (err error) { - if btlc.repoName == "" { +func (pmlc *PackageManagerLoginCommand) Run() (err error) { + if pmlc.repoName == "" { // Prompt the user to select a virtual repository that matches the project type. - if err = btlc.GetRepositoryNameFromUserInteractively(); err != nil { + if err = pmlc.getRepositoryNameFromUserInteractively(); err != nil { return err } } - // Configure the appropriate tool based on the project type. - switch btlc.buildTool { + // Configure the appropriate package manager based on the project type. + switch pmlc.packageManager { case project.Npm: - err = btlc.configureNpm() + err = pmlc.configureNpm() case project.Yarn: - err = btlc.configureYarn() + err = pmlc.configureYarn() case project.Pip, project.Pipenv: - err = btlc.configurePip() + err = pmlc.configurePip() case project.Poetry: - err = btlc.configurePoetry() + err = pmlc.configurePoetry() case project.Go: - err = btlc.configureGo() + err = pmlc.configureGo() default: - err = errorutils.CheckErrorf("unsupported build tool: %s", btlc.buildTool) + err = errorutils.CheckErrorf("unsupported package manager: %s", pmlc.packageManager) } if err != nil { - return fmt.Errorf("failed to configure %s: %w", btlc.buildTool.String(), err) + return fmt.Errorf("failed to configure %s: %w", pmlc.packageManager.String(), err) } - log.Info(fmt.Sprintf("Successfully configured %s to use JFrog Artifactory repository '%s'.", btlc.buildTool.String(), btlc.repoName)) + log.Info(fmt.Sprintf("Successfully configured %s to use JFrog Artifactory repository '%s'.", pmlc.packageManager.String(), pmlc.repoName)) return nil } -// GetRepositoryNameFromUserInteractively prompts the user to select a compatible virtual repository. -func (btlc *BuildToolLoginCommand) GetRepositoryNameFromUserInteractively() error { - // Map the build tool to its corresponding package type. - packageType, err := buildToolToPackageType(btlc.buildTool) +// getRepositoryNameFromUserInteractively prompts the user to select a compatible virtual repository. +func (pmlc *PackageManagerLoginCommand) getRepositoryNameFromUserInteractively() error { + // Map the package manager to its corresponding package type. + packageType, err := packageManagerToPackageType(pmlc.packageManager) if err != nil { return err } @@ -114,7 +114,7 @@ func (btlc *BuildToolLoginCommand) GetRepositoryNameFromUserInteractively() erro } // Prompt for repository selection based on filter parameters. - btlc.repoName, err = utils.SelectRepositoryInteractively(btlc.serverDetails, repoFilterParams) + pmlc.repoName, err = utils.SelectRepositoryInteractively(pmlc.serverDetails, repoFilterParams) return err } @@ -122,8 +122,8 @@ func (btlc *BuildToolLoginCommand) GetRepositoryNameFromUserInteractively() erro // Runs the following command: // // pip config set global.index-url https://:@/artifactory/api/pypi//simple -func (btlc *BuildToolLoginCommand) configurePip() error { - repoWithCredsUrl, err := pythoncommands.GetPypiRepoUrl(btlc.serverDetails, btlc.repoName, false) +func (pmlc *PackageManagerLoginCommand) configurePip() error { + repoWithCredsUrl, err := pythoncommands.GetPypiRepoUrl(pmlc.serverDetails, pmlc.repoName, false) if err != nil { return err } @@ -135,12 +135,12 @@ func (btlc *BuildToolLoginCommand) configurePip() error { // // poetry config repositories. https:///artifactory/api/pypi//simple // poetry config http-basic. -func (btlc *BuildToolLoginCommand) configurePoetry() error { - repoUrl, username, password, err := pythoncommands.GetPypiRepoUrlWithCredentials(btlc.serverDetails, btlc.repoName, false) +func (pmlc *PackageManagerLoginCommand) configurePoetry() error { + repoUrl, username, password, err := pythoncommands.GetPypiRepoUrlWithCredentials(pmlc.serverDetails, pmlc.repoName, false) if err != nil { return err } - return pythoncommands.RunPoetryConfig(repoUrl.String(), username, password, btlc.repoName) + return pythoncommands.RunPoetryConfig(repoUrl.String(), username, password, pmlc.repoName) } // configureNpm configures npm to use the Artifactory repository URL and sets authentication. @@ -155,14 +155,14 @@ func (btlc *BuildToolLoginCommand) configurePoetry() error { // For basic auth: // // npm config set //your-artifactory-url/artifactory/api/npm//:_auth "" -func (btlc *BuildToolLoginCommand) configureNpm() error { - repoUrl := commandsutils.GetNpmRepositoryUrl(btlc.repoName, btlc.serverDetails.ArtifactoryUrl) +func (pmlc *PackageManagerLoginCommand) configureNpm() error { + repoUrl := commandsutils.GetNpmRepositoryUrl(pmlc.repoName, pmlc.serverDetails.ArtifactoryUrl) if err := npm.ConfigSet(commandsutils.NpmConfigRegistryKey, repoUrl, "npm"); err != nil { return err } - authKey, authValue := commandsutils.GetNpmAuthKeyValue(btlc.serverDetails, repoUrl) + authKey, authValue := commandsutils.GetNpmAuthKeyValue(pmlc.serverDetails, repoUrl) if authKey != "" && authValue != "" { return npm.ConfigSet(authKey, authValue, "npm") } @@ -181,14 +181,14 @@ func (btlc *BuildToolLoginCommand) configureNpm() error { // For basic auth: // // yarn config set //your-artifactory-url/artifactory/api/npm//:_auth "" -func (btlc *BuildToolLoginCommand) configureYarn() error { - repoUrl := commandsutils.GetNpmRepositoryUrl(btlc.repoName, btlc.serverDetails.ArtifactoryUrl) +func (pmlc *PackageManagerLoginCommand) configureYarn() error { + repoUrl := commandsutils.GetNpmRepositoryUrl(pmlc.repoName, pmlc.serverDetails.ArtifactoryUrl) if err := yarn.ConfigSet(commandsutils.NpmConfigRegistryKey, repoUrl, "yarn", false); err != nil { return err } - authKey, authValue := commandsutils.GetNpmAuthKeyValue(btlc.serverDetails, repoUrl) + authKey, authValue := commandsutils.GetNpmAuthKeyValue(pmlc.serverDetails, repoUrl) if authKey != "" && authValue != "" { return yarn.ConfigSet(authKey, authValue, "yarn", false) } @@ -199,8 +199,8 @@ func (btlc *BuildToolLoginCommand) configureYarn() error { // Runs the following command: // // go env -w GOPROXY=https://:@/artifactory/go/,direct -func (btlc *BuildToolLoginCommand) configureGo() error { - repoWithCredsUrl, err := gocommands.GetArtifactoryRemoteRepoUrl(btlc.serverDetails, btlc.repoName, gocommands.GoProxyUrlParams{Direct: true}) +func (pmlc *PackageManagerLoginCommand) configureGo() error { + repoWithCredsUrl, err := gocommands.GetArtifactoryRemoteRepoUrl(pmlc.serverDetails, pmlc.repoName, gocommands.GoProxyUrlParams{Direct: true}) if err != nil { return err } diff --git a/artifactory/commands/python/poetry.go b/artifactory/commands/python/poetry.go index 6ef05dfd4..ec362653e 100644 --- a/artifactory/commands/python/poetry.go +++ b/artifactory/commands/python/poetry.go @@ -187,17 +187,16 @@ func poetryUpdate() (err error) { func addRepoToPyprojectFile(filepath, poetryRepoName, repoUrl string) error { viper.SetConfigType("toml") viper.SetConfigFile(filepath) - err := viper.ReadInConfig() - if err != nil { + if err := viper.ReadInConfig(); err != nil { return errorutils.CheckErrorf("Failed to read pyproject.toml: %s", err.Error()) } viper.Set("tool.poetry.source", []map[string]string{{"name": poetryRepoName, "url": repoUrl}}) - err = viper.WriteConfig() - if err != nil { + if err := viper.WriteConfig(); err != nil { return errorutils.CheckErrorf("Failed to add tool.poetry.source to pyproject.toml: %s", err.Error()) + } log.Info(fmt.Sprintf("Added tool.poetry.source name:%q url:%q", poetryRepoName, repoUrl)) - return err + return nil } func (pc *PoetryCommand) CommandName() string { diff --git a/artifactory/commands/python/python.go b/artifactory/commands/python/python.go index b4f707fea..3a2a94709 100644 --- a/artifactory/commands/python/python.go +++ b/artifactory/commands/python/python.go @@ -181,8 +181,7 @@ func GetPypiRepoUrl(serverDetails *config.ServerDetails, repository string, isCu func RunConfigCommand(buildTool project.ProjectType, args []string) error { log.Debug("Running", buildTool.String(), "config command...") configCmd := gofrogcmd.NewCommand(buildTool.String(), "config", args) - err := gofrogcmd.RunCmd(configCmd) - if err != nil { + if err := gofrogcmd.RunCmd(configCmd); err != nil { return errorutils.CheckErrorf("%s config command failed with: %q", buildTool.String(), err) } return nil diff --git a/artifactory/utils/npm/config-delete.go b/artifactory/utils/npm/config-delete.go deleted file mode 100644 index bcd8467e7..000000000 --- a/artifactory/utils/npm/config-delete.go +++ /dev/null @@ -1,24 +0,0 @@ -package npm - -import ( - gofrogcmd "github.com/jfrog/gofrog/io" - "github.com/jfrog/jfrog-client-go/utils/errorutils" -) - -func ConfigDelete(key, executablePath string) error { - configGetCmdConfig := createConfigDeleteCmdConfig(executablePath, key) - _, err := gofrogcmd.RunCmdOutput(configGetCmdConfig) - if err != nil { - return errorutils.CheckError(err) - } - return nil -} - -func createConfigDeleteCmdConfig(executablePath, key string) *NpmConfig { - return &NpmConfig{ - Npm: executablePath, - Command: []string{"config", "delete", key}, - StrWriter: nil, - ErrWriter: nil, - } -} diff --git a/artifactory/utils/npm/config-get.go b/artifactory/utils/npm/configget.go similarity index 88% rename from artifactory/utils/npm/config-get.go rename to artifactory/utils/npm/configget.go index 5fff4649f..876182b82 100644 --- a/artifactory/utils/npm/config-get.go +++ b/artifactory/utils/npm/configget.go @@ -7,6 +7,7 @@ import ( "github.com/jfrog/jfrog-client-go/utils/errorutils" ) +// This method runs "npm config get" command and returns the value of the specified npm configuration. func ConfigGet(npmFlags []string, confName, executablePath string) (string, error) { configGetCmdConfig := createConfigGetCmdConfig(executablePath, confName, npmFlags) output, err := gofrogcmd.RunCmdOutput(configGetCmdConfig) diff --git a/artifactory/utils/npm/config-list.go b/artifactory/utils/npm/configlist.go similarity index 100% rename from artifactory/utils/npm/config-list.go rename to artifactory/utils/npm/configlist.go diff --git a/artifactory/utils/npm/config-set.go b/artifactory/utils/npm/configset.go similarity index 88% rename from artifactory/utils/npm/config-set.go rename to artifactory/utils/npm/configset.go index c80c9c53c..bcdfbcb48 100644 --- a/artifactory/utils/npm/config-set.go +++ b/artifactory/utils/npm/configset.go @@ -5,6 +5,7 @@ import ( "github.com/jfrog/jfrog-client-go/utils/errorutils" ) +// This method runs "npm config set" command and sets the npm configuration. func ConfigSet(key, value, executablePath string) error { configGetCmdConfig := createConfigSetCmdConfig(executablePath, key, value) _, err := gofrogcmd.RunCmdOutput(configGetCmdConfig) diff --git a/artifactory/utils/yarn/configdelete.go b/artifactory/utils/yarn/configdelete.go deleted file mode 100644 index 63058c6c7..000000000 --- a/artifactory/utils/yarn/configdelete.go +++ /dev/null @@ -1,21 +0,0 @@ -package yarn - -import ( - gofrogcmd "github.com/jfrog/gofrog/io" - "github.com/jfrog/jfrog-client-go/utils/errorutils" -) - -func ConfigDelete(key, executablePath string) error { - configGetCmdConfig := createConfigDeleteCmdConfig(executablePath, key) - _, err := gofrogcmd.RunCmdOutput(configGetCmdConfig) - return errorutils.CheckError(err) -} - -func createConfigDeleteCmdConfig(executablePath, key string) *YarnConfig { - return &YarnConfig{ - Executable: executablePath, - Command: []string{"config", "delete", key}, - StrWriter: nil, - ErrWriter: nil, - } -} diff --git a/artifactory/utils/yarn/configget.go b/artifactory/utils/yarn/configget.go index c121e7b62..48d14c3b1 100644 --- a/artifactory/utils/yarn/configget.go +++ b/artifactory/utils/yarn/configget.go @@ -6,6 +6,7 @@ import ( "strings" ) +// This method runs "yarn config set" command and sets the yarn configuration. func ConfigGet(key, executablePath string, jsonOutput bool) (string, error) { var flags []string = nil if jsonOutput { diff --git a/artifactory/utils/yarn/configset.go b/artifactory/utils/yarn/configset.go index ae602ac3a..6f15ed1b7 100644 --- a/artifactory/utils/yarn/configset.go +++ b/artifactory/utils/yarn/configset.go @@ -5,6 +5,7 @@ import ( "github.com/jfrog/jfrog-client-go/utils/errorutils" ) +// This method runs "yarn config set" command and sets the yarn configuration. func ConfigSet(key, value, executablePath string, jsonInput bool) error { var flags []string = nil if jsonInput { diff --git a/go.mod b/go.mod index 444b7ca2e..6b27ec515 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/jfrog/jfrog-cli-core/v2 -go 1.22.7 +go 1.22.9 require github.com/c-bata/go-prompt v0.2.5 // Should not be updated to 0.2.6 due to a bug (https://github.com/jfrog/jfrog-cli-core/pull/372) @@ -22,11 +22,11 @@ require ( github.com/stretchr/testify v1.9.0 github.com/urfave/cli v1.22.16 github.com/vbauerster/mpb/v8 v8.8.3 - golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c - golang.org/x/mod v0.21.0 - golang.org/x/sync v0.8.0 - golang.org/x/term v0.25.0 - golang.org/x/text v0.19.0 + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f + golang.org/x/mod v0.22.0 + golang.org/x/sync v0.9.0 + golang.org/x/term v0.26.0 + golang.org/x/text v0.20.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -35,7 +35,7 @@ require ( github.com/BurntSushi/toml v1.4.0 // indirect github.com/CycloneDX/cyclonedx-go v0.9.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v1.0.0 // indirect + github.com/ProtonMail/go-crypto v1.1.2 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/andybalholm/brotli v1.1.0 // indirect @@ -88,10 +88,10 @@ require ( github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.28.0 // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/tools v0.26.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/tools v0.27.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index d32408f32..bce43914e 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ github.com/CycloneDX/cyclonedx-go v0.9.0/go.mod h1:NE/EWvzELOFlG6+ljX/QeMlVt9VKc github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= -github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.1.2 h1:A7JbD57ThNqh7XjmHE+PXpQ3Dqt3BrSAC0AL0Go3KS0= +github.com/ProtonMail/go-crypto v1.1.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= @@ -23,7 +23,6 @@ github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oM github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c-bata/go-prompt v0.2.5 h1:3zg6PecEywxNn0xiqcXHD96fkbxghD+gdB2tbsYfl+Y= github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -35,7 +34,6 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= @@ -218,41 +216,24 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -266,41 +247,21 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From cd0ce91e135f1b5284567b5ada04461ee5b617f8 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 11 Nov 2024 15:18:50 +0200 Subject: [PATCH 26/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- ...ildtoollogin.go => packagemanagerlogin.go} | 2 +- ...in_test.go => packagemanagerlogin_test.go} | 36 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) rename artifactory/commands/buildtoollogin/{buildtoollogin.go => packagemanagerlogin.go} (99%) rename artifactory/commands/buildtoollogin/{buildtoollogin_test.go => packagemanagerlogin_test.go} (91%) diff --git a/artifactory/commands/buildtoollogin/buildtoollogin.go b/artifactory/commands/buildtoollogin/packagemanagerlogin.go similarity index 99% rename from artifactory/commands/buildtoollogin/buildtoollogin.go rename to artifactory/commands/buildtoollogin/packagemanagerlogin.go index dc6360f2c..c06c74135 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin.go +++ b/artifactory/commands/buildtoollogin/packagemanagerlogin.go @@ -1,4 +1,4 @@ -package buildtoollogin +package packagemanagerlogin import ( "fmt" diff --git a/artifactory/commands/buildtoollogin/buildtoollogin_test.go b/artifactory/commands/buildtoollogin/packagemanagerlogin_test.go similarity index 91% rename from artifactory/commands/buildtoollogin/buildtoollogin_test.go rename to artifactory/commands/buildtoollogin/packagemanagerlogin_test.go index 8e68dcf91..40e4d3098 100644 --- a/artifactory/commands/buildtoollogin/buildtoollogin_test.go +++ b/artifactory/commands/buildtoollogin/packagemanagerlogin_test.go @@ -1,4 +1,4 @@ -package buildtoollogin +package packagemanagerlogin import ( "fmt" @@ -41,8 +41,8 @@ var testCases = []struct { }, } -func createTestBuildToolLoginCommand(buildTool project.ProjectType) *BuildToolLoginCommand { - cmd := NewBuildToolLoginCommand(buildTool) +func createTestPackageManagerLoginCommand(packageManager project.ProjectType) *PackageManagerLoginCommand { + cmd := NewPackageManagerLoginCommand(packageManager) cmd.repoName = "test-repo" dummyUrl := "https://acme.jfrog.io" cmd.serverDetails = &config.ServerDetails{Url: dummyUrl, ArtifactoryUrl: dummyUrl + "/artifactory"} @@ -50,7 +50,7 @@ func createTestBuildToolLoginCommand(buildTool project.ProjectType) *BuildToolLo return cmd } -func TestBuildToolLoginCommand_Npm(t *testing.T) { +func TestPackageManagerLoginCommand_Npm(t *testing.T) { // Create a temporary directory to act as the environment's npmrc file location. tempDir := t.TempDir() npmrcFilePath := filepath.Join(tempDir, ".npmrc") @@ -58,7 +58,7 @@ func TestBuildToolLoginCommand_Npm(t *testing.T) { // Set NPM_CONFIG_USERCONFIG to point to the temporary npmrc file path. t.Setenv("NPM_CONFIG_USERCONFIG", npmrcFilePath) - npmLoginCmd := createTestBuildToolLoginCommand(project.Npm) + npmLoginCmd := createTestPackageManagerLoginCommand(project.Npm) for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { @@ -96,7 +96,7 @@ func TestBuildToolLoginCommand_Npm(t *testing.T) { } } -func TestBuildToolLoginCommand_Yarn(t *testing.T) { +func TestPackageManagerLoginCommand_Yarn(t *testing.T) { // Retrieve the home directory and construct the .yarnrc file path. homeDir, err := os.UserHomeDir() assert.NoError(t, err) @@ -109,7 +109,7 @@ func TestBuildToolLoginCommand_Yarn(t *testing.T) { assert.NoError(t, restoreYarnrcFunc()) }() - yarnLoginCmd := createTestBuildToolLoginCommand(project.Yarn) + yarnLoginCmd := createTestPackageManagerLoginCommand(project.Yarn) for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { @@ -147,17 +147,17 @@ func TestBuildToolLoginCommand_Yarn(t *testing.T) { } } -func TestBuildToolLoginCommand_Pip(t *testing.T) { +func TestPackageManagerLoginCommand_Pip(t *testing.T) { // pip and pipenv share the same configuration file. - testBuildToolLoginCommandPip(t, project.Pip) + testPackageManagerLoginCommandPip(t, project.Pip) } -func TestBuildToolLoginCommand_Pipenv(t *testing.T) { +func TestPackageManagerLoginCommand_Pipenv(t *testing.T) { // pip and pipenv share the same configuration file. - testBuildToolLoginCommandPip(t, project.Pipenv) + testPackageManagerLoginCommandPip(t, project.Pipenv) } -func testBuildToolLoginCommandPip(t *testing.T, buildTool project.ProjectType) { +func testPackageManagerLoginCommandPip(t *testing.T, packageManager project.ProjectType) { var pipConfFilePath string if coreutils.IsWindows() { pipConfFilePath = filepath.Join(os.Getenv("APPDATA"), "pip", "pip.ini") @@ -174,7 +174,7 @@ func testBuildToolLoginCommandPip(t *testing.T, buildTool project.ProjectType) { assert.NoError(t, restorePipConfFunc()) }() - pipLoginCmd := createTestBuildToolLoginCommand(buildTool) + pipLoginCmd := createTestPackageManagerLoginCommand(packageManager) for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { @@ -211,7 +211,7 @@ func testBuildToolLoginCommandPip(t *testing.T, buildTool project.ProjectType) { } } -func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { +func TestPackageManagerLoginCommand_configurePoetry(t *testing.T) { // Retrieve the home directory and construct the .yarnrc file path. homeDir, err := os.UserHomeDir() assert.NoError(t, err) @@ -239,7 +239,7 @@ func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { assert.NoError(t, restorePoetryAuthFunc()) }() - poetryLoginCmd := createTestBuildToolLoginCommand(project.Poetry) + poetryLoginCmd := createTestPackageManagerLoginCommand(project.Poetry) for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { @@ -286,14 +286,14 @@ func TestBuildToolLoginCommand_configurePoetry(t *testing.T) { } } -func TestBuildToolLoginCommand_Go(t *testing.T) { +func TestPackageManagerLoginCommand_Go(t *testing.T) { goProxyEnv := "GOPROXY" // Restore the original value of the GOPROXY environment variable after the test. restoreGoProxy := clientTestUtils.SetEnvWithCallbackAndAssert(t, goProxyEnv, "") defer restoreGoProxy() - // Assuming createTestBuildToolLoginCommand initializes your Go login command - goLoginCmd := createTestBuildToolLoginCommand(project.Go) + // Assuming createTestPackageManagerLoginCommand initializes your Go login command + goLoginCmd := createTestPackageManagerLoginCommand(project.Go) for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { From 31b0627adce8cd530a3dc660b8c95e709b0cfc47 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 11 Nov 2024 16:02:21 +0200 Subject: [PATCH 27/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../commands/buildtoollogin/packagemanagerlogin.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/artifactory/commands/buildtoollogin/packagemanagerlogin.go b/artifactory/commands/buildtoollogin/packagemanagerlogin.go index c06c74135..d787ae1c3 100644 --- a/artifactory/commands/buildtoollogin/packagemanagerlogin.go +++ b/artifactory/commands/buildtoollogin/packagemanagerlogin.go @@ -17,10 +17,9 @@ import ( "github.com/jfrog/jfrog-client-go/utils/log" ) -// PackageManagerLoginCommand configures registries and authentication for various package managers (npm, Yarn, Pip, Pipenv, Poetry, Go) -// based on the specified project type. +// PackageManagerLoginCommand configures registries and authentication for various package manager (npm, Yarn, Pip, Pipenv, Poetry, Go) type PackageManagerLoginCommand struct { - // packageManager represents the type of project (e.g., NPM, Yarn). + // packageManager represents the type of package manager (e.g., NPM, Yarn). packageManager project.ProjectType // repoName is the name of the repository used for configuration. repoName string @@ -30,7 +29,7 @@ type PackageManagerLoginCommand struct { commandName string } -// NewPackageManagerLoginCommand initializes a new PackageManagerLoginCommand for the specified project type +// NewPackageManagerLoginCommand initializes a new PackageManagerLoginCommand for the specified package manager // and automatically sets a command name for the login operation. func NewPackageManagerLoginCommand(packageManager project.ProjectType) *PackageManagerLoginCommand { return &PackageManagerLoginCommand{ @@ -69,16 +68,16 @@ func (pmlc *PackageManagerLoginCommand) ServerDetails() (*config.ServerDetails, return pmlc.serverDetails, nil } -// Run executes the configuration method corresponding to the project type specified for the command. +// Run executes the configuration method corresponding to the package manager specified for the command. func (pmlc *PackageManagerLoginCommand) Run() (err error) { if pmlc.repoName == "" { - // Prompt the user to select a virtual repository that matches the project type. + // Prompt the user to select a virtual repository that matches the package manager. if err = pmlc.getRepositoryNameFromUserInteractively(); err != nil { return err } } - // Configure the appropriate package manager based on the project type. + // Configure the appropriate package manager based on the package manager. switch pmlc.packageManager { case project.Npm: err = pmlc.configureNpm() From 89cdb971dbdd0c992d0ec9d08a05126575c12365 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 11 Nov 2024 16:11:28 +0200 Subject: [PATCH 28/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin.go | 0 .../packagemanagerlogin_test.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename artifactory/commands/{buildtoollogin => packagemanagerlogin}/packagemanagerlogin.go (100%) rename artifactory/commands/{buildtoollogin => packagemanagerlogin}/packagemanagerlogin_test.go (100%) diff --git a/artifactory/commands/buildtoollogin/packagemanagerlogin.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go similarity index 100% rename from artifactory/commands/buildtoollogin/packagemanagerlogin.go rename to artifactory/commands/packagemanagerlogin/packagemanagerlogin.go diff --git a/artifactory/commands/buildtoollogin/packagemanagerlogin_test.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go similarity index 100% rename from artifactory/commands/buildtoollogin/packagemanagerlogin_test.go rename to artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go From 02252d23f0cf948ff0394ea5a120f0e9f057ce35 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 11 Nov 2024 19:28:31 +0200 Subject: [PATCH 29/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/dotnet/dotnetcommand.go | 43 ++++++++++--- .../commands/dotnet/dotnetcommand_test.go | 4 +- .../packagemanagerlogin.go | 35 +++++++++++ .../packagemanagerlogin_test.go | 63 +++++++++++++++++++ 4 files changed, 133 insertions(+), 12 deletions(-) diff --git a/artifactory/commands/dotnet/dotnetcommand.go b/artifactory/commands/dotnet/dotnetcommand.go index a51d62c2e..3f83a14d4 100644 --- a/artifactory/commands/dotnet/dotnetcommand.go +++ b/artifactory/commands/dotnet/dotnetcommand.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/jfrog/build-info-go/build" "github.com/jfrog/build-info-go/build/utils/dotnet" - "github.com/jfrog/gofrog/io" + frogio "github.com/jfrog/gofrog/io" commonBuild "github.com/jfrog/jfrog-cli-core/v2/common/build" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-client-go/auth" @@ -19,7 +19,7 @@ import ( ) const ( - SourceName = "JFrogCli" + SourceName = "JFrogArtifactory" configFilePattern = "jfrog.cli.nuget." dotnetTestError = `the command failed with an error. @@ -159,21 +159,44 @@ func changeWorkingDir(newWorkingDir string) (string, error) { return newWorkingDir, errorutils.CheckError(err) } -// Runs nuget sources add command -func AddSourceToNugetConfig(cmdType dotnet.ToolchainType, configFileName, sourceUrl, user, password string) error { +// Runs nuget/dotnet source add command +func AddSourceToNugetConfig(cmdType dotnet.ToolchainType, sourceUrl, user, password string) error { cmd, err := dotnet.CreateDotnetAddSourceCmd(cmdType, sourceUrl) if err != nil { return err } flagPrefix := cmdType.GetTypeFlagPrefix() - cmd.CommandFlags = append(cmd.CommandFlags, flagPrefix+"configfile", configFileName) cmd.CommandFlags = append(cmd.CommandFlags, flagPrefix+"name", SourceName) cmd.CommandFlags = append(cmd.CommandFlags, flagPrefix+"username", user) cmd.CommandFlags = append(cmd.CommandFlags, flagPrefix+"password", password) - output, err := io.RunCmdOutput(cmd) - log.Debug("'Add sources' command executed. Output:", output) - return err + stdOut, errorOut, _, err := frogio.RunCmdWithOutputParser(cmd, false) + if err != nil { + return fmt.Errorf("failed to add source: %w\n%s", err, strings.TrimSpace(stdOut+errorOut)) + } + return nil +} + +// Runs nuget/dotnet source remove command +func RemoveSourceFromNugetConfigIfExists(cmdType dotnet.ToolchainType) error { + cmd, err := dotnet.NewToolchainCmd(cmdType) + if err != nil { + return err + } + if cmdType == dotnet.DotnetCore { + cmd.Command = append(cmd.Command, "nuget", "remove", "source", SourceName) + } else { + cmd.Command = append(cmd.Command, "sources", "remove") + cmd.CommandFlags = append(cmd.CommandFlags, "-name", SourceName) + } + stdOut, stdErr, _, err := frogio.RunCmdWithOutputParser(cmd, false) + if err != nil { + if strings.Contains(stdOut+stdErr, "Unable to find") { + return nil + } + return fmt.Errorf("failed to remove source: %w\n%s", err, strings.TrimSpace(stdOut+stdErr)) + } + return nil } // Checks if the user provided input such as -configfile flag or -Source flag. @@ -266,7 +289,7 @@ func InitNewConfig(configDirPath, repoName string, server *config.ServerDetails, // Adds a source to the nuget config template func addSourceToNugetTemplate(configFile *os.File, server *config.ServerDetails, useNugetV2 bool, repoName string) error { - sourceUrl, user, password, err := getSourceDetails(server, repoName, useNugetV2) + sourceUrl, user, password, err := GetSourceDetails(server, repoName, useNugetV2) if err != nil { return err } @@ -282,7 +305,7 @@ func addSourceToNugetTemplate(configFile *os.File, server *config.ServerDetails, return err } -func getSourceDetails(details *config.ServerDetails, repoName string, useNugetV2 bool) (sourceURL, user, password string, err error) { +func GetSourceDetails(details *config.ServerDetails, repoName string, useNugetV2 bool) (sourceURL, user, password string, err error) { var u *url.URL u, err = url.Parse(details.ArtifactoryUrl) if errorutils.CheckError(err) != nil { diff --git a/artifactory/commands/dotnet/dotnetcommand_test.go b/artifactory/commands/dotnet/dotnetcommand_test.go index a89fade31..e2aa89f99 100644 --- a/artifactory/commands/dotnet/dotnetcommand_test.go +++ b/artifactory/commands/dotnet/dotnetcommand_test.go @@ -129,14 +129,14 @@ func TestGetSourceDetails(t *testing.T) { Password: "pass", } repoName := "repo-name" - url, user, pass, err := getSourceDetails(server, repoName, false) + url, user, pass, err := GetSourceDetails(server, repoName, false) assert.NoError(t, err) assert.Equal(t, "user", user) assert.Equal(t, "pass", pass) assert.Equal(t, "https://server.com/artifactory/api/nuget/v3/repo-name", url) server.Password = "" server.AccessToken = "abc123" - url, user, pass, err = getSourceDetails(server, repoName, true) + url, user, pass, err = GetSourceDetails(server, repoName, true) assert.Equal(t, "user", user) assert.Equal(t, "abc123", pass) assert.NoError(t, err) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go index d787ae1c3..d4d7f1081 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go @@ -2,7 +2,9 @@ package packagemanagerlogin import ( "fmt" + bidotnet "github.com/jfrog/build-info-go/build/utils/dotnet" biutils "github.com/jfrog/build-info-go/utils" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/dotnet" gocommands "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/golang" pythoncommands "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/python" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" @@ -47,6 +49,8 @@ func packageManagerToPackageType(packageManager project.ProjectType) (string, er return repository.Pypi, nil case project.Go: return repository.Go, nil + case project.Nuget, project.Dotnet: + return repository.Nuget, nil default: return "", errorutils.CheckErrorf("unsupported package manager: %s", packageManager) } @@ -89,6 +93,8 @@ func (pmlc *PackageManagerLoginCommand) Run() (err error) { err = pmlc.configurePoetry() case project.Go: err = pmlc.configureGo() + case project.Nuget, project.Dotnet: + err = pmlc.configureDotnetNuget() default: err = errorutils.CheckErrorf("unsupported package manager: %s", pmlc.packageManager) } @@ -205,3 +211,32 @@ func (pmlc *PackageManagerLoginCommand) configureGo() error { } return biutils.RunGo([]string{"env", "-w", "GOPROXY=" + repoWithCredsUrl}, "") } + +// configureDotnetNuget configures NuGet or .NET Core to use the specified Artifactory repository with credentials. +// Adds the repository source to the NuGet configuration file, using appropriate credentials for authentication. +// The following command is run for dotnet: +// +// dotnet nuget add source --name "https://acme.jfrog.io/artifactory/api/nuget/{repository-name}" --username --password +// +// For NuGet: +// +// nuget sources add -Name -Source "https://acme.jfrog.io/artifactory/api/nuget/{repository-name}" -Username -Password +func (pmlc *PackageManagerLoginCommand) configureDotnetNuget() error { + // Retrieve repository URL and credentials for NuGet or .NET Core. + sourceUrl, user, password, err := dotnet.GetSourceDetails(pmlc.serverDetails, pmlc.repoName, false) + if err != nil { + return err + } + + // Determine the appropriate toolchain type (NuGet or .NET Core). + toolchainType := bidotnet.DotnetCore + if pmlc.packageManager == project.Nuget { + toolchainType = bidotnet.Nuget + } + err = dotnet.RemoveSourceFromNugetConfigIfExists(toolchainType) + if err != nil { + return err + } + // Add the repository as a source in the NuGet configuration with credentials for authentication. + return dotnet.AddSourceToNugetConfig(toolchainType, sourceUrl, user, password) +} diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go index 40e4d3098..232dc9f8c 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go @@ -2,6 +2,7 @@ package packagemanagerlogin import ( "fmt" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/dotnet" cmdutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" "github.com/jfrog/jfrog-cli-core/v2/common/project" "github.com/jfrog/jfrog-cli-core/v2/utils/config" @@ -326,3 +327,65 @@ func TestPackageManagerLoginCommand_Go(t *testing.T) { }) } } + +func TestBuildToolLoginCommand_configureNuget(t *testing.T) { + testBuildToolLoginCommandConfigureDotnetNuget(t, project.Nuget) +} + +func TestBuildToolLoginCommand_configureDotnet(t *testing.T) { + testBuildToolLoginCommandConfigureDotnetNuget(t, project.Dotnet) +} + +func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager project.ProjectType) { + // Retrieve the home directory and construct the NuGet.config file path. + homeDir, err := os.UserHomeDir() + assert.NoError(t, err) + var nugetConfigDir string + if io.IsWindows() { + nugetConfigDir = "AppData" + } else if packageManager == project.Nuget { + nugetConfigDir = ".config" + } else if packageManager == project.Dotnet { + nugetConfigDir = ".nuget" + } + nugetConfigFilePath := filepath.Join(homeDir, nugetConfigDir, "NuGet", "NuGet.config") + + // Back up the existing NuGet.config and ensure restoration after the test. + restoreNugetConfigFunc, err := ioutils.BackupFile(nugetConfigFilePath, ".nuget.config.backup") + assert.NoError(t, err) + defer func() { + assert.NoError(t, restoreNugetConfigFunc()) + }() + + nugetLoginCmd := createTestPackageManagerLoginCommand(packageManager) + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + // Set up server details for the current test case's authentication type. + nugetLoginCmd.serverDetails.SetUser(testCase.user) + nugetLoginCmd.serverDetails.SetPassword(testCase.password) + nugetLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) + + // Run the login command and ensure no errors occur. + if !assert.NoError(t, nugetLoginCmd.Run()) { + t.FailNow() + } + + // Validate that the repository URL was set correctly in Nuget.config. + // Read the contents of the temporary Poetry config file. + nugetConfigContentBytes, err := os.ReadFile(nugetConfigFilePath) + assert.NoError(t, err) + nugetConfigContent := string(nugetConfigContentBytes) + + assert.Contains(t, nugetConfigContent, fmt.Sprintf("add key=\"%s\" value=\"https://acme.jfrog.io/artifactory/api/nuget/v3/test-repo\"", dotnet.SourceName)) + + if testCase.accessToken != "" { + // Validate token-based authentication (The token is encoded so we can't test it) + assert.Contains(t, nugetConfigContent, fmt.Sprintf("", auth.ExtractUsernameFromAccessToken(testCase.accessToken))) + } else if testCase.user != "" && testCase.password != "" { + // Validate basic nugetConfigContent with user and password. (The password is encoded so we can't test it) + assert.Contains(t, nugetConfigContent, fmt.Sprintf("", testCase.user)) + } + }) + } +} From 76fd3b44a014ba4156e61a9585d547394ac92d12 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 11 Nov 2024 19:36:34 +0200 Subject: [PATCH 30/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin/packagemanagerlogin_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go index 232dc9f8c..681f0d522 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go @@ -341,11 +341,12 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager homeDir, err := os.UserHomeDir() assert.NoError(t, err) var nugetConfigDir string - if io.IsWindows() { + switch { + case io.IsWindows(): nugetConfigDir = "AppData" - } else if packageManager == project.Nuget { + case packageManager == project.Nuget: nugetConfigDir = ".config" - } else if packageManager == project.Dotnet { + case packageManager == project.Dotnet: nugetConfigDir = ".nuget" } nugetConfigFilePath := filepath.Join(homeDir, nugetConfigDir, "NuGet", "NuGet.config") From 77e8d710e8818b1a79ccc8fb5c926c6b0814b280 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Tue, 12 Nov 2024 11:38:15 +0200 Subject: [PATCH 31/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin/packagemanagerlogin_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go index 681f0d522..bbd5e5492 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go @@ -343,10 +343,10 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager var nugetConfigDir string switch { case io.IsWindows(): - nugetConfigDir = "AppData" - case packageManager == project.Nuget: + nugetConfigDir = os.Getenv("APPDATA") + case io.IsMacOS() && packageManager == project.Nuget: nugetConfigDir = ".config" - case packageManager == project.Dotnet: + default: nugetConfigDir = ".nuget" } nugetConfigFilePath := filepath.Join(homeDir, nugetConfigDir, "NuGet", "NuGet.config") From f28f01483e2d812d32951fd813c45e3d0202e38b Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Tue, 12 Nov 2024 11:47:59 +0200 Subject: [PATCH 32/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin/packagemanagerlogin_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go index bbd5e5492..039241ae7 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go @@ -376,6 +376,12 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager // Read the contents of the temporary Poetry config file. nugetConfigContentBytes, err := os.ReadFile(nugetConfigFilePath) assert.NoError(t, err) + nugetConfigContentBytes, err = os.ReadFile(filepath.Join(homeDir, ".config", "NuGet", "NuGet.config")) + assert.NoError(t, err) + nugetConfigContentBytes, err = os.ReadFile(filepath.Join(homeDir, ".nuget", "NuGet", "NuGet.config")) + assert.NoError(t, err) + nugetConfigContentBytes, err = os.ReadFile(filepath.Join("etc", "opt", "NuGet", "NuGet.config")) + assert.NoError(t, err) nugetConfigContent := string(nugetConfigContentBytes) assert.Contains(t, nugetConfigContent, fmt.Sprintf("add key=\"%s\" value=\"https://acme.jfrog.io/artifactory/api/nuget/v3/test-repo\"", dotnet.SourceName)) From 0e959a3d2b06106f51fc2d57c7ca311fb5d6ffbe Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Tue, 12 Nov 2024 11:53:40 +0200 Subject: [PATCH 33/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin/packagemanagerlogin_test.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go index 039241ae7..161f67e98 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go @@ -371,17 +371,15 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager if !assert.NoError(t, nugetLoginCmd.Run()) { t.FailNow() } + assert.FileExists(t, filepath.Join(homeDir, ".config", "NuGet", "NuGet.config")) + assert.FileExists(t, filepath.Join(homeDir, ".nuget", "NuGet", "NuGet.config")) + assert.FileExists(t, filepath.Join("etc", "opt", "NuGet", "NuGet.config")) + t.FailNow() // Validate that the repository URL was set correctly in Nuget.config. // Read the contents of the temporary Poetry config file. nugetConfigContentBytes, err := os.ReadFile(nugetConfigFilePath) assert.NoError(t, err) - nugetConfigContentBytes, err = os.ReadFile(filepath.Join(homeDir, ".config", "NuGet", "NuGet.config")) - assert.NoError(t, err) - nugetConfigContentBytes, err = os.ReadFile(filepath.Join(homeDir, ".nuget", "NuGet", "NuGet.config")) - assert.NoError(t, err) - nugetConfigContentBytes, err = os.ReadFile(filepath.Join("etc", "opt", "NuGet", "NuGet.config")) - assert.NoError(t, err) nugetConfigContent := string(nugetConfigContentBytes) assert.Contains(t, nugetConfigContent, fmt.Sprintf("add key=\"%s\" value=\"https://acme.jfrog.io/artifactory/api/nuget/v3/test-repo\"", dotnet.SourceName)) From 01e0f45e23d041d9f8c59f8008aa680ffc1d7756 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Tue, 12 Nov 2024 12:24:21 +0200 Subject: [PATCH 34/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin_test.go | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go index 161f67e98..21579ad4b 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go @@ -345,11 +345,11 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager case io.IsWindows(): nugetConfigDir = os.Getenv("APPDATA") case io.IsMacOS() && packageManager == project.Nuget: - nugetConfigDir = ".config" + nugetConfigDir = filepath.Join(homeDir, ".config") default: - nugetConfigDir = ".nuget" + nugetConfigDir = filepath.Join(homeDir, ".nuget") } - nugetConfigFilePath := filepath.Join(homeDir, nugetConfigDir, "NuGet", "NuGet.config") + nugetConfigFilePath := filepath.Join(nugetConfigDir, "NuGet", "NuGet.config") // Back up the existing NuGet.config and ensure restoration after the test. restoreNugetConfigFunc, err := ioutils.BackupFile(nugetConfigFilePath, ".nuget.config.backup") @@ -371,9 +371,22 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager if !assert.NoError(t, nugetLoginCmd.Run()) { t.FailNow() } - assert.FileExists(t, filepath.Join(homeDir, ".config", "NuGet", "NuGet.config")) - assert.FileExists(t, filepath.Join(homeDir, ".nuget", "NuGet", "NuGet.config")) - assert.FileExists(t, filepath.Join("etc", "opt", "NuGet", "NuGet.config")) + found := "" + err = filepath.WalkDir(homeDir, func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + + // Check if the file name is "NuGet.Config" + if d.Type().IsRegular() && d.Name() == "NuGet.Config" { + fmt.Printf("Found NuGet.Config at: %s\n", path) + found = path + } + + return nil + }) + assert.NoError(t, err) + assert.FileExists(t, found) t.FailNow() // Validate that the repository URL was set correctly in Nuget.config. From 95bf72e626aa093c39519a339c7a9415ddfccd2a Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Tue, 12 Nov 2024 12:31:53 +0200 Subject: [PATCH 35/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go index 21579ad4b..b013d16d1 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go @@ -343,16 +343,17 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager var nugetConfigDir string switch { case io.IsWindows(): - nugetConfigDir = os.Getenv("APPDATA") - case io.IsMacOS() && packageManager == project.Nuget: - nugetConfigDir = filepath.Join(homeDir, ".config") + nugetConfigDir = filepath.Join("AppData", "Roaming") + case packageManager == project.Nuget: + nugetConfigDir = ".config" default: - nugetConfigDir = filepath.Join(homeDir, ".nuget") + nugetConfigDir = ".nuget" } - nugetConfigFilePath := filepath.Join(nugetConfigDir, "NuGet", "NuGet.config") + + nugetConfigFilePath := filepath.Join(homeDir, nugetConfigDir, "NuGet", "NuGet.config") // Back up the existing NuGet.config and ensure restoration after the test. - restoreNugetConfigFunc, err := ioutils.BackupFile(nugetConfigFilePath, ".nuget.config.backup") + restoreNugetConfigFunc, err := ioutils.BackupFile(nugetConfigFilePath, packageManager.String()+".config.backup") assert.NoError(t, err) defer func() { assert.NoError(t, restoreNugetConfigFunc()) @@ -388,7 +389,6 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager assert.NoError(t, err) assert.FileExists(t, found) - t.FailNow() // Validate that the repository URL was set correctly in Nuget.config. // Read the contents of the temporary Poetry config file. nugetConfigContentBytes, err := os.ReadFile(nugetConfigFilePath) From c90cbb36964a54e87c12e50e8a69738a43eac050 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Tue, 12 Nov 2024 12:41:26 +0200 Subject: [PATCH 36/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin_test.go | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go index b013d16d1..78c41860a 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go @@ -12,6 +12,7 @@ import ( "github.com/jfrog/jfrog-client-go/utils/io" clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "os" "os/exec" "path/filepath" @@ -69,9 +70,7 @@ func TestPackageManagerLoginCommand_Npm(t *testing.T) { npmLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) // Run the login command and ensure no errors occur. - if !assert.NoError(t, npmLoginCmd.Run()) { - t.FailNow() - } + require.NoError(t, npmLoginCmd.Run()) // Read the contents of the temporary npmrc file. npmrcContentBytes, err := os.ReadFile(npmrcFilePath) @@ -120,9 +119,7 @@ func TestPackageManagerLoginCommand_Yarn(t *testing.T) { yarnLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) // Run the login command and ensure no errors occur. - if !assert.NoError(t, yarnLoginCmd.Run()) { - t.FailNow() - } + require.NoError(t, yarnLoginCmd.Run()) // Read the contents of the temporary npmrc file. yarnrcContentBytes, err := os.ReadFile(yarnrcFilePath) @@ -185,9 +182,7 @@ func testPackageManagerLoginCommandPip(t *testing.T, packageManager project.Proj pipLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) // Run the login command and ensure no errors occur. - if !assert.NoError(t, pipLoginCmd.Run()) { - t.FailNow() - } + require.NoError(t, pipLoginCmd.Run()) // Read the contents of the temporary pip config file. pipConfigContentBytes, err := os.ReadFile(pipConfFilePath) @@ -250,9 +245,7 @@ func TestPackageManagerLoginCommand_configurePoetry(t *testing.T) { poetryLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) // Run the login command and ensure no errors occur. - if !assert.NoError(t, poetryLoginCmd.Run()) { - t.FailNow() - } + require.NoError(t, poetryLoginCmd.Run()) // Validate that the repository URL was set correctly in config.toml. // Read the contents of the temporary Poetry config file. @@ -304,9 +297,7 @@ func TestPackageManagerLoginCommand_Go(t *testing.T) { goLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) // Run the login command and ensure no errors occur. - if !assert.NoError(t, goLoginCmd.Run()) { - t.FailNow() - } + require.NoError(t, goLoginCmd.Run()) // Get the value of the GOPROXY environment variable. outputBytes, err := exec.Command("go", "env", "GOPROXY").Output() @@ -369,9 +360,7 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager nugetLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) // Run the login command and ensure no errors occur. - if !assert.NoError(t, nugetLoginCmd.Run()) { - t.FailNow() - } + require.NoError(t, nugetLoginCmd.Run()) found := "" err = filepath.WalkDir(homeDir, func(path string, d os.DirEntry, err error) error { if err != nil { @@ -392,7 +381,9 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager // Validate that the repository URL was set correctly in Nuget.config. // Read the contents of the temporary Poetry config file. nugetConfigContentBytes, err := os.ReadFile(nugetConfigFilePath) - assert.NoError(t, err) + require.NoError(t, err) + assert.FileExists(t, nugetConfigFilePath) + nugetConfigContent := string(nugetConfigContentBytes) assert.Contains(t, nugetConfigContent, fmt.Sprintf("add key=\"%s\" value=\"https://acme.jfrog.io/artifactory/api/nuget/v3/test-repo\"", dotnet.SourceName)) From 5f3706683705db32800e4a67d364cf31081e48d6 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Tue, 12 Nov 2024 12:49:02 +0200 Subject: [PATCH 37/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go index 78c41860a..30d62bde5 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go @@ -343,12 +343,12 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager nugetConfigFilePath := filepath.Join(homeDir, nugetConfigDir, "NuGet", "NuGet.config") - // Back up the existing NuGet.config and ensure restoration after the test. - restoreNugetConfigFunc, err := ioutils.BackupFile(nugetConfigFilePath, packageManager.String()+".config.backup") - assert.NoError(t, err) - defer func() { - assert.NoError(t, restoreNugetConfigFunc()) - }() + //// Back up the existing NuGet.config and ensure restoration after the test. + //restoreNugetConfigFunc, err := ioutils.BackupFile(nugetConfigFilePath, packageManager.String()+".config.backup") + //assert.NoError(t, err) + //defer func() { + // assert.NoError(t, restoreNugetConfigFunc()) + //}() nugetLoginCmd := createTestPackageManagerLoginCommand(packageManager) @@ -376,13 +376,13 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager return nil }) assert.NoError(t, err) - assert.FileExists(t, found) + assert.NotEmpty(t, found) // Validate that the repository URL was set correctly in Nuget.config. // Read the contents of the temporary Poetry config file. nugetConfigContentBytes, err := os.ReadFile(nugetConfigFilePath) - require.NoError(t, err) assert.FileExists(t, nugetConfigFilePath) + require.NoError(t, err) nugetConfigContent := string(nugetConfigContentBytes) From 1ecb8edbc2ba8b931c294a7436c9967fc6b8c224 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Tue, 12 Nov 2024 12:57:06 +0200 Subject: [PATCH 38/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin_test.go | 31 +++++-------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go index 30d62bde5..8bb95abde 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go @@ -341,14 +341,14 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager nugetConfigDir = ".nuget" } - nugetConfigFilePath := filepath.Join(homeDir, nugetConfigDir, "NuGet", "NuGet.config") + nugetConfigFilePath := filepath.Join(homeDir, nugetConfigDir, "NuGet", "NuGet.Config") - //// Back up the existing NuGet.config and ensure restoration after the test. - //restoreNugetConfigFunc, err := ioutils.BackupFile(nugetConfigFilePath, packageManager.String()+".config.backup") - //assert.NoError(t, err) - //defer func() { - // assert.NoError(t, restoreNugetConfigFunc()) - //}() + // Back up the existing NuGet.config and ensure restoration after the test. + restoreNugetConfigFunc, err := ioutils.BackupFile(nugetConfigFilePath, packageManager.String()+".config.backup") + assert.NoError(t, err) + defer func() { + assert.NoError(t, restoreNugetConfigFunc()) + }() nugetLoginCmd := createTestPackageManagerLoginCommand(packageManager) @@ -361,27 +361,10 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager // Run the login command and ensure no errors occur. require.NoError(t, nugetLoginCmd.Run()) - found := "" - err = filepath.WalkDir(homeDir, func(path string, d os.DirEntry, err error) error { - if err != nil { - return err - } - - // Check if the file name is "NuGet.Config" - if d.Type().IsRegular() && d.Name() == "NuGet.Config" { - fmt.Printf("Found NuGet.Config at: %s\n", path) - found = path - } - - return nil - }) - assert.NoError(t, err) - assert.NotEmpty(t, found) // Validate that the repository URL was set correctly in Nuget.config. // Read the contents of the temporary Poetry config file. nugetConfigContentBytes, err := os.ReadFile(nugetConfigFilePath) - assert.FileExists(t, nugetConfigFilePath) require.NoError(t, err) nugetConfigContent := string(nugetConfigContentBytes) From 6e3f8b96d9f43117903eef47aa3778410e14831e Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Sun, 17 Nov 2024 14:21:40 +0200 Subject: [PATCH 39/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../commands/packagemanagerlogin/packagemanagerlogin.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go index d4d7f1081..2a3958669 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go @@ -76,7 +76,7 @@ func (pmlc *PackageManagerLoginCommand) ServerDetails() (*config.ServerDetails, func (pmlc *PackageManagerLoginCommand) Run() (err error) { if pmlc.repoName == "" { // Prompt the user to select a virtual repository that matches the package manager. - if err = pmlc.getRepositoryNameFromUserInteractively(); err != nil { + if err = pmlc.promptUserToSelectRepository(); err != nil { return err } } @@ -106,8 +106,8 @@ func (pmlc *PackageManagerLoginCommand) Run() (err error) { return nil } -// getRepositoryNameFromUserInteractively prompts the user to select a compatible virtual repository. -func (pmlc *PackageManagerLoginCommand) getRepositoryNameFromUserInteractively() error { +// promptUserToSelectRepository prompts the user to select a compatible virtual repository. +func (pmlc *PackageManagerLoginCommand) promptUserToSelectRepository() error { // Map the package manager to its corresponding package type. packageType, err := packageManagerToPackageType(pmlc.packageManager) if err != nil { @@ -233,8 +233,7 @@ func (pmlc *PackageManagerLoginCommand) configureDotnetNuget() error { if pmlc.packageManager == project.Nuget { toolchainType = bidotnet.Nuget } - err = dotnet.RemoveSourceFromNugetConfigIfExists(toolchainType) - if err != nil { + if err = dotnet.RemoveSourceFromNugetConfigIfExists(toolchainType); err != nil { return err } // Add the repository as a source in the NuGet configuration with credentials for authentication. From 746d67dbc3d30d3c95970fcb3b25c9e7426d0bb5 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 20 Nov 2024 19:31:12 +0200 Subject: [PATCH 40/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../container/containermanagercommand.go | 6 +- .../packagemanagerlogin.go | 78 ++++++++++++++++--- .../utils/container/containermanager.go | 8 +- artifactory/utils/repositoryutils.go | 3 +- common/project/projectconfig.go | 15 ++++ utils/ioutils/questionnaire.go | 8 +- 6 files changed, 96 insertions(+), 22 deletions(-) diff --git a/artifactory/commands/container/containermanagercommand.go b/artifactory/commands/container/containermanagercommand.go index 6190a5693..eb4e74efe 100644 --- a/artifactory/commands/container/containermanagercommand.go +++ b/artifactory/commands/container/containermanagercommand.go @@ -41,7 +41,11 @@ func (cm *ContainerCommand) PerformLogin(serverDetails *config.ServerDetails, co } } loginConfig := &container.ContainerManagerLoginConfig{ServerDetails: serverDetails} - return container.ContainerManagerLogin(cm.image, loginConfig, containerManagerType) + imageRegistry, err := cm.image.GetRegistry() + if err != nil { + return err + } + return container.ContainerManagerLogin(imageRegistry, loginConfig, containerManagerType) } return nil } diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go index 2a3958669..6dafcccea 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go @@ -10,15 +10,40 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/repository" commandsutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/container" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/npm" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/yarn" "github.com/jfrog/jfrog-cli-core/v2/common/project" "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-client-go/artifactory/services" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" + "net/url" ) +// PackageManagerToRepositoryPackageType maps project types to corresponding Artifactory repository package types. +var PackageManagerToRepositoryPackageType = map[project.ProjectType]string{ + // Npm package managers + project.Npm: repository.Npm, + project.Yarn: repository.Npm, + + // Python (pypi) package managers + project.Pip: repository.Pypi, + project.Pipenv: repository.Pypi, + project.Poetry: repository.Pypi, + + // Nuget package managers + project.Nuget: repository.Nuget, + project.Dotnet: repository.Nuget, + + // Docker package managers + project.Docker: repository.Docker, + project.Podman: repository.Docker, + + project.Go: repository.Go, +} + // PackageManagerLoginCommand configures registries and authentication for various package manager (npm, Yarn, Pip, Pipenv, Poetry, Go) type PackageManagerLoginCommand struct { // packageManager represents the type of package manager (e.g., NPM, Yarn). @@ -42,18 +67,12 @@ func NewPackageManagerLoginCommand(packageManager project.ProjectType) *PackageM // packageManagerToPackageType maps project types to corresponding Artifactory package types (e.g., npm, pypi). func packageManagerToPackageType(packageManager project.ProjectType) (string, error) { - switch packageManager { - case project.Npm, project.Yarn: - return repository.Npm, nil - case project.Pip, project.Pipenv, project.Poetry: - return repository.Pypi, nil - case project.Go: - return repository.Go, nil - case project.Nuget, project.Dotnet: - return repository.Nuget, nil - default: - return "", errorutils.CheckErrorf("unsupported package manager: %s", packageManager) + // Retrieve the package type from the map. + if packageType, exists := PackageManagerToRepositoryPackageType[packageManager]; exists { + return packageType, nil } + // Return an error if the package manager is unsupported. + return "", errorutils.CheckErrorf("unsupported package manager: %s", packageManager) } // CommandName returns the name of the login command. @@ -95,6 +114,8 @@ func (pmlc *PackageManagerLoginCommand) Run() (err error) { err = pmlc.configureGo() case project.Nuget, project.Dotnet: err = pmlc.configureDotnetNuget() + case project.Docker, project.Podman: + err = pmlc.configureContainer() default: err = errorutils.CheckErrorf("unsupported package manager: %s", pmlc.packageManager) } @@ -102,7 +123,7 @@ func (pmlc *PackageManagerLoginCommand) Run() (err error) { return fmt.Errorf("failed to configure %s: %w", pmlc.packageManager.String(), err) } - log.Info(fmt.Sprintf("Successfully configured %s to use JFrog Artifactory repository '%s'.", pmlc.packageManager.String(), pmlc.repoName)) + log.Output(fmt.Sprintf("Successfully configured %s to use JFrog Artifactory repository '%s'.", coreutils.PrintBoldTitle(pmlc.packageManager.String()), coreutils.PrintBoldTitle(pmlc.repoName))) return nil } @@ -239,3 +260,36 @@ func (pmlc *PackageManagerLoginCommand) configureDotnetNuget() error { // Add the repository as a source in the NuGet configuration with credentials for authentication. return dotnet.AddSourceToNugetConfig(toolchainType, sourceUrl, user, password) } + +// configureContainer configures container managers like Docker or Podman to authenticate with JFrog Artifactory. +// It performs a login using the container manager's CLI command. +// +// For Docker: +// +// docker login -u -p +// +// For Podman: +// +// podman login -u -p +func (pmlc *PackageManagerLoginCommand) configureContainer() error { + var containerManagerType container.ContainerManagerType + switch pmlc.packageManager { + case project.Docker: + containerManagerType = container.DockerClient + case project.Podman: + containerManagerType = container.Podman + default: + return errorutils.CheckErrorf("unsupported container manager: %s", pmlc.packageManager) + } + // Parse the URL to remove the scheme (https:// or http://) + parsedURL, err := url.Parse(pmlc.serverDetails.GetUrl()) + if err != nil { + return err + } + urlWithoutScheme := parsedURL.Host + parsedURL.Path + return container.ContainerManagerLogin( + urlWithoutScheme, + &container.ContainerManagerLoginConfig{ServerDetails: pmlc.serverDetails}, + containerManagerType, + ) +} diff --git a/artifactory/utils/container/containermanager.go b/artifactory/utils/container/containermanager.go index 8c16c6659..9867352bc 100644 --- a/artifactory/utils/container/containermanager.go +++ b/artifactory/utils/container/containermanager.go @@ -189,11 +189,7 @@ func (loginCmd *LoginCmd) RunCmd() error { // First we'll try to log in assuming a proxy-less tag (e.g. "registry-address/docker-repo/image:ver"). // If fails, we will try assuming a reverse proxy tag (e.g. "registry-address-docker-repo/image:ver"). -func ContainerManagerLogin(image *Image, config *ContainerManagerLoginConfig, containerManager ContainerManagerType) error { - imageRegistry, err := image.GetRegistry() - if err != nil { - return err - } +func ContainerManagerLogin(imageRegistry string, config *ContainerManagerLoginConfig, containerManager ContainerManagerType) error { username := config.ServerDetails.User password := config.ServerDetails.Password // If access-token exists, perform login with it. @@ -206,7 +202,7 @@ func ContainerManagerLogin(image *Image, config *ContainerManagerLoginConfig, co } // Perform login. cmd := &LoginCmd{DockerRegistry: imageRegistry, Username: username, Password: password, containerManager: containerManager} - err = cmd.RunCmd() + err := cmd.RunCmd() if exitCode := coreutils.GetExitCode(err, 0, 0, false); exitCode == coreutils.ExitCodeNoError { // Login succeeded return nil diff --git a/artifactory/utils/repositoryutils.go b/artifactory/utils/repositoryutils.go index 40e11b30f..1e3aa39a6 100644 --- a/artifactory/utils/repositoryutils.go +++ b/artifactory/utils/repositoryutils.go @@ -1,6 +1,7 @@ package utils import ( + "fmt" "github.com/jfrog/gofrog/datastructures" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" @@ -101,7 +102,7 @@ func SelectRepositoryInteractively(serverDetails *config.ServerDetails, repoFilt return filteredRepos[0], nil } // Prompt the user to select a repository. - return ioutils.AskFromListWithMismatchConfirmation("Please select a repository to login to:", "Repository not found.", ioutils.ConvertToSuggests(filteredRepos)), nil + return ioutils.AskFromListWithMismatchConfirmation(fmt.Sprintf("Please select a %s %s repository to configure:", repoFilterParams.RepoType, repoFilterParams.PackageType), "Repository not found.", ioutils.ConvertToSuggests(filteredRepos)), nil } // GetFilteredRepositoriesWithFilterParams returns the names of local, remote, virtual, and federated repositories filtered by their names and type. diff --git a/common/project/projectconfig.go b/common/project/projectconfig.go index 92134ae75..d8afed93c 100644 --- a/common/project/projectconfig.go +++ b/common/project/projectconfig.go @@ -26,6 +26,7 @@ const ( type ProjectType int const ( + // When adding new ProjectType here, Must also add it as a string to the ProjectTypes slice Go ProjectType = iota Pip Pipenv @@ -41,6 +42,8 @@ const ( Terraform Cocoapods Swift + Docker + Podman ) type ConfigType string @@ -66,12 +69,24 @@ var ProjectTypes = []string{ "terraform", "cocoapods", "swift", + "docker", + "podman", } func (projectType ProjectType) String() string { return ProjectTypes[projectType] } +// FromString converts a string to its corresponding ProjectType +func FromString(value string) ProjectType { + for i, v := range ProjectTypes { + if v == value { + return ProjectType(i) + } + } + return -1 +} + type MissingResolverErr struct { message string } diff --git a/utils/ioutils/questionnaire.go b/utils/ioutils/questionnaire.go index 74459ac43..c089db3a8 100644 --- a/utils/ioutils/questionnaire.go +++ b/utils/ioutils/questionnaire.go @@ -1,6 +1,8 @@ package ioutils import ( + "fmt" + "os" "regexp" "strconv" "strings" @@ -82,7 +84,9 @@ func interruptKeyBind() prompt.Option { interrupt := prompt.KeyBind{ Key: prompt.ControlC, Fn: func(buf *prompt.Buffer) { - panic("Interrupted") + // Gracefully exit the program + fmt.Println("\nOperation interrupted. Exiting...") + os.Exit(0) }, } return prompt.OptionAddKeyBind(interrupt) @@ -177,7 +181,7 @@ func validateAnswer(answer string, options []prompt.Suggest, allowVars bool) boo // If the provided answer does not appear in list, confirm the choice. func AskFromListWithMismatchConfirmation(promptPrefix, misMatchMsg string, options []prompt.Suggest) string { for { - answer := prompt.Input(promptPrefix+" ", prefixCompleter(options), interruptKeyBind()) + answer := prompt.Input(promptPrefix+" ", prefixCompleter(options), interruptKeyBind(), prompt.OptionShowCompletionAtStart(), prompt.OptionCompletionOnDown()) if answer == "" { log.Output(EmptyValueMsg) } From 43d1a80bf3a261bb13296a3364943e5ba04b1161 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Thu, 21 Nov 2024 11:45:28 +0200 Subject: [PATCH 41/41] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin/packagemanagerlogin.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go index 6dafcccea..a2a4fbb9f 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go @@ -19,11 +19,13 @@ import ( "github.com/jfrog/jfrog-client-go/artifactory/services" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/log" + "golang.org/x/exp/maps" "net/url" + "sort" ) -// PackageManagerToRepositoryPackageType maps project types to corresponding Artifactory repository package types. -var PackageManagerToRepositoryPackageType = map[project.ProjectType]string{ +// packageManagerToRepositoryPackageType maps project types to corresponding Artifactory repository package types. +var packageManagerToRepositoryPackageType = map[project.ProjectType]string{ // Npm package managers project.Npm: repository.Npm, project.Yarn: repository.Npm, @@ -65,10 +67,19 @@ func NewPackageManagerLoginCommand(packageManager project.ProjectType) *PackageM } } +// GetSupportedPackageManagersList returns a sorted list of supported package managers. +func GetSupportedPackageManagersList() []project.ProjectType { + allSupportedPackageManagers := maps.Keys(packageManagerToRepositoryPackageType) + sort.Slice(allSupportedPackageManagers, func(i, j int) bool { + return allSupportedPackageManagers[i] < allSupportedPackageManagers[j] + }) + return allSupportedPackageManagers +} + // packageManagerToPackageType maps project types to corresponding Artifactory package types (e.g., npm, pypi). func packageManagerToPackageType(packageManager project.ProjectType) (string, error) { // Retrieve the package type from the map. - if packageType, exists := PackageManagerToRepositoryPackageType[packageManager]; exists { + if packageType, exists := packageManagerToRepositoryPackageType[packageManager]; exists { return packageType, nil } // Return an error if the package manager is unsupported.