Skip to content

Commit e59d990

Browse files
committed
Use --all and --slug for skill update
1 parent bf0fc76 commit e59d990

9 files changed

Lines changed: 542 additions & 136 deletions

File tree

agent/plugins/commands/update/update.go

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ var askYesNo = coreutils.AskYesNo
2424
var isNonInteractive = agentcommon.IsNonInteractive
2525

2626
const updateAllConfirmPrompt = "Update all discovered plugins under the given harness(es) to their latest version in the repository? " +
27-
"Each install folder name is used as the repository slug (same as update --slug). " +
2827
"Matching packages will be updated, including installs that were not made with JFrog CLI."
2928

3029
// pluginBackupDirName is the directory under the plugins parent where update backups are stored.
@@ -50,45 +49,66 @@ type preUpdate struct {
5049
func RunUpdate(c *components.Context) error {
5150
all := c.GetBoolFlagValue("all")
5251
slugFlag := strings.TrimSpace(c.GetStringFlagValue("slug"))
52+
if err := validateUpdateArgs(c, all, slugFlag); err != nil {
53+
return err
54+
}
55+
56+
opts, err := newUpdate(c)
57+
if err != nil {
58+
return err
59+
}
60+
if all {
61+
return runUpdateAllMode(opts)
62+
}
63+
return runSingleSlugUpdate(c, opts, slugFlag)
64+
}
65+
66+
func validateUpdateArgs(c *components.Context, all bool, slugFlag string) error {
5367
if !all && slugFlag == "" {
5468
if c.GetNumberOfArgs() > 0 {
5569
return fmt.Errorf("unexpected positional argument(s); use --slug to specify the plugin")
5670
}
5771
return fmt.Errorf("usage: jf agent plugins update --slug <slug> (--harness <name[,name...]> [--global] [--project-dir <dir>] | --path <dir>) [--repo <repo>] [--version <ver>] [--dry-run] [--force] [--format <table|json>]\n jf agent plugins update --all --harness <name[,name...]> [--global] [--project-dir <dir>] [--repo <repo>] [--dry-run] [--force] [--format <table|json>]")
5872
}
59-
if all {
60-
if slugFlag != "" {
61-
return fmt.Errorf("--all cannot be combined with --slug; it updates every installed plugin for the given --harness list")
62-
}
63-
if c.GetNumberOfArgs() > 0 {
64-
return fmt.Errorf("unexpected positional argument(s); use --slug or --all")
65-
}
66-
if strings.TrimSpace(c.GetStringFlagValue("version")) != "" {
67-
return fmt.Errorf("--all cannot be combined with --version; it always updates to the latest version")
68-
}
69-
if strings.TrimSpace(c.GetStringFlagValue("path")) != "" {
70-
return fmt.Errorf("--all cannot be combined with --path; --path targets a single install directory")
71-
}
73+
if !all {
74+
return nil
7275
}
73-
74-
opts, err := newUpdate(c)
75-
if err != nil {
76-
return err
76+
if slugFlag != "" {
77+
return fmt.Errorf("--all cannot be combined with --slug; it updates every installed plugin for the given --harness list")
78+
}
79+
if c.GetNumberOfArgs() > 0 {
80+
return fmt.Errorf("unexpected positional argument(s); use --slug or --all")
81+
}
82+
if strings.TrimSpace(c.GetStringFlagValue("version")) != "" {
83+
return fmt.Errorf("--all cannot be combined with --version; it always updates to the latest version")
7784
}
78-
if all && opts.flags.AbsoluteInstallBaseDir != "" {
85+
if strings.TrimSpace(c.GetStringFlagValue("path")) != "" {
86+
return fmt.Errorf("--all cannot be combined with --path; --path targets a single install directory")
87+
}
88+
return nil
89+
}
90+
91+
func validateUpdateAllTargets(flags agentcommon.InstallFlagsResult) error {
92+
if flags.AbsoluteInstallBaseDir != "" {
7993
return fmt.Errorf("--all requires --harness; --path is not supported")
8094
}
81-
if all && len(opts.flags.Specs) == 0 {
95+
if len(flags.Specs) == 0 {
8296
return fmt.Errorf("--all requires --harness <name[,name...]>")
8397
}
98+
return nil
99+
}
84100

85-
if all {
86-
if err := confirmUpdateAll(opts); err != nil {
87-
return err
88-
}
89-
return runUpdateAll(opts)
101+
func runUpdateAllMode(opts update) error {
102+
if err := validateUpdateAllTargets(opts.flags); err != nil {
103+
return err
90104
}
105+
if err := confirmUpdateAll(opts); err != nil {
106+
return err
107+
}
108+
return runUpdateAll(opts)
109+
}
91110

111+
func runSingleSlugUpdate(c *components.Context, opts update, slugFlag string) error {
92112
if c.GetNumberOfArgs() > 0 {
93113
return fmt.Errorf("unexpected positional argument(s); use --slug to specify the plugin")
94114
}
@@ -145,7 +165,7 @@ func runUpdateOnSlug(opts update, slug, requestedVersion string) error {
145165
return err
146166
}
147167

148-
targetVersion, err := resolveTargetVersion(opts.serverDetails, opts.repoKey, slug, requestedVersion, opts.quiet)
168+
targetVersion, err := resolvePluginVersion(opts.serverDetails, opts.repoKey, slug, requestedVersion, opts.quiet)
149169
if err != nil {
150170
return err
151171
}
@@ -294,10 +314,6 @@ func finalizeUpdateAll(combined []agentcommon.UpdateAllSummaryRow, outcome updat
294314
return nil
295315
}
296316

297-
func resolveTargetVersion(serverDetails *config.ServerDetails, repoKey, slug, requested string, quiet bool) (string, error) {
298-
return resolvePluginVersion(serverDetails, repoKey, slug, requested, quiet)
299-
}
300-
301317
// updateSlugAcrossTargets fetches the slug once and runs the backup+copy loop per target.
302318
// Returns the per-target summary rows. Targets that are not installed or already at the
303319
// target version are reported without performing a download.

agent/plugins/commands/update/update_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ func TestUpdateOnePlugin_SuccessRemovesBackup(t *testing.T) {
159159
assert.Contains(t, string(data), "2.0.0")
160160
}
161161

162-
func TestResolveTargetVersion_ExplicitUsedDirectly(t *testing.T) {
162+
func TestResolvePluginVersion_ExplicitUsedDirectly(t *testing.T) {
163163
restore := resolvePluginVersion
164164
resolvePluginVersion = func(_ *config.ServerDetails, repoKey, slug, requested string, quiet bool) (string, error) {
165165
assert.Equal(t, "repo", repoKey)
@@ -170,13 +170,13 @@ func TestResolveTargetVersion_ExplicitUsedDirectly(t *testing.T) {
170170
}
171171
t.Cleanup(func() { resolvePluginVersion = restore })
172172

173-
got, err := resolveTargetVersion(nil, "repo", "slug", "1.2.3", true)
173+
got, err := resolvePluginVersion(nil, "repo", "slug", "1.2.3", true)
174174
require.NoError(t, err)
175175
assert.Equal(t, "1.2.3", got)
176176
}
177177

178-
func TestResolveTargetVersion_RejectsInvalid(t *testing.T) {
179-
_, err := resolveTargetVersion(nil, "repo", "slug", "not-a-version", true)
178+
func TestResolvePluginVersion_RejectsInvalid(t *testing.T) {
179+
_, err := resolvePluginVersion(nil, "repo", "slug", "not-a-version", true)
180180
require.Error(t, err)
181181
}
182182

agent/skills/cli/cli.go

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,15 @@ func GetSubCommands() []components.Command {
3535
Action: install.RunInstall,
3636
},
3737
{
38-
Name: "update",
39-
Flags: flagkit.GetCommandFlags(flagkit.SkillsUpdate),
40-
Description: "Update an installed skill to the latest (or a specific) version. Same targeting flags as install: use --harness (comma-separated) with --project-dir (default: current directory) or --global, or --path <dir> for a direct update at <dir>/<slug>. Pre-update checks skip targets that are not installed or already at the target version (use --force to re-download). Logs skip and failure reasons when not quiet. Downloads once for all targets. Use --dry-run to preview, --format json for machine-readable summaries.",
41-
Arguments: getUpdateArguments(),
42-
Action: update.RunUpdate,
38+
Name: "update",
39+
Flags: flagkit.GetCommandFlags(flagkit.SkillsUpdate),
40+
Description: "Update an installed skill to the latest (or a specific) version. " +
41+
"Use --slug with --harness (comma-separated) and --project-dir or --global; or --slug with --path <dir>. " +
42+
"With --all (requires --harness), updates every discovered skill under those harnesses to latest in one summary table " +
43+
"(interactive confirmation before proceeding; folder name is the slug, same as --slug). " +
44+
"Skips targets not installed or already at the target version (use --force to re-download). " +
45+
"Use --dry-run to preview, --format json for machine-readable summaries.",
46+
Action: update.RunUpdate,
4347
},
4448
{
4549
Name: "search",
@@ -85,15 +89,6 @@ func getInstallArguments() []components.Argument {
8589
}
8690
}
8791

88-
func getUpdateArguments() []components.Argument {
89-
return []components.Argument{
90-
{
91-
Name: "slug",
92-
Description: "Skill name/slug to update.",
93-
},
94-
}
95-
}
96-
9792
func getDeleteArguments() []components.Argument {
9893
return []components.Argument{
9994
{

agent/skills/cli/cli_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package cli
2+
3+
import (
4+
"testing"
5+
6+
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestGetSubCommands_UpdateSupportsAll(t *testing.T) {
11+
commands := GetSubCommands()
12+
byName := make(map[string]components.Command, len(commands))
13+
for _, cmd := range commands {
14+
byName[cmd.Name] = cmd
15+
}
16+
17+
updateCmd := byName["update"]
18+
assert.NotNil(t, updateCmd.Action)
19+
assert.Empty(t, updateCmd.Arguments)
20+
assert.Contains(t, updateCmd.Description, "Update an installed skill")
21+
assert.Contains(t, updateCmd.Description, "--slug")
22+
assert.Contains(t, updateCmd.Description, "--all")
23+
}

0 commit comments

Comments
 (0)