From bb36015553ee3fe70da0c5fb21e1c322612c0c0b Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Fri, 25 Oct 2024 18:11:22 +0300 Subject: [PATCH 01/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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. From 6e5def882d74b79a824108b2c9d83cb391f4bcff Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Tue, 24 Dec 2024 14:51:32 +0200 Subject: [PATCH 42/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin/packagemanagerlogin.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go index a2a4fbb9f..eb00804c3 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go @@ -76,14 +76,22 @@ func GetSupportedPackageManagersList() []project.ProjectType { return allSupportedPackageManagers } +func IsSupportedPackageManager(packageManager project.ProjectType) bool { + _, exists := packageManagerToRepositoryPackageType[packageManager] + return exists +} + // 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 { return packageType, nil } + if !IsSupportedPackageManager(packageManager) { + return "", errorutils.CheckErrorf("unsupported package type for package manager: %s", packageManager) + } // Return an error if the package manager is unsupported. - return "", errorutils.CheckErrorf("unsupported package manager: %s", packageManager) + return packageManagerToRepositoryPackageType[packageManager], nil } // CommandName returns the name of the login command. From a89e2f510000b9d1619fc7fac2be71bb0eaf1d14 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Tue, 24 Dec 2024 15:11:46 +0200 Subject: [PATCH 43/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 940b45df5..9e57755e1 100644 --- a/go.mod +++ b/go.mod @@ -89,14 +89,14 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/net v0.31.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.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 ) -replace github.com/jfrog/jfrog-client-go => github.com/eyalbe4/jfrog-client-go v1.28.1-0.20241223165051-2717483395bc +replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20241223175448-88f1089d0694 // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20241121100855-e7a75ceee2bd diff --git a/go.sum b/go.sum index 4ad9a0961..faa992cee 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,6 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/eyalbe4/jfrog-client-go v1.28.1-0.20241223165051-2717483395bc h1:VLvI2P75Fc8iJVrydKEMcmeBCcKeUHmB7lLEm5P8aYU= -github.com/eyalbe4/jfrog-client-go v1.28.1-0.20241223165051-2717483395bc/go.mod h1:2ySOMva54L3EYYIlCBYBTcTgqfrrQ19gtpA/MWfA/ec= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= github.com/forPelevin/gomoji v1.2.0/go.mod h1:8+Z3KNGkdslmeGZBC3tCrwMrcPy5GRzAD+gL9NAwMXg= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -95,6 +93,8 @@ github.com/jfrog/build-info-go v1.10.7 h1:10NVHYg0193gJpQft+S4WQfvYMtj5jlwwhJRvk github.com/jfrog/build-info-go v1.10.7/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4= +github.com/jfrog/jfrog-client-go v1.28.1-0.20241223175448-88f1089d0694 h1:1EP8yAhB+SGTXhfGa/w4S748S12qJKXU4u/yiAJxnX0= +github.com/jfrog/jfrog-client-go v1.28.1-0.20241223175448-88f1089d0694/go.mod h1:2ySOMva54L3EYYIlCBYBTcTgqfrrQ19gtpA/MWfA/ec= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -229,8 +229,8 @@ golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1 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.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= From 3a1bcf00d2aac3d825c53fb6826ff8d243724b76 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 25 Dec 2024 18:28:34 +0200 Subject: [PATCH 44/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin.go | 48 ++++++++++--------- .../packagemanagerlogin_test.go | 25 ++++++++++ common/project/projectconfig_test.go | 30 ++++++++++++ 3 files changed, 80 insertions(+), 23 deletions(-) create mode 100644 common/project/projectconfig_test.go diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go index eb00804c3..59b315ec3 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go @@ -21,7 +21,7 @@ import ( "github.com/jfrog/jfrog-client-go/utils/log" "golang.org/x/exp/maps" "net/url" - "sort" + "slices" ) // packageManagerToRepositoryPackageType maps project types to corresponding Artifactory repository package types. @@ -52,6 +52,8 @@ type PackageManagerLoginCommand struct { packageManager project.ProjectType // repoName is the name of the repository used for configuration. repoName string + // projectKey is the JFrog Project key in JFrog Platform. + projectKey string // serverDetails contains Artifactory server configuration. serverDetails *config.ServerDetails // commandName specifies the command for this instance. @@ -70,8 +72,9 @@ 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] + // Sort keys based on their natural enum order + slices.SortFunc(allSupportedPackageManagers, func(a, b project.ProjectType) int { + return int(a) - int(b) }) return allSupportedPackageManagers } @@ -81,19 +84,6 @@ func IsSupportedPackageManager(packageManager project.ProjectType) bool { return exists } -// 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 { - return packageType, nil - } - if !IsSupportedPackageManager(packageManager) { - return "", errorutils.CheckErrorf("unsupported package type for package manager: %s", packageManager) - } - // Return an error if the package manager is unsupported. - return packageManagerToRepositoryPackageType[packageManager], nil -} - // CommandName returns the name of the login command. func (pmlc *PackageManagerLoginCommand) CommandName() string { return pmlc.commandName @@ -110,8 +100,24 @@ func (pmlc *PackageManagerLoginCommand) ServerDetails() (*config.ServerDetails, return pmlc.serverDetails, nil } +// SetRepoName assigns the repository name to the command. +func (pmlc *PackageManagerLoginCommand) SetRepoName(repoName string) *PackageManagerLoginCommand { + pmlc.repoName = repoName + return pmlc +} + +// SetProjectKey assigns the project key to the command. +func (pmlc *PackageManagerLoginCommand) SetProjectKey(projectKey string) *PackageManagerLoginCommand { + pmlc.projectKey = projectKey + return pmlc +} + // Run executes the configuration method corresponding to the package manager specified for the command. func (pmlc *PackageManagerLoginCommand) Run() (err error) { + if IsSupportedPackageManager(pmlc.packageManager) { + return errorutils.CheckErrorf("unsupported package manager: %s", pmlc.packageManager) + } + if pmlc.repoName == "" { // Prompt the user to select a virtual repository that matches the package manager. if err = pmlc.promptUserToSelectRepository(); err != nil { @@ -147,15 +153,11 @@ func (pmlc *PackageManagerLoginCommand) Run() (err 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 { - return err - } +func (pmlc *PackageManagerLoginCommand) promptUserToSelectRepository() (err error) { repoFilterParams := services.RepositoriesFilterParams{ RepoType: utils.Virtual.String(), - PackageType: packageType, + PackageType: packageManagerToRepositoryPackageType[pmlc.packageManager], + ProjectKey: pmlc.projectKey, } // Prompt for repository selection based on filter parameters. diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go index 8bb95abde..9783e8133 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go @@ -13,6 +13,7 @@ import ( clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/exp/slices" "os" "os/exec" "path/filepath" @@ -52,6 +53,13 @@ func createTestPackageManagerLoginCommand(packageManager project.ProjectType) *P return cmd } +func TestPackageManagerLoginCommand_NotSupported(t *testing.T) { + notSupportedLoginCmd := createTestPackageManagerLoginCommand(project.Cocoapods) + err := notSupportedLoginCmd.Run() + assert.Error(t, err) + assert.ErrorContains(t, err, "unsupported package manager") +} + func TestPackageManagerLoginCommand_Npm(t *testing.T) { // Create a temporary directory to act as the environment's npmrc file location. tempDir := t.TempDir() @@ -381,3 +389,20 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager }) } } + +func TestGetSupportedPackageManagersList(t *testing.T) { + result := GetSupportedPackageManagersList() + // Check that Go is before Pip, and Pip is before Npm using GreaterOrEqual + assert.GreaterOrEqual(t, slices.Index(result, project.Pip), slices.Index(result, project.Go), "Go should come before Pip") + assert.GreaterOrEqual(t, slices.Index(result, project.Npm), slices.Index(result, project.Pip), "Pip should come before Npm") +} + +func TestIsSupportedPackageManager(t *testing.T) { + // Test valid package managers + for pm := range packageManagerToRepositoryPackageType { + assert.True(t, IsSupportedPackageManager(pm), "Package manager %s should be supported", pm) + } + + // Test unsupported package manager + assert.False(t, IsSupportedPackageManager(project.Cocoapods), "Package manager Cocoapods should not be supported") +} diff --git a/common/project/projectconfig_test.go b/common/project/projectconfig_test.go new file mode 100644 index 000000000..92bd849e6 --- /dev/null +++ b/common/project/projectconfig_test.go @@ -0,0 +1,30 @@ +package project + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestFromString(t *testing.T) { + // Test valid conversions + tests := []struct { + input string + expected ProjectType + }{ + {"go", Go}, + {"pip", Pip}, + {"npm", Npm}, + {"pnpm", Pnpm}, + } + + for _, test := range tests { + t.Run(test.input, func(t *testing.T) { + result := FromString(test.input) + assert.Equal(t, test.expected, result, "For input %s, expected %v but got %v", test.input, test.expected, result) + }) + } + + // Test invalid conversion + result := FromString("InvalidProject") + assert.Equal(t, ProjectType(-1), result, "Expected -1 for invalid project type") +} From 52894c8735c41a3aabc3e12858b29c8e069cfdf1 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 25 Dec 2024 18:53:15 +0200 Subject: [PATCH 45/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/packagemanagerlogin/packagemanagerlogin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go index 59b315ec3..d8ad17de6 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go @@ -114,7 +114,7 @@ func (pmlc *PackageManagerLoginCommand) SetProjectKey(projectKey string) *Packag // Run executes the configuration method corresponding to the package manager specified for the command. func (pmlc *PackageManagerLoginCommand) Run() (err error) { - if IsSupportedPackageManager(pmlc.packageManager) { + if !IsSupportedPackageManager(pmlc.packageManager) { return errorutils.CheckErrorf("unsupported package manager: %s", pmlc.packageManager) } From dc79716fa809f7540b4c82dae18f2f78708ba6e5 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Thu, 26 Dec 2024 15:18:59 +0200 Subject: [PATCH 46/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin/packagemanagerlogin.go | 8 ++++---- common/project/projectconfig.go | 4 ++-- common/project/projectconfig_test.go | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go index d8ad17de6..e462724a2 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go @@ -287,11 +287,11 @@ func (pmlc *PackageManagerLoginCommand) configureDotnetNuget() error { // // For Docker: // -// docker login -u -p +// echo | docker login -u --password-stdin // // For Podman: // -// podman login -u -p +// echo | podman login -u --password-stdin func (pmlc *PackageManagerLoginCommand) configureContainer() error { var containerManagerType container.ContainerManagerType switch pmlc.packageManager { @@ -303,11 +303,11 @@ func (pmlc *PackageManagerLoginCommand) configureContainer() error { 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()) + parsedPlatformURL, err := url.Parse(pmlc.serverDetails.GetUrl()) if err != nil { return err } - urlWithoutScheme := parsedURL.Host + parsedURL.Path + urlWithoutScheme := parsedPlatformURL.Host + parsedPlatformURL.Path return container.ContainerManagerLogin( urlWithoutScheme, &container.ContainerManagerLoginConfig{ServerDetails: pmlc.serverDetails}, diff --git a/common/project/projectconfig.go b/common/project/projectconfig.go index d8afed93c..646f014eb 100644 --- a/common/project/projectconfig.go +++ b/common/project/projectconfig.go @@ -79,8 +79,8 @@ func (projectType ProjectType) String() string { // FromString converts a string to its corresponding ProjectType func FromString(value string) ProjectType { - for i, v := range ProjectTypes { - if v == value { + for i, projectType := range ProjectTypes { + if projectType == value { return ProjectType(i) } } diff --git a/common/project/projectconfig_test.go b/common/project/projectconfig_test.go index 92bd849e6..9a7edafc5 100644 --- a/common/project/projectconfig_test.go +++ b/common/project/projectconfig_test.go @@ -7,7 +7,7 @@ import ( func TestFromString(t *testing.T) { // Test valid conversions - tests := []struct { + testCases := []struct { input string expected ProjectType }{ @@ -17,14 +17,14 @@ func TestFromString(t *testing.T) { {"pnpm", Pnpm}, } - for _, test := range tests { - t.Run(test.input, func(t *testing.T) { - result := FromString(test.input) - assert.Equal(t, test.expected, result, "For input %s, expected %v but got %v", test.input, test.expected, result) + for _, testCase := range testCases { + t.Run(testCase.input, func(t *testing.T) { + result := FromString(testCase.input) + assert.Equal(t, testCase.expected, result) }) } // Test invalid conversion result := FromString("InvalidProject") - assert.Equal(t, ProjectType(-1), result, "Expected -1 for invalid project type") + assert.Equal(t, ProjectType(-1), result) } From 4908ff5e641981d384732f646c1f68040728be6d Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Thu, 26 Dec 2024 18:42:37 +0200 Subject: [PATCH 47/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- utils/ioutils/questionnaire.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/utils/ioutils/questionnaire.go b/utils/ioutils/questionnaire.go index c089db3a8..40ba1b965 100644 --- a/utils/ioutils/questionnaire.go +++ b/utils/ioutils/questionnaire.go @@ -1,8 +1,6 @@ package ioutils import ( - "fmt" - "os" "regexp" "strconv" "strings" @@ -84,9 +82,7 @@ func interruptKeyBind() prompt.Option { interrupt := prompt.KeyBind{ Key: prompt.ControlC, Fn: func(buf *prompt.Buffer) { - // Gracefully exit the program - fmt.Println("\nOperation interrupted. Exiting...") - os.Exit(0) + panic("Operation interrupted. Exiting...") }, } return prompt.OptionAddKeyBind(interrupt) From 84f3f27361f085f673f776036c748372abcc71fb Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 6 Jan 2025 16:42:45 +0200 Subject: [PATCH 48/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/dotnet/dotnetcommand.go | 54 +++++++- .../commands/dotnet/dotnetcommand_test.go | 127 ++++++++++++++++++ .../packagemanagerlogin.go | 57 +++++--- .../packagemanagerlogin_test.go | 125 +++++++---------- artifactory/commands/python/pip.go | 21 +++ artifactory/commands/python/poetry.go | 4 +- artifactory/commands/python/python.go | 2 +- 7 files changed, 292 insertions(+), 98 deletions(-) diff --git a/artifactory/commands/dotnet/dotnetcommand.go b/artifactory/commands/dotnet/dotnetcommand.go index 57cd73d4f..3756c27f7 100644 --- a/artifactory/commands/dotnet/dotnetcommand.go +++ b/artifactory/commands/dotnet/dotnetcommand.go @@ -15,6 +15,7 @@ import ( "net/url" "os" "path" + "path/filepath" "strings" ) @@ -168,7 +169,7 @@ func changeWorkingDir(newWorkingDir string) (string, error) { } // Runs nuget/dotnet source add command -func AddSourceToNugetConfig(cmdType dotnet.ToolchainType, sourceUrl, user, password string) error { +func AddSourceToNugetConfig(cmdType dotnet.ToolchainType, sourceUrl, user, password, customConfigPath string) error { cmd, err := dotnet.CreateDotnetAddSourceCmd(cmdType, sourceUrl) if err != nil { return err @@ -178,6 +179,11 @@ func AddSourceToNugetConfig(cmdType dotnet.ToolchainType, sourceUrl, user, passw cmd.CommandFlags = append(cmd.CommandFlags, flagPrefix+"name", SourceName) cmd.CommandFlags = append(cmd.CommandFlags, flagPrefix+"username", user) cmd.CommandFlags = append(cmd.CommandFlags, flagPrefix+"password", password) + + if customConfigPath != "" { + addConfigFileFlag(cmd, customConfigPath) + } + 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)) @@ -185,8 +191,9 @@ func AddSourceToNugetConfig(cmdType dotnet.ToolchainType, sourceUrl, user, passw return nil } -// Runs nuget/dotnet source remove command -func RemoveSourceFromNugetConfigIfExists(cmdType dotnet.ToolchainType) error { +// RemoveSourceFromNugetConfigIfExists runs the nuget/dotnet source remove command. +// Removes the source if it exists in the configuration. +func RemoveSourceFromNugetConfigIfExists(cmdType dotnet.ToolchainType, customConfigPath string) error { cmd, err := dotnet.NewToolchainCmd(cmdType) if err != nil { return err @@ -197,9 +204,14 @@ func RemoveSourceFromNugetConfigIfExists(cmdType dotnet.ToolchainType) error { cmd.Command = append(cmd.Command, "sources", "remove") cmd.CommandFlags = append(cmd.CommandFlags, "-name", SourceName) } + + if customConfigPath != "" { + addConfigFileFlag(cmd, customConfigPath) + } + stdOut, stdErr, _, err := frogio.RunCmdWithOutputParser(cmd, false) if err != nil { - if strings.Contains(stdOut+stdErr, "Unable to find") { + if strings.Contains(stdOut+stdErr, "Unable to find") || strings.Contains(stdOut+stdErr, "does not exist") { return nil } return fmt.Errorf("failed to remove source: %w\n%s", err, strings.TrimSpace(stdOut+stdErr)) @@ -207,6 +219,40 @@ func RemoveSourceFromNugetConfigIfExists(cmdType dotnet.ToolchainType) error { return nil } +// GetConfigPathFromEnvIfProvided returns the path to the custom NuGet.Config file if it was provided by the user. +func GetConfigPathFromEnvIfProvided(cmdType dotnet.ToolchainType) string { + if cmdType == dotnet.DotnetCore { + if customDotnetDir := os.Getenv("DOTNET_CLI_HOME"); customDotnetDir != "" { + return filepath.Join(customDotnetDir, "NuGet.Config") + } + } + return os.Getenv("NUGET_CONFIG_FILE") +} + +// CreateConfigFileIfNeeded creates a new config file if it does not exist. +func CreateConfigFileIfNeeded(customConfigPath string) error { + // Ensure the file exists, create an empty one it if not + if _, err := os.Stat(customConfigPath); os.IsNotExist(err) { + if err = os.MkdirAll(filepath.Dir(customConfigPath), 0755); err != nil { + return err + } + // Write the default config content to the file + if err = os.WriteFile(customConfigPath, []byte(""), 0644); err != nil { + return err + } + } + return nil +} + +func addConfigFileFlag(cmd *dotnet.Cmd, configFilePath string) { + // Add the config file flag if needed. + if cmd.GetToolchain() == dotnet.DotnetCore { + cmd.CommandFlags = append(cmd.CommandFlags, "--configfile", configFilePath) + } else { + cmd.CommandFlags = append(cmd.CommandFlags, "-ConfigFile", configFilePath) + } +} + // Checks if the user provided input such as -configfile flag or -Source flag. // If those flags were provided, NuGet will use the provided configs (default config file or the one with -configfile) // If neither provided, we are initializing our own config. diff --git a/artifactory/commands/dotnet/dotnetcommand_test.go b/artifactory/commands/dotnet/dotnetcommand_test.go index f64af2bbd..32f3117ed 100644 --- a/artifactory/commands/dotnet/dotnetcommand_test.go +++ b/artifactory/commands/dotnet/dotnetcommand_test.go @@ -223,3 +223,130 @@ func createNewDotnetModule(t *testing.T, tmpDir string) *build.DotnetModule { assert.NoError(t, err) return module } + +func TestGetConfigPathFromEnvIfProvided(t *testing.T) { + testCases := []struct { + name string + mockEnv map[string]string + cmdType dotnet.ToolchainType + expectedPath string + }{ + { + name: "DotnetCore with DOTNET_CLI_HOME", + mockEnv: map[string]string{ + "DOTNET_CLI_HOME": "/custom/dotnet", + }, + cmdType: dotnet.DotnetCore, + expectedPath: "/custom/dotnet/NuGet.Config", + }, + { + name: "NuGet with NUGET_CONFIG_FILE", + mockEnv: map[string]string{ + "NUGET_CONFIG_FILE": "/custom/nuget.config", + }, + cmdType: dotnet.Nuget, + expectedPath: "/custom/nuget.config", + }, + { + name: "No env variable", + mockEnv: map[string]string{}, + cmdType: dotnet.Nuget, + expectedPath: "", + }, + } + + // Test the function with different environment variable settings + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + restoreEnv := testsutils.SetEnvWithCallbackAndAssert(t, "DOTNET_CLI_HOME", testCase.mockEnv["DOTNET_CLI_HOME"]) + defer restoreEnv() + + // Set other environment variables if needed + if testCase.mockEnv["NUGET_CONFIG_FILE"] != "" { + restoreEnvNuGet := testsutils.SetEnvWithCallbackAndAssert(t, "NUGET_CONFIG_FILE", testCase.mockEnv["NUGET_CONFIG_FILE"]) + defer restoreEnvNuGet() + } + result := GetConfigPathFromEnvIfProvided(testCase.cmdType) + assert.Equal(t, testCase.expectedPath, result) + }) + } +} + +func TestCreateConfigFileIfNeeded(t *testing.T) { + tests := []struct { + name string + configPath string + fileExists bool + expectedError error + }{ + { + name: "File does not exist, create file with default content", + configPath: "/custom/path/NuGet.Config", + fileExists: false, + }, + { + name: "File exists, no changes", + configPath: "/custom/path/NuGet.Config", + fileExists: true, + }, + } + + // Setup for testing file existence and creation + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + configPath := filepath.Join(t.TempDir(), tt.configPath) + if tt.fileExists { + assert.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0777)) + _, err := os.Create(configPath) + assert.NoError(t, err) + } + err := CreateConfigFileIfNeeded(configPath) + assert.NoError(t, err) + + if !tt.fileExists { + // Read the content of the file + content, err := os.ReadFile(configPath) + assert.NoError(t, err) + + // Assert the content is the default config content + assert.Equal(t, "", string(content)) + } + }) + } +} + +func TestAddConfigFileFlag(t *testing.T) { + tests := []struct { + name string + toolchainType dotnet.ToolchainType + configFilePath string + expectedFlags []string + }{ + { + name: "DotnetCore toolchain", + toolchainType: dotnet.DotnetCore, + configFilePath: "/path/to/NuGet.Config", + expectedFlags: []string{"--configfile", "/path/to/NuGet.Config"}, + }, + { + name: "NuGet toolchain", + toolchainType: dotnet.Nuget, + configFilePath: "/path/to/NuGet.Config", + expectedFlags: []string{"-ConfigFile", "/path/to/NuGet.Config"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock command object + cmd, err := dotnet.NewToolchainCmd(tt.toolchainType) + assert.NoError(t, err) + + // Call the function + addConfigFileFlag(cmd, tt.configFilePath) + + // Assert that the flags are as expected + assert.Equal(t, tt.expectedFlags, cmd.CommandFlags) + }) + } +} diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go index e462724a2..c92e9ce9c 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go @@ -28,6 +28,7 @@ import ( var packageManagerToRepositoryPackageType = map[project.ProjectType]string{ // Npm package managers project.Npm: repository.Npm, + project.Pnpm: repository.Npm, project.Yarn: repository.Npm, // Python (pypi) package managers @@ -127,8 +128,8 @@ func (pmlc *PackageManagerLoginCommand) Run() (err error) { // Configure the appropriate package manager based on the package manager. switch pmlc.packageManager { - case project.Npm: - err = pmlc.configureNpm() + case project.Npm, project.Pnpm: + err = pmlc.configureNpmPnpm() case project.Yarn: err = pmlc.configureYarn() case project.Pip, project.Pipenv: @@ -169,12 +170,14 @@ func (pmlc *PackageManagerLoginCommand) promptUserToSelectRepository() (err erro // Runs the following command: // // pip config set global.index-url https://:@/artifactory/api/pypi//simple +// +// Note: Custom configuration file can be set by setting the PIP_CONFIG_FILE environment variable. func (pmlc *PackageManagerLoginCommand) configurePip() error { repoWithCredsUrl, err := pythoncommands.GetPypiRepoUrl(pmlc.serverDetails, pmlc.repoName, false) if err != nil { return err } - return pythoncommands.RunConfigCommand(project.Pip, []string{"set", "global.index-url", repoWithCredsUrl}) + return pythoncommands.RunPipConfig(repoWithCredsUrl) } // configurePoetry configures Poetry to use the specified repository and authentication credentials. @@ -182,6 +185,8 @@ func (pmlc *PackageManagerLoginCommand) configurePip() error { // // poetry config repositories. https:///artifactory/api/pypi//simple // poetry config http-basic. +// +// Note: Custom configuration file can be set by setting the POETRY_CONFIG_DIR environment variable. func (pmlc *PackageManagerLoginCommand) configurePoetry() error { repoUrl, username, password, err := pythoncommands.GetPypiRepoUrlWithCredentials(pmlc.serverDetails, pmlc.repoName, false) if err != nil { @@ -190,28 +195,29 @@ func (pmlc *PackageManagerLoginCommand) configurePoetry() error { return pythoncommands.RunPoetryConfig(repoUrl.String(), username, password, pmlc.repoName) } -// configureNpm configures npm to use the Artifactory repository URL and sets authentication. +// configureNpmPnpm configures npm to use the Artifactory repository URL and sets authentication. Pnpm supports the same commands. // Runs the following commands: // -// npm config set registry https:///artifactory/api/npm/ +// npm/pnpm config set registry https:///artifactory/api/npm/ // // For token-based auth: // -// npm config set //your-artifactory-url/artifactory/api/npm//:_authToken "" +// npm/pnpm config set //your-artifactory-url/artifactory/api/npm//:_authToken "" // // For basic auth: // -// npm config set //your-artifactory-url/artifactory/api/npm//:_auth "" -func (pmlc *PackageManagerLoginCommand) configureNpm() error { +// npm/pnpm config set //your-artifactory-url/artifactory/api/npm//:_auth "" +// +// Note: Custom configuration file can be set by setting the NPM_CONFIG_USERCONFIG environment variable. +func (pmlc *PackageManagerLoginCommand) configureNpmPnpm() error { repoUrl := commandsutils.GetNpmRepositoryUrl(pmlc.repoName, pmlc.serverDetails.ArtifactoryUrl) - - if err := npm.ConfigSet(commandsutils.NpmConfigRegistryKey, repoUrl, "npm"); err != nil { + if err := npm.ConfigSet(commandsutils.NpmConfigRegistryKey, repoUrl, pmlc.packageManager.String()); err != nil { return err } authKey, authValue := commandsutils.GetNpmAuthKeyValue(pmlc.serverDetails, repoUrl) if authKey != "" && authValue != "" { - return npm.ConfigSet(authKey, authValue, "npm") + return npm.ConfigSet(authKey, authValue, pmlc.packageManager.String()) } return nil } @@ -228,10 +234,11 @@ func (pmlc *PackageManagerLoginCommand) configureNpm() error { // For basic auth: // // yarn config set //your-artifactory-url/artifactory/api/npm//:_auth "" -func (pmlc *PackageManagerLoginCommand) configureYarn() error { +// +// Note: Custom configuration file can be set by setting the YARN_RC_FILENAME environment variable. +func (pmlc *PackageManagerLoginCommand) configureYarn() (err error) { repoUrl := commandsutils.GetNpmRepositoryUrl(pmlc.repoName, pmlc.serverDetails.ArtifactoryUrl) - - if err := yarn.ConfigSet(commandsutils.NpmConfigRegistryKey, repoUrl, "yarn", false); err != nil { + if err = yarn.ConfigSet(commandsutils.NpmConfigRegistryKey, repoUrl, "yarn", false); err != nil { return err } @@ -263,6 +270,8 @@ func (pmlc *PackageManagerLoginCommand) configureGo() error { // For NuGet: // // nuget sources add -Name -Source "https://acme.jfrog.io/artifactory/api/nuget/{repository-name}" -Username -Password +// +// Note: Custom dotnet/nuget configuration file can be set by setting the DOTNET_CLI_HOME/NUGET_CONFIG_FILE environment variable. 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) @@ -270,16 +279,28 @@ func (pmlc *PackageManagerLoginCommand) configureDotnetNuget() error { return err } - // Determine the appropriate toolchain type (NuGet or .NET Core). + // Determine toolchain type based on the package manager toolchainType := bidotnet.DotnetCore if pmlc.packageManager == project.Nuget { toolchainType = bidotnet.Nuget } - if err = dotnet.RemoveSourceFromNugetConfigIfExists(toolchainType); err != nil { + + // Get config path from the environment if provided + customConfigPath := dotnet.GetConfigPathFromEnvIfProvided(toolchainType) + if customConfigPath != "" { + // Ensure the config file exists + if err = dotnet.CreateConfigFileIfNeeded(customConfigPath); err != nil { + return err + } + } + + // Remove existing source if it exists + if err = dotnet.RemoveSourceFromNugetConfigIfExists(toolchainType, customConfigPath); 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) + + // Add the repository as a source in the NuGet configuration with credentials for authentication + return dotnet.AddSourceToNugetConfig(toolchainType, sourceUrl, user, password, customConfigPath) } // configureContainer configures container managers like Docker or Podman to authenticate with JFrog Artifactory. diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go index 9783e8133..2ba6685d8 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go @@ -9,7 +9,6 @@ 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" clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -61,6 +60,14 @@ func TestPackageManagerLoginCommand_NotSupported(t *testing.T) { } func TestPackageManagerLoginCommand_Npm(t *testing.T) { + testPackageManagerLoginCommandNpmPnpm(t, project.Npm) +} + +func TestPackageManagerLoginCommand_Pnpm(t *testing.T) { + testPackageManagerLoginCommandNpmPnpm(t, project.Pnpm) +} + +func testPackageManagerLoginCommandNpmPnpm(t *testing.T, packageManager project.ProjectType) { // Create a temporary directory to act as the environment's npmrc file location. tempDir := t.TempDir() npmrcFilePath := filepath.Join(tempDir, ".npmrc") @@ -68,17 +75,17 @@ func TestPackageManagerLoginCommand_Npm(t *testing.T) { // Set NPM_CONFIG_USERCONFIG to point to the temporary npmrc file path. t.Setenv("NPM_CONFIG_USERCONFIG", npmrcFilePath) - npmLoginCmd := createTestPackageManagerLoginCommand(project.Npm) + loginCmd := 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. - npmLoginCmd.serverDetails.SetUser(testCase.user) - npmLoginCmd.serverDetails.SetPassword(testCase.password) - npmLoginCmd.serverDetails.SetAccessToken(testCase.accessToken) + loginCmd.serverDetails.SetUser(testCase.user) + loginCmd.serverDetails.SetPassword(testCase.password) + loginCmd.serverDetails.SetAccessToken(testCase.accessToken) // Run the login command and ensure no errors occur. - require.NoError(t, npmLoginCmd.Run()) + require.NoError(t, loginCmd.Run()) // Read the contents of the temporary npmrc file. npmrcContentBytes, err := os.ReadFile(npmrcFilePath) @@ -154,31 +161,34 @@ func TestPackageManagerLoginCommand_Yarn(t *testing.T) { } func TestPackageManagerLoginCommand_Pip(t *testing.T) { - // pip and pipenv share the same configuration file. - testPackageManagerLoginCommandPip(t, project.Pip) + // Test with global configuration file. + testPackageManagerLoginCommandPip(t, project.Pip, false) + // Test with custom configuration file. + testPackageManagerLoginCommandPip(t, project.Pip, true) } -func TestPackageManagerLoginCommand_Pipenv(t *testing.T) { - // pip and pipenv share the same configuration file. - testPackageManagerLoginCommandPip(t, project.Pipenv) -} - -func testPackageManagerLoginCommandPip(t *testing.T, packageManager project.ProjectType) { +func testPackageManagerLoginCommandPip(t *testing.T, packageManager project.ProjectType, customConfig bool) { var pipConfFilePath string - if coreutils.IsWindows() { - pipConfFilePath = filepath.Join(os.Getenv("APPDATA"), "pip", "pip.ini") + if customConfig { + pipConfFilePath = filepath.Join(t.TempDir(), "pip.conf") + restoreEnv := clientTestUtils.SetEnvWithCallbackAndAssert(t, "PIP_CONFIG_FILE", pipConfFilePath) + defer restoreEnv() } else { - // Retrieve the home directory and construct the pip.conf file path. - homeDir, err := os.UserHomeDir() + if coreutils.IsWindows() { + 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") + } + // Back up the existing .pip.conf file and ensure restoration after the test. + restorePipConfFunc, err := ioutils.BackupFile(pipConfFilePath, ".pipconf.backup") assert.NoError(t, err) - pipConfFilePath = filepath.Join(homeDir, ".config", "pip", "pip.conf") + defer func() { + assert.NoError(t, restorePipConfFunc()) + }() } - // 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 := createTestPackageManagerLoginCommand(packageManager) @@ -216,33 +226,11 @@ func testPackageManagerLoginCommandPip(t *testing.T, packageManager project.Proj } func TestPackageManagerLoginCommand_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") - } - - 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. - 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()) - }() - + configDir := t.TempDir() + poetryConfigFilePath := filepath.Join(configDir, "config.toml") + poetryAuthFilePath := filepath.Join(configDir, "auth.toml") + restoreEnv := clientTestUtils.SetEnvWithCallbackAndAssert(t, "POETRY_CONFIG_DIR", configDir) + defer restoreEnv() poetryLoginCmd := createTestPackageManagerLoginCommand(project.Poetry) for _, testCase := range testCases { @@ -308,7 +296,7 @@ func TestPackageManagerLoginCommand_Go(t *testing.T) { require.NoError(t, goLoginCmd.Run()) // Get the value of the GOPROXY environment variable. - outputBytes, err := exec.Command("go", "env", "GOPROXY").Output() + outputBytes, err := exec.Command("go", "env", goProxyEnv).Output() assert.NoError(t, err) goProxy := string(outputBytes) @@ -336,27 +324,18 @@ func TestBuildToolLoginCommand_configureDotnet(t *testing.T) { } 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 - switch { - case io.IsWindows(): - nugetConfigDir = filepath.Join("AppData", "Roaming") - case packageManager == project.Nuget: - nugetConfigDir = ".config" - default: - nugetConfigDir = ".nuget" - } - - nugetConfigFilePath := filepath.Join(homeDir, nugetConfigDir, "NuGet", "NuGet.Config") + var nugetConfigFilePath string - // 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()) - }() + // Set the NuGet.config file path to a custom location. + if packageManager == project.Dotnet { + nugetConfigFilePath = filepath.Join(t.TempDir(), "NuGet.Config") + restoreEnv := clientTestUtils.SetEnvWithCallbackAndAssert(t, "DOTNET_CLI_HOME", filepath.Dir(nugetConfigFilePath)) + defer restoreEnv() + } else { + nugetConfigFilePath = filepath.Join(t.TempDir(), "nuget.config") + restoreEnv := clientTestUtils.SetEnvWithCallbackAndAssert(t, "NUGET_CONFIG_FILE", nugetConfigFilePath) + defer restoreEnv() + } nugetLoginCmd := createTestPackageManagerLoginCommand(packageManager) diff --git a/artifactory/commands/python/pip.go b/artifactory/commands/python/pip.go index 8b7b2267a..3356c78e8 100644 --- a/artifactory/commands/python/pip.go +++ b/artifactory/commands/python/pip.go @@ -1,8 +1,12 @@ package python import ( + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/common/project" "io" + "os" "os/exec" + "path/filepath" "github.com/jfrog/build-info-go/entities" "github.com/jfrog/build-info-go/utils/pythonutils" @@ -46,6 +50,23 @@ func (pc *PipCommand) SetCommandName(commandName string) *PipCommand { return pc } +// Configure the repository URL for pipenv to use Artifactory as a repository. +func RunPipConfig(repoWithCredsUrl string) error { + // If PIP_CONFIG_FILE is set, write the configuration to the custom config file manually. + // Using 'pip config set' native command is not supported together with PIP_CONFIG_FILE. + if customPipConfigPath := os.Getenv("PIP_CONFIG_FILE"); customPipConfigPath != "" { + if err := os.MkdirAll(filepath.Dir(customPipConfigPath), os.ModePerm); err != nil { + return err + } + // Write the configuration to pip.conf. + configContent := fmt.Sprintf("[global]\nindex-url = %s\n", repoWithCredsUrl) + return os.WriteFile(customPipConfigPath, []byte(configContent), 0644) + } + + // If PIP_CONFIG_FILE is not set, use 'pip config set' native command. + return runConfigCommand(project.Pip, []string{"set", "global.index-url", repoWithCredsUrl}) +} + func (pc *PipCommand) CommandName() string { return "rt_python_pip" } diff --git a/artifactory/commands/python/poetry.go b/artifactory/commands/python/poetry.go index ec362653e..019287cb2 100644 --- a/artifactory/commands/python/poetry.go +++ b/artifactory/commands/python/poetry.go @@ -164,14 +164,14 @@ func ConfigPoetryRepo(url, username, password, configRepoName string) error { 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}) + 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}) + return runConfigCommand(project.Poetry, []string{poetryConfigAuthPrefix + configRepoName, username, password}) } func poetryUpdate() (err error) { diff --git a/artifactory/commands/python/python.go b/artifactory/commands/python/python.go index 3a2a94709..140745d00 100644 --- a/artifactory/commands/python/python.go +++ b/artifactory/commands/python/python.go @@ -178,7 +178,7 @@ func GetPypiRepoUrl(serverDetails *config.ServerDetails, repository string, isCu return rtUrl.String(), err } -func RunConfigCommand(buildTool project.ProjectType, args []string) error { +func runConfigCommand(buildTool project.ProjectType, args []string) error { log.Debug("Running", buildTool.String(), "config command...") configCmd := gofrogcmd.NewCommand(buildTool.String(), "config", args) if err := gofrogcmd.RunCmd(configCmd); err != nil { From 97c4fe2e5841cd573999dc43c8bbc6d4d40b7a7a Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 6 Jan 2025 16:59:36 +0200 Subject: [PATCH 49/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../commands/packagemanagerlogin/packagemanagerlogin.go | 6 +++++- artifactory/utils/repositoryutils.go | 5 ++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go index c92e9ce9c..d07f542ab 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go +++ b/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go @@ -162,7 +162,11 @@ func (pmlc *PackageManagerLoginCommand) promptUserToSelectRepository() (err erro } // Prompt for repository selection based on filter parameters. - pmlc.repoName, err = utils.SelectRepositoryInteractively(pmlc.serverDetails, repoFilterParams) + pmlc.repoName, err = utils.SelectRepositoryInteractively( + pmlc.serverDetails, + repoFilterParams, + fmt.Sprintf("To configure %s, we need you to select a %s repository in Artifactory", repoFilterParams.PackageType, repoFilterParams.RepoType)) + return err } diff --git a/artifactory/utils/repositoryutils.go b/artifactory/utils/repositoryutils.go index 1e3aa39a6..a11b91c6c 100644 --- a/artifactory/utils/repositoryutils.go +++ b/artifactory/utils/repositoryutils.go @@ -1,7 +1,6 @@ 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" @@ -81,7 +80,7 @@ func IsRemoteRepo(repoName string, serviceManager artifactory.ArtifactoryService } // 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) { +func SelectRepositoryInteractively(serverDetails *config.ServerDetails, repoFilterParams services.RepositoriesFilterParams, promptMessage string) (string, error) { sm, err := CreateServiceManager(serverDetails, 3, 0, false) if err != nil { return "", err @@ -102,7 +101,7 @@ func SelectRepositoryInteractively(serverDetails *config.ServerDetails, repoFilt return filteredRepos[0], nil } // Prompt the user to select a repository. - return ioutils.AskFromListWithMismatchConfirmation(fmt.Sprintf("Please select a %s %s repository to configure:", repoFilterParams.RepoType, repoFilterParams.PackageType), "Repository not found.", ioutils.ConvertToSuggests(filteredRepos)), nil + return ioutils.AskFromListWithMismatchConfirmation(promptMessage, "Repository not found.", ioutils.ConvertToSuggests(filteredRepos)), nil } // GetFilteredRepositoriesWithFilterParams returns the names of local, remote, virtual, and federated repositories filtered by their names and type. From 2733b6069962e61017baaf286ff84d0c65ffb4a2 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 6 Jan 2025 17:10:24 +0200 Subject: [PATCH 50/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .../packagemanagerlogin.go => setup/setup.go} | 126 +++++++++--------- .../setup_test.go} | 48 +++---- 2 files changed, 87 insertions(+), 87 deletions(-) rename artifactory/commands/{packagemanagerlogin/packagemanagerlogin.go => setup/setup.go} (73%) rename artifactory/commands/{packagemanagerlogin/packagemanagerlogin_test.go => setup/setup_test.go} (90%) diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go b/artifactory/commands/setup/setup.go similarity index 73% rename from artifactory/commands/packagemanagerlogin/packagemanagerlogin.go rename to artifactory/commands/setup/setup.go index d07f542ab..b378a2b5b 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go +++ b/artifactory/commands/setup/setup.go @@ -1,4 +1,4 @@ -package packagemanagerlogin +package setup import ( "fmt" @@ -47,8 +47,8 @@ var packageManagerToRepositoryPackageType = map[project.ProjectType]string{ project.Go: repository.Go, } -// PackageManagerLoginCommand configures registries and authentication for various package manager (npm, Yarn, Pip, Pipenv, Poetry, Go) -type PackageManagerLoginCommand struct { +// SetupCommand configures registries and authentication for various package manager (npm, Yarn, Pip, Pipenv, Poetry, Go) +type SetupCommand struct { // packageManager represents the type of package manager (e.g., NPM, Yarn). packageManager project.ProjectType // repoName is the name of the repository used for configuration. @@ -61,10 +61,10 @@ type PackageManagerLoginCommand struct { commandName string } -// NewPackageManagerLoginCommand initializes a new PackageManagerLoginCommand for the specified package manager +// NewSetupCommand initializes a new SetupCommand for the specified package manager // and automatically sets a command name for the login operation. -func NewPackageManagerLoginCommand(packageManager project.ProjectType) *PackageManagerLoginCommand { - return &PackageManagerLoginCommand{ +func NewSetupCommand(packageManager project.ProjectType) *SetupCommand { + return &SetupCommand{ packageManager: packageManager, commandName: packageManager.String() + "_login", } @@ -86,84 +86,84 @@ func IsSupportedPackageManager(packageManager project.ProjectType) bool { } // CommandName returns the name of the login command. -func (pmlc *PackageManagerLoginCommand) CommandName() string { - return pmlc.commandName +func (sc *SetupCommand) CommandName() string { + return sc.commandName } // SetServerDetails assigns the server configuration details to the command. -func (pmlc *PackageManagerLoginCommand) SetServerDetails(serverDetails *config.ServerDetails) *PackageManagerLoginCommand { - pmlc.serverDetails = serverDetails - return pmlc +func (sc *SetupCommand) SetServerDetails(serverDetails *config.ServerDetails) *SetupCommand { + sc.serverDetails = serverDetails + return sc } // ServerDetails returns the stored server configuration details. -func (pmlc *PackageManagerLoginCommand) ServerDetails() (*config.ServerDetails, error) { - return pmlc.serverDetails, nil +func (sc *SetupCommand) ServerDetails() (*config.ServerDetails, error) { + return sc.serverDetails, nil } // SetRepoName assigns the repository name to the command. -func (pmlc *PackageManagerLoginCommand) SetRepoName(repoName string) *PackageManagerLoginCommand { - pmlc.repoName = repoName - return pmlc +func (sc *SetupCommand) SetRepoName(repoName string) *SetupCommand { + sc.repoName = repoName + return sc } // SetProjectKey assigns the project key to the command. -func (pmlc *PackageManagerLoginCommand) SetProjectKey(projectKey string) *PackageManagerLoginCommand { - pmlc.projectKey = projectKey - return pmlc +func (sc *SetupCommand) SetProjectKey(projectKey string) *SetupCommand { + sc.projectKey = projectKey + return sc } // Run executes the configuration method corresponding to the package manager specified for the command. -func (pmlc *PackageManagerLoginCommand) Run() (err error) { - if !IsSupportedPackageManager(pmlc.packageManager) { - return errorutils.CheckErrorf("unsupported package manager: %s", pmlc.packageManager) +func (sc *SetupCommand) Run() (err error) { + if !IsSupportedPackageManager(sc.packageManager) { + return errorutils.CheckErrorf("unsupported package manager: %s", sc.packageManager) } - if pmlc.repoName == "" { + if sc.repoName == "" { // Prompt the user to select a virtual repository that matches the package manager. - if err = pmlc.promptUserToSelectRepository(); err != nil { + if err = sc.promptUserToSelectRepository(); err != nil { return err } } // Configure the appropriate package manager based on the package manager. - switch pmlc.packageManager { + switch sc.packageManager { case project.Npm, project.Pnpm: - err = pmlc.configureNpmPnpm() + err = sc.configureNpmPnpm() case project.Yarn: - err = pmlc.configureYarn() + err = sc.configureYarn() case project.Pip, project.Pipenv: - err = pmlc.configurePip() + err = sc.configurePip() case project.Poetry: - err = pmlc.configurePoetry() + err = sc.configurePoetry() case project.Go: - err = pmlc.configureGo() + err = sc.configureGo() case project.Nuget, project.Dotnet: - err = pmlc.configureDotnetNuget() + err = sc.configureDotnetNuget() case project.Docker, project.Podman: - err = pmlc.configureContainer() + err = sc.configureContainer() default: - err = errorutils.CheckErrorf("unsupported package manager: %s", pmlc.packageManager) + err = errorutils.CheckErrorf("unsupported package manager: %s", sc.packageManager) } if err != nil { - return fmt.Errorf("failed to configure %s: %w", pmlc.packageManager.String(), err) + return fmt.Errorf("failed to configure %s: %w", sc.packageManager.String(), err) } - log.Output(fmt.Sprintf("Successfully configured %s to use JFrog Artifactory repository '%s'.", coreutils.PrintBoldTitle(pmlc.packageManager.String()), coreutils.PrintBoldTitle(pmlc.repoName))) + log.Output(fmt.Sprintf("Successfully configured %s to use JFrog Artifactory repository '%s'.", coreutils.PrintBoldTitle(sc.packageManager.String()), coreutils.PrintBoldTitle(sc.repoName))) return nil } // promptUserToSelectRepository prompts the user to select a compatible virtual repository. -func (pmlc *PackageManagerLoginCommand) promptUserToSelectRepository() (err error) { +func (sc *SetupCommand) promptUserToSelectRepository() (err error) { repoFilterParams := services.RepositoriesFilterParams{ RepoType: utils.Virtual.String(), - PackageType: packageManagerToRepositoryPackageType[pmlc.packageManager], - ProjectKey: pmlc.projectKey, + PackageType: packageManagerToRepositoryPackageType[sc.packageManager], + ProjectKey: sc.projectKey, } // Prompt for repository selection based on filter parameters. - pmlc.repoName, err = utils.SelectRepositoryInteractively( - pmlc.serverDetails, + sc.repoName, err = utils.SelectRepositoryInteractively( + sc.serverDetails, repoFilterParams, fmt.Sprintf("To configure %s, we need you to select a %s repository in Artifactory", repoFilterParams.PackageType, repoFilterParams.RepoType)) @@ -176,8 +176,8 @@ func (pmlc *PackageManagerLoginCommand) promptUserToSelectRepository() (err erro // pip config set global.index-url https://:@/artifactory/api/pypi//simple // // Note: Custom configuration file can be set by setting the PIP_CONFIG_FILE environment variable. -func (pmlc *PackageManagerLoginCommand) configurePip() error { - repoWithCredsUrl, err := pythoncommands.GetPypiRepoUrl(pmlc.serverDetails, pmlc.repoName, false) +func (sc *SetupCommand) configurePip() error { + repoWithCredsUrl, err := pythoncommands.GetPypiRepoUrl(sc.serverDetails, sc.repoName, false) if err != nil { return err } @@ -191,12 +191,12 @@ func (pmlc *PackageManagerLoginCommand) configurePip() error { // poetry config http-basic. // // Note: Custom configuration file can be set by setting the POETRY_CONFIG_DIR environment variable. -func (pmlc *PackageManagerLoginCommand) configurePoetry() error { - repoUrl, username, password, err := pythoncommands.GetPypiRepoUrlWithCredentials(pmlc.serverDetails, pmlc.repoName, false) +func (sc *SetupCommand) configurePoetry() error { + repoUrl, username, password, err := pythoncommands.GetPypiRepoUrlWithCredentials(sc.serverDetails, sc.repoName, false) if err != nil { return err } - return pythoncommands.RunPoetryConfig(repoUrl.String(), username, password, pmlc.repoName) + return pythoncommands.RunPoetryConfig(repoUrl.String(), username, password, sc.repoName) } // configureNpmPnpm configures npm to use the Artifactory repository URL and sets authentication. Pnpm supports the same commands. @@ -213,15 +213,15 @@ func (pmlc *PackageManagerLoginCommand) configurePoetry() error { // npm/pnpm config set //your-artifactory-url/artifactory/api/npm//:_auth "" // // Note: Custom configuration file can be set by setting the NPM_CONFIG_USERCONFIG environment variable. -func (pmlc *PackageManagerLoginCommand) configureNpmPnpm() error { - repoUrl := commandsutils.GetNpmRepositoryUrl(pmlc.repoName, pmlc.serverDetails.ArtifactoryUrl) - if err := npm.ConfigSet(commandsutils.NpmConfigRegistryKey, repoUrl, pmlc.packageManager.String()); err != nil { +func (sc *SetupCommand) configureNpmPnpm() error { + repoUrl := commandsutils.GetNpmRepositoryUrl(sc.repoName, sc.serverDetails.ArtifactoryUrl) + if err := npm.ConfigSet(commandsutils.NpmConfigRegistryKey, repoUrl, sc.packageManager.String()); err != nil { return err } - authKey, authValue := commandsutils.GetNpmAuthKeyValue(pmlc.serverDetails, repoUrl) + authKey, authValue := commandsutils.GetNpmAuthKeyValue(sc.serverDetails, repoUrl) if authKey != "" && authValue != "" { - return npm.ConfigSet(authKey, authValue, pmlc.packageManager.String()) + return npm.ConfigSet(authKey, authValue, sc.packageManager.String()) } return nil } @@ -240,13 +240,13 @@ func (pmlc *PackageManagerLoginCommand) configureNpmPnpm() error { // yarn config set //your-artifactory-url/artifactory/api/npm//:_auth "" // // Note: Custom configuration file can be set by setting the YARN_RC_FILENAME environment variable. -func (pmlc *PackageManagerLoginCommand) configureYarn() (err error) { - repoUrl := commandsutils.GetNpmRepositoryUrl(pmlc.repoName, pmlc.serverDetails.ArtifactoryUrl) +func (sc *SetupCommand) configureYarn() (err error) { + repoUrl := commandsutils.GetNpmRepositoryUrl(sc.repoName, sc.serverDetails.ArtifactoryUrl) if err = yarn.ConfigSet(commandsutils.NpmConfigRegistryKey, repoUrl, "yarn", false); err != nil { return err } - authKey, authValue := commandsutils.GetNpmAuthKeyValue(pmlc.serverDetails, repoUrl) + authKey, authValue := commandsutils.GetNpmAuthKeyValue(sc.serverDetails, repoUrl) if authKey != "" && authValue != "" { return yarn.ConfigSet(authKey, authValue, "yarn", false) } @@ -257,8 +257,8 @@ func (pmlc *PackageManagerLoginCommand) configureYarn() (err error) { // Runs the following command: // // go env -w GOPROXY=https://:@/artifactory/go/,direct -func (pmlc *PackageManagerLoginCommand) configureGo() error { - repoWithCredsUrl, err := gocommands.GetArtifactoryRemoteRepoUrl(pmlc.serverDetails, pmlc.repoName, gocommands.GoProxyUrlParams{Direct: true}) +func (sc *SetupCommand) configureGo() error { + repoWithCredsUrl, err := gocommands.GetArtifactoryRemoteRepoUrl(sc.serverDetails, sc.repoName, gocommands.GoProxyUrlParams{Direct: true}) if err != nil { return err } @@ -276,16 +276,16 @@ func (pmlc *PackageManagerLoginCommand) configureGo() error { // nuget sources add -Name -Source "https://acme.jfrog.io/artifactory/api/nuget/{repository-name}" -Username -Password // // Note: Custom dotnet/nuget configuration file can be set by setting the DOTNET_CLI_HOME/NUGET_CONFIG_FILE environment variable. -func (pmlc *PackageManagerLoginCommand) configureDotnetNuget() error { +func (sc *SetupCommand) configureDotnetNuget() error { // Retrieve repository URL and credentials for NuGet or .NET Core. - sourceUrl, user, password, err := dotnet.GetSourceDetails(pmlc.serverDetails, pmlc.repoName, false) + sourceUrl, user, password, err := dotnet.GetSourceDetails(sc.serverDetails, sc.repoName, false) if err != nil { return err } // Determine toolchain type based on the package manager toolchainType := bidotnet.DotnetCore - if pmlc.packageManager == project.Nuget { + if sc.packageManager == project.Nuget { toolchainType = bidotnet.Nuget } @@ -317,25 +317,25 @@ func (pmlc *PackageManagerLoginCommand) configureDotnetNuget() error { // For Podman: // // echo | podman login -u --password-stdin -func (pmlc *PackageManagerLoginCommand) configureContainer() error { +func (sc *SetupCommand) configureContainer() error { var containerManagerType container.ContainerManagerType - switch pmlc.packageManager { + switch sc.packageManager { case project.Docker: containerManagerType = container.DockerClient case project.Podman: containerManagerType = container.Podman default: - return errorutils.CheckErrorf("unsupported container manager: %s", pmlc.packageManager) + return errorutils.CheckErrorf("unsupported container manager: %s", sc.packageManager) } // Parse the URL to remove the scheme (https:// or http://) - parsedPlatformURL, err := url.Parse(pmlc.serverDetails.GetUrl()) + parsedPlatformURL, err := url.Parse(sc.serverDetails.GetUrl()) if err != nil { return err } urlWithoutScheme := parsedPlatformURL.Host + parsedPlatformURL.Path return container.ContainerManagerLogin( urlWithoutScheme, - &container.ContainerManagerLoginConfig{ServerDetails: pmlc.serverDetails}, + &container.ContainerManagerLoginConfig{ServerDetails: sc.serverDetails}, containerManagerType, ) } diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go b/artifactory/commands/setup/setup_test.go similarity index 90% rename from artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go rename to artifactory/commands/setup/setup_test.go index 2ba6685d8..a3dedbb20 100644 --- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go +++ b/artifactory/commands/setup/setup_test.go @@ -1,4 +1,4 @@ -package packagemanagerlogin +package setup import ( "fmt" @@ -43,8 +43,8 @@ var testCases = []struct { }, } -func createTestPackageManagerLoginCommand(packageManager project.ProjectType) *PackageManagerLoginCommand { - cmd := NewPackageManagerLoginCommand(packageManager) +func createTestSetupCommand(packageManager project.ProjectType) *SetupCommand { + cmd := NewSetupCommand(packageManager) cmd.repoName = "test-repo" dummyUrl := "https://acme.jfrog.io" cmd.serverDetails = &config.ServerDetails{Url: dummyUrl, ArtifactoryUrl: dummyUrl + "/artifactory"} @@ -52,22 +52,22 @@ func createTestPackageManagerLoginCommand(packageManager project.ProjectType) *P return cmd } -func TestPackageManagerLoginCommand_NotSupported(t *testing.T) { - notSupportedLoginCmd := createTestPackageManagerLoginCommand(project.Cocoapods) +func TestSetupCommand_NotSupported(t *testing.T) { + notSupportedLoginCmd := createTestSetupCommand(project.Cocoapods) err := notSupportedLoginCmd.Run() assert.Error(t, err) assert.ErrorContains(t, err, "unsupported package manager") } -func TestPackageManagerLoginCommand_Npm(t *testing.T) { - testPackageManagerLoginCommandNpmPnpm(t, project.Npm) +func TestSetupCommand_Npm(t *testing.T) { + testSetupCommandNpmPnpm(t, project.Npm) } -func TestPackageManagerLoginCommand_Pnpm(t *testing.T) { - testPackageManagerLoginCommandNpmPnpm(t, project.Pnpm) +func TestSetupCommand_Pnpm(t *testing.T) { + testSetupCommandNpmPnpm(t, project.Pnpm) } -func testPackageManagerLoginCommandNpmPnpm(t *testing.T, packageManager project.ProjectType) { +func testSetupCommandNpmPnpm(t *testing.T, packageManager project.ProjectType) { // Create a temporary directory to act as the environment's npmrc file location. tempDir := t.TempDir() npmrcFilePath := filepath.Join(tempDir, ".npmrc") @@ -75,7 +75,7 @@ func testPackageManagerLoginCommandNpmPnpm(t *testing.T, packageManager project. // Set NPM_CONFIG_USERCONFIG to point to the temporary npmrc file path. t.Setenv("NPM_CONFIG_USERCONFIG", npmrcFilePath) - loginCmd := createTestPackageManagerLoginCommand(packageManager) + loginCmd := createTestSetupCommand(packageManager) for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { @@ -111,7 +111,7 @@ func testPackageManagerLoginCommandNpmPnpm(t *testing.T, packageManager project. } } -func TestPackageManagerLoginCommand_Yarn(t *testing.T) { +func TestSetupCommand_Yarn(t *testing.T) { // Retrieve the home directory and construct the .yarnrc file path. homeDir, err := os.UserHomeDir() assert.NoError(t, err) @@ -124,7 +124,7 @@ func TestPackageManagerLoginCommand_Yarn(t *testing.T) { assert.NoError(t, restoreYarnrcFunc()) }() - yarnLoginCmd := createTestPackageManagerLoginCommand(project.Yarn) + yarnLoginCmd := createTestSetupCommand(project.Yarn) for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { @@ -160,14 +160,14 @@ func TestPackageManagerLoginCommand_Yarn(t *testing.T) { } } -func TestPackageManagerLoginCommand_Pip(t *testing.T) { +func TestSetupCommand_Pip(t *testing.T) { // Test with global configuration file. - testPackageManagerLoginCommandPip(t, project.Pip, false) + testSetupCommandPip(t, project.Pip, false) // Test with custom configuration file. - testPackageManagerLoginCommandPip(t, project.Pip, true) + testSetupCommandPip(t, project.Pip, true) } -func testPackageManagerLoginCommandPip(t *testing.T, packageManager project.ProjectType, customConfig bool) { +func testSetupCommandPip(t *testing.T, packageManager project.ProjectType, customConfig bool) { var pipConfFilePath string if customConfig { pipConfFilePath = filepath.Join(t.TempDir(), "pip.conf") @@ -190,7 +190,7 @@ func testPackageManagerLoginCommandPip(t *testing.T, packageManager project.Proj }() } - pipLoginCmd := createTestPackageManagerLoginCommand(packageManager) + pipLoginCmd := createTestSetupCommand(packageManager) for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { @@ -225,13 +225,13 @@ func testPackageManagerLoginCommandPip(t *testing.T, packageManager project.Proj } } -func TestPackageManagerLoginCommand_configurePoetry(t *testing.T) { +func TestSetupCommand_configurePoetry(t *testing.T) { configDir := t.TempDir() poetryConfigFilePath := filepath.Join(configDir, "config.toml") poetryAuthFilePath := filepath.Join(configDir, "auth.toml") restoreEnv := clientTestUtils.SetEnvWithCallbackAndAssert(t, "POETRY_CONFIG_DIR", configDir) defer restoreEnv() - poetryLoginCmd := createTestPackageManagerLoginCommand(project.Poetry) + poetryLoginCmd := createTestSetupCommand(project.Poetry) for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { @@ -276,14 +276,14 @@ func TestPackageManagerLoginCommand_configurePoetry(t *testing.T) { } } -func TestPackageManagerLoginCommand_Go(t *testing.T) { +func TestSetupCommand_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 createTestPackageManagerLoginCommand initializes your Go login command - goLoginCmd := createTestPackageManagerLoginCommand(project.Go) + // Assuming createTestSetupCommand initializes your Go login command + goLoginCmd := createTestSetupCommand(project.Go) for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { @@ -337,7 +337,7 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager defer restoreEnv() } - nugetLoginCmd := createTestPackageManagerLoginCommand(packageManager) + nugetLoginCmd := createTestSetupCommand(packageManager) for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { From 3a8cfe51b903d6856a22cf45b7b548d33f17a002 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 6 Jan 2025 17:38:12 +0200 Subject: [PATCH 51/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/python/pip.go | 21 ++++++-------------- artifactory/commands/python/pip_test.go | 26 +++++++++++++++++++++++++ artifactory/commands/python/poetry.go | 4 ++-- artifactory/commands/python/python.go | 2 +- artifactory/commands/setup/setup.go | 8 +++++++- 5 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 artifactory/commands/python/pip_test.go diff --git a/artifactory/commands/python/pip.go b/artifactory/commands/python/pip.go index 3356c78e8..ca6ac8dee 100644 --- a/artifactory/commands/python/pip.go +++ b/artifactory/commands/python/pip.go @@ -2,7 +2,6 @@ package python import ( "fmt" - "github.com/jfrog/jfrog-cli-core/v2/common/project" "io" "os" "os/exec" @@ -50,21 +49,13 @@ func (pc *PipCommand) SetCommandName(commandName string) *PipCommand { return pc } -// Configure the repository URL for pipenv to use Artifactory as a repository. -func RunPipConfig(repoWithCredsUrl string) error { - // If PIP_CONFIG_FILE is set, write the configuration to the custom config file manually. - // Using 'pip config set' native command is not supported together with PIP_CONFIG_FILE. - if customPipConfigPath := os.Getenv("PIP_CONFIG_FILE"); customPipConfigPath != "" { - if err := os.MkdirAll(filepath.Dir(customPipConfigPath), os.ModePerm); err != nil { - return err - } - // Write the configuration to pip.conf. - configContent := fmt.Sprintf("[global]\nindex-url = %s\n", repoWithCredsUrl) - return os.WriteFile(customPipConfigPath, []byte(configContent), 0644) +func CreatePipConfigManually(customPipConfigPath, repoWithCredsUrl string) error { + if err := os.MkdirAll(filepath.Dir(customPipConfigPath), os.ModePerm); err != nil { + return err } - - // If PIP_CONFIG_FILE is not set, use 'pip config set' native command. - return runConfigCommand(project.Pip, []string{"set", "global.index-url", repoWithCredsUrl}) + // Write the configuration to pip.conf. + configContent := fmt.Sprintf("[global]\nindex-url = %s\n", repoWithCredsUrl) + return os.WriteFile(customPipConfigPath, []byte(configContent), 0644) } func (pc *PipCommand) CommandName() string { diff --git a/artifactory/commands/python/pip_test.go b/artifactory/commands/python/pip_test.go new file mode 100644 index 000000000..321a2a074 --- /dev/null +++ b/artifactory/commands/python/pip_test.go @@ -0,0 +1,26 @@ +package python + +import ( + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" +) + +func TestCreatePipConfigManually(t *testing.T) { + // Define the test parameters + customConfigPath := filepath.Join(t.TempDir(), "/tmp/test/pip.conf") + repoWithCredsUrl := "https://example.com/simple/" + expectedContent := "[global]\nindex-url = https://example.com/simple/\n" + + // Call the function under test + err := CreatePipConfigManually(customConfigPath, repoWithCredsUrl) + + // Assert no error occurred + assert.NoError(t, err) + + // Verify the file exists and has the correct content + fileContent, err := os.ReadFile(customConfigPath) + assert.NoError(t, err) + assert.Equal(t, expectedContent, string(fileContent)) +} diff --git a/artifactory/commands/python/poetry.go b/artifactory/commands/python/poetry.go index 019287cb2..ec362653e 100644 --- a/artifactory/commands/python/poetry.go +++ b/artifactory/commands/python/poetry.go @@ -164,14 +164,14 @@ func ConfigPoetryRepo(url, username, password, configRepoName string) error { 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}) + 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}) + return RunConfigCommand(project.Poetry, []string{poetryConfigAuthPrefix + configRepoName, username, password}) } func poetryUpdate() (err error) { diff --git a/artifactory/commands/python/python.go b/artifactory/commands/python/python.go index 140745d00..3a2a94709 100644 --- a/artifactory/commands/python/python.go +++ b/artifactory/commands/python/python.go @@ -178,7 +178,7 @@ func GetPypiRepoUrl(serverDetails *config.ServerDetails, repository string, isCu return rtUrl.String(), err } -func runConfigCommand(buildTool project.ProjectType, args []string) error { +func RunConfigCommand(buildTool project.ProjectType, args []string) error { log.Debug("Running", buildTool.String(), "config command...") configCmd := gofrogcmd.NewCommand(buildTool.String(), "config", args) if err := gofrogcmd.RunCmd(configCmd); err != nil { diff --git a/artifactory/commands/setup/setup.go b/artifactory/commands/setup/setup.go index b378a2b5b..ab9118255 100644 --- a/artifactory/commands/setup/setup.go +++ b/artifactory/commands/setup/setup.go @@ -21,6 +21,7 @@ import ( "github.com/jfrog/jfrog-client-go/utils/log" "golang.org/x/exp/maps" "net/url" + "os" "slices" ) @@ -181,7 +182,12 @@ func (sc *SetupCommand) configurePip() error { if err != nil { return err } - return pythoncommands.RunPipConfig(repoWithCredsUrl) + // If PIP_CONFIG_FILE is set, write the configuration to the custom config file manually. + // Using 'pip config set' native command is not supported together with PIP_CONFIG_FILE. + if customPipConfigPath := os.Getenv("PIP_CONFIG_FILE"); customPipConfigPath != "" { + return pythoncommands.CreatePipConfigManually(customPipConfigPath, repoWithCredsUrl) + } + return pythoncommands.RunConfigCommand(project.Pip, []string{"set", "global.index-url", repoWithCredsUrl}) } // configurePoetry configures Poetry to use the specified repository and authentication credentials. From a0afe89f81a2a99edf70de215b62307043629393 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 6 Jan 2025 17:42:02 +0200 Subject: [PATCH 52/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/python/pip_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/artifactory/commands/python/pip_test.go b/artifactory/commands/python/pip_test.go index 321a2a074..92079ada6 100644 --- a/artifactory/commands/python/pip_test.go +++ b/artifactory/commands/python/pip_test.go @@ -10,6 +10,7 @@ import ( func TestCreatePipConfigManually(t *testing.T) { // Define the test parameters customConfigPath := filepath.Join(t.TempDir(), "/tmp/test/pip.conf") + // #nosec G101 -- False positive - no hardcoded credentials. repoWithCredsUrl := "https://example.com/simple/" expectedContent := "[global]\nindex-url = https://example.com/simple/\n" From b3e78b85827f2bfd47e0d030260c5ba354b5cb0e Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 6 Jan 2025 17:48:57 +0200 Subject: [PATCH 53/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5765ee044..c00aad986 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,6 +38,9 @@ jobs: with: dotnet-version: '6.x' + - name: Install pnpm + uses: pnpm/action-setup@v4 + # Install Mono on Ubuntu to run nuget.exe (due to this issue on Ubuntu 24 that hasn't been fixed yet - https://github.com/NuGet/setup-nuget/issues/168) - name: Install Mono on Ubuntu if: matrix.os == 'ubuntu' From 4a048aefd41f5d800748dec6deb40be0e99b8fb7 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 6 Jan 2025 18:02:31 +0200 Subject: [PATCH 54/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c00aad986..3c290bd83 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,7 +40,8 @@ jobs: - name: Install pnpm uses: pnpm/action-setup@v4 - + with: + version: 9 # Install Mono on Ubuntu to run nuget.exe (due to this issue on Ubuntu 24 that hasn't been fixed yet - https://github.com/NuGet/setup-nuget/issues/168) - name: Install Mono on Ubuntu if: matrix.os == 'ubuntu' From 91f9a1980d56359ba813eb9cf479e316c31ba95d Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 6 Jan 2025 18:27:16 +0200 Subject: [PATCH 55/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/dotnet/dotnetcommand_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/artifactory/commands/dotnet/dotnetcommand_test.go b/artifactory/commands/dotnet/dotnetcommand_test.go index 32f3117ed..2651af90f 100644 --- a/artifactory/commands/dotnet/dotnetcommand_test.go +++ b/artifactory/commands/dotnet/dotnetcommand_test.go @@ -1,6 +1,7 @@ package dotnet import ( + "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" "os" "path/filepath" "reflect" @@ -267,7 +268,7 @@ func TestGetConfigPathFromEnvIfProvided(t *testing.T) { defer restoreEnvNuGet() } result := GetConfigPathFromEnvIfProvided(testCase.cmdType) - assert.Equal(t, testCase.expectedPath, result) + assert.Equal(t, testCase.expectedPath, ioutils.WinToUnixPathSeparator(result)) }) } } @@ -297,8 +298,7 @@ func TestCreateConfigFileIfNeeded(t *testing.T) { configPath := filepath.Join(t.TempDir(), tt.configPath) if tt.fileExists { assert.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0777)) - _, err := os.Create(configPath) - assert.NoError(t, err) + assert.NoError(t, os.WriteFile(configPath, []byte{}, 0644)) } err := CreateConfigFileIfNeeded(configPath) assert.NoError(t, err) From 0a734da3daee131a5e1409a0a5f154d83cc0f48b Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 8 Jan 2025 15:10:16 +0200 Subject: [PATCH 56/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/setup/setup.go | 11 ++++++++--- artifactory/commands/setup/setup_test.go | 6 +++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/artifactory/commands/setup/setup.go b/artifactory/commands/setup/setup.go index ab9118255..82f9c19d1 100644 --- a/artifactory/commands/setup/setup.go +++ b/artifactory/commands/setup/setup.go @@ -71,14 +71,19 @@ func NewSetupCommand(packageManager project.ProjectType) *SetupCommand { } } -// GetSupportedPackageManagersList returns a sorted list of supported package managers. -func GetSupportedPackageManagersList() []project.ProjectType { +// GetSupportedPackageManagersList returns a sorted list of supported package manager names as strings. +func GetSupportedPackageManagersList() []string { allSupportedPackageManagers := maps.Keys(packageManagerToRepositoryPackageType) // Sort keys based on their natural enum order slices.SortFunc(allSupportedPackageManagers, func(a, b project.ProjectType) int { return int(a) - int(b) }) - return allSupportedPackageManagers + // Convert enums to their string representation + result := make([]string, len(allSupportedPackageManagers)) + for i, manager := range allSupportedPackageManagers { + result[i] = manager.String() + } + return result } func IsSupportedPackageManager(packageManager project.ProjectType) bool { diff --git a/artifactory/commands/setup/setup_test.go b/artifactory/commands/setup/setup_test.go index a3dedbb20..725ec5da2 100644 --- a/artifactory/commands/setup/setup_test.go +++ b/artifactory/commands/setup/setup_test.go @@ -371,9 +371,9 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager func TestGetSupportedPackageManagersList(t *testing.T) { result := GetSupportedPackageManagersList() - // Check that Go is before Pip, and Pip is before Npm using GreaterOrEqual - assert.GreaterOrEqual(t, slices.Index(result, project.Pip), slices.Index(result, project.Go), "Go should come before Pip") - assert.GreaterOrEqual(t, slices.Index(result, project.Npm), slices.Index(result, project.Pip), "Pip should come before Npm") + // Check that "Go" is before "Pip", and "Pip" is before "Npm" + assert.GreaterOrEqual(t, slices.Index(result, project.Go.String()), slices.Index(result, project.Pip.String()), "Go should come before Pip") + assert.GreaterOrEqual(t, slices.Index(result, project.Pip.String()), slices.Index(result, project.Npm.String()), "Pip should come before Npm") } func TestIsSupportedPackageManager(t *testing.T) { From 0c20c771d99d8fbb19b0aea4b9730a094c6012f2 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 8 Jan 2025 15:21:43 +0200 Subject: [PATCH 57/70] Update .github/workflows/test.yml Co-authored-by: Yahav Itschak --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3c290bd83..4977965ef 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,6 +42,7 @@ jobs: uses: pnpm/action-setup@v4 with: version: 9 + # Install Mono on Ubuntu to run nuget.exe (due to this issue on Ubuntu 24 that hasn't been fixed yet - https://github.com/NuGet/setup-nuget/issues/168) - name: Install Mono on Ubuntu if: matrix.os == 'ubuntu' From 6ac61ffda7efeb31a48bec3dd72d8c009ac13255 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 8 Jan 2025 16:01:15 +0200 Subject: [PATCH 58/70] Update artifactory/commands/dotnet/dotnetcommand_test.go Co-authored-by: Yahav Itschak --- artifactory/commands/dotnet/dotnetcommand_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artifactory/commands/dotnet/dotnetcommand_test.go b/artifactory/commands/dotnet/dotnetcommand_test.go index 2651af90f..0fa00975e 100644 --- a/artifactory/commands/dotnet/dotnetcommand_test.go +++ b/artifactory/commands/dotnet/dotnetcommand_test.go @@ -297,7 +297,7 @@ func TestCreateConfigFileIfNeeded(t *testing.T) { t.Run(tt.name, func(t *testing.T) { configPath := filepath.Join(t.TempDir(), tt.configPath) if tt.fileExists { - assert.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0777)) + assert.NoError(t, os.MkdirAll(filepath.Dir(configPath), os.ModePerm)) assert.NoError(t, os.WriteFile(configPath, []byte{}, 0644)) } err := CreateConfigFileIfNeeded(configPath) From 2b347b89cf423506e9ad2d57bf4286fda3bfc847 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 8 Jan 2025 16:01:23 +0200 Subject: [PATCH 59/70] Update artifactory/commands/dotnet/dotnetcommand_test.go Co-authored-by: Yahav Itschak --- artifactory/commands/dotnet/dotnetcommand_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artifactory/commands/dotnet/dotnetcommand_test.go b/artifactory/commands/dotnet/dotnetcommand_test.go index 0fa00975e..39f63b212 100644 --- a/artifactory/commands/dotnet/dotnetcommand_test.go +++ b/artifactory/commands/dotnet/dotnetcommand_test.go @@ -336,7 +336,7 @@ func TestAddConfigFileFlag(t *testing.T) { }, } - for _, tt := range tests { + for _, testCase := range testCases { t.Run(tt.name, func(t *testing.T) { // Create a mock command object cmd, err := dotnet.NewToolchainCmd(tt.toolchainType) From 17f4f5f1f470d863cee31be3018291d244b498ec Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 8 Jan 2025 17:17:19 +0200 Subject: [PATCH 60/70] Update artifactory/commands/dotnet/dotnetcommand.go Co-authored-by: Yahav Itschak --- artifactory/commands/dotnet/dotnetcommand.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artifactory/commands/dotnet/dotnetcommand.go b/artifactory/commands/dotnet/dotnetcommand.go index 3756c27f7..b91e9cb99 100644 --- a/artifactory/commands/dotnet/dotnetcommand.go +++ b/artifactory/commands/dotnet/dotnetcommand.go @@ -214,7 +214,7 @@ func RemoveSourceFromNugetConfigIfExists(cmdType dotnet.ToolchainType, customCon if strings.Contains(stdOut+stdErr, "Unable to find") || strings.Contains(stdOut+stdErr, "does not exist") { return nil } - return fmt.Errorf("failed to remove source: %w\n%s", err, strings.TrimSpace(stdOut+stdErr)) + return errorutils.CheckErrorf("failed to remove source: %w\n%s\n%s", err, strings.TrimSpace(stdOut), strings.TrimSpace(stdErr)) } return nil } From 51b9b204be03c7ae69c99c2b2a368c57936d38ab Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 8 Jan 2025 17:31:14 +0200 Subject: [PATCH 61/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/dotnet/dotnetcommand.go | 22 ++++----- .../commands/dotnet/dotnetcommand_test.go | 45 +++++++++---------- artifactory/commands/golang/go_test.go | 12 ++--- artifactory/commands/python/python_test.go | 10 ++--- 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/artifactory/commands/dotnet/dotnetcommand.go b/artifactory/commands/dotnet/dotnetcommand.go index 3756c27f7..bf742db34 100644 --- a/artifactory/commands/dotnet/dotnetcommand.go +++ b/artifactory/commands/dotnet/dotnetcommand.go @@ -231,17 +231,19 @@ func GetConfigPathFromEnvIfProvided(cmdType dotnet.ToolchainType) string { // CreateConfigFileIfNeeded creates a new config file if it does not exist. func CreateConfigFileIfNeeded(customConfigPath string) error { - // Ensure the file exists, create an empty one it if not - if _, err := os.Stat(customConfigPath); os.IsNotExist(err) { - if err = os.MkdirAll(filepath.Dir(customConfigPath), 0755); err != nil { - return err - } - // Write the default config content to the file - if err = os.WriteFile(customConfigPath, []byte(""), 0644); err != nil { - return err - } + // Ensure the file exists + exists, err := fileutils.IsFileExists(customConfigPath, false) + if err != nil || exists { + return err + } + // If the file does not exist, create it + if err = os.MkdirAll(filepath.Dir(customConfigPath), 0755); err != nil { + return err + } + // Write the default config content to the file + if err = os.WriteFile(customConfigPath, []byte(""), 0644); err != nil { + return err } - return nil } func addConfigFileFlag(cmd *dotnet.Cmd, configFilePath string) { diff --git a/artifactory/commands/dotnet/dotnetcommand_test.go b/artifactory/commands/dotnet/dotnetcommand_test.go index 2651af90f..11f27c36e 100644 --- a/artifactory/commands/dotnet/dotnetcommand_test.go +++ b/artifactory/commands/dotnet/dotnetcommand_test.go @@ -274,7 +274,7 @@ func TestGetConfigPathFromEnvIfProvided(t *testing.T) { } func TestCreateConfigFileIfNeeded(t *testing.T) { - tests := []struct { + testCases := []struct { name string configPath string fileExists bool @@ -293,17 +293,17 @@ func TestCreateConfigFileIfNeeded(t *testing.T) { } // Setup for testing file existence and creation - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - configPath := filepath.Join(t.TempDir(), tt.configPath) - if tt.fileExists { + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + configPath := filepath.Join(t.TempDir(), testCase.configPath) + if testCase.fileExists { assert.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0777)) assert.NoError(t, os.WriteFile(configPath, []byte{}, 0644)) } err := CreateConfigFileIfNeeded(configPath) assert.NoError(t, err) - if !tt.fileExists { + if !testCase.fileExists { // Read the content of the file content, err := os.ReadFile(configPath) assert.NoError(t, err) @@ -316,37 +316,34 @@ func TestCreateConfigFileIfNeeded(t *testing.T) { } func TestAddConfigFileFlag(t *testing.T) { - tests := []struct { - name string - toolchainType dotnet.ToolchainType - configFilePath string - expectedFlags []string + testCases := []struct { + name string + toolchainType dotnet.ToolchainType + expectedFlags []string }{ { - name: "DotnetCore toolchain", - toolchainType: dotnet.DotnetCore, - configFilePath: "/path/to/NuGet.Config", - expectedFlags: []string{"--configfile", "/path/to/NuGet.Config"}, + name: "DotnetCore toolchain", + toolchainType: dotnet.DotnetCore, + expectedFlags: []string{"--configfile", "/path/to/NuGet.Config"}, }, { - name: "NuGet toolchain", - toolchainType: dotnet.Nuget, - configFilePath: "/path/to/NuGet.Config", - expectedFlags: []string{"-ConfigFile", "/path/to/NuGet.Config"}, + name: "NuGet toolchain", + toolchainType: dotnet.Nuget, + expectedFlags: []string{"-ConfigFile", "/path/to/NuGet.Config"}, }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { // Create a mock command object - cmd, err := dotnet.NewToolchainCmd(tt.toolchainType) + cmd, err := dotnet.NewToolchainCmd(testCase.toolchainType) assert.NoError(t, err) // Call the function - addConfigFileFlag(cmd, tt.configFilePath) + addConfigFileFlag(cmd, "/path/to/NuGet.Config") // Assert that the flags are as expected - assert.Equal(t, tt.expectedFlags, cmd.CommandFlags) + assert.Equal(t, testCase.expectedFlags, cmd.CommandFlags) }) } } diff --git a/artifactory/commands/golang/go_test.go b/artifactory/commands/golang/go_test.go index 7b334a3fe..8fc86ad9e 100644 --- a/artifactory/commands/golang/go_test.go +++ b/artifactory/commands/golang/go_test.go @@ -128,7 +128,7 @@ func TestGetArtifactoryApiUrl(t *testing.T) { } func TestGoProxyUrlParams_BuildUrl(t *testing.T) { - tests := []struct { + testCases := []struct { name string RepoName string Direct bool @@ -153,15 +153,15 @@ func TestGoProxyUrlParams_BuildUrl(t *testing.T) { ExpectedUrl: "https://test/prefix/api/go/go", }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { remoteUrl, err := url.Parse("https://test") require.NoError(t, err) gdu := &GoProxyUrlParams{ - Direct: tt.Direct, - EndpointPrefix: tt.EndpointPrefix, + Direct: testCase.Direct, + EndpointPrefix: testCase.EndpointPrefix, } - assert.Equalf(t, tt.ExpectedUrl, gdu.BuildUrl(remoteUrl, tt.RepoName), "BuildUrl(%v, %v)", remoteUrl, tt.RepoName) + assert.Equalf(t, testCase.ExpectedUrl, gdu.BuildUrl(remoteUrl, testCase.RepoName), "BuildUrl(%v, %v)", remoteUrl, testCase.RepoName) }) } } diff --git a/artifactory/commands/python/python_test.go b/artifactory/commands/python/python_test.go index 2b25468c9..18666ce45 100644 --- a/artifactory/commands/python/python_test.go +++ b/artifactory/commands/python/python_test.go @@ -10,7 +10,7 @@ import ( ) func TestGetPypiRepoUrlWithCredentials(t *testing.T) { - tests := []struct { + testCases := []struct { name string curationCmd bool }{ @@ -24,11 +24,11 @@ func TestGetPypiRepoUrlWithCredentials(t *testing.T) { }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - url, _, _, err := GetPypiRepoUrlWithCredentials(&config.ServerDetails{}, "test", tt.curationCmd) + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + url, _, _, err := GetPypiRepoUrlWithCredentials(&config.ServerDetails{}, "test", testCase.curationCmd) require.NoError(t, err) - assert.Equal(t, tt.curationCmd, strings.Contains(url.Path, coreutils.CurationPassThroughApi)) + assert.Equal(t, testCase.curationCmd, strings.Contains(url.Path, coreutils.CurationPassThroughApi)) }) } } From b5158003c9dfaeb986127431de44919e460fd3a3 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 8 Jan 2025 17:31:42 +0200 Subject: [PATCH 62/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/dotnet/dotnetcommand.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/artifactory/commands/dotnet/dotnetcommand.go b/artifactory/commands/dotnet/dotnetcommand.go index bf742db34..357ce6169 100644 --- a/artifactory/commands/dotnet/dotnetcommand.go +++ b/artifactory/commands/dotnet/dotnetcommand.go @@ -241,9 +241,7 @@ func CreateConfigFileIfNeeded(customConfigPath string) error { return err } // Write the default config content to the file - if err = os.WriteFile(customConfigPath, []byte(""), 0644); err != nil { - return err - } + return os.WriteFile(customConfigPath, []byte(""), 0644) } func addConfigFileFlag(cmd *dotnet.Cmd, configFilePath string) { From a789a1f92fa33edb55de9a9e9f9fdaf6f003817d Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 8 Jan 2025 18:19:03 +0200 Subject: [PATCH 63/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/setup/setup_test.go | 54 +++++++++++++----------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/artifactory/commands/setup/setup_test.go b/artifactory/commands/setup/setup_test.go index 725ec5da2..1433b3de1 100644 --- a/artifactory/commands/setup/setup_test.go +++ b/artifactory/commands/setup/setup_test.go @@ -9,7 +9,6 @@ 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" - clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/exp/slices" @@ -170,24 +169,14 @@ func TestSetupCommand_Pip(t *testing.T) { func testSetupCommandPip(t *testing.T, packageManager project.ProjectType, customConfig bool) { var pipConfFilePath string if customConfig { + // For custom configuration file, set the PIP_CONFIG_FILE environment variable to point to the temporary pip.conf file. pipConfFilePath = filepath.Join(t.TempDir(), "pip.conf") - restoreEnv := clientTestUtils.SetEnvWithCallbackAndAssert(t, "PIP_CONFIG_FILE", pipConfFilePath) - defer restoreEnv() + t.Setenv("PIP_CONFIG_FILE", pipConfFilePath) } else { - if coreutils.IsWindows() { - 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") - } - // 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()) - }() + // For global configuration file, back up the existing pip.conf file and ensure restoration after the test. + var restoreFunc func() + pipConfFilePath, restoreFunc = globalGlobalPipConfigPath(t) + defer restoreFunc() } pipLoginCmd := createTestSetupCommand(packageManager) @@ -225,12 +214,30 @@ func testSetupCommandPip(t *testing.T, packageManager project.ProjectType, custo } } +// globalGlobalPipConfigPath returns the path to the global pip.conf file and a backup function to restore the original file. +func globalGlobalPipConfigPath(t *testing.T) (string, func()) { + var pipConfFilePath string + if coreutils.IsWindows() { + 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") + } + // Back up the existing .pip.conf file and ensure restoration after the test. + restorePipConfFunc, err := ioutils.BackupFile(pipConfFilePath, ".pipconf.backup") + assert.NoError(t, err) + return pipConfFilePath, func() { + assert.NoError(t, restorePipConfFunc()) + } +} + func TestSetupCommand_configurePoetry(t *testing.T) { configDir := t.TempDir() poetryConfigFilePath := filepath.Join(configDir, "config.toml") poetryAuthFilePath := filepath.Join(configDir, "auth.toml") - restoreEnv := clientTestUtils.SetEnvWithCallbackAndAssert(t, "POETRY_CONFIG_DIR", configDir) - defer restoreEnv() + t.Setenv("POETRY_CONFIG_DIR", configDir) poetryLoginCmd := createTestSetupCommand(project.Poetry) for _, testCase := range testCases { @@ -279,8 +286,7 @@ func TestSetupCommand_configurePoetry(t *testing.T) { func TestSetupCommand_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() + t.Setenv(goProxyEnv, "") // Assuming createTestSetupCommand initializes your Go login command goLoginCmd := createTestSetupCommand(project.Go) @@ -329,12 +335,10 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager // Set the NuGet.config file path to a custom location. if packageManager == project.Dotnet { nugetConfigFilePath = filepath.Join(t.TempDir(), "NuGet.Config") - restoreEnv := clientTestUtils.SetEnvWithCallbackAndAssert(t, "DOTNET_CLI_HOME", filepath.Dir(nugetConfigFilePath)) - defer restoreEnv() + t.Setenv("DOTNET_CLI_HOME", filepath.Dir(nugetConfigFilePath)) } else { nugetConfigFilePath = filepath.Join(t.TempDir(), "nuget.config") - restoreEnv := clientTestUtils.SetEnvWithCallbackAndAssert(t, "NUGET_CONFIG_FILE", nugetConfigFilePath) - defer restoreEnv() + t.Setenv("NUGET_CONFIG_FILE", nugetConfigFilePath) } nugetLoginCmd := createTestSetupCommand(packageManager) From c609123102e3a7c9d9b3939e51c469169f6e4bfd Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 8 Jan 2025 18:20:57 +0200 Subject: [PATCH 64/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/dotnet/dotnetcommand_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/artifactory/commands/dotnet/dotnetcommand_test.go b/artifactory/commands/dotnet/dotnetcommand_test.go index 11f27c36e..223486e4a 100644 --- a/artifactory/commands/dotnet/dotnetcommand_test.go +++ b/artifactory/commands/dotnet/dotnetcommand_test.go @@ -259,13 +259,11 @@ func TestGetConfigPathFromEnvIfProvided(t *testing.T) { // Test the function with different environment variable settings for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - restoreEnv := testsutils.SetEnvWithCallbackAndAssert(t, "DOTNET_CLI_HOME", testCase.mockEnv["DOTNET_CLI_HOME"]) - defer restoreEnv() + t.Setenv("DOTNET_CLI_HOME", testCase.mockEnv["DOTNET_CLI_HOME"]) // Set other environment variables if needed if testCase.mockEnv["NUGET_CONFIG_FILE"] != "" { - restoreEnvNuGet := testsutils.SetEnvWithCallbackAndAssert(t, "NUGET_CONFIG_FILE", testCase.mockEnv["NUGET_CONFIG_FILE"]) - defer restoreEnvNuGet() + t.Setenv("NUGET_CONFIG_FILE", testCase.mockEnv["NUGET_CONFIG_FILE"]) } result := GetConfigPathFromEnvIfProvided(testCase.cmdType) assert.Equal(t, testCase.expectedPath, ioutils.WinToUnixPathSeparator(result)) From d9df61ac5f6d6e4a4dab7d0fcc92cee5cb50f7cb Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 8 Jan 2025 19:00:48 +0200 Subject: [PATCH 65/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/setup/setup_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/artifactory/commands/setup/setup_test.go b/artifactory/commands/setup/setup_test.go index 1433b3de1..7fffd7724 100644 --- a/artifactory/commands/setup/setup_test.go +++ b/artifactory/commands/setup/setup_test.go @@ -374,10 +374,10 @@ func testBuildToolLoginCommandConfigureDotnetNuget(t *testing.T, packageManager } func TestGetSupportedPackageManagersList(t *testing.T) { - result := GetSupportedPackageManagersList() + packageManagersList := GetSupportedPackageManagersList() // Check that "Go" is before "Pip", and "Pip" is before "Npm" - assert.GreaterOrEqual(t, slices.Index(result, project.Go.String()), slices.Index(result, project.Pip.String()), "Go should come before Pip") - assert.GreaterOrEqual(t, slices.Index(result, project.Pip.String()), slices.Index(result, project.Npm.String()), "Pip should come before Npm") + assert.Less(t, slices.Index(packageManagersList, project.Go.String()), slices.Index(packageManagersList, project.Pip.String()), "Go should come before Pip") + assert.Less(t, slices.Index(packageManagersList, project.Pip.String()), slices.Index(packageManagersList, project.Npm.String()), "Pip should come before Npm") } func TestIsSupportedPackageManager(t *testing.T) { From cb49813753f794fcd7c9a879e5b66369f80af5d9 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 8 Jan 2025 19:07:22 +0200 Subject: [PATCH 66/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/dotnet/dotnetcommand.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/artifactory/commands/dotnet/dotnetcommand.go b/artifactory/commands/dotnet/dotnetcommand.go index b1b1f6a45..6c72ff6a3 100644 --- a/artifactory/commands/dotnet/dotnetcommand.go +++ b/artifactory/commands/dotnet/dotnetcommand.go @@ -184,9 +184,9 @@ func AddSourceToNugetConfig(cmdType dotnet.ToolchainType, sourceUrl, user, passw addConfigFileFlag(cmd, customConfigPath) } - stdOut, errorOut, _, err := frogio.RunCmdWithOutputParser(cmd, false) + _, _, _, err = frogio.RunCmdWithOutputParser(cmd, false) if err != nil { - return fmt.Errorf("failed to add source: %w\n%s", err, strings.TrimSpace(stdOut+errorOut)) + return fmt.Errorf("failed to add source: %w", err) } return nil } @@ -214,7 +214,7 @@ func RemoveSourceFromNugetConfigIfExists(cmdType dotnet.ToolchainType, customCon if strings.Contains(stdOut+stdErr, "Unable to find") || strings.Contains(stdOut+stdErr, "does not exist") { return nil } - return errorutils.CheckErrorf("failed to remove source: %w\n%s\n%s", err, strings.TrimSpace(stdOut), strings.TrimSpace(stdErr)) + return errorutils.CheckErrorf("failed to remove source: %s", err.Error()) } return nil } From 5e43e9ab21cd08c7f7af8f693672e404c2f1a351 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Wed, 8 Jan 2025 19:36:39 +0200 Subject: [PATCH 67/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- go.mod | 14 +++++++------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 702fa6e99..c240a7309 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/jfrog/jfrog-cli-core/v2 -go 1.23.3 +go 1.23.4 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) @@ -21,7 +21,7 @@ require ( github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 github.com/urfave/cli v1.22.16 - github.com/vbauerster/mpb/v8 v8.8.3 + github.com/vbauerster/mpb/v8 v8.9.1 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 golang.org/x/mod v0.22.0 golang.org/x/sync v0.10.0 @@ -35,20 +35,20 @@ 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.1.2 // indirect + github.com/ProtonMail/go-crypto v1.1.3 // 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 github.com/cloudflare/circl v1.3.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect - github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/cyphar/filepath-securejoin v0.2.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dsnet/compress v0.0.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.5.0 // indirect - github.com/go-git/go-git/v5 v5.12.0 // indirect + github.com/go-git/go-billy/v5 v5.6.0 // indirect + github.com/go-git/go-git/v5 v5.13.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect @@ -76,7 +76,7 @@ require ( github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/skeema/knownhosts v1.2.2 // indirect + github.com/skeema/knownhosts v1.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect diff --git a/go.sum b/go.sum index ffce8e369..856eb31e4 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.1.2 h1:A7JbD57ThNqh7XjmHE+PXpQ3Dqt3BrSAC0AL0Go3KS0= -github.com/ProtonMail/go-crypto v1.1.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= +github.com/ProtonMail/go-crypto v1.1.3/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= @@ -38,8 +38,8 @@ github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vc github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= +github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -47,8 +47,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= -github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug= +github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= @@ -57,16 +57,16 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= -github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= -github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= +github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= -github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= +github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= +github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ= github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= @@ -138,8 +138,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= @@ -169,8 +169,8 @@ github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWR github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= -github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= +github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -203,8 +203,8 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= -github.com/vbauerster/mpb/v8 v8.8.3 h1:dTOByGoqwaTJYPubhVz3lO5O6MK553XVgUo33LdnNsQ= -github.com/vbauerster/mpb/v8 v8.8.3/go.mod h1:JfCCrtcMsJwP6ZwMn9e5LMnNyp3TVNpUWWkN+nd4EWk= +github.com/vbauerster/mpb/v8 v8.9.1 h1:LH5R3lXPfE2e3lIGxN7WNWv3Hl5nWO6LRi2B0L0ERHw= +github.com/vbauerster/mpb/v8 v8.9.1/go.mod h1:4XMvznPh8nfe2NpnDo1QTPvW9MVkUhbG90mPWvmOzcQ= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= From 287788a73f9f2f4e48e912669fae506a570b2c63 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Thu, 9 Jan 2025 12:39:00 +0200 Subject: [PATCH 68/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/utils/utils.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/artifactory/utils/utils.go b/artifactory/utils/utils.go index 0ebe42265..8cef7b4ff 100644 --- a/artifactory/utils/utils.go +++ b/artifactory/utils/utils.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "io" "net/http" "net/url" @@ -314,7 +315,7 @@ func ValidateRepoExists(repoKey string, serviceDetails auth.ServiceDetails) erro } exists, err := servicesManager.IsRepoExists(repoKey) if err != nil { - return err + return fmt.Errorf("failed while attempting to check if repository %q exists in Artifactory: %w", repoKey, err) } if !exists { From 5e7af1dc1d76bf871321abf4dd69d654e60eab1a Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Thu, 9 Jan 2025 12:42:30 +0200 Subject: [PATCH 69/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/setup/setup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artifactory/commands/setup/setup.go b/artifactory/commands/setup/setup.go index 82f9c19d1..d701d1b60 100644 --- a/artifactory/commands/setup/setup.go +++ b/artifactory/commands/setup/setup.go @@ -67,7 +67,7 @@ type SetupCommand struct { func NewSetupCommand(packageManager project.ProjectType) *SetupCommand { return &SetupCommand{ packageManager: packageManager, - commandName: packageManager.String() + "_login", + commandName: "setup_" + packageManager.String(), } } From 707bb56eba3932bfab2d960be8a7d7fc5bcf45a6 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Thu, 9 Jan 2025 12:43:09 +0200 Subject: [PATCH 70/70] Improve upload archive progress bar Signed-off-by: Michael Sverdlov --- artifactory/commands/setup/setup.go | 1 - 1 file changed, 1 deletion(-) diff --git a/artifactory/commands/setup/setup.go b/artifactory/commands/setup/setup.go index d701d1b60..213d8f62f 100644 --- a/artifactory/commands/setup/setup.go +++ b/artifactory/commands/setup/setup.go @@ -63,7 +63,6 @@ type SetupCommand struct { } // NewSetupCommand initializes a new SetupCommand for the specified package manager -// and automatically sets a command name for the login operation. func NewSetupCommand(packageManager project.ProjectType) *SetupCommand { return &SetupCommand{ packageManager: packageManager,