Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
EyalDelarea committed Jan 7, 2025
2 parents a6d8289 + 506d2fe commit 0e85e18
Show file tree
Hide file tree
Showing 20 changed files with 266 additions and 84 deletions.
6 changes: 5 additions & 1 deletion artifactory/commands/container/containermanagercommand.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
119 changes: 97 additions & 22 deletions artifactory/commands/packagemanagerlogin/packagemanagerlogin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,50 @@ 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"
"golang.org/x/exp/maps"
"net/url"
"slices"
)

// 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).
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.
Expand All @@ -40,20 +69,19 @@ 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)
}
// GetSupportedPackageManagersList returns a sorted list of supported package managers.
func GetSupportedPackageManagersList() []project.ProjectType {
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
}

func IsSupportedPackageManager(packageManager project.ProjectType) bool {
_, exists := packageManagerToRepositoryPackageType[packageManager]
return exists
}

// CommandName returns the name of the login command.
Expand All @@ -72,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 {
Expand All @@ -95,27 +139,25 @@ 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)
}
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))
log.Output(fmt.Sprintf("Successfully configured %s to use JFrog Artifactory repository '%s'.", coreutils.PrintBoldTitle(pmlc.packageManager.String()), coreutils.PrintBoldTitle(pmlc.repoName)))
return nil
}

// 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.
Expand Down Expand Up @@ -239,3 +281,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:
//
// echo <password> | docker login <artifactory-url-without-scheme> -u <username> --password-stdin
//
// For Podman:
//
// echo <password> | podman login <artifactory-url-without-scheme> -u <username> --password-stdin
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://)
parsedPlatformURL, err := url.Parse(pmlc.serverDetails.GetUrl())
if err != nil {
return err
}
urlWithoutScheme := parsedPlatformURL.Host + parsedPlatformURL.Path
return container.ContainerManagerLogin(
urlWithoutScheme,
&container.ContainerManagerLoginConfig{ServerDetails: pmlc.serverDetails},
containerManagerType,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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")
}
24 changes: 18 additions & 6 deletions artifactory/utils/commandsummary/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,38 @@ const (
buildInfoSection summarySection = "buildInfo"
)

// addGitHubTrackingToUrl adds GitHub-related query parameters to a given URL if the GITHUB_WORKFLOW environment variable is set.
const (
// The source of the request
sourceParamKey = "s"
githubSourceValue = "1"
// The metric to track
metricParamKey = "m"
githubMetricValue = "3"

jobIDKey = "gh_job_id"
sectionKey = "gh_section"
workflowEnvKey = "GITHUB_WORKFLOW"
)

func addGitHubTrackingToUrl(urlStr string, section summarySection) (string, error) {
// Check if GITHUB_WORKFLOW environment variable is set
githubWorkflow := os.Getenv("GITHUB_WORKFLOW")
githubWorkflow := os.Getenv(workflowEnvKey)
if githubWorkflow == "" {
// Return the original URL if the variable is not set
return urlStr, nil
}

// Parse the input URL
parsedUrl, err := url.Parse(urlStr)
if errorutils.CheckError(err) != nil {
// Return an error if the URL is invalid
return "", err
}

// Get the query parameters and add the GitHub tracking parameters
queryParams := parsedUrl.Query()
queryParams.Set("gh_job_id", githubWorkflow)
queryParams.Set("gh_section", string(section))
queryParams.Set(sourceParamKey, githubSourceValue)
queryParams.Set(metricParamKey, githubMetricValue)
queryParams.Set(jobIDKey, githubWorkflow)
queryParams.Set(sectionKey, string(section))
parsedUrl.RawQuery = queryParams.Encode()

// Return the modified URL
Expand Down
20 changes: 14 additions & 6 deletions artifactory/utils/commandsummary/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ func TestGenerateArtifactUrl(t *testing.T) {
majorVersion int
expected string
}{
{"artifactory 7 without project", "", 7, "https://myplatform.com/ui/repos/tree/General/repo/path/file?clearFilter=true&gh_job_id=JFrog+CLI+Core+Tests&gh_section=test-section"},
{"artifactory 7 with project", "proj", 7, "https://myplatform.com/ui/repos/tree/General/repo/path/file?clearFilter=true&gh_job_id=JFrog+CLI+Core+Tests&gh_section=test-section"},
{"artifactory 6 without project", "", 6, "https://myplatform.com/artifactory/webapp/?gh_job_id=JFrog+CLI+Core+Tests&gh_section=test-section#/artifacts/browse/tree/General/repo/path/file"},
{"artifactory 7 without project", "", 7, "https://myplatform.com/ui/repos/tree/General/repo/path/file?clearFilter=true&gh_job_id=JFrog+CLI+Core+Tests&gh_section=test-section&m=3&s=1"},
{"artifactory 7 with project", "proj", 7, "https://myplatform.com/ui/repos/tree/General/repo/path/file?clearFilter=true&gh_job_id=JFrog+CLI+Core+Tests&gh_section=test-section&m=3&s=1"},
{"artifactory 6 without project", "", 6, "https://myplatform.com/artifactory/webapp/?gh_job_id=JFrog+CLI+Core+Tests&gh_section=test-section&m=3&s=1#/artifacts/browse/tree/General/repo/path/file"},
}
StaticMarkdownConfig.setPlatformUrl(testPlatformUrl)
for _, testCase := range cases {
Expand Down Expand Up @@ -71,7 +71,7 @@ func TestAddGitHubTrackingToUrl(t *testing.T) {
"https://example.com/path",
buildInfoSection,
"workflow123",
"https://example.com/path?gh_job_id=workflow123&gh_section=buildInfo",
"https://example.com/path?gh_job_id=workflow123&gh_section=buildInfo&m=3&s=1",
false,
},
{
Expand All @@ -87,15 +87,23 @@ func TestAddGitHubTrackingToUrl(t *testing.T) {
"https://example.com/path?existing_param=value",
packagesSection,
"workflow123",
"https://example.com/path?existing_param=value&gh_job_id=workflow123&gh_section=packages",
"https://example.com/path?existing_param=value&gh_job_id=workflow123&gh_section=packages&m=3&s=1",
false,
},
{
"GITHUB_WORKFLOW with special characters",
"https://example.com/path",
artifactsSection,
"workflow with spaces & special?characters",
"https://example.com/path?gh_job_id=workflow+with+spaces+%26+special%3Fcharacters&gh_section=artifacts",
"https://example.com/path?gh_job_id=workflow+with+spaces+%26+special%3Fcharacters&gh_section=artifacts&m=3&s=1",
false,
},
{
"URL with spaces",
"https://example.com/path?existing_param=value",
packagesSection,
"workflow space",
"https://example.com/path?existing_param=value&gh_job_id=workflow+space&gh_section=packages&m=3&s=1",
false,
},
}
Expand Down
8 changes: 2 additions & 6 deletions artifactory/utils/container/containermanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion artifactory/utils/repositoryutils.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@


| Build Info| Security Violations| Security Issues|
|:---------|:------------|:------------|
| [buildName 123](http://myJFrogPlatform/builds/buildName/123?gh_job_id=JFrog+CLI+Core+Tests&gh_section=buildInfo) | Not scanned| Not scanned|
| Build Info | Security Violations | Security Issues |
| :--------- | :------------ | :------------ |
| [buildName 123](http://myJFrogPlatform/builds/buildName/123?gh_job_id=JFrog+CLI+Core+Tests&gh_section=buildInfo&m=3&s=1) | Not scanned | Not scanned |


Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@

| Artifacts | Security Violations | Security Issues |
| :------------ | :--------------------- | :------------------ |
| <pre>πŸ“¦ docker-local<br>└── πŸ“ image2<br> └── πŸ“ sha256:552c<br> └── <a href='https://myplatform.com/ui/repos/tree/General/docker-local/image2/sha256:552c/sha256__aae9?clearFilter=true&gh_job_id=JFrog+CLI+Core+Tests&gh_section=packages' target="_blank">sha256__aae9</a><br><br></pre> | Not scanned | Not scanned |
| <pre>πŸ“¦ docker-local<br>└── πŸ“ image2<br> └── πŸ“ sha256:552c<br> └── <a href='https://myplatform.com/ui/repos/tree/General/docker-local/image2/sha256:552c/sha256__aae9?clearFilter=true&gh_job_id=JFrog+CLI+Core+Tests&gh_section=packages&m=3&s=1' target="_blank">sha256__aae9</a><br><br></pre> | Not scanned | Not scanned |
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<pre>πŸ“¦ generic-local
└── πŸ“ path
└── πŸ“ to
└── <a href='https://myplatform.com/ui/repos/tree/General/generic-local/path/to/artifact2?clearFilter=true&gh_job_id=JFrog+CLI+Core+Tests&gh_section=artifacts' target="_blank">artifact2</a>
└── <a href='https://myplatform.com/ui/repos/tree/General/generic-local/path/to/artifact2?clearFilter=true&gh_job_id=JFrog+CLI+Core+Tests&gh_section=artifacts&m=3&s=1' target="_blank">artifact2</a>

</pre>

Loading

0 comments on commit 0e85e18

Please sign in to comment.