Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Package manager login command - Npm, Yarn, Pip, Pipenv, Poetry,Go, Nuget, Dotnet #1285

Merged
merged 48 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
bb36015
Improve repositores code
sverdlov93 Oct 25, 2024
6583684
Improve repositores code
sverdlov93 Oct 25, 2024
fb20e20
Improve repositores code
sverdlov93 Oct 27, 2024
9a20c6c
Merge branch 'dev' of https://github.com/jfrog/jfrog-cli-core into np…
sverdlov93 Oct 29, 2024
ed7b39d
Improve repositores code
sverdlov93 Oct 30, 2024
633d8a0
Improve repositores code
sverdlov93 Nov 3, 2024
74c9a47
Improve repositores code
sverdlov93 Nov 3, 2024
e0e5024
Improve repositores code
sverdlov93 Nov 4, 2024
f51ae6d
Improve repositores code
sverdlov93 Nov 4, 2024
7233563
Merge branch 'dev' into npm-login
sverdlov93 Nov 5, 2024
f8e793a
Improve repositores code
sverdlov93 Nov 6, 2024
c53b4c2
Merge branch 'npm-login' of https://github.com/sverdlov93/jfrog-cli-c…
sverdlov93 Nov 6, 2024
c3e0486
Improve repositores code
sverdlov93 Nov 6, 2024
e8cc9f8
Improve repositores code
sverdlov93 Nov 6, 2024
95ba696
Improve repositores code
sverdlov93 Nov 6, 2024
3dff2bb
Improve repositores code
sverdlov93 Nov 6, 2024
0f0856e
Improve repositores code
sverdlov93 Nov 6, 2024
f280aa5
Improve repositores code
sverdlov93 Nov 6, 2024
ecb74bb
Improve repositores code
sverdlov93 Nov 6, 2024
cd0fda3
Improve repositores code
sverdlov93 Nov 6, 2024
547fa02
Improve repositores code
sverdlov93 Nov 6, 2024
265fea7
Improve repositores code
sverdlov93 Nov 6, 2024
e8f884c
Improve repositores code
sverdlov93 Nov 7, 2024
2ee5950
Merge branch 'dev' of https://github.com/jfrog/jfrog-cli-core into np…
sverdlov93 Nov 7, 2024
aec982a
Improve repositores code
sverdlov93 Nov 7, 2024
57df296
Improve repositores code
sverdlov93 Nov 7, 2024
e8d5842
Improve repositores code
sverdlov93 Nov 7, 2024
f84e6f8
Improve repositores code
sverdlov93 Nov 7, 2024
8c2b1c6
Merge branch 'dev' into npm-login
sverdlov93 Nov 10, 2024
e6afac1
Merge branch 'dev' of https://github.com/jfrog/jfrog-cli-core into np…
sverdlov93 Nov 10, 2024
f6eed54
Merge branch 'dev' into npm-login
sverdlov93 Nov 11, 2024
be5cdcb
Improve upload archive progress bar
sverdlov93 Nov 11, 2024
1087833
Merge remote-tracking branch 'sverdlov93/npm-login' into npm-login
sverdlov93 Nov 11, 2024
cd0ce91
Improve upload archive progress bar
sverdlov93 Nov 11, 2024
31b0627
Improve upload archive progress bar
sverdlov93 Nov 11, 2024
89cdb97
Improve upload archive progress bar
sverdlov93 Nov 11, 2024
02252d2
Improve upload archive progress bar
sverdlov93 Nov 11, 2024
76fd3b4
Improve upload archive progress bar
sverdlov93 Nov 11, 2024
77e8d71
Improve upload archive progress bar
sverdlov93 Nov 12, 2024
f28f014
Improve upload archive progress bar
sverdlov93 Nov 12, 2024
0e959a3
Improve upload archive progress bar
sverdlov93 Nov 12, 2024
01e0f45
Improve upload archive progress bar
sverdlov93 Nov 12, 2024
95bf72e
Improve upload archive progress bar
sverdlov93 Nov 12, 2024
c90cbb3
Improve upload archive progress bar
sverdlov93 Nov 12, 2024
5f37066
Improve upload archive progress bar
sverdlov93 Nov 12, 2024
1ecb8ed
Improve upload archive progress bar
sverdlov93 Nov 12, 2024
6e3f8b9
Improve upload archive progress bar
sverdlov93 Nov 17, 2024
07af542
Merge branch 'dev' of https://github.com/jfrog/jfrog-cli-core into np…
sverdlov93 Nov 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 33 additions & 10 deletions artifactory/commands/dotnet/dotnetcommand.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -19,7 +19,7 @@ import (
)

const (
SourceName = "JFrogCli"
SourceName = "JFrogArtifactory"
configFilePattern = "jfrog.cli.nuget."

dotnetTestError = `the command failed with an error.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions artifactory/commands/dotnet/dotnetcommand_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions artifactory/commands/golang/go.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion artifactory/commands/golang/go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
242 changes: 242 additions & 0 deletions artifactory/commands/packagemanagerlogin/packagemanagerlogin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
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"
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"
)

// PackageManagerLoginCommand configures registries and authentication for various package manager (npm, Yarn, Pip, Pipenv, Poetry, Go)
type PackageManagerLoginCommand struct {
sverdlov93 marked this conversation as resolved.
Show resolved Hide resolved
// 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
// serverDetails contains Artifactory server configuration.
serverDetails *config.ServerDetails
// commandName specifies the command for this instance.
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{
packageManager: packageManager,
commandName: packageManager.String() + "_login",
}
}

// 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)
}
}

// CommandName returns the name of the login command.
func (pmlc *PackageManagerLoginCommand) CommandName() string {
return pmlc.commandName
}

// SetServerDetails assigns the server configuration details to the command.
func (pmlc *PackageManagerLoginCommand) SetServerDetails(serverDetails *config.ServerDetails) *PackageManagerLoginCommand {
pmlc.serverDetails = serverDetails
return pmlc
}

// ServerDetails returns the stored server configuration details.
func (pmlc *PackageManagerLoginCommand) ServerDetails() (*config.ServerDetails, error) {
return pmlc.serverDetails, nil
}

// 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 package manager.
if err = pmlc.getRepositoryNameFromUserInteractively(); err != nil {
return err
}
}

// Configure the appropriate package manager based on the package manager.
switch pmlc.packageManager {
case project.Npm:
err = pmlc.configureNpm()
case project.Yarn:
err = pmlc.configureYarn()
case project.Pip, project.Pipenv:
err = pmlc.configurePip()
case project.Poetry:
err = pmlc.configurePoetry()
case project.Go:
err = pmlc.configureGo()
case project.Nuget, project.Dotnet:
err = pmlc.configureDotnetNuget()
sverdlov93 marked this conversation as resolved.
Show resolved Hide resolved
default:
err = errorutils.CheckErrorf("unsupported package manager: %s", pmlc.packageManager)
}
if err != nil {
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))
return nil
}

// getRepositoryNameFromUserInteractively prompts the user to select a compatible virtual repository.
func (pmlc *PackageManagerLoginCommand) getRepositoryNameFromUserInteractively() error {
sverdlov93 marked this conversation as resolved.
Show resolved Hide resolved
// Map the package manager to its corresponding package type.
packageType, err := packageManagerToPackageType(pmlc.packageManager)
if err != nil {
return err
}
repoFilterParams := services.RepositoriesFilterParams{
RepoType: utils.Virtual.String(),
PackageType: packageType,
}

// Prompt for repository selection based on filter parameters.
pmlc.repoName, err = utils.SelectRepositoryInteractively(pmlc.serverDetails, repoFilterParams)
return err
}

// 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://<user>:<token>@<your-artifactory-url>/artifactory/api/pypi/<repo-name>/simple
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})
}

// configurePoetry configures Poetry to use the specified repository and authentication credentials.
// Runs the following commands:
//
// poetry config repositories.<repo-name> https://<your-artifactory-url>/artifactory/api/pypi/<repo-name>/simple
// poetry config http-basic.<repo-name> <user> <password/token>
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, pmlc.repoName)
}

// configureNpm configures npm to use the Artifactory repository URL and sets authentication.
// Runs the following commands:
//
// npm config set registry https://<your-artifactory-url>/artifactory/api/npm/<repo-name>
//
// For token-based auth:
//
// npm config set //your-artifactory-url/artifactory/api/npm/<repo-name>/:_authToken "<token>"
//
// For basic auth:
//
// npm config set //your-artifactory-url/artifactory/api/npm/<repo-name>/:_auth "<base64-encoded-username:password>"
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(pmlc.serverDetails, repoUrl)
if authKey != "" && authValue != "" {
return npm.ConfigSet(authKey, authValue, "npm")
}
return nil
}

// configureYarn configures Yarn to use the specified Artifactory repository and sets authentication.
// Runs the following commands:
//
// yarn config set registry https://<your-artifactory-url>/artifactory/api/npm/<repo-name>
//
// For token-based auth:
//
// yarn config set //your-artifactory-url/artifactory/api/npm/<repo-name>/:_authToken "<token>"
//
// For basic auth:
//
// yarn config set //your-artifactory-url/artifactory/api/npm/<repo-name>/:_auth "<base64-encoded-username:password>"
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(pmlc.serverDetails, repoUrl)
if authKey != "" && authValue != "" {
return yarn.ConfigSet(authKey, authValue, "yarn", false)
}
return nil
}

// configureGo configures Go to use the Artifactory repository for GOPROXY.
// Runs the following command:
//
// go env -w GOPROXY=https://<user>:<token>@<your-artifactory-url>/artifactory/go/<repo-name>,direct
func (pmlc *PackageManagerLoginCommand) configureGo() error {
repoWithCredsUrl, err := gocommands.GetArtifactoryRemoteRepoUrl(pmlc.serverDetails, pmlc.repoName, gocommands.GoProxyUrlParams{Direct: true})
if err != nil {
return err
}
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 <JFrog-Artifactory> "https://acme.jfrog.io/artifactory/api/nuget/{repository-name}" --username <your-username> --password <your-password>
//
// For NuGet:
//
// nuget sources add -Name <JFrog-Artifactory> -Source "https://acme.jfrog.io/artifactory/api/nuget/{repository-name}" -Username <your-username> -Password <your-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
}
sverdlov93 marked this conversation as resolved.
Show resolved Hide resolved
// Add the repository as a source in the NuGet configuration with credentials for authentication.
return dotnet.AddSourceToNugetConfig(toolchainType, sourceUrl, user, password)
}
Loading
Loading