From fd75398243b3aced7ba17a36e47598a12f062d3a Mon Sep 17 00:00:00 2001 From: danielr Date: Wed, 16 Sep 2026 09:54:47 +0300 Subject: [PATCH 1/4] APP-6884 - Add-auto-promote-stages --- .../commands/application/application_utils.go | 5 +++ .../commands/application/create_app_cmd.go | 5 ++- .../application/create_app_cmd_test.go | 22 ++++++++-- .../application/testfiles/full-spec.json | 4 ++ .../commands/application/update_app_cmd.go | 5 ++- .../application/update_app_cmd_test.go | 22 ++++++++++ apptrust/commands/flags.go | 4 ++ apptrust/commands/flags_test.go | 22 ++++++++++ apptrust/common/keys.go | 1 + apptrust/model/app_descriptor.go | 1 + apptrust/model/app_descriptor_test.go | 40 +++++++++++++++++++ e2e/application_test.go | 17 +++++++- 12 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 apptrust/commands/flags_test.go create mode 100644 apptrust/model/app_descriptor_test.go diff --git a/apptrust/commands/application/application_utils.go b/apptrust/commands/application/application_utils.go index 0e016a5..901ebb8 100644 --- a/apptrust/commands/application/application_utils.go +++ b/apptrust/commands/application/application_utils.go @@ -92,5 +92,10 @@ func populateApplicationFromFlags(ctx *components.Context, descriptor *model.App descriptor.MonitorPolicy = monitorPolicy } + if ctx.IsFlagSet(commands.AutoPromoteStagesFlag) { + autoPromoteStages := utils.ParseSliceFlag(ctx.GetStringFlagValue(commands.AutoPromoteStagesFlag)) + descriptor.AutoPromoteStages = &autoPromoteStages + } + return nil } diff --git a/apptrust/commands/application/create_app_cmd.go b/apptrust/commands/application/create_app_cmd.go index d3489bb..1577cbe 100644 --- a/apptrust/commands/application/create_app_cmd.go +++ b/apptrust/commands/application/create_app_cmd.go @@ -165,6 +165,7 @@ func validateNoSpecAndFlagsTogether(ctx *components.Context) error { commands.UserOwnersFlag, commands.GroupOwnersFlag, commands.MonitorPolicyFlag, + commands.AutoPromoteStagesFlag, } for _, flag := range otherAppFlags { if ctx.IsFlagSet(flag) { @@ -199,13 +200,15 @@ Common patterns: $ jf apptrust app-create my-app --project=default --business-criticality=high --maturity-level=production $ jf apptrust app-create my-app --project=default --labels="team=core;area=platform" --user-owners="alice;bob" $ jf apptrust app-create my-app --project=default --monitor-policy="type=version_count, value=5" + $ jf apptrust app-create my-app --project=default --auto-promote-stages="DEV;PROD" $ jf apptrust app-create my-app --spec=app-spec.json --spec-vars="ENV=prod" Gotchas: -- --spec is mutually exclusive with --application-name, --project, --desc, --business-criticality, --maturity-level, --labels, --user-owners, --group-owners, --monitor-policy. +- --spec is mutually exclusive with --application-name, --project, --desc, --business-criticality, --maturity-level, --labels, --user-owners, --group-owners, --monitor-policy, --auto-promote-stages. - If --application-name is omitted, the application-key is used as the display name. - --labels uses semicolon separators (not commas) and key=value pairs. - --monitor-policy takes 'type=[, value=]'. 'value' is required (positive integer) when type is "time_frame_in_months" or "version_count", and must be omitted when type is "none". +- --auto-promote-stages uses semicolon separators and preserves the supplied stage order. Related: jf apptrust app-update, jf apptrust app-delete, jf apptrust version-create`, Category: common.CategoryApplication, diff --git a/apptrust/commands/application/create_app_cmd_test.go b/apptrust/commands/application/create_app_cmd_test.go index ac1ca7c..6fa6b3b 100644 --- a/apptrust/commands/application/create_app_cmd_test.go +++ b/apptrust/commands/application/create_app_cmd_test.go @@ -7,6 +7,7 @@ import ( "github.com/urfave/cli" + "github.com/jfrog/jfrog-cli-application/apptrust/commands" "github.com/jfrog/jfrog-cli-application/apptrust/model" mockapps "github.com/jfrog/jfrog-cli-application/apptrust/service/applications/mocks" "github.com/jfrog/jfrog-cli-core/v2/plugins/components" @@ -23,6 +24,7 @@ func TestCreateAppCommand_Run_Flags(t *testing.T) { businessCriticality := "high" maturityLevel := "production" monitorPolicyValue := 5 + autoPromoteStages := []string{"DEV", "PROD"} ctx := &components.Context{ Arguments: []string{"app-key"}, @@ -36,6 +38,7 @@ func TestCreateAppCommand_Run_Flags(t *testing.T) { ctx.AddStringFlag("user-owners", "john.doe;jane.smith") ctx.AddStringFlag("group-owners", "devops;security") ctx.AddStringFlag("monitor-policy", "type=version_count, value=5") + ctx.AddStringFlag("auto-promote-stages", "DEV;PROD") ctx.AddStringFlag("url", "https://example.com") requestPayload := &model.AppDescriptor{ @@ -49,9 +52,10 @@ func TestCreateAppCommand_Run_Flags(t *testing.T) { {Key: "env", Value: "prod"}, {Key: "region", Value: "us-east"}, }, - UserOwners: &[]string{"john.doe", "jane.smith"}, - GroupOwners: &[]string{"devops", "security"}, - MonitorPolicy: &model.MonitorPolicy{Type: model.MonitorPolicyTypeVersionCount, Value: &monitorPolicyValue}, + UserOwners: &[]string{"john.doe", "jane.smith"}, + GroupOwners: &[]string{"devops", "security"}, + MonitorPolicy: &model.MonitorPolicy{Type: model.MonitorPolicyTypeVersionCount, Value: &monitorPolicyValue}, + AutoPromoteStages: &autoPromoteStages, } mockAppService := mockapps.NewMockApplicationService(ctrl) @@ -145,6 +149,7 @@ func TestCreateAppCommand_Run_FullSpecFile(t *testing.T) { expectedDescription := "A comprehensive test application" expectedMaturityLevel := "production" expectedBusinessCriticality := "high" + expectedAutoPromoteStages := []string{"DEV", "PROD"} expectedPayload := &model.AppDescriptor{ ApplicationKey: "app-full", ApplicationName: "test-app-full", @@ -152,6 +157,7 @@ func TestCreateAppCommand_Run_FullSpecFile(t *testing.T) { Description: &expectedDescription, MaturityLevel: &expectedMaturityLevel, BusinessCriticality: &expectedBusinessCriticality, + AutoPromoteStages: &expectedAutoPromoteStages, Labels: &[]model.LabelEntry{ {Key: "environment", Value: "production"}, {Key: "environment", Value: "staging"}, @@ -337,3 +343,13 @@ func TestCreateAppCommand_Error_SpecAndFlags(t *testing.T) { assert.Error(t, err) assert.Contains(t, err.Error(), "the flag --project is not allowed when --spec is provided") } + +func TestCreateAppCommand_Error_SpecAndAutoPromoteStages(t *testing.T) { + ctx := &components.Context{} + ctx.AddStringFlag(commands.SpecFlag, "./testfiles/minimal-spec.json") + ctx.AddStringFlag(commands.AutoPromoteStagesFlag, "DEV;PROD") + + err := validateNoSpecAndFlagsTogether(ctx) + + assert.EqualError(t, err, "the flag --auto-promote-stages is not allowed when --spec is provided.") +} diff --git a/apptrust/commands/application/testfiles/full-spec.json b/apptrust/commands/application/testfiles/full-spec.json index 6c854cf..bcd5f5c 100644 --- a/apptrust/commands/application/testfiles/full-spec.json +++ b/apptrust/commands/application/testfiles/full-spec.json @@ -4,6 +4,10 @@ "description": "A comprehensive test application", "maturity_level": "production", "criticality": "high", + "auto_promote_stages": [ + "DEV", + "PROD" + ], "labels": [ { "key": "environment", diff --git a/apptrust/commands/application/update_app_cmd.go b/apptrust/commands/application/update_app_cmd.go index 0f667da..88e728b 100644 --- a/apptrust/commands/application/update_app_cmd.go +++ b/apptrust/commands/application/update_app_cmd.go @@ -93,7 +93,7 @@ func GetUpdateAppCommand(appContext app.Context) components.Command { return components.Command{ Name: commands.AppUpdate, Description: "Update an existing application", - AIDescription: `Update metadata (display name, description, criticality, maturity, labels, owners) of an existing application identified by its key. + AIDescription: `Update metadata (display name, description, criticality, maturity, labels, owners, auto-promotion stages) of an existing application identified by its key. When to use: - Change display attributes (name, description, criticality, maturity) of an existing application. @@ -110,11 +110,14 @@ Common patterns: $ jf apptrust app-update my-app --remove-labels="env=staging" $ jf apptrust app-update my-app --user-owners="alice;bob" --group-owners="platform-team" $ jf apptrust app-update my-app --monitor-policy="type=version_count, value=5" + $ jf apptrust app-update my-app --auto-promote-stages="DEV;PROD" + $ jf apptrust app-update my-app --auto-promote-stages="" Gotchas: - --labels replaces the full label set; --add-labels and --remove-labels modify incrementally. - --user-owners / --group-owners take a semicolon-separated list and send exactly the owners you specify; there are no incremental add/remove-owner flags (unlike --add-labels / --remove-labels for labels). - --monitor-policy takes 'type=[, value=]'. 'value' is required (positive integer) when type is "time_frame_in_months" or "version_count", and must be omitted when type is "none". When --monitor-policy is not provided, the current policy is left unchanged. +- --auto-promote-stages uses semicolon separators and preserves the supplied stage order. Pass an empty value to disable auto-promotion; omit the flag to leave the current stages unchanged. - Application key cannot be changed; use app-delete and app-create if you need a different key. Related: jf apptrust app-create, jf apptrust app-delete`, diff --git a/apptrust/commands/application/update_app_cmd_test.go b/apptrust/commands/application/update_app_cmd_test.go index ccb7099..22d318d 100644 --- a/apptrust/commands/application/update_app_cmd_test.go +++ b/apptrust/commands/application/update_app_cmd_test.go @@ -268,6 +268,28 @@ func TestUpdateAppCommand_FlagsSuite(t *testing.T) { MonitorPolicy: &model.MonitorPolicy{Type: model.MonitorPolicyTypeVersionCount, Value: &monitorPolicyValue}, }, }, + { + name: "auto-promote-stages", + ctxSetup: func(ctx *components.Context) { + ctx.Arguments = []string{"app-key"} + ctx.AddStringFlag("auto-promote-stages", "DEV;PROD") + }, + expectsPayload: &model.AppDescriptor{ + ApplicationKey: "app-key", + AutoPromoteStages: &[]string{"DEV", "PROD"}, + }, + }, + { + name: "empty auto-promote-stages clears configuration", + ctxSetup: func(ctx *components.Context) { + ctx.Arguments = []string{"app-key"} + ctx.AddStringFlag("auto-promote-stages", "") + }, + expectsPayload: &model.AppDescriptor{ + ApplicationKey: "app-key", + AutoPromoteStages: &[]string{}, + }, + }, { name: "invalid add-labels format - missing equals", ctxSetup: func(ctx *components.Context) { diff --git a/apptrust/commands/flags.go b/apptrust/commands/flags.go index 6f2ad66..afd7c02 100644 --- a/apptrust/commands/flags.go +++ b/apptrust/commands/flags.go @@ -51,6 +51,7 @@ const ( UserOwnersFlag = "user-owners" GroupOwnersFlag = "group-owners" MonitorPolicyFlag = "monitor-policy" + AutoPromoteStagesFlag = "auto-promote-stages" SyncFlag = "sync" PromotionTypeFlag = "promotion-type" DryRunFlag = "dry-run" @@ -113,6 +114,7 @@ var flagsMap = map[string]components.Flag{ UserOwnersFlag: components.NewStringFlag(UserOwnersFlag, "semicolon-separated (;) list of user owners in the form of \"user1;user2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }), GroupOwnersFlag: components.NewStringFlag(GroupOwnersFlag, "semicolon-separated (;) list of group owners in the form of \"group1;group2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }), MonitorPolicyFlag: components.NewStringFlag(MonitorPolicyFlag, "Defines how long application versions remain monitored, in the form of 'type=[, value=]'. Supported types: "+coreutils.ListToText(model.MonitorPolicyTypeValues)+". For '"+model.MonitorPolicyTypeTimeframe+"' or '"+model.MonitorPolicyTypeVersionCount+"', 'value' is the number of months or versions to keep monitoring (a positive integer). For '"+model.MonitorPolicyTypeNone+"', monitoring is disabled and 'value' must be omitted.", func(f *components.StringFlag) { f.Mandatory = false }), + AutoPromoteStagesFlag: components.NewStringFlag(AutoPromoteStagesFlag, "Semicolon-separated (;) lifecycle stages through which new application versions are automatically promoted. Pass an empty value to disable auto-promotion.", func(f *components.StringFlag) { f.Mandatory = false }), SyncFlag: components.NewBoolFlag(SyncFlag, "Whether to synchronize the operation.", components.WithBoolDefaultValueTrue()), PromotionTypeFlag: components.NewStringFlag(PromotionTypeFlag, "The promotion type. The following values are supported: "+coreutils.ListToText(model.PromotionTypeValues), func(f *components.StringFlag) { f.Mandatory = false; f.DefaultValue = model.PromotionTypeCopy }), DryRunFlag: components.NewBoolFlag(DryRunFlag, "Perform a simulation of the operation.", components.WithBoolDefaultValueFalse()), @@ -315,6 +317,7 @@ var commandFlags = map[string][]string{ UserOwnersFlag, GroupOwnersFlag, MonitorPolicyFlag, + AutoPromoteStagesFlag, SpecFlag, SpecVarsFlag, }, @@ -334,6 +337,7 @@ var commandFlags = map[string][]string{ UserOwnersFlag, GroupOwnersFlag, MonitorPolicyFlag, + AutoPromoteStagesFlag, }, AppDelete: { diff --git a/apptrust/commands/flags_test.go b/apptrust/commands/flags_test.go new file mode 100644 index 0000000..3de8102 --- /dev/null +++ b/apptrust/commands/flags_test.go @@ -0,0 +1,22 @@ +package commands + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestApplicationCommandsIncludeAutoPromoteStagesFlag(t *testing.T) { + for _, command := range []string{AppCreate, AppUpdate} { + t.Run(command, func(t *testing.T) { + var found bool + for _, flag := range GetCommandFlags(command) { + if flag.GetName() == AutoPromoteStagesFlag { + found = true + break + } + } + assert.True(t, found, "%s should expose --%s", command, AutoPromoteStagesFlag) + }) + } +} diff --git a/apptrust/common/keys.go b/apptrust/common/keys.go index 18025d5..f142927 100644 --- a/apptrust/common/keys.go +++ b/apptrust/common/keys.go @@ -20,4 +20,5 @@ var OrderedAppKeys = []string{ "description", "criticality", "maturity_level", + "auto_promote_stages", } diff --git a/apptrust/model/app_descriptor.go b/apptrust/model/app_descriptor.go index c7f18b3..fecba8d 100644 --- a/apptrust/model/app_descriptor.go +++ b/apptrust/model/app_descriptor.go @@ -67,4 +67,5 @@ type AppDescriptor struct { UserOwners *[]string `json:"user_owners,omitempty"` GroupOwners *[]string `json:"group_owners,omitempty"` MonitorPolicy *MonitorPolicy `json:"monitor_policy,omitempty"` + AutoPromoteStages *[]string `json:"auto_promote_stages,omitempty"` } diff --git a/apptrust/model/app_descriptor_test.go b/apptrust/model/app_descriptor_test.go new file mode 100644 index 0000000..ca9b39b --- /dev/null +++ b/apptrust/model/app_descriptor_test.go @@ -0,0 +1,40 @@ +package model + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAppDescriptorAutoPromoteStagesJSON(t *testing.T) { + t.Run("omitted when flag is not supplied", func(t *testing.T) { + payload, err := json.Marshal(AppDescriptor{ApplicationKey: "app-key"}) + require.NoError(t, err) + + assert.NotContains(t, string(payload), "auto_promote_stages") + }) + + t.Run("preserves configured order", func(t *testing.T) { + stages := []string{"DEV", "PROD"} + payload, err := json.Marshal(AppDescriptor{ + ApplicationKey: "app-key", + AutoPromoteStages: &stages, + }) + require.NoError(t, err) + + assert.JSONEq(t, `{"application_key":"app-key","auto_promote_stages":["DEV","PROD"]}`, string(payload)) + }) + + t.Run("includes empty list to disable auto-promotion", func(t *testing.T) { + stages := []string{} + payload, err := json.Marshal(AppDescriptor{ + ApplicationKey: "app-key", + AutoPromoteStages: &stages, + }) + require.NoError(t, err) + + assert.JSONEq(t, `{"application_key":"app-key","auto_promote_stages":[]}`, string(payload)) + }) +} diff --git a/e2e/application_test.go b/e2e/application_test.go index 88b4bc0..4d49123 100644 --- a/e2e/application_test.go +++ b/e2e/application_test.go @@ -22,6 +22,7 @@ func TestCreateApp(t *testing.T) { userOwners := []string{"admin", "developer"} groupOwners := []string{"devops-team", "security-team"} monitorPolicyValue := 5 + autoPromoteStages := []string{"DEV", "PROD"} err := utils.AppTrustCli.Exec("app-create", appKey, "--project="+projectKey, @@ -32,7 +33,8 @@ func TestCreateApp(t *testing.T) { "--labels=env=prod;team=devops", "--user-owners="+strings.Join(userOwners, ";"), "--group-owners="+strings.Join(groupOwners, ";"), - "--monitor-policy=type=version_count, value=5") + "--monitor-policy=type=version_count, value=5", + "--auto-promote-stages="+strings.Join(autoPromoteStages, ";")) assert.NoError(t, err) // Fetch and verify the application was created correctly @@ -48,6 +50,7 @@ func TestCreateApp(t *testing.T) { assert.Equal(t, userOwners, *app.UserOwners) assert.Equal(t, groupOwners, *app.GroupOwners) assert.Equal(t, &model.MonitorPolicy{Type: model.MonitorPolicyTypeVersionCount, Value: &monitorPolicyValue}, app.MonitorPolicy) + assert.Equal(t, autoPromoteStages, *app.AutoPromoteStages) utils.DeleteApplication(t, appKey) } @@ -65,6 +68,7 @@ func TestUpdateApp(t *testing.T) { updatedUserOwners := []string{"app-admin", "frog"} updatedGroupOwners := []string{"dev-team", "security-team"} monitorPolicyValue := 6 + updatedAutoPromoteStages := []string{"DEV", "PROD"} err := utils.AppTrustCli.Exec("app-update", appKey, "--application-name="+updatedAppName, @@ -74,7 +78,8 @@ func TestUpdateApp(t *testing.T) { "--labels=env=qa;team=dev", "--user-owners="+strings.Join(updatedUserOwners, ";"), "--group-owners="+strings.Join(updatedGroupOwners, ";"), - "--monitor-policy=type=time_frame_in_months, value=6") + "--monitor-policy=type=time_frame_in_months, value=6", + "--auto-promote-stages="+strings.Join(updatedAutoPromoteStages, ";")) assert.NoError(t, err) // Fetch and verify the application was updated correctly @@ -90,6 +95,14 @@ func TestUpdateApp(t *testing.T) { assert.Equal(t, updatedUserOwners, *app.UserOwners) assert.Equal(t, updatedGroupOwners, *app.GroupOwners) assert.Equal(t, &model.MonitorPolicy{Type: model.MonitorPolicyTypeTimeframe, Value: &monitorPolicyValue}, app.MonitorPolicy) + assert.Equal(t, updatedAutoPromoteStages, *app.AutoPromoteStages) + + err = utils.AppTrustCli.Exec("app-update", appKey, "--auto-promote-stages=") + assert.NoError(t, err) + + app, _, err = utils.GetApplication(appKey) + assert.NoError(t, err) + assert.Nil(t, app.AutoPromoteStages) utils.DeleteApplication(t, appKey) } From 64e25b72c77202ec71f8311b73e9c10d83d22b27 Mon Sep 17 00:00:00 2001 From: danielr Date: Wed, 16 Sep 2026 11:09:56 +0300 Subject: [PATCH 2/4] APP-6884 - Add-auto-promote-stages --- e2e/application_test.go | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/e2e/application_test.go b/e2e/application_test.go index 4d49123..88b4bc0 100644 --- a/e2e/application_test.go +++ b/e2e/application_test.go @@ -22,7 +22,6 @@ func TestCreateApp(t *testing.T) { userOwners := []string{"admin", "developer"} groupOwners := []string{"devops-team", "security-team"} monitorPolicyValue := 5 - autoPromoteStages := []string{"DEV", "PROD"} err := utils.AppTrustCli.Exec("app-create", appKey, "--project="+projectKey, @@ -33,8 +32,7 @@ func TestCreateApp(t *testing.T) { "--labels=env=prod;team=devops", "--user-owners="+strings.Join(userOwners, ";"), "--group-owners="+strings.Join(groupOwners, ";"), - "--monitor-policy=type=version_count, value=5", - "--auto-promote-stages="+strings.Join(autoPromoteStages, ";")) + "--monitor-policy=type=version_count, value=5") assert.NoError(t, err) // Fetch and verify the application was created correctly @@ -50,7 +48,6 @@ func TestCreateApp(t *testing.T) { assert.Equal(t, userOwners, *app.UserOwners) assert.Equal(t, groupOwners, *app.GroupOwners) assert.Equal(t, &model.MonitorPolicy{Type: model.MonitorPolicyTypeVersionCount, Value: &monitorPolicyValue}, app.MonitorPolicy) - assert.Equal(t, autoPromoteStages, *app.AutoPromoteStages) utils.DeleteApplication(t, appKey) } @@ -68,7 +65,6 @@ func TestUpdateApp(t *testing.T) { updatedUserOwners := []string{"app-admin", "frog"} updatedGroupOwners := []string{"dev-team", "security-team"} monitorPolicyValue := 6 - updatedAutoPromoteStages := []string{"DEV", "PROD"} err := utils.AppTrustCli.Exec("app-update", appKey, "--application-name="+updatedAppName, @@ -78,8 +74,7 @@ func TestUpdateApp(t *testing.T) { "--labels=env=qa;team=dev", "--user-owners="+strings.Join(updatedUserOwners, ";"), "--group-owners="+strings.Join(updatedGroupOwners, ";"), - "--monitor-policy=type=time_frame_in_months, value=6", - "--auto-promote-stages="+strings.Join(updatedAutoPromoteStages, ";")) + "--monitor-policy=type=time_frame_in_months, value=6") assert.NoError(t, err) // Fetch and verify the application was updated correctly @@ -95,14 +90,6 @@ func TestUpdateApp(t *testing.T) { assert.Equal(t, updatedUserOwners, *app.UserOwners) assert.Equal(t, updatedGroupOwners, *app.GroupOwners) assert.Equal(t, &model.MonitorPolicy{Type: model.MonitorPolicyTypeTimeframe, Value: &monitorPolicyValue}, app.MonitorPolicy) - assert.Equal(t, updatedAutoPromoteStages, *app.AutoPromoteStages) - - err = utils.AppTrustCli.Exec("app-update", appKey, "--auto-promote-stages=") - assert.NoError(t, err) - - app, _, err = utils.GetApplication(appKey) - assert.NoError(t, err) - assert.Nil(t, app.AutoPromoteStages) utils.DeleteApplication(t, appKey) } From cfc3b6ebc8ac46322d3568e6b9b6d593d719d609 Mon Sep 17 00:00:00 2001 From: danielr Date: Thu, 17 Sep 2026 13:09:47 +0300 Subject: [PATCH 3/4] APP-6884 - Add-auto-promote-stages --- .../commands/application/create_app_cmd.go | 1 - .../commands/application/update_app_cmd.go | 2 +- apptrust/commands/flags_test.go | 22 ------------------- 3 files changed, 1 insertion(+), 24 deletions(-) delete mode 100644 apptrust/commands/flags_test.go diff --git a/apptrust/commands/application/create_app_cmd.go b/apptrust/commands/application/create_app_cmd.go index 1577cbe..28f5aa1 100644 --- a/apptrust/commands/application/create_app_cmd.go +++ b/apptrust/commands/application/create_app_cmd.go @@ -208,7 +208,6 @@ Gotchas: - If --application-name is omitted, the application-key is used as the display name. - --labels uses semicolon separators (not commas) and key=value pairs. - --monitor-policy takes 'type=[, value=]'. 'value' is required (positive integer) when type is "time_frame_in_months" or "version_count", and must be omitted when type is "none". -- --auto-promote-stages uses semicolon separators and preserves the supplied stage order. Related: jf apptrust app-update, jf apptrust app-delete, jf apptrust version-create`, Category: common.CategoryApplication, diff --git a/apptrust/commands/application/update_app_cmd.go b/apptrust/commands/application/update_app_cmd.go index 88e728b..33d8632 100644 --- a/apptrust/commands/application/update_app_cmd.go +++ b/apptrust/commands/application/update_app_cmd.go @@ -117,7 +117,7 @@ Gotchas: - --labels replaces the full label set; --add-labels and --remove-labels modify incrementally. - --user-owners / --group-owners take a semicolon-separated list and send exactly the owners you specify; there are no incremental add/remove-owner flags (unlike --add-labels / --remove-labels for labels). - --monitor-policy takes 'type=[, value=]'. 'value' is required (positive integer) when type is "time_frame_in_months" or "version_count", and must be omitted when type is "none". When --monitor-policy is not provided, the current policy is left unchanged. -- --auto-promote-stages uses semicolon separators and preserves the supplied stage order. Pass an empty value to disable auto-promotion; omit the flag to leave the current stages unchanged. +- --auto-promote-stages uses semicolon separators. Pass an empty value to disable auto-promotion; omit the flag to leave the current stages unchanged. - Application key cannot be changed; use app-delete and app-create if you need a different key. Related: jf apptrust app-create, jf apptrust app-delete`, diff --git a/apptrust/commands/flags_test.go b/apptrust/commands/flags_test.go deleted file mode 100644 index 3de8102..0000000 --- a/apptrust/commands/flags_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package commands - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestApplicationCommandsIncludeAutoPromoteStagesFlag(t *testing.T) { - for _, command := range []string{AppCreate, AppUpdate} { - t.Run(command, func(t *testing.T) { - var found bool - for _, flag := range GetCommandFlags(command) { - if flag.GetName() == AutoPromoteStagesFlag { - found = true - break - } - } - assert.True(t, found, "%s should expose --%s", command, AutoPromoteStagesFlag) - }) - } -} From b64e6105f7524dadcbe0c48c48d786933e40c0d2 Mon Sep 17 00:00:00 2001 From: danielr Date: Thu, 17 Sep 2026 14:04:27 +0300 Subject: [PATCH 4/4] APP-6884 - Add-auto-promote-stages --- apptrust/commands/flags.go | 2 +- apptrust/model/app_descriptor_test.go | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/apptrust/commands/flags.go b/apptrust/commands/flags.go index afd7c02..4a6ad7a 100644 --- a/apptrust/commands/flags.go +++ b/apptrust/commands/flags.go @@ -114,7 +114,7 @@ var flagsMap = map[string]components.Flag{ UserOwnersFlag: components.NewStringFlag(UserOwnersFlag, "semicolon-separated (;) list of user owners in the form of \"user1;user2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }), GroupOwnersFlag: components.NewStringFlag(GroupOwnersFlag, "semicolon-separated (;) list of group owners in the form of \"group1;group2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }), MonitorPolicyFlag: components.NewStringFlag(MonitorPolicyFlag, "Defines how long application versions remain monitored, in the form of 'type=[, value=]'. Supported types: "+coreutils.ListToText(model.MonitorPolicyTypeValues)+". For '"+model.MonitorPolicyTypeTimeframe+"' or '"+model.MonitorPolicyTypeVersionCount+"', 'value' is the number of months or versions to keep monitoring (a positive integer). For '"+model.MonitorPolicyTypeNone+"', monitoring is disabled and 'value' must be omitted.", func(f *components.StringFlag) { f.Mandatory = false }), - AutoPromoteStagesFlag: components.NewStringFlag(AutoPromoteStagesFlag, "Semicolon-separated (;) lifecycle stages through which new application versions are automatically promoted. Pass an empty value to disable auto-promotion.", func(f *components.StringFlag) { f.Mandatory = false }), + AutoPromoteStagesFlag: components.NewStringFlag(AutoPromoteStagesFlag, "Semicolon-separated (;) lifecycle stages through which new application versions are automatically promoted.", func(f *components.StringFlag) { f.Mandatory = false }), SyncFlag: components.NewBoolFlag(SyncFlag, "Whether to synchronize the operation.", components.WithBoolDefaultValueTrue()), PromotionTypeFlag: components.NewStringFlag(PromotionTypeFlag, "The promotion type. The following values are supported: "+coreutils.ListToText(model.PromotionTypeValues), func(f *components.StringFlag) { f.Mandatory = false; f.DefaultValue = model.PromotionTypeCopy }), DryRunFlag: components.NewBoolFlag(DryRunFlag, "Perform a simulation of the operation.", components.WithBoolDefaultValueFalse()), diff --git a/apptrust/model/app_descriptor_test.go b/apptrust/model/app_descriptor_test.go index ca9b39b..480bffb 100644 --- a/apptrust/model/app_descriptor_test.go +++ b/apptrust/model/app_descriptor_test.go @@ -16,17 +16,6 @@ func TestAppDescriptorAutoPromoteStagesJSON(t *testing.T) { assert.NotContains(t, string(payload), "auto_promote_stages") }) - t.Run("preserves configured order", func(t *testing.T) { - stages := []string{"DEV", "PROD"} - payload, err := json.Marshal(AppDescriptor{ - ApplicationKey: "app-key", - AutoPromoteStages: &stages, - }) - require.NoError(t, err) - - assert.JSONEq(t, `{"application_key":"app-key","auto_promote_stages":["DEV","PROD"]}`, string(payload)) - }) - t.Run("includes empty list to disable auto-promotion", func(t *testing.T) { stages := []string{} payload, err := json.Marshal(AppDescriptor{