From 02252d23f0cf948ff0394ea5a120f0e9f057ce35 Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Mon, 11 Nov 2024 19:28:31 +0200 Subject: [PATCH] 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)) + } + }) + } +}