diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 5765ee044..4977965ef 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -38,6 +38,11 @@ jobs:
with:
dotnet-version: '6.x'
+ - 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'
diff --git a/artifactory/commands/dotnet/dotnetcommand.go b/artifactory/commands/dotnet/dotnetcommand.go
index 57cd73d4f..6c72ff6a3 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,15 +179,21 @@ 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)
- stdOut, errorOut, _, err := frogio.RunCmdWithOutputParser(cmd, false)
+
+ if customConfigPath != "" {
+ addConfigFileFlag(cmd, customConfigPath)
+ }
+
+ _, _, _, 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
}
-// 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,16 +204,55 @@ 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))
+ return errorutils.CheckErrorf("failed to remove source: %s", err.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
+ 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
+ return os.WriteFile(customConfigPath, []byte(""), 0644)
+}
+
+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..223486e4a 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"
@@ -223,3 +224,124 @@ 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) {
+ t.Setenv("DOTNET_CLI_HOME", testCase.mockEnv["DOTNET_CLI_HOME"])
+
+ // Set other environment variables if needed
+ if testCase.mockEnv["NUGET_CONFIG_FILE"] != "" {
+ t.Setenv("NUGET_CONFIG_FILE", testCase.mockEnv["NUGET_CONFIG_FILE"])
+ }
+ result := GetConfigPathFromEnvIfProvided(testCase.cmdType)
+ assert.Equal(t, testCase.expectedPath, ioutils.WinToUnixPathSeparator(result))
+ })
+ }
+}
+
+func TestCreateConfigFileIfNeeded(t *testing.T) {
+ testCases := []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 _, 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 !testCase.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) {
+ testCases := []struct {
+ name string
+ toolchainType dotnet.ToolchainType
+ expectedFlags []string
+ }{
+ {
+ name: "DotnetCore toolchain",
+ toolchainType: dotnet.DotnetCore,
+ expectedFlags: []string{"--configfile", "/path/to/NuGet.Config"},
+ },
+ {
+ name: "NuGet toolchain",
+ toolchainType: dotnet.Nuget,
+ expectedFlags: []string{"-ConfigFile", "/path/to/NuGet.Config"},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ // Create a mock command object
+ cmd, err := dotnet.NewToolchainCmd(testCase.toolchainType)
+ assert.NoError(t, err)
+
+ // Call the function
+ addConfigFileFlag(cmd, "/path/to/NuGet.Config")
+
+ // Assert that the flags are as expected
+ 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/pip.go b/artifactory/commands/python/pip.go
index 8b7b2267a..ca6ac8dee 100644
--- a/artifactory/commands/python/pip.go
+++ b/artifactory/commands/python/pip.go
@@ -1,8 +1,11 @@
package python
import (
+ "fmt"
"io"
+ "os"
"os/exec"
+ "path/filepath"
"github.com/jfrog/build-info-go/entities"
"github.com/jfrog/build-info-go/utils/pythonutils"
@@ -46,6 +49,15 @@ func (pc *PipCommand) SetCommandName(commandName string) *PipCommand {
return pc
}
+func CreatePipConfigManually(customPipConfigPath, repoWithCredsUrl string) error {
+ 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 (pc *PipCommand) CommandName() string {
return "rt_python_pip"
}
diff --git a/artifactory/commands/python/pip_test.go b/artifactory/commands/python/pip_test.go
new file mode 100644
index 000000000..92079ada6
--- /dev/null
+++ b/artifactory/commands/python/pip_test.go
@@ -0,0 +1,27 @@
+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")
+ // #nosec G101 -- False positive - no hardcoded credentials.
+ 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/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))
})
}
}
diff --git a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go b/artifactory/commands/setup/setup.go
similarity index 58%
rename from artifactory/commands/packagemanagerlogin/packagemanagerlogin.go
rename to artifactory/commands/setup/setup.go
index e462724a2..213d8f62f 100644
--- a/artifactory/commands/packagemanagerlogin/packagemanagerlogin.go
+++ b/artifactory/commands/setup/setup.go
@@ -1,4 +1,4 @@
-package packagemanagerlogin
+package setup
import (
"fmt"
@@ -21,6 +21,7 @@ import (
"github.com/jfrog/jfrog-client-go/utils/log"
"golang.org/x/exp/maps"
"net/url"
+ "os"
"slices"
)
@@ -28,6 +29,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
@@ -46,8 +48,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.
@@ -60,23 +62,27 @@ type PackageManagerLoginCommand struct {
commandName string
}
-// 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{
+// NewSetupCommand initializes a new SetupCommand for the specified package manager
+func NewSetupCommand(packageManager project.ProjectType) *SetupCommand {
+ return &SetupCommand{
packageManager: packageManager,
- commandName: packageManager.String() + "_login",
+ commandName: "setup_" + packageManager.String(),
}
}
-// 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 {
@@ -85,83 +91,87 @@ 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 {
- case project.Npm:
- err = pmlc.configureNpm()
+ switch sc.packageManager {
+ case project.Npm, project.Pnpm:
+ 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, repoFilterParams)
+ 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))
+
return err
}
@@ -169,11 +179,18 @@ func (pmlc *PackageManagerLoginCommand) promptUserToSelectRepository() (err erro
// Runs the following command:
//
// pip config set global.index-url https://:@/artifactory/api/pypi//simple
-func (pmlc *PackageManagerLoginCommand) configurePip() error {
- repoWithCredsUrl, err := pythoncommands.GetPypiRepoUrl(pmlc.serverDetails, pmlc.repoName, false)
+//
+// Note: Custom configuration file can be set by setting the PIP_CONFIG_FILE environment variable.
+func (sc *SetupCommand) configurePip() error {
+ repoWithCredsUrl, err := pythoncommands.GetPypiRepoUrl(sc.serverDetails, sc.repoName, false)
if err != nil {
return err
}
+ // 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})
}
@@ -182,36 +199,39 @@ func (pmlc *PackageManagerLoginCommand) configurePip() error {
//
// poetry config repositories. https:///artifactory/api/pypi//simple
// poetry config http-basic.
-func (pmlc *PackageManagerLoginCommand) configurePoetry() error {
- repoUrl, username, password, err := pythoncommands.GetPypiRepoUrlWithCredentials(pmlc.serverDetails, pmlc.repoName, false)
+//
+// Note: Custom configuration file can be set by setting the POETRY_CONFIG_DIR environment variable.
+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)
}
-// 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 {
- repoUrl := commandsutils.GetNpmRepositoryUrl(pmlc.repoName, pmlc.serverDetails.ArtifactoryUrl)
-
- if err := npm.ConfigSet(commandsutils.NpmConfigRegistryKey, repoUrl, "npm"); err != nil {
+// 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 (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, "npm")
+ return npm.ConfigSet(authKey, authValue, sc.packageManager.String())
}
return nil
}
@@ -228,14 +248,15 @@ func (pmlc *PackageManagerLoginCommand) configureNpm() error {
// For basic auth:
//
// yarn config set //your-artifactory-url/artifactory/api/npm//:_auth ""
-func (pmlc *PackageManagerLoginCommand) configureYarn() error {
- repoUrl := commandsutils.GetNpmRepositoryUrl(pmlc.repoName, pmlc.serverDetails.ArtifactoryUrl)
-
- if err := yarn.ConfigSet(commandsutils.NpmConfigRegistryKey, repoUrl, "yarn", false); err != nil {
+//
+// Note: Custom configuration file can be set by setting the YARN_RC_FILENAME environment variable.
+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)
}
@@ -246,8 +267,8 @@ func (pmlc *PackageManagerLoginCommand) configureYarn() 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
}
@@ -263,23 +284,37 @@ func (pmlc *PackageManagerLoginCommand) configureGo() error {
// For NuGet:
//
// nuget sources add -Name -Source "https://acme.jfrog.io/artifactory/api/nuget/{repository-name}" -Username -Password
-func (pmlc *PackageManagerLoginCommand) configureDotnetNuget() error {
+//
+// Note: Custom dotnet/nuget configuration file can be set by setting the DOTNET_CLI_HOME/NUGET_CONFIG_FILE environment variable.
+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 the appropriate toolchain type (NuGet or .NET Core).
+ // Determine toolchain type based on the package manager
toolchainType := bidotnet.DotnetCore
- if pmlc.packageManager == project.Nuget {
+ if sc.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.
@@ -292,25 +327,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 76%
rename from artifactory/commands/packagemanagerlogin/packagemanagerlogin_test.go
rename to artifactory/commands/setup/setup_test.go
index 9783e8133..7fffd7724 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"
@@ -9,8 +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"
"golang.org/x/exp/slices"
@@ -44,8 +42,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"}
@@ -53,14 +51,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) {
+func TestSetupCommand_Npm(t *testing.T) {
+ testSetupCommandNpmPnpm(t, project.Npm)
+}
+
+func TestSetupCommand_Pnpm(t *testing.T) {
+ testSetupCommandNpmPnpm(t, project.Pnpm)
+}
+
+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")
@@ -68,17 +74,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 := createTestSetupCommand(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)
@@ -104,7 +110,7 @@ func TestPackageManagerLoginCommand_Npm(t *testing.T) {
}
}
-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)
@@ -117,7 +123,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) {
@@ -153,34 +159,27 @@ func TestPackageManagerLoginCommand_Yarn(t *testing.T) {
}
}
-func TestPackageManagerLoginCommand_Pip(t *testing.T) {
- // pip and pipenv share the same configuration file.
- testPackageManagerLoginCommandPip(t, project.Pip)
-}
-
-func TestPackageManagerLoginCommand_Pipenv(t *testing.T) {
- // pip and pipenv share the same configuration file.
- testPackageManagerLoginCommandPip(t, project.Pipenv)
+func TestSetupCommand_Pip(t *testing.T) {
+ // Test with global configuration file.
+ testSetupCommandPip(t, project.Pip, false)
+ // Test with custom configuration file.
+ testSetupCommandPip(t, project.Pip, true)
}
-func testPackageManagerLoginCommandPip(t *testing.T, packageManager project.ProjectType) {
+func testSetupCommandPip(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 {
+ // 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")
+ t.Setenv("PIP_CONFIG_FILE", pipConfFilePath)
} 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")
+ // 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()
}
- // 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)
+ pipLoginCmd := createTestSetupCommand(packageManager)
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
@@ -215,35 +214,31 @@ 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")
+// 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")
}
-
- 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")
+ // 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, restorePoetryConfigFunc())
- assert.NoError(t, restorePoetryAuthFunc())
- }()
+ return pipConfFilePath, func() {
+ assert.NoError(t, restorePipConfFunc())
+ }
+}
- poetryLoginCmd := createTestPackageManagerLoginCommand(project.Poetry)
+func TestSetupCommand_configurePoetry(t *testing.T) {
+ configDir := t.TempDir()
+ poetryConfigFilePath := filepath.Join(configDir, "config.toml")
+ poetryAuthFilePath := filepath.Join(configDir, "auth.toml")
+ t.Setenv("POETRY_CONFIG_DIR", configDir)
+ poetryLoginCmd := createTestSetupCommand(project.Poetry)
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
@@ -288,14 +283,13 @@ 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()
+ t.Setenv(goProxyEnv, "")
- // 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) {
@@ -308,7 +302,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,29 +330,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"
- }
+ var nugetConfigFilePath string
- 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())
- }()
+ // Set the NuGet.config file path to a custom location.
+ if packageManager == project.Dotnet {
+ nugetConfigFilePath = filepath.Join(t.TempDir(), "NuGet.Config")
+ t.Setenv("DOTNET_CLI_HOME", filepath.Dir(nugetConfigFilePath))
+ } else {
+ nugetConfigFilePath = filepath.Join(t.TempDir(), "nuget.config")
+ t.Setenv("NUGET_CONFIG_FILE", nugetConfigFilePath)
+ }
- nugetLoginCmd := createTestPackageManagerLoginCommand(packageManager)
+ nugetLoginCmd := createTestSetupCommand(packageManager)
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
@@ -391,10 +374,10 @@ 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")
+ packageManagersList := GetSupportedPackageManagersList()
+ // Check that "Go" is before "Pip", and "Pip" is 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) {
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.
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 {
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=