Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2449217
RTECO-1648 - Implement jf agent apm command with JFrog Artifactory au…
udaykb2 Jul 27, 2026
9daad04
Merge branch 'main' into RTECO-1648-apm-support-implementation
udaykb2 Jul 28, 2026
ea04030
RTECO-1648 - Fix gosec findings in apm package (Go-Sec CI check)
udaykb2 Jul 28, 2026
859187e
RTECO-1648 - Bump jfrog-cli-core to the pushed RTECO-1648 commit
udaykb2 Jul 28, 2026
ff8d85e
RTECO-1648 - Drop direct-credential flags from apm, rename flagkit keys
udaykb2 Jul 28, 2026
0d839d2
RTECO-1648 - Replace regexp version parsing with a plain function
udaykb2 Jul 28, 2026
8f9e3b1
RTECO-1648 - Drop --server-id/--repo from apm, rename flagkit key, cl…
udaykb2 Jul 28, 2026
192cf73
RTECO-1648 - Fix apm.yml registries: block silently discarded with de…
udaykb2 Jul 28, 2026
700376f
Fix TestResolveRepoNameFromRegistry failing on Windows
udaykb2 Jul 30, 2026
f6e3616
Add AI help descriptions for APM commands (install, publish, update)
udaykb2 Jul 30, 2026
fabe9b1
Merge branch 'main' into RTECO-1648-apm-support-implementation
udaykb2 Jul 30, 2026
17333ab
RTECO-1648 - Replace AQL checksum lookup with HEAD, fix PR #518 revie…
udaykb2 Aug 1, 2026
f218bf8
Document apm passthrough capability and fix its --help handling
udaykb2 Aug 1, 2026
1d4c81e
Merge branch 'main' into RTECO-1648-apm-support-implementation
udaykb2 Aug 1, 2026
93293ae
Isolate AgentPackages const from the PackageTypes alignment group
udaykb2 Aug 1, 2026
e3d6c56
Add agent-apm entry to packageManagerConfigs
udaykb2 Aug 1, 2026
5dfbbf6
Fix 3 new CodeRabbit findings on PR #518
udaykb2 Aug 1, 2026
f28d7ac
Deduplicate repeated string literals and trim comments in apm code
udaykb2 Aug 1, 2026
40891e2
Fix apm publish artifact linkage in Artifactory build browser
udaykb2 Aug 2, 2026
727005d
Add comprehensive tests for BuildRegistryEntry and token generation
udaykb2 Aug 2, 2026
6964fe1
Fix APM access token generation: wrong endpoint and response field
udaykb2 Aug 2, 2026
db4844f
Detect APM validation failures that exit with code 0
udaykb2 Aug 2, 2026
86b9aa6
Resolve build-info repo name from --registry/default instead of host-…
udaykb2 Aug 2, 2026
bfdcddb
Unify apm install/publish build-info module IDs to name:version
udaykb2 Aug 2, 2026
dab7393
Fix data race sharing one HttpClientDetails across concurrent checksu…
udaykb2 Aug 3, 2026
cdbc586
Add local-zip fallback for publish checksum, matching cargo/ruby's pa…
udaykb2 Aug 3, 2026
5fd8b16
Fix build-info gaps in apm install/publish/update: dry-run, global,
udaykb2 Aug 3, 2026
5cc0db5
Merge branch 'main' into RTECO-1648-apm-support-implementation
udaykb2 Aug 4, 2026
e3b9a34
Give apm dependencies a single dev/prod/transitive scope, pnpm-style
udaykb2 Aug 4, 2026
04dcc73
Add real command examples to install/update help, matching publish's
udaykb2 Aug 4, 2026
20eccdf
Only log APM build-info skip messages when collection is enabled
udaykb2 Aug 4, 2026
207f450
Remove APM --global build-info skip special-casing
udaykb2 Aug 4, 2026
be0940c
Add apt command package from main for CLI compatibility
udaykb2 Aug 4, 2026
3a4ca0f
Merge main into RTECO-1648-apm-support-implementation
udaykb2 Aug 14, 2026
493ba9c
Remove separate folder for passthrough
udaykb2 Aug 14, 2026
2229d1c
RTECO-1648 - Bump min supported apm version to 0.23.0 and rename setu…
udaykb2 Aug 16, 2026
e2fe466
Merge branch 'main' into RTECO-1648-apm-support-implementation
udaykb2 Aug 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions agent/apm/cli/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cli

import (
"github.com/jfrog/jfrog-cli-artifactory/agent/apm/commands/install"
"github.com/jfrog/jfrog-cli-artifactory/agent/apm/commands/publish"
"github.com/jfrog/jfrog-cli-artifactory/agent/apm/commands/update"
"github.com/jfrog/jfrog-cli-artifactory/cliutils/flagkit"
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
)

// GetSubCommands returns the leaf commands for `jf agent apm`.
// Commands not listed here fall through to the passthrough handler set on the parent.
// "lock" is deliberately not listed here — it doesn't deploy anything, so there's nothing
// a build actually consumed to report; it's served by the generic passthrough like every
// other read/resolve-only apm command.
func GetSubCommands() []components.Command {
return []components.Command{
{
Name: "install",
Flags: flagkit.GetCommandFlags(flagkit.AgentApm),
// SkipFlagParsing so apm-native flags (e.g. --frozen) that aren't in jf's own
// declared flag set above aren't rejected by urfave/cli before reaching apm.
// RunInstall extracts jf's own flags manually via ExtractApmSubcommandOptions.
SkipFlagParsing: true,
Description: "Install APM packages with JFrog Artifactory authentication.",
AIDescription: install.GetAIDescription(),
Action: install.RunInstall,
},
{
Name: "publish",
Flags: flagkit.GetCommandFlags(flagkit.AgentApm),
SkipFlagParsing: true,
Description: "Publish an APM package to JFrog Artifactory.",
AIDescription: publish.GetAIDescription(),
Action: publish.RunPublish,
},
{
Name: "update",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need to support update command for build info collection? can you please give an example here why ?

Flags: flagkit.GetCommandFlags(flagkit.AgentApm),
SkipFlagParsing: true,
Description: "Refresh APM dependencies to their latest matching refs, with build-info collection.",
AIDescription: update.GetAIDescription(),
Action: update.RunUpdate,
},
}
}
35 changes: 35 additions & 0 deletions agent/apm/commands/install/help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package install

func GetDescription() string {
return "Install APM packages with JFrog Artifactory authentication."
}

func GetAIDescription() string {
return `Install packages declared in apm.yml with authenticated access to JFrog Artifactory registries.

When to use:
- Installing packages into an agent project that has apm.yml configured.
- Accessing private or curated packages from Artifactory via registry credentials.
- Collecting build-info about package dependencies in CI/CD pipelines.

Prerequisites:
- apm CLI (>= 0.1.0) installed and in PATH.
- A registry declared in apm.yml's registries: block or configured via jf setup agent-apm.
- Read permission on the source Artifactory agentpackages repository.

Common patterns:
$ jf agent apm install
$ jf agent apm install --build-name=my-build --build-number=1

Build info:
- Enabled with --build-name and --build-number flags.
- Captures installed packages and their transitive dependencies.
- Published to Artifactory for traceability and compliance.

Environment:
- Credentials injected via APM_REGISTRY_TOKEN_<NAME>, APM_REGISTRY_USER_<NAME>, APM_REGISTRY_PASS_<NAME>.
- Registry configuration sourced from ~/.apm/config.json (set by jf setup agent-apm).
- Lockfile apm.lock.yaml created in working directory.

Related: jf agent apm publish, jf agent apm update, jf setup agent-apm`
}
101 changes: 101 additions & 0 deletions agent/apm/commands/install/install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package install

import (
"fmt"
"os"
"path/filepath"

apmcommon "github.com/jfrog/jfrog-cli-artifactory/agent/apm/common"
agentcommon "github.com/jfrog/jfrog-cli-artifactory/agent/common"
buildUtils "github.com/jfrog/jfrog-cli-core/v2/common/build"
"github.com/jfrog/jfrog-cli-core/v2/common/commands"
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
"github.com/jfrog/jfrog-cli-core/v2/utils/config"
"github.com/jfrog/jfrog-client-go/utils/log"
)

// ApmInstallCommand runs `apm install` with JFrog Artifactory authentication and collects
// build-info from the resulting apm.lock.yaml.
//
// Unlike passthrough commands, install never accepts --repo: no other package-manager
// integration in this CLI supports declaring a new repository at run time either - they all
// require the one-time `jf setup <tool>` step first (§3, "jf setup agent-apm"). A registry
// must already be declared (via jf setup agent-apm or apm.yml's own registries: block) before
// install can authenticate against it.
type ApmInstallCommand struct {
args []string
serverDetails *config.ServerDetails
buildConfiguration *buildUtils.BuildConfiguration
}

func NewApmInstallCommand() *ApmInstallCommand {
return &ApmInstallCommand{}
}

func (c *ApmInstallCommand) SetArgs(args []string) *ApmInstallCommand {
c.args = args
return c
}

func (c *ApmInstallCommand) SetServerDetails(serverDetails *config.ServerDetails) *ApmInstallCommand {
c.serverDetails = serverDetails
return c
}

func (c *ApmInstallCommand) SetBuildConfiguration(buildConfiguration *buildUtils.BuildConfiguration) *ApmInstallCommand {
c.buildConfiguration = buildConfiguration
return c
}

func (c *ApmInstallCommand) CommandName() string {
return "rt_agent_apm_install"
}

func (c *ApmInstallCommand) ServerDetails() (*config.ServerDetails, error) {
return c.serverDetails, nil
}

func (c *ApmInstallCommand) Run() error {
log.Info("Running apm install...")

if err := apmcommon.RunApmSubcommandWithAuth("install", c.args, c.serverDetails); err != nil {
return fmt.Errorf("run apm install: %w", err)
}

workingDir, err := os.Getwd()
if err != nil {
log.Warn("apm install completed, but could not determine working directory for build info:", err.Error())
} else {
lockfilePath := filepath.Join(workingDir, apmcommon.ApmLockfileName)
manifestPath := filepath.Join(workingDir, apmcommon.ApmManifestName)
if biErr := apmcommon.CollectAndSaveInstallBuildInfo(lockfilePath, manifestPath, c.serverDetails, c.buildConfiguration); biErr != nil {
log.Warn("apm install completed, but build info collection failed:", biErr.Error())
}
}

log.Info("apm install finished successfully.")
return nil
}

// RunInstall is the CLI action handler for `jf agent apm install`.
func RunInstall(c *components.Context) error {
if apmcommon.IsHelpRequest(c.Arguments) {
return apmcommon.RunApmCommand(nil, "install", []string{"--help"})
}

opts, err := apmcommon.ExtractApmSubcommandOptions(c.Arguments)
if err != nil {
return err
}
serverDetails, err := agentcommon.GetServerDetails(c)
if err != nil {
return err
}

cmd := NewApmInstallCommand().
SetArgs(opts.RemainingArgs).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean by remaining args? it seems vague can you please change the name.

SetServerDetails(serverDetails).
SetBuildConfiguration(opts.BuildConfig)

return commands.ExecWithPackageManager(cmd, "agent-apm")
}
77 changes: 77 additions & 0 deletions agent/apm/commands/passthrough/passthrough.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package passthrough

import (
apmcommon "github.com/jfrog/jfrog-cli-artifactory/agent/apm/common"
agentcommon "github.com/jfrog/jfrog-cli-artifactory/agent/common"
"github.com/jfrog/jfrog-cli-core/v2/common/commands"
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
"github.com/jfrog/jfrog-cli-core/v2/utils/config"
"github.com/jfrog/jfrog-client-go/utils/log"
)

// ApmPassthroughCommand forwards any apm subcommand with auth environment injected.
type ApmPassthroughCommand struct {
subcmd string
args []string
serverDetails *config.ServerDetails
}

func NewApmPassthroughCommand() *ApmPassthroughCommand {
return &ApmPassthroughCommand{}
}

func (c *ApmPassthroughCommand) SetSubcmd(subcmd string) *ApmPassthroughCommand {
c.subcmd = subcmd
return c
}

func (c *ApmPassthroughCommand) SetArgs(args []string) *ApmPassthroughCommand {
c.args = args
return c
}

func (c *ApmPassthroughCommand) SetServerDetails(serverDetails *config.ServerDetails) *ApmPassthroughCommand {
c.serverDetails = serverDetails
return c
}

func (c *ApmPassthroughCommand) CommandName() string {
return "rt_agent_apm_" + c.subcmd
}

func (c *ApmPassthroughCommand) ServerDetails() (*config.ServerDetails, error) {
return c.serverDetails, nil
}

func (c *ApmPassthroughCommand) Run() error {
log.Info("Running apm " + c.subcmd + "...")
return apmcommon.RunApmSubcommandWithAuth(c.subcmd, c.args, c.serverDetails)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

// RunApmPassthroughDefault handles any `jf agent apm <subcmd>` where <subcmd> is not one of the
// registered subcommands (install/publish/update). The subcmd is the first element of
// c.Arguments; every remaining element is forwarded to apm untouched. Auth always comes from
// the default configured JFrog server - passthrough takes no flags of its own at all, so there's
// nothing to extract from c.Arguments.
func RunApmPassthroughDefault(c *components.Context) error {
if len(c.Arguments) == 0 {
return apmcommon.RunApmCommand(nil, "--help", nil)
}

subcmd := c.Arguments[0]
if apmcommon.IsHelpRequest([]string{subcmd}) {
return apmcommon.RunApmCommand(nil, "--help", nil)
}

serverDetails, err := agentcommon.GetServerDetails(c)
if err != nil {
return err
}

cmd := NewApmPassthroughCommand().
SetSubcmd(subcmd).
SetArgs(c.Arguments[1:]).
SetServerDetails(serverDetails)

return commands.ExecWithPackageManager(cmd, "agent-apm")
}
42 changes: 42 additions & 0 deletions agent/apm/commands/publish/help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package publish

func GetDescription() string {
return "Publish an APM package to JFrog Artifactory."
}

func GetAIDescription() string {
return `Publish an agent package to a JFrog Artifactory agentpackages repository with authenticated access.

When to use:
- Publishing custom agent packages for installation across multiple projects.
- Packaging skills, tools, or other agent extensions for organizational use.
- Creating reproducible, versioned deployments of agent components.

Prerequisites:
- apm CLI (>= 0.1.0) installed and in PATH.
- An apm.yml file in the package directory (or parent directories).
- Write permission on the Artifactory agentpackages repository.
- Registry configured via jf setup agent-apm or apm.yml's registries: block.

Common patterns:
$ jf agent apm publish my-org/my-package
$ jf agent apm publish my-org/my-package --build-name=my-build --build-number=1
$ jf agent apm publish my-org/my-package --build-name=my-build --build-number=1 --module=my-module

Package format:
- Directory with apm.yml declaring name, version, and description.
- Optional skills/ subdirectory containing Cursor Agent Skills.
- Version in apm.yml becomes the published package version.

Build info:
- Enabled with --build-name and --build-number flags.
- Captures package metadata and publishing source.
- Published to Artifactory for traceability and compliance.
- Optional --module to group multiple packages in the same build.

Environment:
- Credentials injected via APM_REGISTRY_TOKEN_<NAME>, APM_REGISTRY_USER_<NAME>, APM_REGISTRY_PASS_<NAME>.
- Registry configuration sourced from ~/.apm/config.json (set by jf setup agent-apm).

Related: jf agent apm install, jf agent apm update, jf setup agent-apm`
}
Loading
Loading