From 764217a225723a8ffbadc748f82097809e746d90 Mon Sep 17 00:00:00 2001 From: Bhanu Reddy Date: Thu, 10 Sep 2026 08:22:02 +0530 Subject: [PATCH 1/5] RTECO-2003: register `jf choco` and add the Chocolatey test suite Registers the choco build tool and wires `jf setup choco` through the existing setup family, which is driven off GetSupportedPackageManagersList() and so needs no registration of its own. The command is registered with SkipFlagParsing so Chocolatey's own flags reach the native client untouched, and is wrapped in WrapCmdWithCurationPostFailureRun passing techutils.Nuget -- Chocolatey packages are .nupkg files served from NuGet repos, so a separate technology would duplicate NuGet's configuration for no behavioural gain. There is no legacy `jf rt choco-*` path, so the command is FlexPack-only by construction and needs no JFROG_RUN_NATIVE gate. Adds choco_test.go (21 integration tests) behind -test.choco, reusing the existing NuGet repositories since Artifactory documents Chocolatey under NuGet and has no distinct package type. Beyond pack/push/install coverage, the suite pins three native behaviours that are easy to assume away, each of which fails silently rather than loudly: - `choco pack --output-directory` still records its artifact. - A bare `choco push`, with no positional path, still records the package Chocolatey found in the folder. - An installed package's version is resolved from its .nuspec, since lib//.nupkg carries no version in the file name. It also asserts the statelessness contract -- no .jfrog/projects and no change to Chocolatey's machine-wide source list -- and that pass-through subcommands neither collect build-info nor require a configured JFrog server. The workflow runs on windows-2022, which ships Chocolatey 2.7.4, and fails early if the runner ever ships older than 2.0, since `choco apikey add` is the 2.x verb. A second, cheap Linux job covers the one behaviour only observable where Chocolatey cannot run: the OS gate, and that --help still works anyway. Co-Authored-By: Claude Opus 5 --- .github/workflows/build-gate.yml | 4 + .github/workflows/chocoTests.yml | 75 +++ buildtools/cli.go | 68 +++ choco_test.go | 775 +++++++++++++++++++++++++++++++ docs/buildtools/choco/help.go | 32 ++ docs/buildtools/setup/help.go | 2 + go.mod | 6 +- go.sum | 12 +- main_test.go | 4 +- utils/cliutils/commandsflags.go | 4 + utils/tests/consts.go | 1 + utils/tests/utils.go | 6 + 12 files changed, 978 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/chocoTests.yml create mode 100644 choco_test.go create mode 100644 docs/buildtools/choco/help.go diff --git a/.github/workflows/build-gate.yml b/.github/workflows/build-gate.yml index c1035934b..b823511e3 100644 --- a/.github/workflows/build-gate.yml +++ b/.github/workflows/build-gate.yml @@ -61,6 +61,10 @@ jobs: needs: gate uses: ./.github/workflows/artifactoryTests.yml secrets: inherit + choco: + needs: gate + uses: ./.github/workflows/chocoTests.yml + secrets: inherit conan: needs: gate uses: ./.github/workflows/conanTests.yml diff --git a/.github/workflows/chocoTests.yml b/.github/workflows/chocoTests.yml new file mode 100644 index 000000000..93c5a58fc --- /dev/null +++ b/.github/workflows/chocoTests.yml @@ -0,0 +1,75 @@ +name: Chocolatey Tests + +on: + workflow_call: + workflow_dispatch: + +jobs: + Choco-Tests: + name: Chocolatey tests (windows) + # Chocolatey is a Windows-only package manager, so unlike the other package + # manager suites this one does not run on a matrix. + runs-on: windows-2022 + steps: + - name: Checkout code + uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + # Safe: this workflow only runs after human approval via the build-gate environment. + allow-unsafe-pr-checkout: true + + - name: Setup FastCI + uses: jfrog-fastci/fastci@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + fastci_otel_token: ${{ secrets.FASTCI_TOKEN }} + + # Chocolatey is pre-installed on GitHub-hosted Windows runners (2.7.4 on windows-2022). + # 'jf setup choco' needs 2.0+ for the 'apikey add' verb - on 1.x, a bare 'choco apikey' + # only lists keys - so fail here with a clear message rather than letting every setup + # test fail later on an argument-parsing error. + - name: Verify Chocolatey + shell: pwsh + run: | + $version = (choco --version).Trim() + Write-Host "Chocolatey version: $version" + if ([int]($version -split '\.')[0] -lt 2) { + Write-Error "'jf setup choco' requires Chocolatey 2.0+ for the 'apikey add' verb; found $version" + exit 1 + } + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Install local Artifactory + uses: jfrog/.github/actions/install-local-artifactory@main + with: + RTLIC: ${{ secrets.RTLIC }} + RT_CONNECTION_TIMEOUT_SECONDS: ${{ env.RT_CONNECTION_TIMEOUT_SECONDS || '1200' }} + + - name: Run Chocolatey tests + run: >- + go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.choco + ${{ env.JFROG_TESTS_IS_EXTERNAL == 'true' && format('--jfrog.url={0} --jfrog.adminToken={1}', env.JFROG_TESTS_URL, env.JFROG_TESTS_LOCAL_ACCESS_TOKEN) || '' }} + + # The OS gate is the one behaviour that can only be observed where Chocolatey cannot run, so it + # needs a non-Windows job. Deliberately cheap: no Artifactory and no Chocolatey, just the two + # tests asserting that 'jf choco' refuses to run and that 'jf choco --help' still works anyway. + Choco-OS-Gate: + name: Chocolatey OS gate (linux) + runs-on: ubuntu-24.04 + steps: + - name: Checkout code + uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + # Safe: this workflow only runs after human approval via the build-gate environment. + allow-unsafe-pr-checkout: true + + - name: Setup Go with cache + uses: jfrog/.github/actions/install-go-with-cache@main + + - name: Assert jf choco refuses to run on a non-Windows host + run: >- + go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.choco + -run 'TestChocoNonWindowsGate|TestChocoHelpWorksOnAllPlatforms' diff --git a/buildtools/cli.go b/buildtools/cli.go index 9e60d7e8b..5b70f280d 100644 --- a/buildtools/cli.go +++ b/buildtools/cli.go @@ -16,6 +16,7 @@ import ( alpinecommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/alpine" aptcommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/apt" cargocommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/cargo" + chococommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/choco" conancommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/conan" nixcommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/nix" nugetcommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/nuget" @@ -64,6 +65,7 @@ import ( "github.com/jfrog/jfrog-cli/docs/buildtools/apkcommand" aptdocs "github.com/jfrog/jfrog-cli/docs/buildtools/apt" "github.com/jfrog/jfrog-cli/docs/buildtools/cargo" + chocodocs "github.com/jfrog/jfrog-cli/docs/buildtools/choco" "github.com/jfrog/jfrog-cli/docs/buildtools/conan" "github.com/jfrog/jfrog-cli/docs/buildtools/conanconfig" "github.com/jfrog/jfrog-cli/docs/buildtools/docker" @@ -248,6 +250,21 @@ func GetCommands() []cli.Command { Category: buildToolsCategory, Action: NugetCmd, }, + { + Name: "choco", + Flags: cliutils.GetCommandFlags(cliutils.Choco), + Usage: corecommon.ResolveDescription(chocodocs.GetDescription(), chocodocs.GetAIDescription()), + HelpName: corecommon.CreateUsage("choco", corecommon.ResolveDescription(chocodocs.GetDescription(), chocodocs.GetAIDescription()), chocodocs.Usage), + UsageText: chocodocs.GetArguments(), + ArgsUsage: common.CreateEnvVars(), + SkipFlagParsing: true, + BashComplete: corecommon.CreateBashCompletionFunc(), + Category: buildToolsCategory, + Action: func(c *cli.Context) error { + cmdName, _ := getCommandName(c.Args()) + return securityCLI.WrapCmdWithCurationPostFailureRun(c, ChocoCmd, techutils.Nuget, cmdName) + }, + }, { Name: "dotnet-config", Flags: cliutils.GetCommandFlags(cliutils.DotnetConfig), @@ -1094,6 +1111,52 @@ func NugetCmd(c *cli.Context) error { return commands.ExecWithPackageManager(nugetCmd, project.Nuget.String()) } +func ChocoCmd(c *cli.Context) error { + if show, err := cliutils.ShowGenericCmdHelpIfNeeded(c, c.Args(), c.Command.Name); show || err != nil { + return err + } + if c.NArg() < 1 { + return cliutils.WrongNumberOfArgumentsHandler(c) + } + args := cliutils.ExtractCommand(c) + args, serverID, err := coreutils.ExtractServerIdFromCommand(args) + if err != nil { + return fmt.Errorf("extract server ID: %w", err) + } + filteredArgs, buildConfiguration, err := build.ExtractBuildDetailsFromArgs(args) + if err != nil { + return err + } + filteredArgs, repoResolve, err := coreutils.ExtractStringOptionFromArgs(filteredArgs, "repo-resolve") + if err != nil { + return fmt.Errorf("extract --repo-resolve: %w", err) + } + filteredArgs, repoDeploy, err := coreutils.ExtractStringOptionFromArgs(filteredArgs, "repo") + if err != nil { + return fmt.Errorf("extract --repo: %w", err) + } + commandName, commandArgs := getCommandName(filteredArgs) + workingDirectory, err := filepath.Abs(".") + if err != nil { + return err + } + command := chococommand.NewChocoFlexPackCommand(). + SetSubCommand(commandName). + SetArgs(commandArgs). + SetRepoResolve(repoResolve). + SetRepoDeploy(repoDeploy). + SetBuildConfiguration(buildConfiguration). + SetWorkingDirectory(workingDirectory) + serverDetails, err := coreConfig.GetSpecificConfig(serverID, true, false) + if err != nil && serverID != "" { + return fmt.Errorf("server-id %q not found: %w", serverID, err) + } + if err == nil { + command.SetServerDetails(serverDetails) + } + return commands.ExecWithPackageManager(command, "choco") +} + func DotnetCmd(c *cli.Context) error { if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil { return err @@ -1946,6 +2009,11 @@ func setupCmd(c *cli.Context) (err error) { return } } + if packageManager == project.Choco { + if err = setup.ValidateChocoPlatform(); err != nil { + return err + } + } setupCmd := setup.NewSetupCommand(packageManager) artDetails, err := cliutils.CreateArtifactoryDetailsByFlags(c) if err != nil { diff --git a/choco_test.go b/choco_test.go new file mode 100644 index 000000000..086d72836 --- /dev/null +++ b/choco_test.go @@ -0,0 +1,775 @@ +package main + +import ( + "fmt" + "net/http" + "net/url" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + buildInfo "github.com/jfrog/build-info-go/entities" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" + "github.com/jfrog/jfrog-cli/inttestutils" + "github.com/jfrog/jfrog-cli/utils/tests" + "github.com/jfrog/jfrog-client-go/http/httpclient" + clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// chocoCommandProperty is the single module property that 'jf choco' records, holding the +// executed Chocolatey command line with any API key redacted. +const chocoCommandProperty = buildInfo.BuildInfoEnvPrefix + "CHOCO_COMMAND" + +// initChocoTest gates every Chocolatey test. Chocolatey is a Windows-only package manager, and +// both 'jf choco' and 'jf setup choco' fail fast on any other OS, so the tests are skipped +// unless a real 'choco' executable is available. +func initChocoTest(t *testing.T) { + if !*tests.TestChoco { + t.Skip("Skipping Chocolatey test. To run Choco test add the '-test.choco=true' option.") + } + if runtime.GOOS != "windows" { + t.Skipf("Skipping Chocolatey test. Chocolatey runs on Windows only. Detected OS: %s", runtime.GOOS) + } + if _, err := exec.LookPath("choco"); err != nil { + t.Skip("Skipping Chocolatey test. The 'choco' executable was not found in PATH.") + } + createJfrogHomeConfig(t, true) +} + +// runChoco runs a 'jf choco' command. Unlike 'jf nuget', the Chocolatey command has no +// JFROG_RUN_NATIVE gate - it always delegates to the native client - and its flag set has no +// --allow-insecure-connections, so neither is set here. +func runChoco(t *testing.T, args ...string) error { + t.Helper() + jfrogCli := coreTests.NewJfrogCli(execMain, "jfrog", "") + return jfrogCli.Exec(args...) +} + +// createChocoPackageSource writes a minimal but valid Chocolatey package layout into a fresh +// temp directory and returns that directory. A nuspec with neither dependencies nor content is +// rejected (NU5017), so a tools script is packed as content - which is also the conventional +// Chocolatey layout and makes the package safe to install. +func createChocoPackageSource(t *testing.T, id, version string) (packageDir, nuspecName string) { + t.Helper() + packageDir = t.TempDir() + toolsDir := filepath.Join(packageDir, "tools") + require.NoError(t, os.MkdirAll(toolsDir, 0o700)) + installScript := "Write-Host 'jfrog-cli-tests Chocolatey package installed'\n" + require.NoError(t, os.WriteFile(filepath.Join(toolsDir, "chocolateyinstall.ps1"), []byte(installScript), 0o600)) + uninstallScript := "Write-Host 'jfrog-cli-tests Chocolatey package uninstalled'\n" + require.NoError(t, os.WriteFile(filepath.Join(toolsDir, "chocolateyuninstall.ps1"), []byte(uninstallScript), 0o600)) + + nuspecName = id + ".nuspec" + nuspecContent := fmt.Sprintf(` + + + %s + %s + jfrog-cli-tests + jfrog-cli-tests + Test package for jf choco integration tests. + + + + +`, id, version) + require.NoError(t, os.WriteFile(filepath.Join(packageDir, nuspecName), []byte(nuspecContent), 0o600)) + return packageDir, nuspecName +} + +// packChocoPackage runs 'jf choco pack' from inside a generated package directory and returns the +// absolute path of the produced .nupkg. 'choco pack' writes the package into the current working +// directory, and the command snapshots that same directory to discover what it produced, so the +// test must run from there. +func packChocoPackage(t *testing.T, id, version string, extraArgs ...string) (nupkgPath string) { + t.Helper() + packageDir, nuspecName := createChocoPackageSource(t, id, version) + packChocoPackageIn(t, packageDir, nuspecName, extraArgs...) + nupkgPath = filepath.Join(packageDir, id+"."+version+".nupkg") + require.FileExists(t, nupkgPath) + return nupkgPath +} + +// packChocoPackageIn runs 'jf choco pack' with packageDir as the working directory. +func packChocoPackageIn(t *testing.T, packageDir, nuspecName string, extraArgs ...string) { + t.Helper() + workingDirectory, err := os.Getwd() + require.NoError(t, err) + defer clientTestUtils.ChangeDirWithCallback(t, workingDirectory, packageDir)() + args := append([]string{"choco", "pack", nuspecName}, extraArgs...) + require.NoError(t, runChoco(t, args...), "'jf choco pack' should succeed") +} + +// pushChocoPackage runs 'jf choco push' for an already packed .nupkg. +func pushChocoPackage(t *testing.T, nupkgPath, repo string, extraArgs ...string) error { + t.Helper() + args := append([]string{"choco", "push", nupkgPath, "--repo=" + repo}, extraArgs...) + return runChoco(t, args...) +} + +// chocoArtifactPath is the repository-relative path a Chocolatey package lands on. The layout is +// flat: the package sits directly under the repository root, with no version directories. +func chocoArtifactPath(repo, id, version string) string { + return repo + "/" + id + "." + version + ".nupkg" +} + +// assertChocoArtifactExists verifies that a pushed package is retrievable from Artifactory. +func assertChocoArtifactExists(t *testing.T, repoRelativePath string) { + t.Helper() + client, err := httpclient.ClientBuilder().Build() + require.NoError(t, err) + _, res, err := client.GetRemoteFileDetails(serverDetails.ArtifactoryUrl+repoRelativePath, artHttpDetails) + require.NoError(t, err, "pushed Chocolatey package should exist at %s", repoRelativePath) + assert.Equal(t, http.StatusOK, res.StatusCode) +} + +// chocoSourceName mirrors the source name that 'jf setup choco' derives from the Artifactory +// hostname and repository key, so tests can clean the machine-wide source up afterwards. +func chocoSourceName(t *testing.T, repo string) string { + t.Helper() + parsedURL, err := url.Parse(serverDetails.ArtifactoryUrl) + require.NoError(t, err) + return "jfrt-" + sanitizeChocoSourceComponent(parsedURL.Hostname()) + "-" + repo +} + +func sanitizeChocoSourceComponent(value string) string { + var builder strings.Builder + for _, character := range strings.ToLower(value) { + if (character >= 'a' && character <= 'z') || (character >= '0' && character <= '9') || character == '.' || character == '_' || character == '-' { + builder.WriteRune(character) + } else { + builder.WriteByte('-') + } + } + return strings.Trim(builder.String(), "-") +} + +// cleanupChocoSource removes a machine-wide Chocolatey source. Removal is best effort: the source +// may never have been created if the test failed early. +func cleanupChocoSource(t *testing.T, sourceName string) { + t.Helper() + t.Cleanup(func() { + if output, err := exec.Command("choco", "source", "remove", "-n="+sourceName).CombinedOutput(); err != nil { + t.Logf("cleanup: 'choco source remove -n=%s' returned %v: %s", sourceName, err, string(output)) + } + }) +} + +// cleanupChocoInstalledPackage uninstalls a package that a test installed machine-wide. +func cleanupChocoInstalledPackage(t *testing.T, packageID string) { + t.Helper() + t.Cleanup(func() { + if output, err := exec.Command("choco", "uninstall", packageID, "-y").CombinedOutput(); err != nil { + t.Logf("cleanup: 'choco uninstall %s -y' returned %v: %s", packageID, err, string(output)) + } + }) +} + +// getPublishedChocoBuildInfo publishes and then reads back the build info of a Chocolatey build. +func getPublishedChocoBuildInfo(t *testing.T, buildName, buildNumber string) buildInfo.BuildInfo { + t.Helper() + require.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + require.NoError(t, err) + require.True(t, found, "build info %s/%s should have been published", buildName, buildNumber) + return publishedBuildInfo.BuildInfo +} + +// getChocoCommandProperty reads the recorded Chocolatey command line out of a module. Module +// properties are typed as interface{} in build-info, so after a publish/fetch round trip they +// arrive as a generic map. +func getChocoCommandProperty(t *testing.T, module buildInfo.Module) string { + t.Helper() + switch properties := module.Properties.(type) { + case nil: + return "" + case map[string]string: + return properties[chocoCommandProperty] + case map[string]interface{}: + value, ok := properties[chocoCommandProperty].(string) + if !ok { + return "" + } + return value + default: + t.Fatalf("unexpected module properties type %T", module.Properties) + return "" + } +} + +// TestChocoPackCollectsArtifacts covers the 'jf choco pack' happy path: the packed .nupkg is +// discovered in the working directory and recorded as a build-info artifact. +func TestChocoPackCollectsArtifacts(t *testing.T) { + initChocoTest(t) + defer cleanTestsHomeEnv() + + id, version := "ChocoPackPkg", "1.0.0" + buildName := tests.ChocoBuildName + "-pack" + buildNumber := "1" + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + packChocoPackage(t, id, version, "--build-name="+buildName, "--build-number="+buildNumber) + + collectedBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber) + require.Len(t, collectedBuildInfo.Modules, 1) + module := collectedBuildInfo.Modules[0] + assert.Equal(t, id+":"+version, module.Id, "pack module id should be ':'") + assert.Equal(t, buildInfo.Nuget, module.Type, "Chocolatey packages are NuGet packages") + require.Len(t, module.Artifacts, 1) + artifact := module.Artifacts[0] + assert.Equal(t, id+"."+version+".nupkg", artifact.Name) + assert.Equal(t, "nupkg", artifact.Type) + assert.Equal(t, artifact.Name, artifact.Path, "the Chocolatey layout is flat, so path equals name") + assert.NotEmpty(t, artifact.Sha1) + assert.NotEmpty(t, artifact.Sha256) + assert.NotEmpty(t, artifact.Md5) + assert.Contains(t, getChocoCommandProperty(t, module), "pack", "the executed choco command should be recorded") +} + +// TestChocoPushBuildInfoAndProperties covers the primary end-to-end flow - pack, then push to a +// local repository - and asserts the artifact layout, the published build info and the build +// properties stamped on the uploaded package. +func TestChocoPushBuildInfoAndProperties(t *testing.T) { + initChocoTest(t) + defer cleanTestsHomeEnv() + + id, version := "ChocoPushPkg", "1.0.0" + buildName := tests.ChocoBuildName + "-push" + buildNumber := "1" + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + nupkgPath := packChocoPackage(t, id, version) + require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo, + "--build-name="+buildName, "--build-number="+buildNumber)) + + artifactPath := chocoArtifactPath(tests.NugetLocalRepo, id, version) + assertChocoArtifactExists(t, artifactPath) + + pushedBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber) + require.Len(t, pushedBuildInfo.Modules, 1) + module := pushedBuildInfo.Modules[0] + assert.Equal(t, id+":"+version, module.Id) + assert.Equal(t, buildInfo.Nuget, module.Type) + require.Len(t, module.Artifacts, 1) + artifact := module.Artifacts[0] + assert.Equal(t, id+"."+version+".nupkg", artifact.Name) + assert.Equal(t, "nupkg", artifact.Type) + assert.Equal(t, tests.NugetLocalRepo, artifact.OriginalDeploymentRepo) + assert.NotEmpty(t, artifact.Sha1) + assert.NotEmpty(t, artifact.Sha256) + assert.NotEmpty(t, artifact.Md5) + assert.Contains(t, getChocoCommandProperty(t, module), "push") + + // Build properties are stamped on the uploaded package so it can be traced back to its build. + properties := getFlexPackItemProps(t, artifactPath) + assert.Equal(t, []string{buildName}, properties["build.name"]) + assert.Equal(t, []string{buildNumber}, properties["build.number"]) + assert.NotEmpty(t, properties["build.timestamp"]) +} + +// TestChocoPushToRemoteRejected verifies that a remote repository is refused as a push target +// before anything is uploaded. +func TestChocoPushToRemoteRejected(t *testing.T) { + initChocoTest(t) + defer cleanTestsHomeEnv() + + nupkgPath := packChocoPackage(t, "ChocoPushRemotePkg", "1.0.0") + err := pushChocoPackage(t, nupkgPath, tests.NugetRemoteRepo) + require.Error(t, err, "pushing to a remote repository must be rejected") + assert.Contains(t, err.Error(), "cannot be used as a Chocolatey push target") +} + +// TestChocoPushSourceRepoMismatchRejected verifies that an explicit --source pointing at one +// repository while --repo names another is rejected, instead of silently pushing to one and +// recording build info against the other. +func TestChocoPushSourceRepoMismatchRejected(t *testing.T) { + initChocoTest(t) + defer cleanTestsHomeEnv() + + nupkgPath := packChocoPackage(t, "ChocoSourceMismatchPkg", "1.0.0") + mismatchedSource := serverDetails.ArtifactoryUrl + "api/nuget/" + tests.NugetVirtualRepo + err := pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo, "-s="+mismatchedSource) + require.Error(t, err, "a --source that disagrees with --repo must be rejected") + assert.Contains(t, err.Error(), "use matching source and --repo values") +} + +// TestChocoPushToVirtualRepoConvention verifies that pushing to a virtual repository forwards to +// its default deployment repository, and that build info records the resolved local repository +// rather than the virtual repository key. +func TestChocoPushToVirtualRepoConvention(t *testing.T) { + initChocoTest(t) + defer cleanTestsHomeEnv() + + id, version := "ChocoVirtualPushPkg", "1.0.0" + buildName := tests.ChocoBuildName + "-virtual-push" + buildNumber := "1" + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + nupkgPath := packChocoPackage(t, id, version) + require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetVirtualRepo, + "--build-name="+buildName, "--build-number="+buildNumber)) + + // The package must land in the virtual repository's default deployment (local) repository. + assertChocoArtifactExists(t, chocoArtifactPath(tests.NugetLocalRepo, id, version)) + + virtualPushBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber) + require.Len(t, virtualPushBuildInfo.Modules, 1) + require.NotEmpty(t, virtualPushBuildInfo.Modules[0].Artifacts) + for _, artifact := range virtualPushBuildInfo.Modules[0].Artifacts { + assert.Equal(t, tests.NugetLocalRepo, artifact.OriginalDeploymentRepo, + "build info must record the resolved local repository, not the virtual repository key") + } +} + +// TestChocoBuildFlagsValidation covers the two partial build-flag cases. They behave differently: +// --build-name alone is accepted and simply collects no build info, while --build-number without +// --build-name is rejected by jf's CLI-wide flag-pair validation. +func TestChocoBuildFlagsValidation(t *testing.T) { + initChocoTest(t) + defer cleanTestsHomeEnv() + + // Build-info is collected when, and only when, both --build-name and --build-number are given. + // Neither flag is a plain passthrough that must still succeed; half of the pair is a mistake + // worth reporting rather than silently ignoring, and it is reported before choco runs at all. + testCases := []struct { + name string + packageID string + buildName string + extraArgs []string + expectError bool + }{ + { + name: "neither build flag pushes without collecting build info", + packageID: "ChocoNoBuildFlagsPkg", + }, + { + name: "build name without build number is rejected", + packageID: "ChocoNameOnlyPkg", + buildName: tests.ChocoBuildName + "-name-only", + expectError: true, + }, + { + name: "build number without build name is rejected", + packageID: "ChocoNumberOnlyPkg", + extraArgs: []string{"--build-number=1"}, + expectError: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + extraArgs := testCase.extraArgs + if testCase.buildName != "" { + extraArgs = append(extraArgs, "--build-name="+testCase.buildName) + t.Cleanup(func() { + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, testCase.buildName, artHttpDetails) + }) + } + nupkgPath := packChocoPackage(t, testCase.packageID, "1.0.0") + err := pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo, extraArgs...) + if testCase.expectError { + require.Error(t, err, "one build flag without the other must be rejected") + assert.Contains(t, err.Error(), "cannot be provided separately") + // The rejection happens before the native command, so nothing was published. + return + } + // A bare passthrough is legal: the package is pushed, there is just no build to publish. + require.NoError(t, err) + assertChocoArtifactExists(t, chocoArtifactPath(tests.NugetLocalRepo, testCase.packageID, "1.0.0")) + }) + } +} + +// TestChocoBuildInfoFromEnvVars verifies that the build name and number can come from the +// standard JFrog CLI environment variables instead of command-line flags. +func TestChocoBuildInfoFromEnvVars(t *testing.T) { + initChocoTest(t) + defer cleanTestsHomeEnv() + + buildName := tests.ChocoBuildName + "-envvars" + buildNumber := "7" + clientTestUtils.SetEnvAndAssert(t, "JFROG_CLI_BUILD_NAME", buildName) + clientTestUtils.SetEnvAndAssert(t, "JFROG_CLI_BUILD_NUMBER", buildNumber) + defer clientTestUtils.UnSetEnvAndAssert(t, "JFROG_CLI_BUILD_NAME") + defer clientTestUtils.UnSetEnvAndAssert(t, "JFROG_CLI_BUILD_NUMBER") + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + nupkgPath := packChocoPackage(t, "ChocoEnvVarPkg", "1.0.0") + require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo)) + + envVarBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber) + require.Len(t, envVarBuildInfo.Modules, 1) + assert.NotEmpty(t, envVarBuildInfo.Modules[0].Artifacts, + "build info must be collected from JFROG_CLI_BUILD_NAME/NUMBER alone") +} + +// TestChocoModuleOverride verifies that --module replaces the default ':' +// module id with a caller-chosen name. +func TestChocoModuleOverride(t *testing.T) { + initChocoTest(t) + defer cleanTestsHomeEnv() + + buildName := tests.ChocoBuildName + "-module-override" + buildNumber := "1" + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + nupkgPath := packChocoPackage(t, "ChocoModuleOverridePkg", "1.0.0") + require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo, + "--build-name="+buildName, "--build-number="+buildNumber, "--module=my-service")) + + moduleOverrideBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber) + require.Len(t, moduleOverrideBuildInfo.Modules, 1) + assert.Equal(t, "my-service", moduleOverrideBuildInfo.Modules[0].Id, + "--module must override the default ':' module id") +} + +// TestChocoCommandPropertyRedactsApiKey verifies that a Chocolatey API key passed through to the +// native client is never recorded in build info. +func TestChocoCommandPropertyRedactsApiKey(t *testing.T) { + initChocoTest(t) + defer cleanTestsHomeEnv() + + id, version := "ChocoRedactPkg", "1.0.0" + buildName := tests.ChocoBuildName + "-redact" + buildNumber := "1" + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + const secret = "super-secret-api-key" + sourceURL := serverDetails.ArtifactoryUrl + "api/nuget/" + tests.NugetLocalRepo + apiKey := chocoPushApiKey(t) + nupkgPath := packChocoPackage(t, id, version) + // An explicit source and API key suppress the credentials the command would otherwise inject, + // so the native push is driven entirely by these arguments. + require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo, + "--build-name="+buildName, "--build-number="+buildNumber, + "-s="+sourceURL, "-k="+apiKey)) + + redactedBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber) + require.Len(t, redactedBuildInfo.Modules, 1) + recordedCommand := getChocoCommandProperty(t, redactedBuildInfo.Modules[0]) + require.NotEmpty(t, recordedCommand) + assert.Contains(t, recordedCommand, "-k=***", "the API key must be redacted in build info") + assert.NotContains(t, recordedCommand, apiKey, "the real credential must never be recorded") +} + +// chocoPushApiKey builds the composite ':' key that Artifactory NuGet endpoints +// expect for authenticated pushes. +func chocoPushApiKey(t *testing.T) string { + t.Helper() + if serverDetails.AccessToken != "" { + return serverDetails.User + ":" + serverDetails.AccessToken + } + return serverDetails.User + ":" + serverDetails.Password +} + +// TestSetupChocoConfiguresSource covers the 'jf setup choco' happy path: the machine-wide +// Chocolatey source for the repository is created and authenticated. +func TestSetupChocoConfiguresSource(t *testing.T) { + initChocoTest(t) + defer cleanTestsHomeEnv() + + sourceName := chocoSourceName(t, tests.NugetVirtualRepo) + cleanupChocoSource(t, sourceName) + + require.NoError(t, runChoco(t, "setup", "choco", "--repo="+tests.NugetVirtualRepo), + "'jf setup choco' should configure the Chocolatey source") + + output, err := exec.Command("choco", "source", "list").CombinedOutput() + require.NoError(t, err, "'choco source list' failed: %s", string(output)) + assert.Contains(t, string(output), sourceName, "the JFrog Chocolatey source should be configured") + // The V2 NuGet endpoint is what Chocolatey speaks; a V3 URL would not work. + assert.Contains(t, string(output), "api/nuget/"+tests.NugetVirtualRepo, + "the source should point at the Artifactory NuGet V2 endpoint") +} + +// TestChocoInstallCollectsDependencies covers the 'jf choco install' happy path end to end: a +// package is packed, pushed, resolved from Artifactory and recorded as a build-info dependency. +func TestChocoInstallCollectsDependencies(t *testing.T) { + initChocoTest(t) + defer cleanTestsHomeEnv() + + id, version := "ChocoInstallPkg", "1.0.0" + buildName := tests.ChocoBuildName + "-install" + buildNumber := "1" + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + nupkgPath := packChocoPackage(t, id, version) + require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo)) + + sourceName := chocoSourceName(t, tests.NugetLocalRepo) + cleanupChocoSource(t, sourceName) + require.NoError(t, runChoco(t, "setup", "choco", "--repo="+tests.NugetLocalRepo)) + + cleanupChocoInstalledPackage(t, id) + requireChocoInstall(t, id, version, sourceName, buildName, buildNumber) + + installBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber) + require.Len(t, installBuildInfo.Modules, 1) + module := installBuildInfo.Modules[0] + assert.Equal(t, buildInfo.Nuget, module.Type) + require.NotEmpty(t, module.Dependencies, "the installed package must be recorded as a dependency") + dependency := module.Dependencies[0] + assert.Equal(t, id+":"+version, dependency.Id) + assert.Equal(t, "nupkg", dependency.Type) + assert.Equal(t, tests.NugetLocalRepo, dependency.Repository, + "--repo-resolve should be recorded as the resolution repository") + assert.Contains(t, getChocoCommandProperty(t, module), "install") +} + +// requireChocoInstall runs 'jf choco install' with a short retry, because a freshly pushed +// package is not immediately searchable through the NuGet endpoint. +func requireChocoInstall(t *testing.T, id, version, sourceName, buildName, buildNumber string) { + t.Helper() + args := []string{"choco", "install", id, "--version=" + version, "--repo-resolve=" + tests.NugetLocalRepo, + "--build-name=" + buildName, "--build-number=" + buildNumber, "-y", "-s=" + sourceName} + var lastErr error + for attempt := 0; attempt < 5; attempt++ { + if attempt > 0 { + time.Sleep(time.Duration(attempt) * 2 * time.Second) + } + if lastErr = runChoco(t, args...); lastErr == nil { + return + } + t.Logf("'jf choco install' attempt %d failed, retrying: %v", attempt+1, lastErr) + } + require.NoError(t, lastErr, "'jf choco install' should succeed once the pushed package is indexed") +} + +// --------------------------------------------------------------------------------------------- +// Regression tests for three behaviours Chocolatey has that the first implementation pass assumed +// away. Each was verified against Chocolatey's own documentation or source, and each failed before +// the accompanying build-info-go fix. +// --------------------------------------------------------------------------------------------- + +// TestChocoPackRespectsOutputDirectory covers 'choco pack --output-directory'. +// +// The flag exists (--out / --outdir / --outputdirectory / --output-directory), so snapshotting only +// the working directory finds nothing the pack produced. That failure is silent: the command +// succeeds, the package is on disk, and the build info simply has no artifacts. +func TestChocoPackRespectsOutputDirectory(t *testing.T) { + initChocoTest(t) + + const id, version = "JfChocoOutDir", "1.0.0" + buildName := tests.ChocoBuildName + "-outdir" + const buildNumber = "1" + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + packageDir, nuspecName := createChocoPackageSource(t, id, version) + outputDir := filepath.Join(packageDir, "build-output") + require.NoError(t, os.MkdirAll(outputDir, 0o700)) + + packChocoPackageIn(t, packageDir, nuspecName, + "--output-directory="+outputDir, + "--build-name="+buildName, "--build-number="+buildNumber) + + require.FileExists(t, filepath.Join(outputDir, id+"."+version+".nupkg"), + "'choco pack' should have written the package into --output-directory") + + publishedBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber) + require.Len(t, publishedBuildInfo.Modules, 1) + require.Len(t, publishedBuildInfo.Modules[0].Artifacts, 1, + "a package written to --output-directory must still be collected; collecting zero artifacts "+ + "here is the silent failure this test exists to catch") + assert.Equal(t, id+"."+version+".nupkg", publishedBuildInfo.Modules[0].Artifacts[0].Name) +} + +// TestChocoPushWithoutPositionalPath covers 'choco push' with no path argument. +// +// The path is optional: with exactly one .nupkg in the folder Chocolatey pushes it. Requiring an +// explicit positional means the package uploads but no artifact is recorded, so it is never +// stamped and never appears in the build info. +func TestChocoPushWithoutPositionalPath(t *testing.T) { + initChocoTest(t) + + const id, version = "JfChocoBarePush", "1.0.0" + buildName := tests.ChocoBuildName + "-bare-push" + const buildNumber = "1" + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + nupkgPath := packChocoPackage(t, id, version) + + workingDirectory, err := os.Getwd() + require.NoError(t, err) + defer clientTestUtils.ChangeDirWithCallback(t, workingDirectory, filepath.Dir(nupkgPath))() + + require.NoError(t, runChoco(t, "choco", "push", + "--repo="+tests.NugetLocalRepo, + "--build-name="+buildName, "--build-number="+buildNumber), + "'choco push' with no positional path must succeed") + + publishedBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber) + require.Len(t, publishedBuildInfo.Modules, 1) + require.Len(t, publishedBuildInfo.Modules[0].Artifacts, 1, + "a push with no positional path must still record the package Chocolatey found in the folder") + assert.Equal(t, id+"."+version+".nupkg", publishedBuildInfo.Modules[0].Artifacts[0].Name) + assertChocoArtifactExists(t, chocoArtifactPath(tests.NugetLocalRepo, id, version)) +} + +// TestChocoInstallRecordsVersionFromInstalledPackage is the end-to-end counterpart to +// resolveInstalledPackage's unit tests: Chocolatey stores lib\\.nupkg with no version in +// the file name, so a version read off that name is always empty and every dependency is dropped. +// Asserting the resolved version here proves the version came from the installed .nuspec. +func TestChocoInstallRecordsVersionFromInstalledPackage(t *testing.T) { + initChocoTest(t) + + const id, version = "JfChocoInstalledVersion", "3.4.5" + buildName := tests.ChocoBuildName + "-installed-version" + const buildNumber = "1" + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + cleanupChocoInstalledPackage(t, id) + + nupkgPath := packChocoPackage(t, id, version) + require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo)) + + sourceName := chocoSourceName(t, tests.NugetLocalRepo) + cleanupChocoSource(t, sourceName) + require.NoError(t, runChoco(t, "setup", "choco", "--repo="+tests.NugetLocalRepo)) + + // Deliberately no --version: a bare install resolves latest, so the recorded version cannot + // have come from the command line either. + requireChocoInstall(t, id, "", sourceName, buildName, buildNumber) + + publishedBuildInfo := getPublishedChocoBuildInfo(t, buildName, buildNumber) + require.Len(t, publishedBuildInfo.Modules, 1) + require.Len(t, publishedBuildInfo.Modules[0].Dependencies, 1) + assert.Equal(t, id+":"+version, publishedBuildInfo.Modules[0].Dependencies[0].Id, + "the installed version must be resolved from the package's .nuspec, not from its file name") +} + +// --------------------------------------------------------------------------------------------- +// Pass-through, statelessness and the platform gate. +// --------------------------------------------------------------------------------------------- + +// initChocoTestAnyPlatform gates only on the feature flag, for the two scenarios that must be +// observed where Chocolatey cannot run. It deliberately configures no JFrog server: the OS gate +// fires before any server interaction, and requiring one here would hide a regression that moved +// the gate behind server resolution. +func initChocoTestAnyPlatform(t *testing.T) { + t.Helper() + if !*tests.TestChoco { + t.Skip("Skipping Chocolatey test. To run Choco test add the '-test.choco=true' option.") + } +} + +// TestChocoNonWindowsGate asserts the command refuses to run off Windows, naming the detected OS +// instead of surfacing a bare "choco: executable file not found". +func TestChocoNonWindowsGate(t *testing.T) { + initChocoTestAnyPlatform(t) + if runtime.GOOS == "windows" { + t.Skip("The OS gate only rejects non-Windows hosts; nothing to assert on Windows.") + } + + err := runChoco(t, "choco", "install", "some-package") + require.Error(t, err, "'jf choco' must refuse to run on a non-Windows host") + assert.Contains(t, err.Error(), "Windows only") + assert.Containsf(t, err.Error(), runtime.GOOS, "the error must name the detected OS (%s)", runtime.GOOS) +} + +// TestChocoHelpWorksOnAllPlatforms asserts the gate lives in the command's Run(), not in its +// registration, so help stays reachable on the machines that cannot run the tool. +func TestChocoHelpWorksOnAllPlatforms(t *testing.T) { + initChocoTestAnyPlatform(t) + assert.NoError(t, runChoco(t, "choco", "--help"), "'jf choco --help' must work on every OS") +} + +// TestChocoPassThroughCollectsNoBuildInfo covers the subcommands that are not build events. The +// config-mutating ones matter most: 'source', 'apikey' and 'config' change machine state, and a +// wrapper whose contract is that it writes no configuration must not treat them as builds. +func TestChocoPassThroughCollectsNoBuildInfo(t *testing.T) { + initChocoTest(t) + + buildName := tests.ChocoBuildName + "-passthrough" + const buildNumber = "1" + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + for _, testCase := range []struct { + name string + args []string + }{ + {"list", []string{"choco", "list", "-r"}}, + {"outdated", []string{"choco", "outdated", "-r"}}, + {"source-list", []string{"choco", "source", "list", "-r"}}, + {"config-list", []string{"choco", "config", "list"}}, + {"feature-list", []string{"choco", "feature", "list"}}, + } { + t.Run(testCase.name, func(t *testing.T) { + args := append(testCase.args, "--build-name="+buildName, "--build-number="+buildNumber) + // The native exit code is passed through; a query returning non-zero is not this + // test's concern. What matters is that nothing was collected. + _ = runChoco(t, args...) + + _, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.Falsef(t, found, "'choco %s' is not a build event and must not produce build info", testCase.name) + }) + } +} + +// TestChocoPassThroughWithoutServerConfigured asserts a pass-through command does not require a +// configured JFrog server. Requiring one would make 'jf choco list' fail on any unconfigured +// machine, leaving the wrapper strictly worse than the tool it wraps. +func TestChocoPassThroughWithoutServerConfigured(t *testing.T) { + initChocoTest(t) + + restoreHomeDir := clientTestUtils.SetEnvWithCallbackAndAssert(t, coreutils.HomeDir, t.TempDir()) + defer restoreHomeDir() + + assert.NoError(t, runChoco(t, "choco", "--version"), + "pass-through must work with no JFrog server configured") +} + +// TestChocoDoesNotMutateChocolateyConfig covers the statelessness contract, and the clearest line +// between 'jf choco' and 'jf setup choco'. chocolatey.config is machine-wide, so an accidental +// write by the wrapper would change behaviour for every user on the machine. +func TestChocoDoesNotMutateChocolateyConfig(t *testing.T) { + initChocoTest(t) + + const id, version = "JfChocoStateless", "1.0.0" + buildName := tests.ChocoBuildName + "-stateless" + const buildNumber = "1" + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + sourcesBefore, err := exec.Command("choco", "source", "list", "-r").CombinedOutput() + require.NoError(t, err) + + packageDir, nuspecName := createChocoPackageSource(t, id, version) + packChocoPackageIn(t, packageDir, nuspecName, "--build-name="+buildName, "--build-number="+buildNumber) + + assert.NoDirExists(t, filepath.Join(packageDir, ".jfrog", "projects"), + "FlexPack is stateless: 'jf choco' must not create a .jfrog/projects directory") + + sourcesAfter, err := exec.Command("choco", "source", "list", "-r").CombinedOutput() + require.NoError(t, err) + assert.Equal(t, string(sourcesBefore), string(sourcesAfter), + "'jf choco' must not modify Chocolatey's machine-wide source list; that is 'jf setup choco's job") +} + +// TestChocoNoBuildFlagsCollectsNothing asserts that without both build coordinates the command is a +// transparent native run: the package is produced, and nothing is published. +func TestChocoNoBuildFlagsCollectsNothing(t *testing.T) { + initChocoTest(t) + + const id, version = "JfChocoNoFlags", "1.0.0" + nupkgPath := packChocoPackage(t, id, version) + assert.FileExists(t, nupkgPath, "the native pack must still run and produce the package") + + _, found, err := tests.GetBuildInfo(serverDetails, tests.ChocoBuildName+"-noflags", "1") + assert.NoError(t, err) + assert.False(t, found, "no build info may be published when the build flags are absent") +} + +// TestChocoPushToNonexistentRepoRejected asserts an unknown repository fails clearly rather than +// surfacing a raw Artifactory 404 from deep inside the push. +func TestChocoPushToNonexistentRepoRejected(t *testing.T) { + initChocoTest(t) + + nupkgPath := packChocoPackage(t, "JfChocoBadRepo", "1.0.0") + require.Error(t, pushChocoPackage(t, nupkgPath, "cli-choco-nonexistent-repo")) +} diff --git a/docs/buildtools/choco/help.go b/docs/buildtools/choco/help.go new file mode 100644 index 000000000..26d7d3671 --- /dev/null +++ b/docs/buildtools/choco/help.go @@ -0,0 +1,32 @@ +package choco + +var Usage = []string{"choco [command options]"} + +func GetDescription() string { + return "Run Chocolatey with optional JFrog build-info collection." +} + +func GetArguments() string { + return ` choco sub-command + Arguments and options for the native Chocolatey command.` +} + +func GetAIDescription() string { + return `Run a native Chocolatey command through JFrog. The command forwards Chocolatey arguments unchanged and optionally records build-info for pack, push, install, and upgrade. + +Prerequisites: +- Chocolatey installed on Windows. +- Run 'jf setup choco' to add authenticated Artifactory sources, or pass the native Chocolatey source/authentication options yourself. + +Examples: + $ jf choco install git -s=jfrt-acme.jfrog.io-choco-virtual --build-name=image --build-number=1 + $ jf choco push tool.1.0.0.nupkg -s=jfrt-acme.jfrog.io-choco-local --repo=choco-local --build-name=tool --build-number=1 + +Gotchas: +- Chocolatey runs on Windows only. +- Build-info is collected only when both '--build-name' and '--build-number' are given; supplying just one is an error. +- The native '-s' source and JFrog '--repo' must identify the same Artifactory endpoint for push build-info. +- Install and upgrade record the requested packages and their transitive dependencies, read from Chocolatey's lib directory. A dependency served by a Chocolatey special source (ruby, cygwin, python, windowsfeatures) never lands there, so it is reported as not found and left out of the build-info. +- In CI, set '--execution-timeout' explicitly. Chocolatey's default of 2700 seconds is mishandled by Chocolatey itself and becomes a five-hour timeout, so a hung install stalls the build instead of failing it. '--execution-timeout=2700' is enough to avoid this. +- This command never writes Chocolatey configuration. Use 'jf setup choco' for that.` +} diff --git a/docs/buildtools/setup/help.go b/docs/buildtools/setup/help.go index 0a8d7eaca..bc989c2f9 100644 --- a/docs/buildtools/setup/help.go +++ b/docs/buildtools/setup/help.go @@ -34,6 +34,7 @@ Not the same command as jf npm-config / jf mvn-config / jf pip-config, which loo Prerequisites: - A configured server (jf c add or jf login), or pass --url/--user/--password/--access-token directly. - The Artifactory repository name for the package manager (a virtual repo where supported). +- For Chocolatey, select a NuGet virtual, local, or remote repository when prompted. Setup creates a named native source ('jfrt--') for each repository, so use -s to choose a configured resolve or publish endpoint. Common patterns: $ jf setup npm @@ -49,6 +50,7 @@ Gotchas: - pnpm and npm can end up on different repositories without any warning. pnpm reads its own configuration first and ~/.npmrc only as a fallback, so a machine with no pnpm setup follows "jf setup npm", but once "jf setup pnpm" has run, a later "jf setup npm --repo b" moves npm alone and pnpm keeps resolving from the repository it was given. If both are in use, run "jf setup" for both. - maven and gradle do not need their client installed: their setup writes settings.xml and a Gradle init script directly, so it works on a machine that only has ./mvnw or ./gradlew. Every other package manager's setup runs its client. helm additionally needs 3.8.0 or newer, because its login targets an OCI registry. - docker/podman authenticate directly against the registry and skip the repository prompt entirely (no --repo needed); helm still goes through repository selection like the other package managers even though its login step doesn't end up using the repo name. +- Chocolatey runs on Windows only and updates machine-wide chocolatey.config, so use an elevated shell. Re-running 'jf setup choco' for one repository refreshes only that repository's 'jfrt--' source; it preserves other Chocolatey sources. Related: jf npm-config, jf go-config, jf pip-config, jf c add` } diff --git a/go.mod b/go.mod index 77e7b901e..5d782a3c3 100644 --- a/go.mod +++ b/go.mod @@ -19,11 +19,11 @@ require ( github.com/buger/jsonparser v1.3.0 github.com/gocarina/gocsv v0.0.0-20260607070740-0735908c6461 github.com/jfrog/archiver/v3 v3.6.4 - github.com/jfrog/build-info-go v1.13.1-0.20260902120316-b325d342b210 + github.com/jfrog/build-info-go v1.13.1-0.20260910024709-07236790e531 github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a - github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260902124259-4c1979144d2f - github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260831061529-c6dd293bccca + github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910024932-d0a38c4106d5 + github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910024742-70e7670146fc github.com/jfrog/jfrog-cli-evidence v0.11.1-0.20260824063609-79b735ec565e github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab github.com/jfrog/jfrog-cli-security v1.36.0 diff --git a/go.sum b/go.sum index d93de0b5f..30d6321e9 100644 --- a/go.sum +++ b/go.sum @@ -390,8 +390,8 @@ github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= github.com/jfrog/archiver/v3 v3.6.4 h1:qHAWCLKwo3+ocHNNoWzGZ8ESl8QQk/lR3W09Pt+ROvE= github.com/jfrog/archiver/v3 v3.6.4/go.mod h1:5V9l+Fte30Y4qe9dUOAd3yNTf8lmtVNuhKNrvI8PMhg= -github.com/jfrog/build-info-go v1.13.1-0.20260902120316-b325d342b210 h1:u1Ijj6fOX9hCzz27L3IpqFqdSsjOlg6Td7URtmNFVR8= -github.com/jfrog/build-info-go v1.13.1-0.20260902120316-b325d342b210/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE= +github.com/jfrog/build-info-go v1.13.1-0.20260910024709-07236790e531 h1:s61qB4SJ+K624OuiuLoJMq2+YRhuukTyhMqMs7owf30= +github.com/jfrog/build-info-go v1.13.1-0.20260910024709-07236790e531/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE= github.com/jfrog/froggit-go v1.23.1 h1:4wmaHeuptxVINbovMaeITzVhi3+VQoc/FFIjF4axzu0= github.com/jfrog/froggit-go v1.23.1/go.mod h1:wRDryqyp3oe+eHgME2mpnEQmO8XBECIPagFwj0nHmdI= github.com/jfrog/go-mockhttp v0.3.1 h1:/wac8v4GMZx62viZmv4wazB5GNKs+GxawuS1u3maJH8= @@ -402,10 +402,10 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a h1:7GhcPfi+k9oOAJdCsKWjymnqH0e7DQZ1soVhbneYnGY= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a/go.mod h1:p8yLtbmCxxQucIbLZKnWu0F+EDtj6NLXbRQCEK/nb6o= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260902124259-4c1979144d2f h1:bsURaQbMymVB4u6R+CWxMho6zy4/kICq9eNrRio6WfI= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260902124259-4c1979144d2f/go.mod h1:Oiq1Gc1RtmaDBmpVMQuG3XlmRAWXkA5bfRraHLwbZF0= -github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260831061529-c6dd293bccca h1:/Ox4k56Pbiow4qbkNrBOmgcnAHwIBjZOsJmS7dURJng= -github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260831061529-c6dd293bccca/go.mod h1:vuARjRZopsCqVcZmWzCgw5Pr9QD1FWvwFxijV4bvJJI= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910024932-d0a38c4106d5 h1:EIc7Zk5qay7jexwboa6R1QTCT9x1/G/zGBLn/z/tdQw= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910024932-d0a38c4106d5/go.mod h1:VQq+fSKV4mTjouNROQWK0Q069cFQBpph+8oDVVgTBBk= +github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910024742-70e7670146fc h1:K/rP18TOhbFTzxq6rEhSMF4Y7IFRzSuhoy6BC9ggG2g= +github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910024742-70e7670146fc/go.mod h1:vuARjRZopsCqVcZmWzCgw5Pr9QD1FWvwFxijV4bvJJI= github.com/jfrog/jfrog-cli-evidence v0.11.1-0.20260824063609-79b735ec565e h1:+QYbewvK+PZKbfPpxYmy0bewhqMFtJPk/tUbCICjf8U= github.com/jfrog/jfrog-cli-evidence v0.11.1-0.20260824063609-79b735ec565e/go.mod h1:I83k7IH/cmMh00LyZOp13jctj8/pPKVHSAlu7GMJgwI= github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab h1:Zn/qB8LYhSu82YDtbqXwErN1RPHTHe/a3gQY6Ti/OBE= diff --git a/main_test.go b/main_test.go index 640018f10..3d6c32e7a 100644 --- a/main_test.go +++ b/main_test.go @@ -77,7 +77,7 @@ func setupIntegrationTests() { InitArtifactoryTests() } - if *tests.TestNpm || *tests.TestPnpm || *tests.TestCargo || *tests.TestGradle || *tests.TestMaven || *tests.TestGo || *tests.TestNuget || *tests.TestPip || *tests.TestPipenv || *tests.TestPoetry || *tests.TestConan || *tests.TestHelm || *tests.TestUv || *tests.TestNix || *tests.TestApt || *tests.TestAlpine || *tests.TestApm || *tests.TestRuby || (*tests.TestArtifactory && !*tests.TestArtifactoryProxy) || *tests.TestArtifactoryProject { + if *tests.TestNpm || *tests.TestPnpm || *tests.TestCargo || *tests.TestGradle || *tests.TestMaven || *tests.TestGo || *tests.TestNuget || *tests.TestChoco || *tests.TestPip || *tests.TestPipenv || *tests.TestPoetry || *tests.TestConan || *tests.TestHelm || *tests.TestUv || *tests.TestNix || *tests.TestApt || *tests.TestAlpine || *tests.TestApm || *tests.TestRuby || (*tests.TestArtifactory && !*tests.TestArtifactoryProxy) || *tests.TestArtifactoryProject { InitBuildToolsTests() } if *tests.TestDocker || *tests.TestPodman || *tests.TestDockerScan { @@ -125,7 +125,7 @@ func tearDownIntegrationTests() { if (*tests.TestArtifactory && !*tests.TestArtifactoryProxy) || *tests.TestArtifactoryProject { CleanArtifactoryTests() } - if *tests.TestNpm || *tests.TestPnpm || *tests.TestGradle || *tests.TestMaven || *tests.TestGo || *tests.TestNuget || *tests.TestPip || *tests.TestPipenv || *tests.TestPoetry || *tests.TestConan || *tests.TestHelm || *tests.TestNix || *tests.TestCargo || *tests.TestApt || *tests.TestAlpine || *tests.TestApm || *tests.TestRuby || *tests.TestDocker || *tests.TestPodman || *tests.TestDockerScan || (*tests.TestArtifactory && !*tests.TestArtifactoryProxy) || *tests.TestArtifactoryProject { + if *tests.TestNpm || *tests.TestPnpm || *tests.TestGradle || *tests.TestMaven || *tests.TestGo || *tests.TestNuget || *tests.TestChoco || *tests.TestPip || *tests.TestPipenv || *tests.TestPoetry || *tests.TestConan || *tests.TestHelm || *tests.TestNix || *tests.TestCargo || *tests.TestApt || *tests.TestAlpine || *tests.TestApm || *tests.TestRuby || *tests.TestDocker || *tests.TestPodman || *tests.TestDockerScan || (*tests.TestArtifactory && !*tests.TestArtifactoryProxy) || *tests.TestArtifactoryProject { CleanBuildToolsTests() } if *tests.TestDistribution { diff --git a/utils/cliutils/commandsflags.go b/utils/cliutils/commandsflags.go index 131210556..2f498ca84 100644 --- a/utils/cliutils/commandsflags.go +++ b/utils/cliutils/commandsflags.go @@ -64,6 +64,7 @@ const ( Yarn = "yarn" NugetConfig = "nuget-config" Nuget = "nuget" + Choco = "choco" Dotnet = "dotnet" DotnetConfig = "dotnet-config" Go = "go" @@ -2259,6 +2260,9 @@ var commandFlags = map[string][]string{ Nuget: { BuildName, BuildNumber, module, Project, allowInsecureConnections, serverId, repoResolve, repo, nugetV2, }, + Choco: { + BuildName, BuildNumber, module, Project, serverId, repoResolve, repo, + }, DotnetConfig: { global, serverIdResolve, repoResolve, nugetV2, }, diff --git a/utils/tests/consts.go b/utils/tests/consts.go index 12f9928f2..a6875cedf 100644 --- a/utils/tests/consts.go +++ b/utils/tests/consts.go @@ -292,6 +292,7 @@ var ( PnpmBuildName = "cli-pnpm-build" YarnBuildName = "cli-yarn-build" NuGetBuildName = "cli-nuget-build" + ChocoBuildName = "cli-choco-build" PipBuildName = "cli-pip-build" PipenvBuildName = "cli-pipenv-build" PoetryBuildName = "cli-poetry-build" diff --git a/utils/tests/utils.go b/utils/tests/utils.go index f4acd5408..915601f90 100644 --- a/utils/tests/utils.go +++ b/utils/tests/utils.go @@ -66,6 +66,7 @@ var ( TestGradle *bool TestMaven *bool TestNuget *bool + TestChoco *bool TestPip *bool TestPipenv *bool TestPoetry *bool @@ -141,6 +142,7 @@ func init() { TestGradle = flag.Bool("test.gradle", false, "Test Gradle") TestMaven = flag.Bool("test.maven", false, "Test Maven") TestNuget = flag.Bool("test.nuget", false, "Test Nuget") + TestChoco = flag.Bool("test.choco", false, "Test Chocolatey") TestPip = flag.Bool("test.pip", false, "Test Pip") TestPipenv = flag.Bool("test.pipenv", false, "Test Pipenv") TestPoetry = flag.Bool("test.poetry", false, "Test Poetry") @@ -491,6 +493,7 @@ func GetNonVirtualRepositories() map[*string]string { TestNpm: {&NpmRepo, &NpmScopedRepo, &NpmRemoteRepo}, TestPnpm: {&NpmRepo, &NpmScopedRepo, &NpmRemoteRepo}, TestNuget: {&NugetRemoteRepo, &NugetLocalRepo}, + TestChoco: {&NugetRemoteRepo, &NugetLocalRepo}, TestPip: {&PypiLocalRepo, &PypiRemoteRepo}, TestPipenv: {&PipenvRemoteRepo}, TestPoetry: {&PoetryLocalRepo, &PoetryRemoteRepo}, @@ -530,6 +533,7 @@ func GetVirtualRepositories() map[*string]string { TestNpm: {}, TestPnpm: {}, TestNuget: {&NugetVirtualRepo}, + TestChoco: {&NugetVirtualRepo}, TestPip: {&PypiVirtualRepo}, TestPipenv: {&PipenvVirtualRepo}, TestPoetry: {&PoetryVirtualRepo}, @@ -578,6 +582,7 @@ func GetBuildNames() []string { TestNpm: {&NpmBuildName, &YarnBuildName}, TestPnpm: {&PnpmBuildName}, TestNuget: {&NuGetBuildName}, + TestChoco: {&ChocoBuildName}, TestPip: {&PipBuildName}, TestPipenv: {&PipenvBuildName}, TestPoetry: {&PoetryBuildName}, @@ -788,6 +793,7 @@ func AddTimestampToGlobalVars() { YarnBuildName += uniqueSuffix MvnBuildName += uniqueSuffix NuGetBuildName += uniqueSuffix + ChocoBuildName += uniqueSuffix PipBuildName += uniqueSuffix PipenvBuildName += uniqueSuffix PoetryBuildName += uniqueSuffix From 3efc39d724068332a3c552fc7a2f316b2a7fae5e Mon Sep 17 00:00:00 2001 From: Bhanu Reddy Date: Thu, 10 Sep 2026 09:05:02 +0530 Subject: [PATCH 2/5] RTECO-2003: re-pin build-info-go and jfrog-cli-artifactory Picks up the lint fixes in both, so this branch builds against the same commits CI runs there. Co-Authored-By: Claude Opus 5 --- go.mod | 6 ++---- go.sum | 18 ++++-------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 0fcfa1f2d..bfe13712a 100644 --- a/go.mod +++ b/go.mod @@ -19,10 +19,10 @@ require ( github.com/buger/jsonparser v1.3.0 github.com/gocarina/gocsv v0.0.0-20260607070740-0735908c6461 github.com/jfrog/archiver/v3 v3.6.5 - github.com/jfrog/build-info-go v1.13.1-0.20260910024709-07236790e531 + github.com/jfrog/build-info-go v1.13.1-0.20260910033343-40d564cbd202 github.com/jfrog/gofrog v1.7.7 github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a - github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910032749-78e4624125f6 + github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910033353-fa3b9e6c6596 github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910032555-f560dade8e04 github.com/jfrog/jfrog-cli-evidence v0.11.1-0.20260824063609-79b735ec565e github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab @@ -203,7 +203,6 @@ require ( github.com/transparency-dev/formats v0.1.1 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/ulikunitz/xz v0.5.16 // indirect - github.com/urfave/cli/v2 v2.27.7 // indirect github.com/vbauerster/cupwriter v0.0.4 // indirect github.com/vbauerster/mpb/v8 v8.14.0 // indirect github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect @@ -213,7 +212,6 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect diff --git a/go.sum b/go.sum index 3a5e4fc0e..96b678516 100644 --- a/go.sum +++ b/go.sum @@ -390,10 +390,8 @@ github.com/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= github.com/jfrog/archiver/v3 v3.6.5 h1:AiNXJoe8jYDOtyykfVuwh26aM4rk/ei+YzBpfBukdzU= github.com/jfrog/archiver/v3 v3.6.5/go.mod h1:5V9l+Fte30Y4qe9dUOAd3yNTf8lmtVNuhKNrvI8PMhg= -github.com/jfrog/build-info-go v1.13.1-0.20260902120316-b325d342b210 h1:u1Ijj6fOX9hCzz27L3IpqFqdSsjOlg6Td7URtmNFVR8= -github.com/jfrog/build-info-go v1.13.1-0.20260902120316-b325d342b210/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE= -github.com/jfrog/build-info-go v1.13.1-0.20260910024709-07236790e531 h1:s61qB4SJ+K624OuiuLoJMq2+YRhuukTyhMqMs7owf30= -github.com/jfrog/build-info-go v1.13.1-0.20260910024709-07236790e531/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE= +github.com/jfrog/build-info-go v1.13.1-0.20260910033343-40d564cbd202 h1:XD2+J0bYh6ZaEJYV6uRvfrrDIFFzdCGSEnp4CKvrJbA= +github.com/jfrog/build-info-go v1.13.1-0.20260910033343-40d564cbd202/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE= github.com/jfrog/froggit-go v1.23.1 h1:4wmaHeuptxVINbovMaeITzVhi3+VQoc/FFIjF4axzu0= github.com/jfrog/froggit-go v1.23.1/go.mod h1:wRDryqyp3oe+eHgME2mpnEQmO8XBECIPagFwj0nHmdI= github.com/jfrog/go-mockhttp v0.3.1 h1:/wac8v4GMZx62viZmv4wazB5GNKs+GxawuS1u3maJH8= @@ -404,12 +402,8 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a h1:7GhcPfi+k9oOAJdCsKWjymnqH0e7DQZ1soVhbneYnGY= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a/go.mod h1:p8yLtbmCxxQucIbLZKnWu0F+EDtj6NLXbRQCEK/nb6o= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260908123315-10cfe381853a h1:Bp/vFrvUHdy4NEKcE3cZ5Dkz4S3IJtSIOv8PtUPbrTQ= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260908123315-10cfe381853a/go.mod h1:Oiq1Gc1RtmaDBmpVMQuG3XlmRAWXkA5bfRraHLwbZF0= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910032749-78e4624125f6 h1:92H2QFE2QE0sPdvZ8/yXqQtkpKafBlBKuAXxLg8s1jg= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910032749-78e4624125f6/go.mod h1:n4ulUoscSbBbRZhOaNU+4f9Tr728ZD3vMY544SzwP7w= -github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260909093400-32a7208a18bd h1:tfC6CtOpqWoU/1V4ymBL0GnhM2kbq5JTGSaAk9eOybg= -github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260909093400-32a7208a18bd/go.mod h1:SwV+DNLBnWLxBeNeZpJk+xxAbqJ8ywq1va56up+AGu4= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910033353-fa3b9e6c6596 h1:8Qtqa7leWVo9WfAyNTpMiHmalSIsw9JZcqaVFwrEKCA= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910033353-fa3b9e6c6596/go.mod h1:n4ulUoscSbBbRZhOaNU+4f9Tr728ZD3vMY544SzwP7w= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910032555-f560dade8e04 h1:2SZDAl1CRqNaXB6iecU1Z+aPiFLLXNS60seZgZPY0hI= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910032555-f560dade8e04/go.mod h1:SwV+DNLBnWLxBeNeZpJk+xxAbqJ8ywq1va56up+AGu4= github.com/jfrog/jfrog-cli-evidence v0.11.1-0.20260824063609-79b735ec565e h1:+QYbewvK+PZKbfPpxYmy0bewhqMFtJPk/tUbCICjf8U= @@ -665,8 +659,6 @@ github.com/ulikunitz/xz v0.5.16 h1:ld6NyySjx5lowVKwJvMRLnW5nxKX/xnpSiFYZ/Lxur0= github.com/ulikunitz/xz v0.5.16/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw= github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ= github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo= -github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= -github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/vbauerster/cupwriter v0.0.4 h1:9sBPe0uXWLZuWQU5lqVbhyFlxX6c09asST/YfatFAys= github.com/vbauerster/cupwriter v0.0.4/go.mod h1:IFyzS6Xis5dnBH/rdAhrnuzg3c+KkUqEN6yE8lhJlDw= github.com/vbauerster/mpb/v8 v8.14.0 h1:55SR80dptMfASxIG/oCEkBXgBhxeSu4GrVsjl16oKmA= @@ -688,8 +680,6 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg= -github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= From 6f1c3a6e8fff53471dcd527ebdeb729e9e83fbcc Mon Sep 17 00:00:00 2001 From: Bhanu Reddy Date: Thu, 10 Sep 2026 09:10:02 +0530 Subject: [PATCH 3/5] RTECO-2003: fix three linter findings in the choco tests - Build the pass-through arg list as a fresh slice. Appending onto testCase.args is free to reuse that slice backing array, which would leak the build flags into the next case. - Drop a dead const left over from an earlier draft of the redaction test, which asserts against the real credential rather than a placeholder. - Assert the published artifact in the installed-version test. That confirms the fixture really is at 3.4.5 before the recorded version is checked, so a wrong version cannot be blamed on a bad fixture -- and it gives chocoArtifactPath a caller whose version is not 1.0.0. Co-Authored-By: Claude Opus 5 --- choco_test.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/choco_test.go b/choco_test.go index 086d72836..405e862f1 100644 --- a/choco_test.go +++ b/choco_test.go @@ -441,7 +441,6 @@ func TestChocoCommandPropertyRedactsApiKey(t *testing.T) { buildNumber := "1" defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) - const secret = "super-secret-api-key" sourceURL := serverDetails.ArtifactoryUrl + "api/nuget/" + tests.NugetLocalRepo apiKey := chocoPushApiKey(t) nupkgPath := packChocoPackage(t, id, version) @@ -627,6 +626,9 @@ func TestChocoInstallRecordsVersionFromInstalledPackage(t *testing.T) { nupkgPath := packChocoPackage(t, id, version) require.NoError(t, pushChocoPackage(t, nupkgPath, tests.NugetLocalRepo)) + // Confirm the fixture really is published at 3.4.5 before asserting what the install recorded, + // so a wrong recorded version cannot be blamed on a bad fixture. + assertChocoArtifactExists(t, chocoArtifactPath(tests.NugetLocalRepo, id, version)) sourceName := chocoSourceName(t, tests.NugetLocalRepo) cleanupChocoSource(t, sourceName) @@ -700,7 +702,11 @@ func TestChocoPassThroughCollectsNoBuildInfo(t *testing.T) { {"feature-list", []string{"choco", "feature", "list"}}, } { t.Run(testCase.name, func(t *testing.T) { - args := append(testCase.args, "--build-name="+buildName, "--build-number="+buildNumber) + // Built as a fresh slice rather than appended onto testCase.args, which would be free + // to reuse that slice's backing array and leak the build flags into the next case. + args := make([]string, 0, len(testCase.args)+2) + args = append(args, testCase.args...) + args = append(args, "--build-name="+buildName, "--build-number="+buildNumber) // The native exit code is passed through; a query returning non-zero is not this // test's concern. What matters is that nothing was collected. _ = runChoco(t, args...) From 7dba2783f76f83a2ef76f8a906be2d647937bace Mon Sep 17 00:00:00 2001 From: Bhanu Reddy Date: Thu, 10 Sep 2026 09:12:36 +0530 Subject: [PATCH 4/5] RTECO-2003: bump x/crypto and re-pin jfrog-cli-artifactory Same three high CVEs Frogbot flagged on the artifactory PR, cleared the same way by moving to x/crypto v0.56.0 before this branch inherits them. Co-Authored-By: Claude Opus 5 --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index bfe13712a..f139c368a 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/jfrog/build-info-go v1.13.1-0.20260910033343-40d564cbd202 github.com/jfrog/gofrog v1.7.7 github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a - github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910033353-fa3b9e6c6596 + github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910034125-49a7b708ccfe github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910032555-f560dade8e04 github.com/jfrog/jfrog-cli-evidence v0.11.1-0.20260824063609-79b735ec565e github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab @@ -221,14 +221,14 @@ require ( go.opentelemetry.io/otel/trace v1.45.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/crypto v0.54.0 // indirect + golang.org/x/crypto v0.56.0 // indirect golang.org/x/mod v0.38.0 // indirect golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/term v0.45.0 // indirect - golang.org/x/text v0.40.0 // indirect + golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect diff --git a/go.sum b/go.sum index 96b678516..eaefa4678 100644 --- a/go.sum +++ b/go.sum @@ -402,8 +402,8 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a h1:7GhcPfi+k9oOAJdCsKWjymnqH0e7DQZ1soVhbneYnGY= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a/go.mod h1:p8yLtbmCxxQucIbLZKnWu0F+EDtj6NLXbRQCEK/nb6o= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910033353-fa3b9e6c6596 h1:8Qtqa7leWVo9WfAyNTpMiHmalSIsw9JZcqaVFwrEKCA= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910033353-fa3b9e6c6596/go.mod h1:n4ulUoscSbBbRZhOaNU+4f9Tr728ZD3vMY544SzwP7w= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910034125-49a7b708ccfe h1:HlixDnldQs0St711yrvLOdTHpWTzIaYH5qO3ZilwC8g= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910034125-49a7b708ccfe/go.mod h1:liZXAaG1GXOdF0LsZ3+bWRjV8vbX2IDl/2SmzR9cdtM= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910032555-f560dade8e04 h1:2SZDAl1CRqNaXB6iecU1Z+aPiFLLXNS60seZgZPY0hI= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910032555-f560dade8e04/go.mod h1:SwV+DNLBnWLxBeNeZpJk+xxAbqJ8ywq1va56up+AGu4= github.com/jfrog/jfrog-cli-evidence v0.11.1-0.20260824063609-79b735ec565e h1:+QYbewvK+PZKbfPpxYmy0bewhqMFtJPk/tUbCICjf8U= @@ -758,8 +758,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= -golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= +golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= golang.org/x/exp v0.0.0-20260727155853-b88d891fe743 h1:ex206bKw+v3K0dm3andkrIF+ijyQKJG1pLgwQ2PYdQM= golang.org/x/exp v0.0.0-20260727155853-b88d891fe743/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -834,8 +834,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 6d12502924268337bdf0ec58b5139300a46212fd Mon Sep 17 00:00:00 2001 From: Bhanu Reddy Date: Thu, 10 Sep 2026 09:22:40 +0530 Subject: [PATCH 5/5] RTECO-2003: bump x/mod and re-pin jfrog-cli-artifactory Same two high CVEs Frogbot flagged on the artifactory PR after the x/crypto bump, cleared the same way before this branch inherits them. Co-Authored-By: Claude Opus 5 --- go.mod | 4 ++-- go.sum | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index f139c368a..f9a664c7f 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/jfrog/build-info-go v1.13.1-0.20260910033343-40d564cbd202 github.com/jfrog/gofrog v1.7.7 github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a - github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910034125-49a7b708ccfe + github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910035002-9e5eaa824782 github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910032555-f560dade8e04 github.com/jfrog/jfrog-cli-evidence v0.11.1-0.20260824063609-79b735ec565e github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab @@ -222,7 +222,7 @@ require ( go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/crypto v0.56.0 // indirect - golang.org/x/mod v0.38.0 // indirect + golang.org/x/mod v0.40.0 // indirect golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.22.0 // indirect diff --git a/go.sum b/go.sum index eaefa4678..648984699 100644 --- a/go.sum +++ b/go.sum @@ -402,8 +402,8 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a h1:7GhcPfi+k9oOAJdCsKWjymnqH0e7DQZ1soVhbneYnGY= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260820134442-c8629258ff3a/go.mod h1:p8yLtbmCxxQucIbLZKnWu0F+EDtj6NLXbRQCEK/nb6o= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910034125-49a7b708ccfe h1:HlixDnldQs0St711yrvLOdTHpWTzIaYH5qO3ZilwC8g= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910034125-49a7b708ccfe/go.mod h1:liZXAaG1GXOdF0LsZ3+bWRjV8vbX2IDl/2SmzR9cdtM= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910035002-9e5eaa824782 h1:fgHYLhpVRQFYd8Uoo9Lv9lfMMwxr7flph8v7m6ulzZY= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260910035002-9e5eaa824782/go.mod h1:rCQHDC+q2hak/Jerz5ptrxCK3P+ONqFnJRW6eX0yNXg= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910032555-f560dade8e04 h1:2SZDAl1CRqNaXB6iecU1Z+aPiFLLXNS60seZgZPY0hI= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260910032555-f560dade8e04/go.mod h1:SwV+DNLBnWLxBeNeZpJk+xxAbqJ8ywq1va56up+AGu4= github.com/jfrog/jfrog-cli-evidence v0.11.1-0.20260824063609-79b735ec565e h1:+QYbewvK+PZKbfPpxYmy0bewhqMFtJPk/tUbCICjf8U= @@ -764,8 +764,8 @@ golang.org/x/exp v0.0.0-20260727155853-b88d891fe743 h1:ex206bKw+v3K0dm3andkrIF+i golang.org/x/exp v0.0.0-20260727155853-b88d891fe743/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= -golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= +golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -842,8 +842,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=