Skip to content

Commit b15a0a0

Browse files
authored
Align Create app version command (#28)
1 parent 2e307e8 commit b15a0a0

15 files changed

+771
-91
lines changed

apptrust/commands/flags.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ const (
4949
ExcludeReposFlag = "exclude-repos"
5050
IncludeReposFlag = "include-repos"
5151
PropsFlag = "props"
52+
TagFlag = "tag"
53+
BuildsFlag = "builds"
54+
ReleaseBundlesFlag = "release-bundles"
55+
SourceVersionFlag = "source-version"
56+
PackagesFlag = "packages"
5257
)
5358

5459
// Flag keys mapped to their corresponding components.Flag definition.
@@ -82,6 +87,11 @@ var flagsMap = map[string]components.Flag{
8287
ExcludeReposFlag: components.NewStringFlag(ExcludeReposFlag, "Semicolon-separated list of repositories to exclude.", func(f *components.StringFlag) { f.Mandatory = false }),
8388
IncludeReposFlag: components.NewStringFlag(IncludeReposFlag, "Semicolon-separated list of repositories to include.", func(f *components.StringFlag) { f.Mandatory = false }),
8489
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 }),
90+
TagFlag: components.NewStringFlag(TagFlag, "A tag to associate with the version.", func(f *components.StringFlag) { f.Mandatory = false }),
91+
BuildsFlag: components.NewStringFlag(BuildsFlag, "List of builds in format 'name1:number1[:timestamp1];name2:number2[:timestamp2]'", func(f *components.StringFlag) { f.Mandatory = false }),
92+
ReleaseBundlesFlag: components.NewStringFlag(ReleaseBundlesFlag, "List of release bundles in format 'name1:version1;name2:version2'", func(f *components.StringFlag) { f.Mandatory = false }),
93+
SourceVersionFlag: components.NewStringFlag(SourceVersionFlag, "Source versions in format 'app1:version1;app2:version2'", func(f *components.StringFlag) { f.Mandatory = false }),
94+
PackagesFlag: components.NewStringFlag(PackagesFlag, "List of packages in format 'name1;name2'", func(f *components.StringFlag) { f.Mandatory = false }),
8595
}
8696

8797
var commandFlags = map[string][]string{
@@ -91,10 +101,13 @@ var commandFlags = map[string][]string{
91101
accessToken,
92102
serverId,
93103
ApplicationKeyFlag,
104+
TagFlag,
105+
PackagesFlag,
94106
PackageTypeFlag,
95-
PackageNameFlag,
96-
PackageVersionFlag,
97107
PackageRepositoryFlag,
108+
BuildsFlag,
109+
ReleaseBundlesFlag,
110+
SourceVersionFlag,
98111
SpecFlag,
99112
SpecVarsFlag,
100113
},
@@ -133,13 +146,18 @@ var commandFlags = map[string][]string{
133146
user,
134147
accessToken,
135148
serverId,
149+
ApplicationKeyFlag,
150+
PackagesFlag,
151+
PackageTypeFlag,
136152
},
137-
138153
PackageUnbind: {
139154
url,
140155
user,
141156
accessToken,
142157
serverId,
158+
ApplicationKeyFlag,
159+
PackagesFlag,
160+
PackageTypeFlag,
143161
},
144162

145163
Ping: {

apptrust/commands/utils/utils.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ import (
1313
"github.com/jfrog/jfrog-client-go/utils/errorutils"
1414
)
1515

16+
const (
17+
EntrySeparator = ";"
18+
PartSeparator = ":"
19+
)
20+
1621
func AssertValueProvided(c *components.Context, fieldName string) error {
1722
if c.GetStringFlagValue(fieldName) == "" {
1823
return errorutils.CheckErrorf("the --%s option is mandatory", fieldName)
@@ -81,3 +86,53 @@ func ValidateEnumFlag(flagName, value string, defaultValue string, allowedValues
8186
return "", errorutils.CheckErrorf("invalid value for --%s: '%s'. Allowed values: %s",
8287
flagName, value, coreutils.ListToText(allowedValues))
8388
}
89+
90+
// ParsePackagesFlag parses a comma-separated list of package name:version pairs into a slice of maps.
91+
// Each map contains keys "name" and "version". Returns an error if any entry is not in the expected format.
92+
// 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"}}
93+
func ParsePackagesFlag(flagValue string) ([]map[string]string, error) {
94+
if flagValue == "" {
95+
return nil, nil
96+
}
97+
pairs := strings.Split(flagValue, ",")
98+
var result []map[string]string
99+
for _, pair := range pairs {
100+
parts := strings.SplitN(strings.TrimSpace(pair), PartSeparator, 2)
101+
if len(parts) != 2 {
102+
return nil, fmt.Errorf("invalid package format: %s (expected <name>:<version>)", pair)
103+
}
104+
result = append(result, map[string]string{"name": parts[0], "version": parts[1]})
105+
}
106+
return result, nil
107+
}
108+
109+
// ParseDelimitedSlice splits a delimited string into a slice of string slices.
110+
// Example: input "a:1;b:2" returns [][]string{{"a","1"},{"b","2"}}
111+
func ParseDelimitedSlice(input string) [][]string {
112+
var result [][]string
113+
if input == "" {
114+
return result
115+
}
116+
entries := strings.Split(input, EntrySeparator)
117+
for _, entry := range entries {
118+
parts := strings.Split(entry, PartSeparator)
119+
result = append(result, parts)
120+
}
121+
return result
122+
}
123+
124+
// ParseNameVersionPairs parses a delimited string (e.g., "name1:version1;name2:version2") into a slice of [2]string pairs.
125+
// Returns an error if any entry does not have exactly two parts.
126+
func ParseNameVersionPairs(input string) ([][2]string, error) {
127+
var result [][2]string
128+
if input == "" {
129+
return result, nil
130+
}
131+
for _, parts := range ParseDelimitedSlice(input) {
132+
if len(parts) != 2 {
133+
return nil, fmt.Errorf("invalid format: %v", parts)
134+
}
135+
result = append(result, [2]string{parts[0], parts[1]})
136+
}
137+
return result, nil
138+
}

apptrust/commands/utils/utils_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,86 @@ func TestValidateEnumFlag(t *testing.T) {
114114
})
115115
}
116116
}
117+
118+
func TestParsePackagesFlag(t *testing.T) {
119+
tests := []struct {
120+
name string
121+
input string
122+
expected []map[string]string
123+
expectErr bool
124+
}{
125+
{"empty string", "", nil, false},
126+
{"single package", "foo:1.0.0", []map[string]string{{"name": "foo", "version": "1.0.0"}}, false},
127+
{"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},
128+
{"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},
129+
{"invalid format", "foo", nil, true},
130+
{"missing version", "foo:", []map[string]string{{"name": "foo", "version": ""}}, false},
131+
{"missing name", ":1.0.0", []map[string]string{{"name": "", "version": "1.0.0"}}, false},
132+
}
133+
for _, tt := range tests {
134+
t.Run(tt.name, func(t *testing.T) {
135+
result, err := ParsePackagesFlag(tt.input)
136+
if tt.expectErr {
137+
if err == nil {
138+
t.Errorf("expected error for input %q, got nil", tt.input)
139+
}
140+
return
141+
}
142+
if err != nil {
143+
t.Errorf("unexpected error for input %q: %v", tt.input, err)
144+
}
145+
if !reflect.DeepEqual(result, tt.expected) {
146+
t.Errorf("ParsePackagesFlag(%q) = %v, want %v", tt.input, result, tt.expected)
147+
}
148+
})
149+
}
150+
}
151+
152+
func TestParseDelimitedSlice(t *testing.T) {
153+
tests := []struct {
154+
name string
155+
input string
156+
expected [][]string
157+
}{
158+
{"empty string", "", nil},
159+
{"single entry", "foo:bar", [][]string{{"foo", "bar"}}},
160+
{"multiple entries", "foo:bar;baz:qux", [][]string{{"foo", "bar"}, {"baz", "qux"}}},
161+
{"entries with extra parts", "a:1:2;b:3", [][]string{{"a", "1", "2"}, {"b", "3"}}},
162+
{"trailing separator", "foo:bar;", [][]string{{"foo", "bar"}, {""}}},
163+
}
164+
for _, tt := range tests {
165+
t.Run(tt.name, func(t *testing.T) {
166+
result := ParseDelimitedSlice(tt.input)
167+
if !reflect.DeepEqual(result, tt.expected) {
168+
t.Errorf("ParseDelimitedSlice(%q) = %v, want %v", tt.input, result, tt.expected)
169+
}
170+
})
171+
}
172+
}
173+
174+
func TestParseNameVersionPairs(t *testing.T) {
175+
tests := []struct {
176+
name string
177+
input string
178+
expected [][2]string
179+
expectErr bool
180+
}{
181+
{"empty string", "", nil, false},
182+
{"single pair", "foo:1.0.0", [][2]string{{"foo", "1.0.0"}}, false},
183+
{"multiple pairs", "foo:1.0.0;bar:2.0.0", [][2]string{{"foo", "1.0.0"}, {"bar", "2.0.0"}}, false},
184+
{"spaces", " foo:1.0.0 ; bar:2.0.0 ", [][2]string{{" foo", "1.0.0 "}, {" bar", "2.0.0 "}}, false},
185+
{"invalid format", "foo", nil, true},
186+
{"too many parts", "foo:1.0.0:extra", nil, true},
187+
}
188+
for _, tt := range tests {
189+
t.Run(tt.name, func(t *testing.T) {
190+
result, err := ParseNameVersionPairs(tt.input)
191+
if tt.expectErr {
192+
assert.Error(t, err, "expected error for input %q", tt.input)
193+
return
194+
}
195+
assert.NoError(t, err, "unexpected error for input %q: %v", tt.input, err)
196+
assert.Equal(t, tt.expected, result, "ParseNameVersionPairs(%q) = %v, want %v", tt.input, result, tt.expected)
197+
})
198+
}
199+
}

0 commit comments

Comments
 (0)