Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions apptrust/commands/application/application_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 3 additions & 1 deletion apptrust/commands/application/create_app_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -199,10 +200,11 @@ 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=<type>[, value=<n>]'. 'value' is required (positive integer) when type is "time_frame_in_months" or "version_count", and must be omitted when type is "none".
Expand Down
22 changes: 19 additions & 3 deletions apptrust/commands/application/create_app_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In the PR description you says the PR includes E2E but I can't find it. Is it planned to be added separately?

"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"
Expand All @@ -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"},
Expand All @@ -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{
Expand All @@ -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)
Expand Down Expand Up @@ -145,13 +149,15 @@ 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",
ProjectKey: "test-project",
Description: &expectedDescription,
MaturityLevel: &expectedMaturityLevel,
BusinessCriticality: &expectedBusinessCriticality,
AutoPromoteStages: &expectedAutoPromoteStages,
Labels: &[]model.LabelEntry{
{Key: "environment", Value: "production"},
{Key: "environment", Value: "staging"},
Expand Down Expand Up @@ -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.")
}
4 changes: 4 additions & 0 deletions apptrust/commands/application/testfiles/full-spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
"description": "A comprehensive test application",
"maturity_level": "production",
"criticality": "high",
"auto_promote_stages": [
"DEV",
"PROD"
],
"labels": [
{
"key": "environment",
Expand Down
5 changes: 4 additions & 1 deletion apptrust/commands/application/update_app_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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=<type>[, value=<n>]'. '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. 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`,
Expand Down
22 changes: 22 additions & 0 deletions apptrust/commands/application/update_app_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions apptrust/commands/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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=<type>[, value=<n>]'. 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.", 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()),
Expand Down Expand Up @@ -315,6 +317,7 @@ var commandFlags = map[string][]string{
UserOwnersFlag,
GroupOwnersFlag,
MonitorPolicyFlag,
AutoPromoteStagesFlag,
SpecFlag,
SpecVarsFlag,
},
Expand All @@ -334,6 +337,7 @@ var commandFlags = map[string][]string{
UserOwnersFlag,
GroupOwnersFlag,
MonitorPolicyFlag,
AutoPromoteStagesFlag,
},

AppDelete: {
Expand Down
1 change: 1 addition & 0 deletions apptrust/common/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ var OrderedAppKeys = []string{
"description",
"criticality",
"maturity_level",
"auto_promote_stages",
}
1 change: 1 addition & 0 deletions apptrust/model/app_descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
29 changes: 29 additions & 0 deletions apptrust/model/app_descriptor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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("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))
})
}
Loading