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
124 changes: 50 additions & 74 deletions apptrust/commands/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,35 +28,29 @@ const (
accessToken = "access-token"
ProjectFlag = "project"

ApplicationKeyFlag = "application-key"
PackageTypeFlag = "package-type"
PackageNameFlag = "package-name"
PackageVersionFlag = "package-version"
PackageRepositoryFlag = "package-repository"
SpecFlag = "spec"
SpecVarsFlag = "spec-vars"
StageVarsFlag = "stage"
ApplicationNameFlag = "application-name"
DescriptionFlag = "desc"
BusinessCriticalityFlag = "business-criticality"
MaturityLevelFlag = "maturity-level"
LabelsFlag = "labels"
UserOwnersFlag = "user-owners"
GroupOwnersFlag = "group-owners"
SigningKeyFlag = "signing-key"
SyncFlag = "sync"
PromotionTypeFlag = "promotion-type"
DryRunFlag = "dry-run"
ExcludeReposFlag = "exclude-repos"
IncludeReposFlag = "include-repos"
PropsFlag = "props"
TagFlag = "tag"
BuildsFlag = "builds"
ReleaseBundlesFlag = "release-bundles"
SourceVersionFlag = "source-version"
PackagesFlag = "packages"
PropertiesFlag = "properties"
DeletePropertyFlag = "delete-property"
ApplicationKeyFlag = "application-key"
SpecFlag = "spec"
SpecVarsFlag = "spec-vars"
StageVarsFlag = "stage"
ApplicationNameFlag = "application-name"
DescriptionFlag = "desc"
BusinessCriticalityFlag = "business-criticality"
MaturityLevelFlag = "maturity-level"
LabelsFlag = "labels"
UserOwnersFlag = "user-owners"
GroupOwnersFlag = "group-owners"
SyncFlag = "sync"
PromotionTypeFlag = "promotion-type"
DryRunFlag = "dry-run"
ExcludeReposFlag = "exclude-repos"
IncludeReposFlag = "include-repos"
PropsFlag = "props"
TagFlag = "tag"
SourceTypeBuildsFlag = "source-type-builds"
SourceTypeReleaseBundlesFlag = "source-type-release-bundles"
SourceTypeApplicationVersionsFlag = "source-type-application-versions"
PropertiesFlag = "properties"
DeletePropertyFlag = "delete-property"
)

// Flag keys mapped to their corresponding components.Flag definition.
Expand All @@ -66,37 +60,31 @@ var flagsMap = map[string]components.Flag{
url: components.NewStringFlag(url, "JFrog Platform URL.", func(f *components.StringFlag) { f.Mandatory = false }),
user: components.NewStringFlag(user, "JFrog username.", func(f *components.StringFlag) { f.Mandatory = false }),
accessToken: components.NewStringFlag(accessToken, "JFrog access token.", func(f *components.StringFlag) { f.Mandatory = false }),
ProjectFlag: components.NewStringFlag(ProjectFlag, "Project key associated with the application.", func(f *components.StringFlag) { f.Mandatory = false }),
ProjectFlag: components.NewStringFlag(ProjectFlag, "Project key associated with the application. This flag is mandatory when the --spec flag is not provided.", func(f *components.StringFlag) { f.Mandatory = false }),

ApplicationKeyFlag: components.NewStringFlag(ApplicationKeyFlag, "Application key.", func(f *components.StringFlag) { f.Mandatory = false }),
PackageTypeFlag: components.NewStringFlag(PackageTypeFlag, "Package type.", func(f *components.StringFlag) { f.Mandatory = false }),
PackageNameFlag: components.NewStringFlag(PackageNameFlag, "Package name.", func(f *components.StringFlag) { f.Mandatory = false }),
PackageVersionFlag: components.NewStringFlag(PackageVersionFlag, "Package version.", func(f *components.StringFlag) { f.Mandatory = false }),
PackageRepositoryFlag: components.NewStringFlag(PackageRepositoryFlag, "Package storing repository.", func(f *components.StringFlag) { f.Mandatory = false }),
SpecFlag: components.NewStringFlag(SpecFlag, "A path to the specification file.", func(f *components.StringFlag) { f.Mandatory = false }),
SpecVarsFlag: components.NewStringFlag(SpecVarsFlag, "List of semicolon-separated (;) variables in the form of \"key1=value1;key2=value2;...\" (wrapped by quotes) to be replaced in the File Spec. In the File Spec, the variables should be used as follows: ${key1}.", func(f *components.StringFlag) { f.Mandatory = false }),
StageVarsFlag: components.NewStringFlag(StageVarsFlag, "Promotion stage.", func(f *components.StringFlag) { f.Mandatory = true }),
ApplicationNameFlag: components.NewStringFlag(ApplicationNameFlag, "The display name of the application.", func(f *components.StringFlag) { f.Mandatory = false }),
DescriptionFlag: components.NewStringFlag(DescriptionFlag, "The description of the application.", func(f *components.StringFlag) { f.Mandatory = false }),
BusinessCriticalityFlag: components.NewStringFlag(BusinessCriticalityFlag, "The business criticality level. The following values are supported: "+coreutils.ListToText(model.BusinessCriticalityValues), func(f *components.StringFlag) { f.Mandatory = false }),
MaturityLevelFlag: components.NewStringFlag(MaturityLevelFlag, "The maturity level.", func(f *components.StringFlag) { f.Mandatory = false }),
LabelsFlag: components.NewStringFlag(LabelsFlag, "List of semicolon-separated (;) labels in the form of \"key1=value1;key2=value2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }),
UserOwnersFlag: components.NewStringFlag(UserOwnersFlag, "Comma-separated list of user owners.", func(f *components.StringFlag) { f.Mandatory = false }),
GroupOwnersFlag: components.NewStringFlag(GroupOwnersFlag, "Comma-separated list of group owners.", func(f *components.StringFlag) { f.Mandatory = false }),
SigningKeyFlag: components.NewStringFlag(SigningKeyFlag, "The GPG/RSA key-pair name given in Artifactory.", 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()),
ExcludeReposFlag: components.NewStringFlag(ExcludeReposFlag, "Semicolon-separated list of repositories to exclude.", func(f *components.StringFlag) { f.Mandatory = false }),
IncludeReposFlag: components.NewStringFlag(IncludeReposFlag, "Semicolon-separated list of repositories to include.", func(f *components.StringFlag) { f.Mandatory = false }),
PropsFlag: components.NewStringFlag(PropsFlag, "Semicolon-separated list of properties in the form of 'key1=value1;key2=value2;...' to be added to each artifact.", func(f *components.StringFlag) { f.Mandatory = false }),
TagFlag: components.NewStringFlag(TagFlag, "A tag to associate with the version. Must contain only alphanumeric characters, hyphens (-), underscores (_), and dots (.).", func(f *components.StringFlag) { f.Mandatory = false }),
BuildsFlag: components.NewStringFlag(BuildsFlag, "List of builds in format 'name1:number1[:timestamp1];name2:number2[:timestamp2]'", func(f *components.StringFlag) { f.Mandatory = false }),
ReleaseBundlesFlag: components.NewStringFlag(ReleaseBundlesFlag, "List of release bundles in format 'name1:version1;name2:version2'", func(f *components.StringFlag) { f.Mandatory = false }),
SourceVersionFlag: components.NewStringFlag(SourceVersionFlag, "Source versions in format 'app1:version1;app2:version2'", func(f *components.StringFlag) { f.Mandatory = false }),
PackagesFlag: components.NewStringFlag(PackagesFlag, "List of packages in format 'name1;name2'", func(f *components.StringFlag) { f.Mandatory = false }),
PropertiesFlag: components.NewStringFlag(PropertiesFlag, "Sets or updates custom properties for the application version in format 'key1=value1[,value2,...];key2=value3[,value4,...]'", func(f *components.StringFlag) { f.Mandatory = false }),
DeletePropertyFlag: components.NewStringFlag(DeletePropertyFlag, "Remove a property key and all its values", func(f *components.StringFlag) { f.Mandatory = false }),
ApplicationKeyFlag: components.NewStringFlag(ApplicationKeyFlag, "Application key.", func(f *components.StringFlag) { f.Mandatory = false }),
SpecFlag: components.NewStringFlag(SpecFlag, "A path to the specification file.", func(f *components.StringFlag) { f.Mandatory = false }),
SpecVarsFlag: components.NewStringFlag(SpecVarsFlag, "List of semicolon-separated (;) variables in the form of \"key1=value1;key2=value2;...\" (wrapped by quotes) to be replaced in the File Spec. In the File Spec, the variables should be used as follows: ${key1}.", func(f *components.StringFlag) { f.Mandatory = false }),
StageVarsFlag: components.NewStringFlag(StageVarsFlag, "Promotion stage.", func(f *components.StringFlag) { f.Mandatory = true }),
ApplicationNameFlag: components.NewStringFlag(ApplicationNameFlag, "The display name of the application.", func(f *components.StringFlag) { f.Mandatory = false }),
DescriptionFlag: components.NewStringFlag(DescriptionFlag, "The description of the application.", func(f *components.StringFlag) { f.Mandatory = false }),
BusinessCriticalityFlag: components.NewStringFlag(BusinessCriticalityFlag, "The business criticality level. The following values are supported: "+coreutils.ListToText(model.BusinessCriticalityValues), func(f *components.StringFlag) { f.Mandatory = false }),
MaturityLevelFlag: components.NewStringFlag(MaturityLevelFlag, "The maturity level.", func(f *components.StringFlag) { f.Mandatory = false }),
LabelsFlag: components.NewStringFlag(LabelsFlag, "List of semicolon-separated (;) labels in the form of \"key1=value1;key2=value2;...\" (wrapped by quotes).", func(f *components.StringFlag) { f.Mandatory = false }),
UserOwnersFlag: components.NewStringFlag(UserOwnersFlag, "Comma-separated list of user owners.", func(f *components.StringFlag) { f.Mandatory = false }),
GroupOwnersFlag: components.NewStringFlag(GroupOwnersFlag, "Comma-separated list of group owners.", 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()),
ExcludeReposFlag: components.NewStringFlag(ExcludeReposFlag, "Semicolon-separated list of repositories to exclude.", func(f *components.StringFlag) { f.Mandatory = false }),
IncludeReposFlag: components.NewStringFlag(IncludeReposFlag, "Semicolon-separated list of repositories to include.", func(f *components.StringFlag) { f.Mandatory = false }),
PropsFlag: components.NewStringFlag(PropsFlag, "Semicolon-separated list of properties in the form of 'key1=value1;key2=value2;...' to be added to each artifact.", func(f *components.StringFlag) { f.Mandatory = false }),
TagFlag: components.NewStringFlag(TagFlag, "A tag to associate with the version. Must contain only alphanumeric characters, hyphens (-), underscores (_), and dots (.).", func(f *components.StringFlag) { f.Mandatory = false }),
SourceTypeBuildsFlag: components.NewStringFlag(SourceTypeBuildsFlag, "List of semicolon-separated (;) builds in the form of 'name=buildName1, id=runID1, [include-deps=true]; name=buildName2, id=runID2, [include-deps=true]' to be included in the new version.", func(f *components.StringFlag) { f.Mandatory = false }),
SourceTypeReleaseBundlesFlag: components.NewStringFlag(SourceTypeReleaseBundlesFlag, "List of semicolon-separated (;) release bundles in the form of 'name=releaseBundleName1, version=version1; name=releaseBundleName2, version=version2' to be included in the new version.", func(f *components.StringFlag) { f.Mandatory = false }),
SourceTypeApplicationVersionsFlag: components.NewStringFlag(SourceTypeApplicationVersionsFlag, "List of semicolon-separated (;) application versions in the form of 'application-key=app1, version=version1; application-key=app2, version=version2' to be included in the new version.", func(f *components.StringFlag) { f.Mandatory = false }),
PropertiesFlag: components.NewStringFlag(PropertiesFlag, "Sets or updates custom properties for the application version in format 'key1=value1[,value2,...];key2=value3[,value4,...]'", func(f *components.StringFlag) { f.Mandatory = false }),
DeletePropertyFlag: components.NewStringFlag(DeletePropertyFlag, "Remove a property key and all its values", func(f *components.StringFlag) { f.Mandatory = false }),
}

var commandFlags = map[string][]string{
Expand All @@ -105,14 +93,10 @@ var commandFlags = map[string][]string{
user,
accessToken,
serverId,
ApplicationKeyFlag,
TagFlag,
PackagesFlag,
PackageTypeFlag,
PackageRepositoryFlag,
BuildsFlag,
ReleaseBundlesFlag,
SourceVersionFlag,
SourceTypeBuildsFlag,
SourceTypeReleaseBundlesFlag,
SourceTypeApplicationVersionsFlag,
SpecFlag,
SpecVarsFlag,
},
Expand Down Expand Up @@ -161,17 +145,13 @@ var commandFlags = map[string][]string{
accessToken,
serverId,
ApplicationKeyFlag,
PackagesFlag,
PackageTypeFlag,
},
PackageUnbind: {
url,
user,
accessToken,
serverId,
ApplicationKeyFlag,
PackagesFlag,
PackageTypeFlag,
},

Ping: {
Expand All @@ -194,7 +174,6 @@ var commandFlags = map[string][]string{
LabelsFlag,
UserOwnersFlag,
GroupOwnersFlag,
SigningKeyFlag,
SpecFlag,
SpecVarsFlag,
},
Expand All @@ -211,9 +190,6 @@ var commandFlags = map[string][]string{
LabelsFlag,
UserOwnersFlag,
GroupOwnersFlag,
SigningKeyFlag,
SpecFlag,
SpecVarsFlag,
},

AppDelete: {
Expand Down
27 changes: 6 additions & 21 deletions apptrust/commands/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,15 @@ func ParseSliceFlag(flagValue string) []string {
// ParseMapFlag parses a semicolon-separated string of key=value pairs into a map[string]string.
// Returns an error if any pair does not contain exactly one '='.
func ParseMapFlag(flagValue string) (map[string]string, error) {
if flagValue == "" {
return ParseKeyValueString(flagValue, ";")
}

func ParseKeyValueString(value, separator string) (map[string]string, error) {
if value == "" {
return nil, nil
}
result := make(map[string]string)
pairs := strings.Split(flagValue, ";")
pairs := strings.Split(value, separator)
for _, pair := range pairs {
keyValue := strings.SplitN(pair, "=", 2)
if len(keyValue) != 2 {
Expand All @@ -87,25 +91,6 @@ func ValidateEnumFlag(flagName, value string, defaultValue string, allowedValues
flagName, value, coreutils.ListToText(allowedValues))
}

// ParsePackagesFlag parses a comma-separated list of package name:version pairs into a slice of maps.
// Each map contains keys "name" and "version". Returns an error if any entry is not in the expected format.
// Example input: "pkg1:1.0.0,pkg2:2.0.0" => []map[string]string{{"name": "pkg1", "version": "1.0.0"}, {"name": "pkg2", "version": "2.0.0"}}
func ParsePackagesFlag(flagValue string) ([]map[string]string, error) {
if flagValue == "" {
return nil, nil
}
pairs := strings.Split(flagValue, ",")
var result []map[string]string
for _, pair := range pairs {
parts := strings.SplitN(strings.TrimSpace(pair), PartSeparator, 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid package format: %s (expected <name>:<version>)", pair)
}
result = append(result, map[string]string{"name": parts[0], "version": parts[1]})
}
return result, nil
}

// ParseDelimitedSlice splits a delimited string into a slice of string slices.
// Example: input "a:1;b:2" returns [][]string{{"a","1"},{"b","2"}}
func ParseDelimitedSlice(input string) [][]string {
Expand Down
34 changes: 0 additions & 34 deletions apptrust/commands/utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,40 +115,6 @@ func TestValidateEnumFlag(t *testing.T) {
}
}

func TestParsePackagesFlag(t *testing.T) {
tests := []struct {
name string
input string
expected []map[string]string
expectErr bool
}{
{"empty string", "", nil, false},
{"single package", "foo:1.0.0", []map[string]string{{"name": "foo", "version": "1.0.0"}}, false},
{"multiple packages", "foo:1.0.0,bar:2.0.0", []map[string]string{{"name": "foo", "version": "1.0.0"}, {"name": "bar", "version": "2.0.0"}}, false},
{"spaces", " foo:1.0.0 , bar:2.0.0 ", []map[string]string{{"name": "foo", "version": "1.0.0"}, {"name": "bar", "version": "2.0.0"}}, false},
{"invalid format", "foo", nil, true},
{"missing version", "foo:", []map[string]string{{"name": "foo", "version": ""}}, false},
{"missing name", ":1.0.0", []map[string]string{{"name": "", "version": "1.0.0"}}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ParsePackagesFlag(tt.input)
if tt.expectErr {
if err == nil {
t.Errorf("expected error for input %q, got nil", tt.input)
}
return
}
if err != nil {
t.Errorf("unexpected error for input %q: %v", tt.input, err)
}
if !reflect.DeepEqual(result, tt.expected) {
t.Errorf("ParsePackagesFlag(%q) = %v, want %v", tt.input, result, tt.expected)
}
})
}
}

func TestParseDelimitedSlice(t *testing.T) {
tests := []struct {
name string
Expand Down
Loading
Loading