From e1cfd15dc806ebea4913028389823b8f18c5287a Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Mon, 22 Jun 2026 03:02:32 +0530 Subject: [PATCH 1/2] RTECO-945 - Wire Alpine APK commands in jfrog-cli Register jf apk / jf apk config / jf apk upload in jfrog-cli and update module dependencies to pick up the new Alpine implementation: - buildtools/cli.go: Add ApkCmd dispatcher with subcommand routing for config, upload, and native apk passthrough; extract --server-id, --repo, --alpine-version, --user, --password, --branch, --arch, --apply from args; builder-pattern construction of ApkCommand / ApkConfigCommand / ApkUploadCommand - docs/buildtools/apkcommand/help.go: Usage, description, and argument reference strings for jf apk --help - utils/cliutils/commandsflags.go: Add Apk constant, apkAlpineVersion / apkArch flag definitions, and Apk command flag list - go.mod: Upgrade jfrog-cli-artifactory to v0.8.1-0.20260621212942-c4258681e69a and build-info-go to v1.13.1-0.20260621212604-330cfe272ba6 Co-authored-by: Cursor --- buildtools/cli.go | 145 +++++++++++++++++++++++++++++ docs/buildtools/apkcommand/help.go | 82 ++++++++++++++++ go.mod | 4 +- go.sum | 8 +- utils/cliutils/commandsflags.go | 16 ++++ 5 files changed, 249 insertions(+), 6 deletions(-) create mode 100644 docs/buildtools/apkcommand/help.go diff --git a/buildtools/cli.go b/buildtools/cli.go index 4daff389c..7607a4996 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: apkcommand.GetDescription(), + HelpName: corecommon.CreateUsage("apk", apkcommand.GetDescription(), apkcommand.Usage), + UsageText: apkcommand.GetArguments(), + ArgsUsage: common.CreateEnvVars(), + SkipFlagParsing: true, + BashComplete: corecommon.CreateBashCompletionFunc("config", "upload", "add", "upgrade", "update", "fetch", "search", "del", "info"), + Category: buildToolsCategory, + Action: ApkCmd, + }, { Name: "ruby-config", Flags: cliutils.GetCommandFlags(cliutils.RubyConfig), @@ -2118,6 +2132,137 @@ func NixCmd(c *cli.Context) error { return commands.ExecWithPackageManager(cmd, "nix") } +// ApkCmd dispatches jf apk subcommands: config, 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 != "" { + serverDetails, err = coreConfig.GetSpecificConfig(serverID, false, false) + } else { + serverDetails, err = coreConfig.GetDefaultServerConf() + } + if err != nil { + log.Warn("No JFrog server configured — skipping credential injection. Run: jf c add") + } + + switch subcmd { + case "config": + return apkConfigSubCmd(remainingArgs, serverDetails, repoKey, alpineVersion, username, password) + 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") + } +} + +// apkConfigSubCmd handles jf apk config: downloads the Artifactory RSA public key and +// optionally writes it to disk. +func apkConfigSubCmd(args []string, serverDetails *coreConfig.ServerDetails, repoKey, alpineVersion, username, password string) error { + args, branch, err := coreutils.ExtractStringOptionFromArgs(args, "branch") + if err != nil { + return errorutils.CheckErrorf("failed to extract --branch: %w", err) + } + if branch == "" { + branch = "main" + } + args, applyFlag, err := coreutils.ExtractBoolFlagFromArgs(args, "apply") + if err != nil { + return errorutils.CheckErrorf("failed to extract --apply: %w", err) + } + cmd := alpinecommand.NewApkConfigCommand(). + SetServerDetails(serverDetails). + SetRepo(repoKey). + SetAlpineVersion(alpineVersion). + SetBranch(branch). + SetUsername(username). + SetPassword(password). + SetApply(applyFlag) + 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..4989746f4 --- /dev/null +++ b/docs/buildtools/apkcommand/help.go @@ -0,0 +1,82 @@ +package apkcommand + +var Usage = []string{ + "apk config --server-id --repo --alpine-version [--branch ]", + "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: configure trust, 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 three modes of operation for working with Artifactory Alpine repositories: + +1. jf apk config — Bootstrap RSA key trust and repository URL. + Downloads the Artifactory RSA public key for the target Alpine repo, + writes it to /etc/apk/keys/, and adds the repo URL to /etc/apk/repositories. + +2. 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. + +3. 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.` +} + +// GetArguments returns the argument reference shown in jf apk --help. +func GetArguments() string { + return ` apk subcommand + Subcommands: + config — bootstrap RSA key trust and repository URL for an Artifactory Alpine repo + 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. + + config-specific flags: + --branch Alpine repo branch (main|community|edge|). Default: main. + + 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 apk config --server-id my-server --repo my-alpine-repo --alpine-version v3.20 + 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 01570e815..17af8e732 100644 --- a/go.mod +++ b/go.mod @@ -18,10 +18,10 @@ 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.20260615080618-42488b58c305 + github.com/jfrog/build-info-go v1.13.1-0.20260621212604-330cfe272ba6 github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-cli-application v1.0.2-0.20260617073349-d68ee3120aa8 - github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260618051529-1b76b6ad2606 + github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260621212942-c4258681e69a github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260615072209-8ccac4f0072e github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260601141509-8df6c9a4bc9b github.com/jfrog/jfrog-cli-platform-services v1.10.1-0.20260601140139-4cefb6add7b7 diff --git a/go.sum b/go.sum index 381622e55..333734640 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.20260615080618-42488b58c305 h1:q7/hTPm6ibQf45CztScTgPb8cAmKIeQ9im0ClISsq7Y= -github.com/jfrog/build-info-go v1.13.1-0.20260615080618-42488b58c305/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE= +github.com/jfrog/build-info-go v1.13.1-0.20260621212604-330cfe272ba6 h1:q+1E4BWMzR053ipbG14E7JyNynVcK38meITma8PDc8U= +github.com/jfrog/build-info-go v1.13.1-0.20260621212604-330cfe272ba6/go.mod h1:CYRUCvLKfyARjoJXLWAxce1qNUxTEtbRKAARkV42vpE= github.com/jfrog/froggit-go v1.22.0 h1:eeN5F8sOUo+h2cXkzArAu4nvSdjkDTAZtgqwrct70qg= github.com/jfrog/froggit-go v1.22.0/go.mod h1:wRDryqyp3oe+eHgME2mpnEQmO8XBECIPagFwj0nHmdI= github.com/jfrog/go-mockhttp v0.3.1 h1:/wac8v4GMZx62viZmv4wazB5GNKs+GxawuS1u3maJH8= @@ -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.20260617073349-d68ee3120aa8 h1:FG+SfgPgrIuBHSos4sw4KNZq2MKxebbCZ6KZZRfaYcs= github.com/jfrog/jfrog-cli-application v1.0.2-0.20260617073349-d68ee3120aa8/go.mod h1:p8yLtbmCxxQucIbLZKnWu0F+EDtj6NLXbRQCEK/nb6o= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260618051529-1b76b6ad2606 h1:hlc8XoqySjbrvKKjxswyXQ/q5I0Px9FcZpVZUTd+T3M= -github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260618051529-1b76b6ad2606/go.mod h1:VqV0Bed11HoBlugAEGa3RumbwnDVslEf0gKocTzLs9s= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260621212942-c4258681e69a h1:Ht4Hqv/XPgSdVLueKHBxU3iPGxMK9PkpWus5vpqw8yU= +github.com/jfrog/jfrog-cli-artifactory v0.8.1-0.20260621212942-c4258681e69a/go.mod h1:REu8s+cg/Ro/gdBw7mgAUX3nXskYhLjzaFEv5QJOl4g= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260615072209-8ccac4f0072e h1:E3B8OyEkCsdEdGsZifTphBDUPrd00yKoemL9+l25Qj8= github.com/jfrog/jfrog-cli-core/v2 v2.60.1-0.20260615072209-8ccac4f0072e/go.mod h1:9R90mhbczGXwW5EGlDs7F08ejQU/xdoDhYHMvzBiqgE= github.com/jfrog/jfrog-cli-evidence v0.9.5-0.20260601141509-8df6c9a4bc9b h1:V0FxnU3xh29y8yJHWymm6rPr1MrjG1DdPQlr3ckImwk= 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 213f8f6e50bb24ad98a78ce60692ee6691fa0c8a Mon Sep 17 00:00:00 2001 From: Naveen Kumar Date: Mon, 22 Jun 2026 03:06:40 +0530 Subject: [PATCH 2/2] RTECO-945 - Fix ineffassign: discard unused args after ExtractBoolFlagFromArgs Co-authored-by: Cursor --- buildtools/cli.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildtools/cli.go b/buildtools/cli.go index 7607a4996..23a228369 100644 --- a/buildtools/cli.go +++ b/buildtools/cli.go @@ -2212,7 +2212,7 @@ func apkConfigSubCmd(args []string, serverDetails *coreConfig.ServerDetails, rep if branch == "" { branch = "main" } - args, applyFlag, err := coreutils.ExtractBoolFlagFromArgs(args, "apply") + _, applyFlag, err := coreutils.ExtractBoolFlagFromArgs(args, "apply") if err != nil { return errorutils.CheckErrorf("failed to extract --apply: %w", err) }