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
24 changes: 21 additions & 3 deletions apptrust/commands/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ const (
ExcludeReposFlag = "exclude-repos"
IncludeReposFlag = "include-repos"
PropsFlag = "props"
TagFlag = "tag"
BuildsFlag = "builds"
ReleaseBundlesFlag = "release-bundles"
SourceVersionFlag = "source-version"
PackagesFlag = "packages"
)

// Flag keys mapped to their corresponding components.Flag definition.
Expand Down Expand Up @@ -82,6 +87,11 @@ var flagsMap = map[string]components.Flag{
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.", 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 }),
}

var commandFlags = map[string][]string{
Expand All @@ -91,10 +101,13 @@ var commandFlags = map[string][]string{
accessToken,
serverId,
ApplicationKeyFlag,
TagFlag,
PackagesFlag,
PackageTypeFlag,
PackageNameFlag,
PackageVersionFlag,
PackageRepositoryFlag,
BuildsFlag,
ReleaseBundlesFlag,
SourceVersionFlag,
SpecFlag,
SpecVarsFlag,
},
Expand Down Expand Up @@ -133,13 +146,18 @@ var commandFlags = map[string][]string{
user,
accessToken,
serverId,
ApplicationKeyFlag,
PackagesFlag,
PackageTypeFlag,
},

PackageUnbind: {
url,
user,
accessToken,
serverId,
ApplicationKeyFlag,
PackagesFlag,
PackageTypeFlag,
},

Ping: {
Expand Down
55 changes: 55 additions & 0 deletions apptrust/commands/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import (
"github.com/jfrog/jfrog-client-go/utils/errorutils"
)

const (
EntrySeparator = ";"
PartSeparator = ":"
)

func AssertValueProvided(c *components.Context, fieldName string) error {
if c.GetStringFlagValue(fieldName) == "" {
return errorutils.CheckErrorf("the --%s option is mandatory", fieldName)
Expand Down Expand Up @@ -81,3 +86,53 @@ func ValidateEnumFlag(flagName, value string, defaultValue string, allowedValues
return "", errorutils.CheckErrorf("invalid value for --%s: '%s'. Allowed values: %s",
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 {
var result [][]string
if input == "" {
return result
}
entries := strings.Split(input, EntrySeparator)
for _, entry := range entries {
parts := strings.Split(entry, PartSeparator)
result = append(result, parts)
}
return result
}

// ParseNameVersionPairs parses a delimited string (e.g., "name1:version1;name2:version2") into a slice of [2]string pairs.
// Returns an error if any entry does not have exactly two parts.
func ParseNameVersionPairs(input string) ([][2]string, error) {
var result [][2]string
if input == "" {
return result, nil
}
for _, parts := range ParseDelimitedSlice(input) {
if len(parts) != 2 {
return nil, fmt.Errorf("invalid format: %v", parts)
}
result = append(result, [2]string{parts[0], parts[1]})
}
return result, nil
}
83 changes: 83 additions & 0 deletions apptrust/commands/utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,86 @@ 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
input string
expected [][]string
}{
{"empty string", "", nil},
{"single entry", "foo:bar", [][]string{{"foo", "bar"}}},
{"multiple entries", "foo:bar;baz:qux", [][]string{{"foo", "bar"}, {"baz", "qux"}}},
{"entries with extra parts", "a:1:2;b:3", [][]string{{"a", "1", "2"}, {"b", "3"}}},
{"trailing separator", "foo:bar;", [][]string{{"foo", "bar"}, {""}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ParseDelimitedSlice(tt.input)
if !reflect.DeepEqual(result, tt.expected) {
t.Errorf("ParseDelimitedSlice(%q) = %v, want %v", tt.input, result, tt.expected)
}
})
}
}

func TestParseNameVersionPairs(t *testing.T) {
tests := []struct {
name string
input string
expected [][2]string
expectErr bool
}{
{"empty string", "", nil, false},
{"single pair", "foo:1.0.0", [][2]string{{"foo", "1.0.0"}}, false},
{"multiple pairs", "foo:1.0.0;bar:2.0.0", [][2]string{{"foo", "1.0.0"}, {"bar", "2.0.0"}}, false},
{"spaces", " foo:1.0.0 ; bar:2.0.0 ", [][2]string{{" foo", "1.0.0 "}, {" bar", "2.0.0 "}}, false},
{"invalid format", "foo", nil, true},
{"too many parts", "foo:1.0.0:extra", nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ParseNameVersionPairs(tt.input)
if tt.expectErr {
assert.Error(t, err, "expected error for input %q", tt.input)
return
}
assert.NoError(t, err, "unexpected error for input %q: %v", tt.input, err)
assert.Equal(t, tt.expected, result, "ParseNameVersionPairs(%q) = %v, want %v", tt.input, result, tt.expected)
})
}
}
Loading
Loading