From db5e2a49cb5c9ce9933f642901a338c60fd792b0 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Sun, 19 Jul 2026 20:59:27 +0530 Subject: [PATCH 1/7] RTECO-945: Wire jf apk commands and e2e tests - buildtools/cli.go: register jf apk command dispatcher (ApkCmd) - commandsflags.go: add Alpine APK flag definitions - apk_test.go: e2e tests for jf apk add/upgrade/upload/update/info/search - apkTests.yml: CI workflow running APK tests on Alpine linux/amd64 - packagealias.go: register apk package type alias - go.mod: bump jfrog-cli-artifactory to 944101b (Alpine APK support) Co-authored-by: Cursor --- .github/workflows/apkTests.yml | 36 +- apk_test.go | 1270 ++++++++++++++++++++++++++++ buildtools/cli.go | 123 +++ docs/buildtools/apkcommand/help.go | 75 ++ go.mod | 8 +- go.sum | 12 +- packagealias/packagealias.go | 1 + utils/cliutils/commandsflags.go | 16 + 8 files changed, 1519 insertions(+), 22 deletions(-) create mode 100644 apk_test.go create mode 100644 docs/buildtools/apkcommand/help.go diff --git a/.github/workflows/apkTests.yml b/.github/workflows/apkTests.yml index ede78c5fe..783c89696 100644 --- a/.github/workflows/apkTests.yml +++ b/.github/workflows/apkTests.yml @@ -80,16 +80,28 @@ jobs: RTLIC: ${{ secrets.RTLIC }} RT_CONNECTION_TIMEOUT_SECONDS: '1200' - # Run on the host runner with the host Go toolchain (1.26+). - # Tests that require the native `apk` binary skip themselves via - # apkAvailable() when apk is not on PATH (Ubuntu runner). - # The Alpine chroot approach will be re-enabled once the Go version - # constraint in go.mod aligns with Alpine's packaged Go release. - - name: Run Alpine APK tests - run: | - go test -v github.com/jfrog/jfrog-cli --timeout 0 --test.alpine \ - ${{ inputs.jfrog_url != '' && format('--jfrog.url={0} --jfrog.adminToken={1}', inputs.jfrog_url, inputs.jfrog_admin_token) || '' }} \ - ${{ inputs.jfrog_user != '' && format('--jfrog.user={0}', inputs.jfrog_user) || '' }} \ - ${{ inputs.jfrog_password != '' && format('--jfrog.password={0}', inputs.jfrog_password) || '' }} + # Step 1: Compile the test binary on the host runner using Go 1.26+. + # Alpine's packaged Go does not yet reach 1.26, so we cross-compile a + # static Linux/amd64 binary here and run it inside the Alpine container. + - name: Build test binary + run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go test -c -o apk.test . + + # Step 2: Run the pre-built binary inside the target Alpine container. + # --network host lets the test binary reach Artifactory on localhost. + - name: Run Alpine APK tests (alpine:${{ matrix.alpine-version }}) env: - CGO_ENABLED: "0" + ALPINE_VER: ${{ matrix.alpine-version }} + run: | + docker run --rm \ + --network host \ + -v "${{ github.workspace }}":/workspace \ + -w /workspace \ + "alpine:${ALPINE_VER#v}" \ + ./apk.test \ + -test.v \ + -test.timeout 0 \ + -test.run TestApk \ + -test.alpine \ + ${{ inputs.jfrog_url != '' && format('-jfrog.url={0} -jfrog.adminToken={1}', inputs.jfrog_url, inputs.jfrog_admin_token) || '' }} \ + ${{ inputs.jfrog_user != '' && format('-jfrog.user={0}', inputs.jfrog_user) || '' }} \ + ${{ inputs.jfrog_password != '' && format('-jfrog.password={0}', inputs.jfrog_password) || '' }} diff --git a/apk_test.go b/apk_test.go new file mode 100644 index 000000000..8ebe2967f --- /dev/null +++ b/apk_test.go @@ -0,0 +1,1270 @@ +package main + +import ( + "crypto/sha256" + "fmt" + "io" + "os" + "os/exec" + "strings" + "testing" + + buildinfo "github.com/jfrog/build-info-go/entities" + "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/generic" + artUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + "github.com/jfrog/jfrog-cli-core/v2/common/spec" + coreutils "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + coretests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" + clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" + + "github.com/jfrog/jfrog-cli/inttestutils" + "github.com/jfrog/jfrog-cli/utils/tests" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// ==================== Initialization ==================== + +func initApkTest(t *testing.T) { + if !*tests.TestAlpine { + t.Skip("Skipping Alpine APK test. To run Alpine APK tests add the '-test.alpine=true' option.") + } + require.True(t, isRepoExist(tests.AlpineLocalRepo), "APK test local repository doesn't exist.") + require.True(t, isRepoExist(tests.AlpineVirtualRepo), "APK test virtual repository doesn't exist.") +} + +// apkAvailable returns true when the `apk` binary is present on this machine. +// Tests that actually invoke `apk` must call t.Skip when this returns false. +func apkAvailable() bool { + _, err := exec.LookPath("apk") + return err == nil +} + +// computeFileSHA256 returns the hex-encoded SHA256 digest of the file at path. +func computeFileSHA256(t *testing.T, path string) string { + t.Helper() + f, err := os.Open(path) + require.NoError(t, err, "open file for SHA256: %s", path) + defer func() { require.NoError(t, f.Close()) }() + h := sha256.New() + _, err = io.Copy(h, f) + require.NoError(t, err, "compute SHA256 for: %s", path) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +// ==================== jf apk add (build info collection) ==================== + +// TestApkAdd_BasicBuildInfo verifies that `jf apk add` records build-info +// dependencies for a single explicitly requested package. +func TestApkAdd_BasicBuildInfo(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + buildName := tests.AlpineBuildName + "-add-basic" + buildNumber := "1" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "curl", + "--build-name="+buildName, "--build-number="+buildNumber, + "--repo="+tests.AlpineVirtualRepo) + if err != nil { + t.Skipf("jf apk add failed (apk system command unavailable or repo not configured): %v", err) + } + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found, "build-info should be published after jf apk add") + + if found { + bi := publishedBuildInfo.BuildInfo + require.Len(t, bi.Modules, 1) + assert.Equal(t, buildinfo.Alpine, bi.Modules[0].Type) + assert.GreaterOrEqual(t, len(bi.Modules[0].Dependencies), 1, + "curl should produce at least 1 dependency (curl itself + transitive)") + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// TestApkAdd_MultiplePackages verifies build-info when installing more than one +// package in a single invocation. +func TestApkAdd_MultiplePackages(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + buildName := tests.AlpineBuildName + "-add-multi" + buildNumber := "1" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "wget", "jq", + "--build-name="+buildName, "--build-number="+buildNumber, + "--repo="+tests.AlpineVirtualRepo) + if err != nil { + t.Skipf("jf apk add failed: %v", err) + } + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found) + + if found && len(publishedBuildInfo.BuildInfo.Modules) > 0 { + deps := publishedBuildInfo.BuildInfo.Modules[0].Dependencies + // wget + jq each pull in multiple transitive deps; at minimum 2 direct ones + assert.GreaterOrEqual(t, len(deps), 2, + "wget + jq should produce at least 2 direct dependencies") + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// TestApkAdd_NoBuildFlags verifies that `jf apk add` without --build-name / --build-number +// still runs natively without a panic or crash. +func TestApkAdd_NoBuildFlags(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "busybox") + if err != nil { + // busybox may already be installed; that's fine. + t.Logf("jf apk add busybox (no build flags): %v", err) + } +} + +// TestApkAdd_BuildNameOnly verifies that supplying only --build-name (no --build-number) +// does not cause a panic; build-info collection is simply skipped. +func TestApkAdd_BuildNameOnly(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "busybox", + "--build-name=apk-name-only-test") + _ = err // command may succeed or fail — just ensure no panic +} + +// ==================== jf apk upgrade (build info collection) ==================== + +// TestApkUpgrade_BuildInfo verifies that `jf apk upgrade` records build-info. +func TestApkUpgrade_BuildInfo(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + buildName := tests.AlpineBuildName + "-upgrade" + buildNumber := "1" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "upgrade", + "--build-name="+buildName, "--build-number="+buildNumber, + "--repo="+tests.AlpineVirtualRepo) + if err != nil { + t.Skipf("jf apk upgrade failed (no packages to upgrade or system unavailable): %v", err) + } + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found, "build-info should exist after jf apk upgrade") + + if found { + require.Len(t, publishedBuildInfo.BuildInfo.Modules, 1) + assert.Equal(t, buildinfo.Alpine, publishedBuildInfo.BuildInfo.Modules[0].Type) + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// ==================== Module override ==================== + +// TestApkAdd_ModuleOverride verifies that --module overrides the module ID in build-info. +func TestApkAdd_ModuleOverride(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + buildName := tests.AlpineBuildName + "-module-override" + buildNumber := "1" + customModule := "my-custom-alpine-module" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "curl", + "--build-name="+buildName, "--build-number="+buildNumber, + "--module="+customModule, + "--repo="+tests.AlpineVirtualRepo) + if err != nil { + t.Skipf("jf apk add failed: %v", err) + } + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found) + + if found && len(publishedBuildInfo.BuildInfo.Modules) > 0 { + assert.Equal(t, customModule, publishedBuildInfo.BuildInfo.Modules[0].Id, + "module ID should be overridden to the custom value") + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// ==================== Build-info JSON structure ==================== + +// TestApkAdd_BuildInfoJSON verifies the basic structure of the published build-info: +// correct name, number, started timestamp, module type, and non-empty dep IDs. +func TestApkAdd_BuildInfoJSON(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + buildName := tests.AlpineBuildName + "-bi-json" + buildNumber := "1" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "jq", + "--build-name="+buildName, "--build-number="+buildNumber, + "--repo="+tests.AlpineVirtualRepo) + if err != nil { + t.Skipf("jf apk add failed: %v", err) + } + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found) + + if found { + bi := publishedBuildInfo.BuildInfo + assert.Equal(t, buildName, bi.Name) + assert.Equal(t, buildNumber, bi.Number) + assert.NotEmpty(t, bi.Started, "build-info should have a Started timestamp") + require.Len(t, bi.Modules, 1) + assert.Equal(t, buildinfo.Alpine, bi.Modules[0].Type) + for _, dep := range bi.Modules[0].Dependencies { + assert.NotEmpty(t, dep.Id, "dependency ID must not be empty") + } + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// ==================== Dependency ID format ==================== + +// TestApkAdd_DepIDFormat verifies that every dependency ID is in the "name:version" format. +func TestApkAdd_DepIDFormat(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + buildName := tests.AlpineBuildName + "-dep-id-format" + buildNumber := "1" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "curl", + "--build-name="+buildName, "--build-number="+buildNumber, + "--repo="+tests.AlpineVirtualRepo) + if err != nil { + t.Skipf("jf apk add failed: %v", err) + } + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found) + + if found && len(publishedBuildInfo.BuildInfo.Modules) > 0 { + for _, dep := range publishedBuildInfo.BuildInfo.Modules[0].Dependencies { + assert.Contains(t, dep.Id, ":", + "dep ID should be 'name:version' format, got: %s", dep.Id) + } + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// ==================== Dependency scopes ==================== + +// TestApkAdd_DepScopes verifies that directly-requested packages get scope "prod" +// and transitive packages get scope "transitive". +func TestApkAdd_DepScopes(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + buildName := tests.AlpineBuildName + "-dep-scopes" + buildNumber := "1" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + // curl brings in transitive deps (libcurl, musl, etc.) + err := jfrogCli.Exec("apk", "add", "curl", + "--build-name="+buildName, "--build-number="+buildNumber, + "--repo="+tests.AlpineVirtualRepo) + if err != nil { + t.Skipf("jf apk add failed: %v", err) + } + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found) + + if found && len(publishedBuildInfo.BuildInfo.Modules) > 0 { + hasProd := false + hasTransitive := false + for _, dep := range publishedBuildInfo.BuildInfo.Modules[0].Dependencies { + for _, scope := range dep.Scopes { + if scope == "prod" { + hasProd = true + } + if scope == "transitive" { + hasTransitive = true + } + } + } + assert.True(t, hasProd, "at least one dependency should have scope 'prod'") + // Transitive deps are present when curl pulls in libcurl, musl, etc. + t.Logf("hasProd=%v hasTransitive=%v (transitive depends on whether new deps were installed)", + hasProd, hasTransitive) + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// ==================== Dependency checksums ==================== + +// TestApkAdd_DepChecksums verifies that at least some dependencies have SHA256 checksums set. +// SHA1 and MD5 come from the local cache; SHA256 from the APK database. +func TestApkAdd_DepChecksums(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + buildName := tests.AlpineBuildName + "-dep-checksums" + buildNumber := "1" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "curl", + "--build-name="+buildName, "--build-number="+buildNumber, + "--repo="+tests.AlpineVirtualRepo) + if err != nil { + t.Skipf("jf apk add failed: %v", err) + } + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found) + + if found && len(publishedBuildInfo.BuildInfo.Modules) > 0 { + depsWithChecksums := 0 + for _, dep := range publishedBuildInfo.BuildInfo.Modules[0].Dependencies { + if dep.Sha1 != "" || dep.Sha256 != "" { + depsWithChecksums++ + } + } + t.Logf("Dependencies with at least one checksum: %d/%d", + depsWithChecksums, len(publishedBuildInfo.BuildInfo.Modules[0].Dependencies)) + // On a machine with a populated APK cache, checksums should be present. + // We assert at least the package itself has one. + assert.Greater(t, depsWithChecksums, 0, + "at least one dep should have a checksum (sha1 or sha256)") + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// ==================== requestedBy chains ==================== + +// TestApkAdd_DepRequestedBy verifies that transitive dependencies carry non-empty RequestedBy. +func TestApkAdd_DepRequestedBy(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + buildName := tests.AlpineBuildName + "-requestedby" + buildNumber := "1" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + // curl has libcurl as a transitive dep, which should carry RequestedBy=[["curl:..."]] + err := jfrogCli.Exec("apk", "add", "curl", + "--build-name="+buildName, "--build-number="+buildNumber, + "--repo="+tests.AlpineVirtualRepo) + if err != nil { + t.Skipf("jf apk add failed: %v", err) + } + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found) + + if found && len(publishedBuildInfo.BuildInfo.Modules) > 0 { + deps := publishedBuildInfo.BuildInfo.Modules[0].Dependencies + if len(deps) <= 1 { + t.Skip("only one dep installed; transitive RequestedBy cannot be validated") + } + hasTransitiveRequestedBy := false + for _, dep := range deps { + if len(dep.RequestedBy) > 0 && len(dep.RequestedBy[0]) > 0 { + hasTransitiveRequestedBy = true + // Each RequestedBy entry should be a non-empty parent ID + for _, chain := range dep.RequestedBy { + assert.NotEmpty(t, chain, "RequestedBy chain entry should not be empty for %s", dep.Id) + } + break + } + } + assert.True(t, hasTransitiveRequestedBy, + "at least one transitive dep should have RequestedBy with a parent ID") + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// ==================== Multiple builds isolation ==================== + +// TestApkAdd_MultipleBuildsIsolated verifies that two sequential `jf apk add` calls with +// different build numbers produce independent build-info records. +func TestApkAdd_MultipleBuildsIsolated(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + buildName := tests.AlpineBuildName + "-multi-isolated" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + + err := jfrogCli.Exec("apk", "add", "curl", + "--build-name="+buildName, "--build-number=1", + "--repo="+tests.AlpineVirtualRepo) + if err != nil { + t.Skipf("jf apk add (build 1) failed: %v", err) + } + assert.NoError(t, artifactoryCli.Exec("bp", buildName, "1")) + + err = jfrogCli.Exec("apk", "add", "jq", + "--build-name="+buildName, "--build-number=2", + "--repo="+tests.AlpineVirtualRepo) + if err != nil { + t.Skipf("jf apk add (build 2) failed: %v", err) + } + assert.NoError(t, artifactoryCli.Exec("bp", buildName, "2")) + + bi1, found1, err := tests.GetBuildInfo(serverDetails, buildName, "1") + assert.NoError(t, err) + assert.True(t, found1, "build 1 should be found") + + bi2, found2, err := tests.GetBuildInfo(serverDetails, buildName, "2") + assert.NoError(t, err) + assert.True(t, found2, "build 2 should be found") + + if found1 && found2 { + assert.NotEqual(t, + bi1.BuildInfo.Modules[0].Dependencies, + bi2.BuildInfo.Modules[0].Dependencies, + "build 1 (curl) and build 2 (jq) should have different dependency sets") + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// ==================== Passthrough commands (no build info) ==================== + +// TestApkPassthroughCommands verifies that every apk sub-command that does not +// produce build-info (update, del, info, search, fetch, fix, audit, version, stats) +// is forwarded to the native apk binary without panicking. +// Each case also confirms that passing --build-name/--build-number does NOT result +// in build-info being published — these commands are read-only or destructive, not +// installation events. +func TestApkPassthroughCommands(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + + cases := []struct { + name string + args []string + }{ + {"update", []string{"apk", "update"}}, + {"update-with-build-flags", []string{"apk", "update", "--build-name=" + tests.AlpineBuildName + "-pt", "--build-number=1"}}, + {"del", []string{"apk", "del", "curl", "--build-name=" + tests.AlpineBuildName + "-pt", "--build-number=1"}}, + {"info", []string{"apk", "info", "musl"}}, + {"search", []string{"apk", "search", "curl"}}, + {"fix", []string{"apk", "fix"}}, + {"audit", []string{"apk", "audit"}}, + {"version", []string{"apk", "version"}}, + {"stats", []string{"apk", "stats"}}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // A non-zero exit is acceptable (e.g. nothing to fix, package absent); + // what must not happen is a panic or an unknown-flag error caused by + // JFrog CLI flags leaking into the native apk invocation. + err := jfrogCli.Exec(tc.args...) + if err != nil { + t.Logf("jf %v: %v (non-zero exit is acceptable for passthrough)", tc.args, err) + } + }) + } +} + +// ==================== jf apk config ==================== + +// TestApkConfig_SetsUpRepo verifies that `jf apk config` runs without error when the +// Artifactory repo key and server details are valid. +// Note: this test requires write access to /etc/apk/repositories (root on Alpine). +func TestApkConfig_SetsUpRepo(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + if os.Getuid() != 0 { + t.Skip("jf apk config modifies /etc/apk/repositories and requires root.") + } + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "config", + "--server-id=default", + "--repo="+tests.AlpineVirtualRepo) + if err != nil { + t.Logf("jf apk config: %v (Artifactory may not be reachable or missing RSA key)", err) + } +} + +// TestApkConfig_UnknownServerID verifies that jf apk config with an unknown +// --server-id returns a clear error rather than silently continuing. +func TestApkConfig_UnknownServerID(t *testing.T) { + initApkTest(t) + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "config", + "--server-id=nonexistent-server-id-xyz", + "--repo="+tests.AlpineVirtualRepo) + assert.Error(t, err, "jf apk config with an unknown --server-id should return a clear error") +} + +// ==================== P0: package not found ==================== + +// TestApkAdd_PackageNotFound verifies that jf apk add with a package that does +// not exist in Artifactory returns a clear error and does not silently fall back +// to the public Alpine CDN. +func TestApkAdd_PackageNotFound(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "nonexistent-pkg-xyz-jfrog-test-12345", + "--repo="+tests.AlpineVirtualRepo, + "--build-name="+tests.AlpineBuildName+"-pkg-not-found", + "--build-number=1") + assert.Error(t, err, + "jf apk add with a nonexistent package should fail, not silently succeed or fall back to CDN") +} + +// ==================== P0: checksum stored in Artifactory ==================== + +// TestApkUpload_ChecksumNotUntrusted verifies that after jf apk upload the +// artifact stored in Artifactory has a non-empty, non-"untrusted" SHA256 +// checksum. Artifactory marks artifacts as "untrusted" when upload does not +// supply X-Checksum headers — this test catches that integration gap. +func TestApkUpload_ChecksumNotUntrusted(t *testing.T) { + initApkTest(t) + + tmpDir, err := os.MkdirTemp("", "apk-chksum-stored-*") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer clientTestUtils.RemoveAllAndAssert(t, tmpDir) + + fakePkgInfo := "pkgname = testpkg-chksum\npkgver = 1.0.0-r0\narch = x86_64\n" + if writeErr := os.WriteFile(tmpDir+"/.PKGINFO", []byte(fakePkgInfo), 0644); writeErr != nil { + t.Fatalf("failed to write .PKGINFO: %v", writeErr) + } + apkPath := tmpDir + "/testpkg-chksum-1.0.0-r0.apk" + if tarErr := exec.Command("tar", "-czf", apkPath, "-C", tmpDir, ".PKGINFO").Run(); tarErr != nil { + t.Skipf("tar not available to build test .apk: %v", tarErr) + } + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + if uploadErr := jfrogCli.Exec("apk", "upload", apkPath, "--repo="+tests.AlpineLocalRepo); uploadErr != nil { + t.Skipf("jf apk upload failed: %v", uploadErr) + } + + // Search Artifactory for the uploaded artifact and verify the stored sha256. + searchSpec := spec.NewBuilder().Pattern(tests.AlpineLocalRepo + "/testpkg-chksum-1.0.0-r0.apk").BuildSpec() + searchCmd := generic.NewSearchCommand() + searchCmd.SetServerDetails(serverDetails).SetSpec(searchSpec) + reader, searchErr := searchCmd.Search() + require.NoError(t, searchErr, "AQL search for uploaded artifact should succeed") + defer func() { _ = reader.Close() }() + + item := new(artUtils.SearchResult) + require.NoError(t, reader.NextRecord(item), + "uploaded artifact must be found in Artifactory — jf apk upload may have failed silently") + assert.NotEmpty(t, item.Sha256, + "Artifactory must store a sha256 checksum for the uploaded .apk artifact") + assert.NotEqual(t, "untrusted", strings.ToLower(item.Sha256), + "Artifactory must not mark the .apk artifact as 'untrusted' — X-Checksum headers may be missing on upload") +} + +// ==================== jf apk upload ==================== + +// TestApkUpload_LocalApkFile verifies that `jf apk upload` can upload a real .apk file +// to Artifactory. The test creates a minimal (but valid enough) .apk archive. +func TestApkUpload_LocalApkFile(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + tmpDir, err := os.MkdirTemp("", "apk-upload-*") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer clientTestUtils.RemoveAllAndAssert(t, tmpDir) + + // Build a minimal .apk (tgz containing just a PKGINFO) via abuild-tar or tar. + // If abuild-tar is not available, skip gracefully. + fakePkgInfo := "pkgname = testpkg\npkgver = 1.0.0-r0\narch = x86_64\n" + pkgInfoPath := tmpDir + "/.PKGINFO" + if writeErr := os.WriteFile(pkgInfoPath, []byte(fakePkgInfo), 0644); writeErr != nil { + t.Fatalf("failed to write .PKGINFO: %v", writeErr) + } + + apkFilePath := fmt.Sprintf("%s/testpkg-1.0.0-r0.apk", tmpDir) + // Create a minimal tar.gz containing the PKGINFO + tarCmd := exec.Command("tar", "-czf", apkFilePath, "-C", tmpDir, ".PKGINFO") + if tarOut, tarErr := tarCmd.CombinedOutput(); tarErr != nil { + t.Skipf("failed to create fake .apk archive with tar: %v\n%s", tarErr, tarOut) + } + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err = jfrogCli.Exec("apk", "upload", apkFilePath, + "--repo="+tests.AlpineLocalRepo, + "--alpine-version=3.18", + "--server-id=default") + if err != nil { + t.Logf("jf apk upload: %v (Artifactory may not accept the synthetic .apk)", err) + } +} + +// ==================== Project key ==================== + +// TestApkAdd_ProjectKey verifies that `jf apk add` with --project is passed through +// correctly; the test is lenient if the project does not exist in Artifactory. +func TestApkAdd_ProjectKey(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + buildName := tests.AlpineBuildName + "-project-key" + buildNumber := "1" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "busybox", + "--build-name="+buildName, "--build-number="+buildNumber, + "--project=testprj", + "--repo="+tests.AlpineVirtualRepo) + if err != nil { + t.Skipf("jf apk add --project failed: %v", err) + } + + publishErr := artifactoryCli.Exec("bp", buildName, buildNumber, "--project=testprj") + if publishErr != nil { + t.Logf("build-publish with --project failed (project may not exist): %v", publishErr) + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// ==================== JFrog CLI flag stripping ==================== + +// TestApkAdd_JFlagStripping verifies that JFrog CLI flags (--build-name, --build-number, +// --repo, --server-id, etc.) are not forwarded to the native apk binary. +// If they were forwarded, apk would reject the unknown flags and exit non-zero. +func TestApkAdd_JFlagStripping(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + // If --build-name etc. are leaked to the native apk binary it will fail with + // "ERROR: unknown option --build-name". A clean exit confirms flag stripping. + err := jfrogCli.Exec("apk", "add", "busybox", + "--build-name=flag-strip-test", + "--build-number=99", + "--repo="+tests.AlpineVirtualRepo, + "--server-id=default") + if err != nil { + // A non-zero exit that is NOT due to unknown flags is OK (package already installed). + t.Logf("jf apk add with all JF flags: %v", err) + } +} + +// ==================== Environment secret filtering ==================== + +// TestApkAdd_EnvSecretFiltering verifies that secret environment variables are not +// leaked into the build-info (e.g. JFROG_CLI_ENV_EXCLUDE works as expected). +func TestApkAdd_EnvSecretFiltering(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + setEnvCallback := clientTestUtils.SetEnvWithCallbackAndAssert(t, "MY_SECRET_TOKEN", "super-secret-value") + defer setEnvCallback() + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + buildName := tests.AlpineBuildName + "-env-secrets" + buildNumber := "1" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "curl", + "--build-name="+buildName, "--build-number="+buildNumber, + "--repo="+tests.AlpineVirtualRepo) + if err != nil { + t.Skipf("jf apk add failed: %v", err) + } + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found) + + if found { + // Scan all env vars captured in build-info; none should contain the secret value. + for k, v := range publishedBuildInfo.BuildInfo.Properties { + assert.NotContains(t, v, "super-secret-value", + "env var %s should not contain secret value in build-info", k) + } + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// ==================== Alpine version flag ==================== + +// TestApkAdd_AlpineVersionFlag verifies that the --alpine-version flag is accepted and +// recorded in the module ID without being passed to the native apk binary. +func TestApkAdd_AlpineVersionFlag(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + buildName := tests.AlpineBuildName + "-alpine-version" + buildNumber := "1" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "curl", + "--build-name="+buildName, "--build-number="+buildNumber, + "--repo="+tests.AlpineVirtualRepo, + "--alpine-version=3.18") + if err != nil { + t.Skipf("jf apk add --alpine-version failed: %v", err) + } + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found) + + if found { + require.Len(t, publishedBuildInfo.BuildInfo.Modules, 1) + assert.Equal(t, buildinfo.Alpine, publishedBuildInfo.BuildInfo.Modules[0].Type) + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// ==================== P0: missing required flag ==================== + +// TestApkAdd_MissingRepo verifies that jf apk add without --repo behaves like other +// JFrog CLI package managers (npm, pip): the command succeeds, build-info is still +// captured with checksums from the local APK DB, and AQL enrichment is simply skipped. +func TestApkAdd_MissingRepo(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + buildName := tests.AlpineBuildName + "-missing-repo" + buildNumber := "1" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + // Deliberately omit --repo — should succeed, consistent with npm/pip behaviour. + err := jfrogCli.Exec("apk", "add", "curl", + "--build-name="+buildName, + "--build-number="+buildNumber) + assert.NoError(t, err, "jf apk add without --repo should succeed (no AQL enrichment, but build-info still captured)") + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found, "build info should be published even without --repo") + if found && len(publishedBuildInfo.BuildInfo.Modules) > 0 { + assert.NotEmpty(t, publishedBuildInfo.BuildInfo.Modules[0].Dependencies, + "dependencies should be recorded even without --repo") + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// ==================== P0: CI/VCS properties ==================== + +// TestApkUpload_CIVCSPropertiesStamped verifies that when jf apk upload is run inside +// a CI environment (GitHub Actions), the published build info causes vcs.provider, +// vcs.org, and vcs.repo properties to be stamped on the artifact in Artifactory. +func TestApkUpload_CIVCSPropertiesStamped(t *testing.T) { + initApkTest(t) + + tmpDir, err := os.MkdirTemp("", "apk-civcs-*") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer clientTestUtils.RemoveAllAndAssert(t, tmpDir) + + buildName := tests.AlpineBuildName + "-civcs" + buildNumber := "1" + + // Simulate GitHub Actions environment (uses real env vars on CI, mock values locally). + cleanupEnv, actualOrg, actualRepo := tests.SetupGitHubActionsEnv(t) + defer cleanupEnv() + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) + + // Build a minimal .apk file. + fakePkgInfo := "pkgname = testpkg-civcs\npkgver = 1.0.0-r0\narch = x86_64\n" + if writeErr := os.WriteFile(tmpDir+"/.PKGINFO", []byte(fakePkgInfo), 0644); writeErr != nil { + t.Fatalf("failed to write .PKGINFO: %v", writeErr) + } + apkPath := tmpDir + "/testpkg-civcs-1.0.0-r0.apk" + if tarErr := exec.Command("tar", "-czf", apkPath, "-C", tmpDir, ".PKGINFO").Run(); tarErr != nil { + t.Skipf("tar not available: %v", tarErr) + } + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + if uploadErr := jfrogCli.Exec("apk", "upload", apkPath, + "--repo="+tests.AlpineLocalRepo, + "--build-name="+buildName, "--build-number="+buildNumber); uploadErr != nil { + t.Skipf("jf apk upload failed: %v", uploadErr) + } + + require.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + // Retrieve the published build info to find the artifact path. + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + require.NoError(t, err) + require.True(t, found, "build info must be published before checking VCS properties") + require.NotEmpty(t, publishedBuildInfo.BuildInfo.Modules, "build info must contain at least one module") + require.NotEmpty(t, publishedBuildInfo.BuildInfo.Modules[0].Artifacts, "module must contain at least one artifact") + + // Use the service manager to query per-artifact properties from Artifactory. + sm, err := artUtils.CreateServiceManager(serverDetails, 3, 1000, false) + require.NoError(t, err) + + for _, module := range publishedBuildInfo.BuildInfo.Modules { + for _, artifact := range module.Artifacts { + fullPath := artifact.OriginalDeploymentRepo + "/" + artifact.Path + props, propsErr := sm.GetItemProps(fullPath) + require.NoError(t, propsErr, "failed to get properties for artifact: %s", fullPath) + require.NotNil(t, props, "properties must not be nil for artifact: %s", fullPath) + + assert.Contains(t, props.Properties, "vcs.provider", + "vcs.provider must be stamped on artifact: %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.provider"], "github", + "vcs.provider must be 'github' for artifact: %s", artifact.Name) + + assert.Contains(t, props.Properties, "vcs.org", + "vcs.org must be stamped on artifact: %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.org"], actualOrg, + "vcs.org must match expected org for artifact: %s", artifact.Name) + + assert.Contains(t, props.Properties, "vcs.repo", + "vcs.repo must be stamped on artifact: %s", artifact.Name) + assert.Contains(t, props.Properties["vcs.repo"], actualRepo, + "vcs.repo must match expected repo for artifact: %s", artifact.Name) + } + } +} + +// ==================== P0 gaps ==================== + +// TestApkUpload_BuildPropertiesStamped verifies that build.name, build.number, and +// build.timestamp are stamped on an uploaded .apk artifact after jf bp. +func TestApkUpload_BuildPropertiesStamped(t *testing.T) { + initApkTest(t) + + tmpDir, err := os.MkdirTemp("", "apk-props-*") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer clientTestUtils.RemoveAllAndAssert(t, tmpDir) + + buildName := tests.AlpineBuildName + "-props" + buildNumber := "1" + + fakePkgInfo := "pkgname = testpkg-props\npkgver = 1.0.0-r0\narch = x86_64\n" + if writeErr := os.WriteFile(tmpDir+"/.PKGINFO", []byte(fakePkgInfo), 0644); writeErr != nil { + t.Fatalf("failed to write .PKGINFO: %v", writeErr) + } + apkPath := tmpDir + "/testpkg-props-1.0.0-r0.apk" + if tarErr := exec.Command("tar", "-czf", apkPath, "-C", tmpDir, ".PKGINFO").Run(); tarErr != nil { + t.Skipf("tar not available to create test .apk: %v", tarErr) + } + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + if uploadErr := jfrogCli.Exec("apk", "upload", apkPath, + "--repo="+tests.AlpineLocalRepo, + "--build-name="+buildName, "--build-number="+buildNumber); uploadErr != nil { + t.Skipf("jf apk upload failed: %v", uploadErr) + } + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + verifyExistInArtifactoryByProps( + []string{tests.AlpineLocalRepo + "/testpkg-props-1.0.0-r0.apk"}, + tests.AlpineLocalRepo+"/testpkg-props-1.0.0-r0.apk", + fmt.Sprintf("build.name=%v;build.number=%v;build.timestamp=*", buildName, buildNumber), + t, + ) + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// TestApkAdd_InvalidRepo verifies that jf apk add with a nonexistent --repo still +// succeeds. Unlike npm/pip (which route all traffic through Artifactory), Alpine APK +// fetches packages from the configured system repositories (CDN by default) unless +// jf apk config has been run first. A nonexistent repo name only means AQL checksum +// enrichment is skipped — it does not block installation or build-info collection. +func TestApkAdd_InvalidRepo(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + buildName := tests.AlpineBuildName + "-invalid-repo" + buildNumber := "1" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "curl", + "--repo=nonexistent-repo-xyz-123", + "--build-name="+buildName, + "--build-number="+buildNumber) + // Command must succeed: native apk fetches from CDN, build-info is captured + // with DB checksums, and AQL enrichment is simply skipped for the bad repo. + assert.NoError(t, err, "jf apk add with an unresolvable --repo should still succeed") + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found, "build info should be published even with an unresolvable --repo") + if found && len(publishedBuildInfo.BuildInfo.Modules) > 0 { + assert.NotEmpty(t, publishedBuildInfo.BuildInfo.Modules[0].Dependencies, + "dependencies should be recorded even with an unresolvable --repo") + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// ==================== P1 gaps ==================== + +// TestApkAdd_BuildNameFromEnvVars verifies that JFROG_CLI_BUILD_NAME and +// JFROG_CLI_BUILD_NUMBER environment variables trigger build-info collection +// even when --build-name / --build-number flags are not passed explicitly. +func TestApkAdd_BuildNameFromEnvVars(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + envBuildName := tests.AlpineBuildName + "-envvar" + envBuildNumber := "1" + + t.Setenv("JFROG_CLI_BUILD_NAME", envBuildName) + t.Setenv("JFROG_CLI_BUILD_NUMBER", envBuildNumber) + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, envBuildName, artHttpDetails) + defer inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, envBuildName, artHttpDetails) + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + if err := jfrogCli.Exec("apk", "add", "curl", "--repo="+tests.AlpineVirtualRepo); err != nil { + t.Skipf("jf apk add failed: %v", err) + } + + require.NoError(t, artifactoryCli.Exec("bp", envBuildName, envBuildNumber)) + + _, found, err := tests.GetBuildInfo(serverDetails, envBuildName, envBuildNumber) + require.NoError(t, err) + assert.True(t, found, "build info should be captured from JFROG_CLI_BUILD_NAME/NUMBER env vars") +} + +// TestApkAdd_UnknownServerID verifies that supplying an unknown --server-id +// causes jf apk add to exit with a non-zero status code. +func TestApkAdd_UnknownServerID(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "curl", + "--repo="+tests.AlpineVirtualRepo, + "--server-id=nonexistent-server-id-xyz") + assert.Error(t, err, "jf apk add with an unknown --server-id should return an error") +} + +// TestApkAdd_ArtifactoryUnreachable verifies that when Artifactory is unreachable +// jf apk add returns a clear error and does not silently fall back to the public CDN. +func TestApkAdd_ArtifactoryUnreachable(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err := jfrogCli.Exec("apk", "add", "curl", + "--repo="+tests.AlpineVirtualRepo, + "--server-id=nonexistent-server-id-xyz") + assert.Error(t, err, "jf apk add should fail when Artifactory is unreachable, not fall back to CDN") +} + +// TestApkAdd_VirtualRepoAggregates verifies that install through the virtual repo +// (which aggregates the local and remote repos) succeeds and records build-info. +func TestApkAdd_VirtualRepoAggregates(t *testing.T) { + initApkTest(t) + if !apkAvailable() { + t.Skip("apk binary not found — test requires Alpine Linux.") + } + + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + buildName := tests.AlpineBuildName + "-virtual-repo" + buildNumber := "1" + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + if err := jfrogCli.Exec("apk", "add", "busybox", + "--repo="+tests.AlpineVirtualRepo, + "--build-name="+buildName, "--build-number="+buildNumber); err != nil { + t.Skipf("jf apk add via virtual repo failed: %v", err) + } + + assert.NoError(t, artifactoryCli.Exec("bp", buildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, buildName, buildNumber) + assert.NoError(t, err) + assert.True(t, found, "build info should be published when installing via virtual repo") + if found { + assert.GreaterOrEqual(t, len(publishedBuildInfo.BuildInfo.Modules[0].Dependencies), 1, + "at least one dependency should be recorded when installing via virtual repo") + } + + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, buildName, artHttpDetails) +} + +// TestApkUpload_ChecksumRoundTrip verifies that the SHA256 checksum of an artifact +// downloaded from Artifactory matches the checksum of the originally uploaded file. +func TestApkUpload_ChecksumRoundTrip(t *testing.T) { + initApkTest(t) + + tmpDir, err := os.MkdirTemp("", "apk-checksum-*") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer clientTestUtils.RemoveAllAndAssert(t, tmpDir) + + fakePkgInfo := "pkgname = testpkg-rt\npkgver = 2.0.0-r0\narch = x86_64\n" + if writeErr := os.WriteFile(tmpDir+"/.PKGINFO", []byte(fakePkgInfo), 0644); writeErr != nil { + t.Fatalf("failed to write .PKGINFO: %v", writeErr) + } + apkPath := tmpDir + "/testpkg-rt-2.0.0-r0.apk" + if tarErr := exec.Command("tar", "-czf", apkPath, "-C", tmpDir, ".PKGINFO").Run(); tarErr != nil { + t.Skipf("tar not available to build test .apk: %v", tarErr) + } + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + if uploadErr := jfrogCli.Exec("apk", "upload", apkPath, "--repo="+tests.AlpineLocalRepo); uploadErr != nil { + t.Skipf("jf apk upload failed: %v", uploadErr) + } + + downloadDir := tmpDir + "/downloaded" + require.NoError(t, os.MkdirAll(downloadDir, 0755)) + assert.NoError(t, artifactoryCli.Exec("dl", + tests.AlpineLocalRepo+"/testpkg-rt-2.0.0-r0.apk", + downloadDir+"/", "--flat")) + + downloadedPath := downloadDir + "/testpkg-rt-2.0.0-r0.apk" + _, statErr := os.Stat(downloadedPath) + assert.NoError(t, statErr, "downloaded artifact should exist on disk") + + assert.Equal(t, + computeFileSHA256(t, apkPath), + computeFileSHA256(t, downloadedPath), + "downloaded artifact SHA256 should match the originally uploaded file") +} + +// TestApkUpload_InsecureTLS verifies --insecure-tls behaviour on jf apk upload. +// Without the flag a self-signed cert connection should fail; with it it should succeed. +// Skipped unless JFROG_CLI_TESTS_INSECURE_TLS_URL is set. +func TestApkUpload_InsecureTLS(t *testing.T) { + initApkTest(t) + + if os.Getenv("JFROG_CLI_TESTS_INSECURE_TLS_URL") == "" { + t.Skip("Skipping TLS test: set JFROG_CLI_TESTS_INSECURE_TLS_URL to an Artifactory with a self-signed cert.") + } + + tmpDir, err := os.MkdirTemp("", "apk-tls-*") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer clientTestUtils.RemoveAllAndAssert(t, tmpDir) + + fakePkgInfo := "pkgname = testpkg-tls\npkgver = 1.0.0-r0\narch = x86_64\n" + if writeErr := os.WriteFile(tmpDir+"/.PKGINFO", []byte(fakePkgInfo), 0644); writeErr != nil { + t.Fatalf("failed to write .PKGINFO: %v", writeErr) + } + apkPath := tmpDir + "/testpkg-tls-1.0.0-r0.apk" + if tarErr := exec.Command("tar", "-czf", apkPath, "-C", tmpDir, ".PKGINFO").Run(); tarErr != nil { + t.Skipf("tar not available: %v", tarErr) + } + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + + // Without --insecure-tls the self-signed cert should cause an error. + assert.Error(t, + jfrogCli.Exec("apk", "upload", apkPath, "--repo="+tests.AlpineLocalRepo), + "upload to self-signed Artifactory without --insecure-tls should fail") + + // With --insecure-tls it should succeed. + assert.NoError(t, + jfrogCli.Exec("apk", "upload", apkPath, "--repo="+tests.AlpineLocalRepo, "--insecure-tls"), + "upload to self-signed Artifactory with --insecure-tls should succeed") +} diff --git a/buildtools/cli.go b/buildtools/cli.go index 4daff389c..566c6729a 100644 --- a/buildtools/cli.go +++ b/buildtools/cli.go @@ -3,6 +3,7 @@ package buildtools import ( "errors" "fmt" + alpinecommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/alpine" conancommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/conan" nixcommand "github.com/jfrog/jfrog-cli-artifactory/artifactory/commands/nix" "io/fs" @@ -68,6 +69,7 @@ import ( huggingfaceuploaddocs "github.com/jfrog/jfrog-cli/docs/buildtools/huggingfaceupload" mvndoc "github.com/jfrog/jfrog-cli/docs/buildtools/mvn" "github.com/jfrog/jfrog-cli/docs/buildtools/mvnconfig" + "github.com/jfrog/jfrog-cli/docs/buildtools/apkcommand" "github.com/jfrog/jfrog-cli/docs/buildtools/nix" "github.com/jfrog/jfrog-cli/docs/buildtools/npmcommand" "github.com/jfrog/jfrog-cli/docs/buildtools/npmconfig" @@ -430,6 +432,18 @@ func GetCommands() []cli.Command { Category: buildToolsCategory, Action: NixCmd, }, + { + Name: "apk", + Flags: cliutils.GetCommandFlags(cliutils.Apk), + Usage: corecommon.ResolveDescription(apkcommand.GetDescription(), apkcommand.GetAIDescription()), + HelpName: corecommon.CreateUsage("apk", corecommon.ResolveDescription(apkcommand.GetDescription(), apkcommand.GetAIDescription()), apkcommand.Usage), + UsageText: apkcommand.GetArguments(), + ArgsUsage: common.CreateEnvVars(), + SkipFlagParsing: true, + BashComplete: corecommon.CreateBashCompletionFunc("upload", "add", "upgrade", "update", "fetch", "search", "del", "info"), + Category: buildToolsCategory, + Action: ApkCmd, + }, { Name: "ruby-config", Flags: cliutils.GetCommandFlags(cliutils.RubyConfig), @@ -2118,6 +2132,115 @@ func NixCmd(c *cli.Context) error { return commands.ExecWithPackageManager(cmd, "nix") } +// ApkCmd dispatches jf apk subcommands: upload and native apk operations. +func ApkCmd(c *cli.Context) error { + if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil { + return err + } + if c.NArg() < 1 { + return cliutils.WrongNumberOfArgumentsHandler(c) + } + + args := cliutils.ExtractCommand(c) + subcmd, remainingArgs := getCommandName(args) + + var ( + serverID string + err error + ) + remainingArgs, serverID, err = coreutils.ExtractServerIdFromCommand(remainingArgs) + if err != nil { + return errorutils.CheckErrorf("failed to extract --server-id: %w", err) + } + + remainingArgs, repoKey, err := coreutils.ExtractStringOptionFromArgs(remainingArgs, "repo") + if err != nil { + return errorutils.CheckErrorf("failed to extract --repo: %w", err) + } + remainingArgs, alpineVersion, err := coreutils.ExtractStringOptionFromArgs(remainingArgs, "alpine-version") + if err != nil { + return errorutils.CheckErrorf("failed to extract --alpine-version: %w", err) + } + remainingArgs, username, err := coreutils.ExtractStringOptionFromArgs(remainingArgs, "user") + if err != nil { + return errorutils.CheckErrorf("failed to extract --user: %w", err) + } + remainingArgs, password, err := coreutils.ExtractStringOptionFromArgs(remainingArgs, "password") + if err != nil { + return errorutils.CheckErrorf("failed to extract --password: %w", err) + } + + var serverDetails *coreConfig.ServerDetails + if serverID != "" { + // User explicitly named a server — fail fast if it doesn't exist. + serverDetails, err = coreConfig.GetSpecificConfig(serverID, false, false) + if err != nil { + return errorutils.CheckErrorf("server ID %q not found in configuration. "+ + "Run 'jf c add' to add it, or omit --server-id to use the default server.", serverID) + } + } else { + serverDetails, err = coreConfig.GetDefaultServerConf() + if err != nil { + log.Warn("No JFrog server configured — skipping credential injection. Run: jf c add") + } + } + + switch subcmd { + case "upload": + return apkUploadSubCmd(c, remainingArgs, serverDetails, repoKey, alpineVersion, username, password) + default: + filteredArgs, buildConfiguration, err := build.ExtractBuildDetailsFromArgs(remainingArgs) + if err != nil { + return err + } + cmd := alpinecommand.NewApkCommand(subcmd). + SetArgs(filteredArgs). + SetServerDetails(serverDetails). + SetBuildConfiguration(buildConfiguration). + SetRepo(repoKey). + SetAlpineVersion(alpineVersion). + SetUsername(username). + SetPassword(password) + return commands.ExecWithPackageManager(cmd, "apk") + } +} + +// apkUploadSubCmd handles jf apk upload: publishes a local .apk file to Artifactory. +func apkUploadSubCmd(c *cli.Context, args []string, serverDetails *coreConfig.ServerDetails, repoKey, alpineVersion, username, password string) error { + filteredArgs, buildConfiguration, err := build.ExtractBuildDetailsFromArgs(args) + if err != nil { + return err + } + + filteredArgs, branch, err := coreutils.ExtractStringOptionFromArgs(filteredArgs, "branch") + if err != nil { + return errorutils.CheckErrorf("failed to extract --branch: %w", err) + } + if branch == "" { + branch = "main" + } + filteredArgs, arch, err := coreutils.ExtractStringOptionFromArgs(filteredArgs, "arch") + if err != nil { + return errorutils.CheckErrorf("failed to extract --arch: %w", err) + } + + if len(filteredArgs) != 1 { + return cliutils.WrongNumberOfArgumentsHandler(c) + } + filePath := filteredArgs[0] + + cmd := alpinecommand.NewApkUploadCommand(filePath). + SetServerDetails(serverDetails). + SetBuildConfiguration(buildConfiguration). + SetRepo(repoKey). + SetAlpineVersion(alpineVersion). + SetBranch(branch). + SetArch(arch). + SetUsername(username). + SetPassword(password) + return commands.ExecWithPackageManager(cmd, "apk") +} + func pythonCmd(c *cli.Context, projectType project.ProjectType) error { if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil { return err diff --git a/docs/buildtools/apkcommand/help.go b/docs/buildtools/apkcommand/help.go new file mode 100644 index 000000000..e0c49f72d --- /dev/null +++ b/docs/buildtools/apkcommand/help.go @@ -0,0 +1,75 @@ +package apkcommand + +var Usage = []string{ + "apk upload --repo --alpine-version [--branch ] [--arch ] [jf-flags]", + "apk add [jf-flags]", + "apk upgrade [packages...] [jf-flags]", + "apk update [jf-flags]", + "apk fetch [jf-flags]", + "apk search [jf-flags]", + "apk del ", + "apk info [packages...]", +} + +// GetDescription returns the short command description shown in jf --help. +func GetDescription() string { + return "Manage Alpine packages via Artifactory: upload packages and run native apk commands with credential injection and Build Info capture." +} + +// GetAIDescription returns the extended description used by AI-assisted help. +func GetAIDescription() string { + return `jf apk provides two modes of operation for working with Artifactory Alpine repositories: + +1. jf apk upload — Publish a local .apk file to Artifactory. + Performs a direct REST PUT (no native apk binary required). Infers arch + and package metadata from the filename. Sets Artifactory properties and + records a Build Info artifact. + +2. jf apk — Wrap native apk commands with Artifactory + credentials and Build Info capture. + Injects HTTP_AUTH into the apk subprocess environment. + For 'add' and 'upgrade', diffs the installed package list before/after + to record dependencies into a Build Info alpine module. + +To configure APK to use Artifactory as its repository, use: jf setup apk` +} + +// GetArguments returns the argument reference shown in jf apk --help. +func GetArguments() string { + return ` apk subcommand + Subcommands: + upload — publish a local .apk file to Artifactory + add — install packages (Build Info + HTTP_AUTH) + upgrade — upgrade packages (Build Info + HTTP_AUTH) + update — refresh index (HTTP_AUTH only) + fetch — download .apk files (HTTP_AUTH only) + search — search index (HTTP_AUTH only) + del — remove packages (passthrough) + info — query package info (passthrough) + + Common flags (all subcommands): + --server-id JFrog server config ID (from jf c add). Default: active server. + --repo Artifactory Alpine repository key. + --alpine-version Alpine release, e.g. v3.20. + --user Override Artifactory username. + --password Override Artifactory password or token. + + upload-specific flags: + --branch Alpine repo branch. Default: main. + --arch CPU architecture. Default: inferred from filename. + --build-name Build Info name. Env fallback: JFROG_CLI_BUILD_NAME. + --build-number Build Info number. Env fallback: JFROG_CLI_BUILD_NUMBER. + --project JFrog Projects key. Env fallback: JFROG_CLI_BUILD_PROJECT. + + add/upgrade flags: + --build-name Build Info name. + --build-number Build Info number. + --project JFrog Projects key. + + Examples: + jf setup apk --server-id my-server --repo my-alpine-repo + jf apk upload ./myapp-1.0.0-r0.x86_64.apk --repo my-alpine-repo --alpine-version v3.20 + jf apk add curl bash --server-id my-server --repo my-alpine-repo \ + --build-name ci-image --build-number 42 + jf apk upgrade --server-id my-server --repo my-alpine-repo` +} diff --git a/go.mod b/go.mod index 3e0e3e6c2..e32160d4c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/jfrog/jfrog-cli -go 1.26.5 +go 1.26.3 replace ( // Should not be updated to 0.11.0+ due to default spec version change (1.6 -> 1.7, https://github.com/CycloneDX/cyclonedx-go/pull/257) @@ -18,11 +18,11 @@ require ( github.com/buger/jsonparser v1.2.0 github.com/gocarina/gocsv v0.0.0-20260607070740-0735908c6461 github.com/jfrog/archiver/v3 v3.6.3 - github.com/jfrog/build-info-go v1.13.1-0.20260713073853-4f3044bf0940 + github.com/jfrog/build-info-go v1.13.1-0.20260717181252-20cc7766a16d github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-cli-application v1.0.2-0.20260713081138-6df6041db819 - github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260714082320-4fd5a71b4b4a - github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260624085155-5ba797de2616 + github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260719152401-944101b51a48 + github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9 github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab github.com/jfrog/jfrog-cli-security v1.31.2 diff --git a/go.sum b/go.sum index 20ea353ce..0f332c53f 100644 --- a/go.sum +++ b/go.sum @@ -394,8 +394,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.3 h1:hkAmPjBw393tPmQ07JknLNWFNZjXdy2xFEnOW9wwOxI= github.com/jfrog/archiver/v3 v3.6.3/go.mod h1:5V9l+Fte30Y4qe9dUOAd3yNTf8lmtVNuhKNrvI8PMhg= -github.com/jfrog/build-info-go v1.13.1-0.20260713073853-4f3044bf0940 h1:LthrPDN8YjDKrDNzOWS/w9NLFyeebdnWkmp8vhma0qo= -github.com/jfrog/build-info-go v1.13.1-0.20260713073853-4f3044bf0940/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE= +github.com/jfrog/build-info-go v1.13.1-0.20260717181252-20cc7766a16d h1:mcOCx1EMATZBOoeDRIkvKA0TMrA9tKMyja2xLmwsl20= +github.com/jfrog/build-info-go v1.13.1-0.20260717181252-20cc7766a16d/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE= github.com/jfrog/froggit-go v1.23.0 h1:HGNIP9ZqoXKXHQONazhCENqIrFLfc8J3aX/F0QelQ2s= github.com/jfrog/froggit-go v1.23.0/go.mod h1:wRDryqyp3oe+eHgME2mpnEQmO8XBECIPagFwj0nHmdI= github.com/jfrog/go-mockhttp v0.3.1 h1:/wac8v4GMZx62viZmv4wazB5GNKs+GxawuS1u3maJH8= @@ -406,10 +406,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.20260713081138-6df6041db819 h1:nbhpYN6Sb+oPJWpEHAaNbnwJJ5R2/g/TDnpCKjC/mRA= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260713081138-6df6041db819/go.mod h1:p8yLtbmCxxQucIbLZKnWu0F+EDtj6NLXbRQCEK/nb6o= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260714082320-4fd5a71b4b4a h1:dySEIu0wXHrPr3P1ZdXeMhcriWHJrr1N43oPLEi8QhQ= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260714082320-4fd5a71b4b4a/go.mod h1:BJ8x7NhWxJvhG5Bjca2GwWlZN2bxlo+8AnpuRnZ9RMM= -github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260624085155-5ba797de2616 h1:bioFXGzf3pF2qnC3LZD1S1saWiHSekL4vdsDSWksj/4= -github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260624085155-5ba797de2616/go.mod h1:9R90mhbczGXwW5EGlDs7F08ejQU/xdoDhYHMvzBiqgE= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260719152401-944101b51a48 h1:PW1yFqwE2H13miXu0Fniw/PRjmy6IaszKLyYmkHLD2w= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260719152401-944101b51a48/go.mod h1:AUkBlPrztP9tO0efigUZSia0GaMTzVriHWE2LdKcPG0= +github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9 h1:GFp/yYXn97xpNRVUpzvy0Ol4WyW61oHyVZ5nxPpdIw0= +github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9/go.mod h1:9R90mhbczGXwW5EGlDs7F08ejQU/xdoDhYHMvzBiqgE= github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f h1:MV4BATdkEoUYJmdPDvaB9EBb8JQZg28n/K4X7dcmyAY= github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f/go.mod h1:t2luv7YHtrKe/Yf1xLZgLOkkiPtk1DsKj0OLXL2GwYo= github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab h1:Zn/qB8LYhSu82YDtbqXwErN1RPHTHe/a3gQY6Ti/OBE= diff --git a/packagealias/packagealias.go b/packagealias/packagealias.go index de3f01629..8c1881f82 100644 --- a/packagealias/packagealias.go +++ b/packagealias/packagealias.go @@ -33,6 +33,7 @@ var SupportedTools = []string{ "docker", "gem", "bundle", + "apk", } // AliasMode represents how a tool should be handled diff --git a/utils/cliutils/commandsflags.go b/utils/cliutils/commandsflags.go index 4cf45994e..33dd7107e 100644 --- a/utils/cliutils/commandsflags.go +++ b/utils/cliutils/commandsflags.go @@ -89,6 +89,7 @@ const ( ConanConfig = "conan-config" Conan = "conan" Nix = "nix" + Apk = "apk" Ping = "ping" RtCurl = "rt-curl" TemplateConsumer = "template-consumer" @@ -161,6 +162,10 @@ const ( serverIdNpm = "server-id-npm" disableTokenRefresh = "disable-token-refresh" + // Alpine (apk) flag keys — repo and branch reuse the existing flag constants. + apkAlpineVersion = "alpine-version" + apkArch = "arch" + passwordStdin = "password-stdin" accessTokenStdin = "access-token-stdin" @@ -776,6 +781,14 @@ var flagsMap = map[string]cli.Flag{ Name: serverId, Usage: "[Optional] Server ID configured using the 'jf config' command. Used in native mode (JFROG_RUN_NATIVE=true) to identify the JFrog server for usage reporting.` `", }, + apkAlpineVersion: cli.StringFlag{ + Name: apkAlpineVersion, + Usage: "[Optional] Alpine release version, e.g. v3.20.` `", + }, + apkArch: cli.StringFlag{ + Name: apkArch, + Usage: "[Optional] CPU architecture override (x86_64, aarch64, armhf, …). Inferred from filename by default.` `", + }, passwordStdin: cli.BoolFlag{ Name: passwordStdin, Usage: "[Default: false] Set to true to provide the password via stdin.` `", @@ -2243,6 +2256,9 @@ var commandFlags = map[string][]string{ Nix: { BuildName, BuildNumber, module, Project, serverId, }, + Apk: { + serverId, repo, apkAlpineVersion, branch, apkArch, BuildName, BuildNumber, module, Project, user, password, + }, Stats: { XrFormat, accessToken, serverId, }, From 6519e09d39837f2d20957f884681c851f25edac3 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Mon, 20 Jul 2026 10:02:05 +0530 Subject: [PATCH 2/7] RTECO-945: Bump jfrog-cli-artifactory to 70719d6 (fix stale repo entries) Co-authored-by: Cursor --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e32160d4c..b16bc74b0 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/jfrog/build-info-go v1.13.1-0.20260717181252-20cc7766a16d github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-cli-application v1.0.2-0.20260713081138-6df6041db819 - github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260719152401-944101b51a48 + github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720043147-70719d6117de github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9 github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab diff --git a/go.sum b/go.sum index 0f332c53f..cd7743ad0 100644 --- a/go.sum +++ b/go.sum @@ -406,8 +406,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.20260713081138-6df6041db819 h1:nbhpYN6Sb+oPJWpEHAaNbnwJJ5R2/g/TDnpCKjC/mRA= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260713081138-6df6041db819/go.mod h1:p8yLtbmCxxQucIbLZKnWu0F+EDtj6NLXbRQCEK/nb6o= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260719152401-944101b51a48 h1:PW1yFqwE2H13miXu0Fniw/PRjmy6IaszKLyYmkHLD2w= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260719152401-944101b51a48/go.mod h1:AUkBlPrztP9tO0efigUZSia0GaMTzVriHWE2LdKcPG0= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720043147-70719d6117de h1:99e2ktyo4aC24F2kK5teI0EmZSLSU6y/wZvrA/8Pt5A= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720043147-70719d6117de/go.mod h1:AUkBlPrztP9tO0efigUZSia0GaMTzVriHWE2LdKcPG0= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9 h1:GFp/yYXn97xpNRVUpzvy0Ol4WyW61oHyVZ5nxPpdIw0= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9/go.mod h1:9R90mhbczGXwW5EGlDs7F08ejQU/xdoDhYHMvzBiqgE= github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f h1:MV4BATdkEoUYJmdPDvaB9EBb8JQZg28n/K4X7dcmyAY= From eeabbd5b8f374b3d8456fb7c557cef76480dfb91 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Mon, 20 Jul 2026 10:42:02 +0530 Subject: [PATCH 3/7] RTECO-945: Bump jfrog-cli-artifactory (handle --no-cache in build info) Co-authored-by: Cursor --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b16bc74b0..cd6cd3445 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/jfrog/build-info-go v1.13.1-0.20260717181252-20cc7766a16d github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-cli-application v1.0.2-0.20260713081138-6df6041db819 - github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720043147-70719d6117de + github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720051152-cc78ec5f8577 github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9 github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab diff --git a/go.sum b/go.sum index cd7743ad0..738ceb788 100644 --- a/go.sum +++ b/go.sum @@ -406,8 +406,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.20260713081138-6df6041db819 h1:nbhpYN6Sb+oPJWpEHAaNbnwJJ5R2/g/TDnpCKjC/mRA= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260713081138-6df6041db819/go.mod h1:p8yLtbmCxxQucIbLZKnWu0F+EDtj6NLXbRQCEK/nb6o= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720043147-70719d6117de h1:99e2ktyo4aC24F2kK5teI0EmZSLSU6y/wZvrA/8Pt5A= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720043147-70719d6117de/go.mod h1:AUkBlPrztP9tO0efigUZSia0GaMTzVriHWE2LdKcPG0= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720051152-cc78ec5f8577 h1:wQUm6iXBOWoDQp4bjbR+Hgq7tXbFNVmcFw+uedwYVQk= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720051152-cc78ec5f8577/go.mod h1:AUkBlPrztP9tO0efigUZSia0GaMTzVriHWE2LdKcPG0= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9 h1:GFp/yYXn97xpNRVUpzvy0Ol4WyW61oHyVZ5nxPpdIw0= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9/go.mod h1:9R90mhbczGXwW5EGlDs7F08ejQU/xdoDhYHMvzBiqgE= github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f h1:MV4BATdkEoUYJmdPDvaB9EBb8JQZg28n/K4X7dcmyAY= From 4408477e988080d30dcd6647765609774d30f3e6 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Mon, 20 Jul 2026 10:44:43 +0530 Subject: [PATCH 4/7] RTECO-945: Bump jfrog-cli-artifactory (reuse --cache-dir fix) Co-authored-by: Cursor --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cd6cd3445..2bcf5756e 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/jfrog/build-info-go v1.13.1-0.20260717181252-20cc7766a16d github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-cli-application v1.0.2-0.20260713081138-6df6041db819 - github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720051152-cc78ec5f8577 + github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720051434-d17b5fcf971d github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9 github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab diff --git a/go.sum b/go.sum index 738ceb788..d06592134 100644 --- a/go.sum +++ b/go.sum @@ -406,8 +406,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.20260713081138-6df6041db819 h1:nbhpYN6Sb+oPJWpEHAaNbnwJJ5R2/g/TDnpCKjC/mRA= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260713081138-6df6041db819/go.mod h1:p8yLtbmCxxQucIbLZKnWu0F+EDtj6NLXbRQCEK/nb6o= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720051152-cc78ec5f8577 h1:wQUm6iXBOWoDQp4bjbR+Hgq7tXbFNVmcFw+uedwYVQk= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720051152-cc78ec5f8577/go.mod h1:AUkBlPrztP9tO0efigUZSia0GaMTzVriHWE2LdKcPG0= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720051434-d17b5fcf971d h1:fkvK347aDyHtxtB30VreLRbO1sDNqkcJhaOz11qF/rk= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720051434-d17b5fcf971d/go.mod h1:AUkBlPrztP9tO0efigUZSia0GaMTzVriHWE2LdKcPG0= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9 h1:GFp/yYXn97xpNRVUpzvy0Ol4WyW61oHyVZ5nxPpdIw0= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9/go.mod h1:9R90mhbczGXwW5EGlDs7F08ejQU/xdoDhYHMvzBiqgE= github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f h1:MV4BATdkEoUYJmdPDvaB9EBb8JQZg28n/K4X7dcmyAY= From 51b8387fcbbfbd6ee3bcaabb292af8ac7548d182 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Mon, 20 Jul 2026 12:09:40 +0530 Subject: [PATCH 5/7] RTECO-945: Bump jfrog-cli-artifactory to 0ef644b Co-authored-by: Cursor --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2bcf5756e..f6b47fb2c 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/jfrog/build-info-go v1.13.1-0.20260717181252-20cc7766a16d github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-cli-application v1.0.2-0.20260713081138-6df6041db819 - github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720051434-d17b5fcf971d + github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720063930-0ef644bbf598 github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9 github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab diff --git a/go.sum b/go.sum index d06592134..e8174e7ce 100644 --- a/go.sum +++ b/go.sum @@ -406,8 +406,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.20260713081138-6df6041db819 h1:nbhpYN6Sb+oPJWpEHAaNbnwJJ5R2/g/TDnpCKjC/mRA= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260713081138-6df6041db819/go.mod h1:p8yLtbmCxxQucIbLZKnWu0F+EDtj6NLXbRQCEK/nb6o= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720051434-d17b5fcf971d h1:fkvK347aDyHtxtB30VreLRbO1sDNqkcJhaOz11qF/rk= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720051434-d17b5fcf971d/go.mod h1:AUkBlPrztP9tO0efigUZSia0GaMTzVriHWE2LdKcPG0= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720063930-0ef644bbf598 h1:JVayyn9ghn6aOX+O0lttrbrE0Vadbj/wT5bXmMIyMpw= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720063930-0ef644bbf598/go.mod h1:AUkBlPrztP9tO0efigUZSia0GaMTzVriHWE2LdKcPG0= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9 h1:GFp/yYXn97xpNRVUpzvy0Ol4WyW61oHyVZ5nxPpdIw0= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9/go.mod h1:9R90mhbczGXwW5EGlDs7F08ejQU/xdoDhYHMvzBiqgE= github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f h1:MV4BATdkEoUYJmdPDvaB9EBb8JQZg28n/K4X7dcmyAY= From 6642be2314c34cbcc615344001b7a791e884d242 Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Mon, 20 Jul 2026 12:41:17 +0530 Subject: [PATCH 6/7] RTECO-945: Bump jfrog-cli-artifactory to cfbb5a4 (requestedBy path) Co-authored-by: Cursor --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f6b47fb2c..507afdd93 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/jfrog/build-info-go v1.13.1-0.20260717181252-20cc7766a16d github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-cli-application v1.0.2-0.20260713081138-6df6041db819 - github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720063930-0ef644bbf598 + github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720071108-cfbb5a4c3d4f github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9 github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab diff --git a/go.sum b/go.sum index e8174e7ce..178b7b352 100644 --- a/go.sum +++ b/go.sum @@ -406,8 +406,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.20260713081138-6df6041db819 h1:nbhpYN6Sb+oPJWpEHAaNbnwJJ5R2/g/TDnpCKjC/mRA= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260713081138-6df6041db819/go.mod h1:p8yLtbmCxxQucIbLZKnWu0F+EDtj6NLXbRQCEK/nb6o= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720063930-0ef644bbf598 h1:JVayyn9ghn6aOX+O0lttrbrE0Vadbj/wT5bXmMIyMpw= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720063930-0ef644bbf598/go.mod h1:AUkBlPrztP9tO0efigUZSia0GaMTzVriHWE2LdKcPG0= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720071108-cfbb5a4c3d4f h1:JNaeLfZEaANV7czzw5cE4NF34knBh6OQtNL8ZwsYAIQ= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720071108-cfbb5a4c3d4f/go.mod h1:AUkBlPrztP9tO0efigUZSia0GaMTzVriHWE2LdKcPG0= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9 h1:GFp/yYXn97xpNRVUpzvy0Ol4WyW61oHyVZ5nxPpdIw0= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9/go.mod h1:9R90mhbczGXwW5EGlDs7F08ejQU/xdoDhYHMvzBiqgE= github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f h1:MV4BATdkEoUYJmdPDvaB9EBb8JQZg28n/K4X7dcmyAY= From 14bf2245dea773e18091dff3f4213d19c4e5be3f Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Mon, 20 Jul 2026 12:57:44 +0530 Subject: [PATCH 7/7] RTECO-945: Bump jfrog-cli-artifactory to f124ded (virtual repo resolution) Co-authored-by: Cursor --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 507afdd93..29da5e68b 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/jfrog/build-info-go v1.13.1-0.20260717181252-20cc7766a16d github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-cli-application v1.0.2-0.20260713081138-6df6041db819 - github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720071108-cfbb5a4c3d4f + github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720072733-f124ded30636 github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9 github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260618062042-6053ab368cab diff --git a/go.sum b/go.sum index 178b7b352..6bad784e9 100644 --- a/go.sum +++ b/go.sum @@ -406,8 +406,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.20260713081138-6df6041db819 h1:nbhpYN6Sb+oPJWpEHAaNbnwJJ5R2/g/TDnpCKjC/mRA= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260713081138-6df6041db819/go.mod h1:p8yLtbmCxxQucIbLZKnWu0F+EDtj6NLXbRQCEK/nb6o= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720071108-cfbb5a4c3d4f h1:JNaeLfZEaANV7czzw5cE4NF34knBh6OQtNL8ZwsYAIQ= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720071108-cfbb5a4c3d4f/go.mod h1:AUkBlPrztP9tO0efigUZSia0GaMTzVriHWE2LdKcPG0= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720072733-f124ded30636 h1:EvKxTbOtzYbqdQQNKVIeXbaLm2OS7XtChVX5KVzwNmE= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260720072733-f124ded30636/go.mod h1:AUkBlPrztP9tO0efigUZSia0GaMTzVriHWE2LdKcPG0= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9 h1:GFp/yYXn97xpNRVUpzvy0Ol4WyW61oHyVZ5nxPpdIw0= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260717173016-2551658ff6b9/go.mod h1:9R90mhbczGXwW5EGlDs7F08ejQU/xdoDhYHMvzBiqgE= github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260618135203-4d2bdd4ee35f h1:MV4BATdkEoUYJmdPDvaB9EBb8JQZg28n/K4X7dcmyAY=