-
Notifications
You must be signed in to change notification settings - Fork 49
RTECO-1648 - Implement jf agent apm command #518
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
udaykb2
wants to merge
37
commits into
main
Choose a base branch
from
RTECO-1648-apm-support-implementation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 33 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 9daad04
Merge branch 'main' into RTECO-1648-apm-support-implementation
udaykb2 ea04030
RTECO-1648 - Fix gosec findings in apm package (Go-Sec CI check)
udaykb2 859187e
RTECO-1648 - Bump jfrog-cli-core to the pushed RTECO-1648 commit
udaykb2 ff8d85e
RTECO-1648 - Drop direct-credential flags from apm, rename flagkit keys
udaykb2 0d839d2
RTECO-1648 - Replace regexp version parsing with a plain function
udaykb2 8f9e3b1
RTECO-1648 - Drop --server-id/--repo from apm, rename flagkit key, cl…
udaykb2 192cf73
RTECO-1648 - Fix apm.yml registries: block silently discarded with de…
udaykb2 700376f
Fix TestResolveRepoNameFromRegistry failing on Windows
udaykb2 f6e3616
Add AI help descriptions for APM commands (install, publish, update)
udaykb2 fabe9b1
Merge branch 'main' into RTECO-1648-apm-support-implementation
udaykb2 17333ab
RTECO-1648 - Replace AQL checksum lookup with HEAD, fix PR #518 revie…
udaykb2 f218bf8
Document apm passthrough capability and fix its --help handling
udaykb2 1d4c81e
Merge branch 'main' into RTECO-1648-apm-support-implementation
udaykb2 93293ae
Isolate AgentPackages const from the PackageTypes alignment group
udaykb2 e3d6c56
Add agent-apm entry to packageManagerConfigs
udaykb2 5dfbbf6
Fix 3 new CodeRabbit findings on PR #518
udaykb2 f28d7ac
Deduplicate repeated string literals and trim comments in apm code
udaykb2 40891e2
Fix apm publish artifact linkage in Artifactory build browser
udaykb2 727005d
Add comprehensive tests for BuildRegistryEntry and token generation
udaykb2 6964fe1
Fix APM access token generation: wrong endpoint and response field
udaykb2 db4844f
Detect APM validation failures that exit with code 0
udaykb2 86b9aa6
Resolve build-info repo name from --registry/default instead of host-…
udaykb2 bfdcddb
Unify apm install/publish build-info module IDs to name:version
udaykb2 dab7393
Fix data race sharing one HttpClientDetails across concurrent checksu…
udaykb2 cdbc586
Add local-zip fallback for publish checksum, matching cargo/ruby's pa…
udaykb2 5fd8b16
Fix build-info gaps in apm install/publish/update: dry-run, global,
udaykb2 5cc0db5
Merge branch 'main' into RTECO-1648-apm-support-implementation
udaykb2 e3b9a34
Give apm dependencies a single dev/prod/transitive scope, pnpm-style
udaykb2 04dcc73
Add real command examples to install/update help, matching publish's
udaykb2 20eccdf
Only log APM build-info skip messages when collection is enabled
udaykb2 207f450
Remove APM --global build-info skip special-casing
udaykb2 be0940c
Add apt command package from main for CLI compatibility
udaykb2 3a4ca0f
Merge main into RTECO-1648-apm-support-implementation
udaykb2 493ba9c
Remove separate folder for passthrough
udaykb2 2229d1c
RTECO-1648 - Bump min supported apm version to 0.23.0 and rename setu…
udaykb2 e2fe466
Merge branch 'main' into RTECO-1648-apm-support-implementation
udaykb2 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| 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 (e.g. | ||
| // "lock", which resolves but deploys nothing) fall through to the parent's passthrough handler. | ||
| 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", | ||
| 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, | ||
| }, | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package cli | ||
|
|
||
| func GetDescription() string { | ||
| return "Agent Package Manager (APM) commands with JFrog Artifactory authentication." | ||
| } | ||
|
|
||
| func GetAIDescription() string { | ||
| return `Run apm against Artifactory-backed registries with credentials injected automatically. Dedicated subcommands install, publish, and update also collect build-info when --build-name and --build-number are set; every other apm command is forwarded with the same authenticated registry access but no build-info collection. | ||
|
|
||
| When to use: | ||
| - Running apm install / publish / update with Artifactory auth and optional build-info. | ||
| - Running any other apm command (lock, outdated, audit, doctor, view, marketplace, mcp, ...) through jf for authenticated registry access. | ||
|
|
||
| Prerequisites: | ||
| - apm CLI installed and on PATH. | ||
| - Registry configured via 'jf setup agent-apm' or an apm.yml registries: block. | ||
| - A configured JFrog Platform server (jf c add / jf login), or pass --server-id. | ||
|
|
||
| Common patterns: | ||
| $ jf agent apm install --build-name=my-build --build-number=1 | ||
| $ jf agent apm publish --package my-org/my-package --build-name=my-build --build-number=1 | ||
| $ jf agent apm update --yes --build-name=my-build --build-number=1 | ||
| $ jf agent apm lock | ||
| $ jf agent apm outdated | ||
|
|
||
| Gotchas: | ||
| - Build-info is collected only by install, publish, and update, and only when both --build-name and --build-number are provided; publish it afterwards with 'jf rt build-publish'. | ||
| - 'jf agent apm <command> --help' shows that command's own help; 'apm --help' lists every native apm command reachable this way. | ||
|
|
||
| Related: jf setup agent-apm, jf agent apm install, jf agent apm publish, jf agent apm update, jf rt build-publish` | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| 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 Artifactory agentpackages repositories, and optionally record a build-info of installed dependencies. | ||
|
|
||
| When to use: | ||
| - Installing packages into an agent project that has apm.yml configured. | ||
| - Pulling private or curated packages from Artifactory. | ||
| - Capturing build-info for an install by passing --build-name and --build-number. | ||
|
|
||
| Prerequisites: | ||
| - apm CLI installed and on 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 my-org/my-package#1.0.0 --target claude | ||
| $ jf agent apm install "my-org/my-package#^1.0.0" --target claude --build-name=my-build --build-number=1 | ||
| $ jf agent apm install --dev my-org/my-dev-tool#1.0.0 | ||
| $ jf agent apm install --dry-run | ||
|
|
||
| Gotchas: | ||
| - A bare tag (#1.0.0) is an exact pin: apm update never moves it. Use a semver range (#^1.0.0, #~1.0.0) if later updates should pick up newer matching versions. | ||
| - --dry-run previews the install without changing anything and skips build-info (nothing real to record). | ||
| - Build-info is collected only when both --build-name and --build-number are provided; publish it afterwards with 'jf rt build-publish'. | ||
|
|
||
| Related: jf agent apm publish, jf agent apm update, jf setup agent-apm, jf rt build-publish` | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| package install | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
|
|
||
| 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" | ||
| ) | ||
|
|
||
| // apmSubcommand is the apm subcommand this package always drives. | ||
| const apmSubcommand = "install" | ||
|
|
||
| // ApmInstallCommand runs `apm install` with JFrog Artifactory authentication and collects | ||
| // build-info from the resulting apm.lock.yaml. Never accepts --repo; a registry must already be | ||
| // declared via jf setup agent-apm or apm.yml's own registries: block. | ||
| 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 apmcommon.CommandNamePrefix + apmSubcommand | ||
| } | ||
|
|
||
| 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(apmSubcommand, c.args, c.serverDetails); err != nil { | ||
| return fmt.Errorf("run apm install: %w", err) | ||
| } | ||
|
|
||
| // Only mention / collect build-info when the user asked for it (--build-name/--build-number or env). | ||
| collectBuildInfo, err := apmcommon.ShouldCollectBuildInfo(c.buildConfiguration) | ||
| if err != nil { | ||
| log.Warn("apm install completed, but could not determine build-info collection state:", err.Error()) | ||
| } else if collectBuildInfo { | ||
| if apmcommon.IsDryRunArg(c.args) { | ||
| log.Info("apm install: --dry-run - nothing was installed, skipping build-info recording.") | ||
| } else if workingDir, wdErr := os.Getwd(); wdErr != nil { | ||
| log.Warn("apm install completed, but could not determine working directory for build info:", wdErr.Error()) | ||
| } else { | ||
| lockfileDir := workingDir | ||
| if rootDir := rootDirFromArgs(c.args); rootDir != "" { | ||
| lockfileDir = rootDir | ||
| if !filepath.IsAbs(lockfileDir) { | ||
| lockfileDir = filepath.Join(workingDir, lockfileDir) | ||
| } | ||
| } | ||
| lockfilePath := filepath.Join(lockfileDir, 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 | ||
| } | ||
|
|
||
| // rootDirFromArgs extracts the value of --root, which redirects apm_modules/ and apm.lock.yaml | ||
| // under DIR instead of the working directory (apm.yml and .apm/ still resolve from $PWD). | ||
| // Returns "" if --root isn't present. | ||
| func rootDirFromArgs(args []string) string { | ||
| for i, arg := range args { | ||
| if arg == "--root" && i+1 < len(args) { | ||
| return args[i+1] | ||
| } | ||
| if cut, ok := strings.CutPrefix(arg, "--root="); ok { | ||
| return cut | ||
| } | ||
| } | ||
| return "" | ||
| } | ||
|
|
||
| // 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, apmSubcommand, []string{apmcommon.HelpFlag}) | ||
| } | ||
|
|
||
| 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). | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, apmcommon.PackageManagerID) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package install | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestRootDirFromArgs(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| args []string | ||
| want string | ||
| }{ | ||
| {name: "--root with space form", args: []string{"--root", "./out"}, want: "./out"}, | ||
| {name: "--root= form", args: []string{"--root=/tmp/build"}, want: "/tmp/build"}, | ||
| {name: "no --root flag", args: []string{"--dry-run"}, want: ""}, | ||
| {name: "--root as last arg with no value", args: []string{"--root"}, want: ""}, | ||
| {name: "empty args", args: []string{}, want: ""}, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| assert.Equal(t, tt.want, rootDirFromArgs(tt.args)) | ||
| }) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| 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 apmcommon.CommandNamePrefix + c.subcmd | ||
| } | ||
|
|
||
| func (c *ApmPassthroughCommand) ServerDetails() (*config.ServerDetails, error) { | ||
| return c.serverDetails, nil | ||
| } | ||
|
|
||
| func (c *ApmPassthroughCommand) Run() error { | ||
| log.Info("Running apm " + apmcommon.SanitizeLogValue(c.subcmd) + "...") | ||
| return apmcommon.RunApmSubcommandWithAuth(c.subcmd, c.args, c.serverDetails) | ||
| } | ||
|
|
||
| // RunApmPassthroughDefault handles any `jf agent apm <subcmd>` not among install/publish/update. | ||
| // Auth always comes from the default configured JFrog server; passthrough takes no flags of its | ||
| // own, so nothing is extracted from c.Arguments beyond the subcommand. | ||
| func RunApmPassthroughDefault(c *components.Context) error { | ||
| if len(c.Arguments) == 0 { | ||
| return apmcommon.RunApmCommand(nil, apmcommon.HelpFlag, nil) | ||
| } | ||
|
|
||
| subcmd := c.Arguments[0] | ||
| if apmcommon.IsHelpRequest([]string{subcmd}) { | ||
| return apmcommon.RunApmCommand(nil, apmcommon.HelpFlag, nil) | ||
| } | ||
| // Show help without resolving server/auth, which a help request never needs. Forward the | ||
| // full remaining arg tail so nested commands like "deps why" get their own help, not "deps"'s. | ||
| if apmcommon.IsHelpRequest(c.Arguments[1:]) { | ||
| return apmcommon.RunApmCommand(nil, subcmd, c.Arguments[1:]) | ||
| } | ||
|
|
||
| serverDetails, err := agentcommon.GetServerDetails(c) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| cmd := NewApmPassthroughCommand(). | ||
| SetSubcmd(subcmd). | ||
| SetArgs(c.Arguments[1:]). | ||
| SetServerDetails(serverDetails) | ||
|
|
||
| return commands.ExecWithPackageManager(cmd, apmcommon.PackageManagerID) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package publish | ||
|
|
||
| func GetDescription() string { | ||
| return "Publish an APM package to JFrog Artifactory." | ||
| } | ||
|
|
||
| func GetAIDescription() string { | ||
| return `Publish an agent package to an Artifactory agentpackages repository with authenticated access, and optionally record a build-info of the published package. | ||
|
|
||
| When to use: | ||
| - Publishing custom agent packages (skills, tools, extensions) for reuse across projects. | ||
| - Creating versioned, reproducible deployments of agent components. | ||
| - Capturing build-info for a publish by passing --build-name and --build-number. | ||
|
|
||
| Prerequisites: | ||
| - apm CLI installed and on PATH. | ||
| - An apm.yml in the package directory (or a parent) declaring name, version, and description. | ||
| - Write permission on the Artifactory agentpackages repository. | ||
| - Registry configured via 'jf setup agent-apm' or an apm.yml registries: block. | ||
|
|
||
| Common patterns: | ||
| $ jf agent apm publish --package my-org/my-package | ||
| $ jf agent apm publish --package my-org/my-package --build-name=my-build --build-number=1 | ||
| $ jf agent apm publish --package my-org/my-package --build-name=my-build --build-number=1 --module=my-module | ||
| $ jf agent apm publish --package my-org/my-package --dry-run | ||
|
|
||
| Gotchas: | ||
| - --package is required (owner/name); it is not inferred from a bare positional argument. | ||
| - --dry-run previews the upload without publishing and skips build-info. | ||
| - Build-info is collected only when both --build-name and --build-number are provided; optional --module groups packages under one module. Publish afterwards with 'jf rt build-publish'. | ||
|
|
||
| Related: jf agent apm install, jf agent apm update, jf setup agent-apm, jf rt build-publish` | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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 ?