Skip to content

Commit d3e3df2

Browse files
committed
refactor
1 parent a68e6aa commit d3e3df2

File tree

9 files changed

+260
-137
lines changed

9 files changed

+260
-137
lines changed

apptrust/commands/flags.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,13 @@ var flagsMap = map[string]components.Flag{
9090
ExcludeReposFlag: components.NewStringFlag(ExcludeReposFlag, "Semicolon-separated list of repositories to exclude.", func(f *components.StringFlag) { f.Mandatory = false }),
9191
IncludeReposFlag: components.NewStringFlag(IncludeReposFlag, "Semicolon-separated list of repositories to include.", func(f *components.StringFlag) { f.Mandatory = false }),
9292
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 }),
93-
TagFlag: components.NewStringFlag(TagFlag, "A tag to associate with the version.", func(f *components.StringFlag) { f.Mandatory = false }),
93+
TagFlag: components.NewStringFlag(TagFlag, "A tag to associate with the version. Must contain only alphanumeric characters, hyphens (-), underscores (_), and dots (.). Examples: 'release-1.2.3', 'v1.0.0', 'production_build'.", func(f *components.StringFlag) { f.Mandatory = false }),
9494
BuildsFlag: components.NewStringFlag(BuildsFlag, "List of builds in format 'name1:number1[:timestamp1];name2:number2[:timestamp2]'", func(f *components.StringFlag) { f.Mandatory = false }),
9595
ReleaseBundlesFlag: components.NewStringFlag(ReleaseBundlesFlag, "List of release bundles in format 'name1:version1;name2:version2'", func(f *components.StringFlag) { f.Mandatory = false }),
9696
SourceVersionFlag: components.NewStringFlag(SourceVersionFlag, "Source versions in format 'app1:version1;app2:version2'", func(f *components.StringFlag) { f.Mandatory = false }),
9797
PackagesFlag: components.NewStringFlag(PackagesFlag, "List of packages in format 'name1;name2'", func(f *components.StringFlag) { f.Mandatory = false }),
98-
PropertiesFlag: components.NewStringFlag(PropertiesFlag, "Sets or updates a custom property for the application version in format 'key=value1[,value2,...]'", func(f *components.StringFlag) { f.Mandatory = false }),
99-
DeletePropertyFlag: components.NewStringFlag(DeletePropertyFlag, "Completely removes the specified property key and all its associated values from the application version", func(f *components.StringFlag) { f.Mandatory = false }),
98+
PropertiesFlag: components.NewStringFlag(PropertiesFlag, "Set or update a property: 'key=val1[,val2,...]'", func(f *components.StringFlag) { f.Mandatory = false }),
99+
DeletePropertyFlag: components.NewStringFlag(DeletePropertyFlag, "Remove a property key and all its values", func(f *components.StringFlag) { f.Mandatory = false }),
100100
}
101101

102102
var commandFlags = map[string][]string{

apptrust/commands/utils/utils.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,45 @@ func ParseNameVersionPairs(input string) ([][2]string, error) {
136136
}
137137
return result, nil
138138
}
139+
140+
// ParsePropertiesFlag parses a properties string into a map of keys to value slices.
141+
// Format: "key1=value1[,value2,...];key2=value3[,value4,...]"
142+
// Examples:
143+
// - "status=rc" -> {"status": ["rc"]}
144+
// - "status=rc,validated" -> {"status": ["rc", "validated"]}
145+
// - "status=rc;deployed_to=staging" -> {"status": ["rc"], "deployed_to": ["staging"]}
146+
// - "old_flag=" -> {"old_flag": []} (clears values)
147+
func ParsePropertiesFlag(propertiesStr string) (map[string][]string, error) {
148+
if propertiesStr == "" {
149+
return nil, nil
150+
}
151+
152+
result := make(map[string][]string)
153+
pairs := strings.Split(propertiesStr, ";")
154+
155+
for _, pair := range pairs {
156+
keyValue := strings.SplitN(strings.TrimSpace(pair), "=", 2)
157+
if len(keyValue) != 2 {
158+
return nil, errorutils.CheckErrorf("invalid property format: \"%s\" (expected key=value1[,value2,...])", pair)
159+
}
160+
161+
key := strings.TrimSpace(keyValue[0])
162+
valuesStr := strings.TrimSpace(keyValue[1])
163+
164+
if key == "" {
165+
return nil, errorutils.CheckErrorf("property key cannot be empty")
166+
}
167+
168+
var values []string
169+
if valuesStr != "" {
170+
values = strings.Split(valuesStr, ",")
171+
for i, v := range values {
172+
values[i] = strings.TrimSpace(v)
173+
}
174+
}
175+
// Always set the key, even with empty values (to clear values)
176+
result[key] = values
177+
}
178+
179+
return result, nil
180+
}

apptrust/commands/utils/utils_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,82 @@ func TestParseNameVersionPairs(t *testing.T) {
197197
})
198198
}
199199
}
200+
201+
func TestParsePropertiesFlag(t *testing.T) {
202+
tests := []struct {
203+
name string
204+
input string
205+
expected map[string][]string
206+
expectErr bool
207+
}{
208+
{
209+
name: "empty string",
210+
input: "",
211+
expected: nil,
212+
},
213+
{
214+
name: "single property with single value",
215+
input: "status=rc",
216+
expected: map[string][]string{
217+
"status": {"rc"},
218+
},
219+
},
220+
{
221+
name: "single property with multiple values",
222+
input: "status=rc,validated",
223+
expected: map[string][]string{
224+
"status": {"rc", "validated"},
225+
},
226+
},
227+
{
228+
name: "multiple properties",
229+
input: "status=rc,validated;deployed_to=staging-A,staging-B",
230+
expected: map[string][]string{
231+
"status": {"rc", "validated"},
232+
"deployed_to": {"staging-A", "staging-B"},
233+
},
234+
},
235+
{
236+
name: "empty values (clears values)",
237+
input: "old_feature_flag=",
238+
expected: map[string][]string{
239+
"old_feature_flag": nil,
240+
},
241+
},
242+
{
243+
name: "with spaces",
244+
input: " status = rc , validated ; deployed_to = staging-A , staging-B ",
245+
expected: map[string][]string{
246+
"status": {"rc", "validated"},
247+
"deployed_to": {"staging-A", "staging-B"},
248+
},
249+
},
250+
{
251+
name: "invalid format - missing =",
252+
input: "invalid-format",
253+
expectErr: true,
254+
},
255+
{
256+
name: "empty key",
257+
input: "=value",
258+
expectErr: true,
259+
},
260+
{
261+
name: "empty key with spaces",
262+
input: " =value",
263+
expectErr: true,
264+
},
265+
}
266+
267+
for _, tt := range tests {
268+
t.Run(tt.name, func(t *testing.T) {
269+
result, err := ParsePropertiesFlag(tt.input)
270+
if tt.expectErr {
271+
assert.Error(t, err)
272+
} else {
273+
assert.NoError(t, err)
274+
assert.Equal(t, tt.expected, result)
275+
}
276+
})
277+
}
278+
}

apptrust/commands/version/update_app_version_cmd.go

Lines changed: 34 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ package version
33
//go:generate ${PROJECT_DIR}/scripts/mockgen.sh ${GOFILE}
44

55
import (
6-
"strings"
7-
86
"github.com/jfrog/jfrog-cli-application/apptrust/app"
97
"github.com/jfrog/jfrog-cli-application/apptrust/commands"
108
"github.com/jfrog/jfrog-cli-application/apptrust/commands/utils"
@@ -17,6 +15,7 @@ import (
1715
"github.com/jfrog/jfrog-cli-core/v2/plugins/components"
1816
coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config"
1917
"github.com/jfrog/jfrog-client-go/utils/errorutils"
18+
"github.com/jfrog/jfrog-client-go/utils/log"
2019
)
2120

2221
type updateAppVersionCommand struct {
@@ -28,11 +27,22 @@ type updateAppVersionCommand struct {
2827
}
2928

3029
func (uv *updateAppVersionCommand) Run() error {
30+
log.Info("Updating application version:", uv.applicationKey, "version:", uv.version)
31+
3132
ctx, err := service.NewContext(*uv.serverDetails)
3233
if err != nil {
34+
log.Error("Failed to create service context:", err)
35+
return err
36+
}
37+
38+
err = uv.versionService.UpdateAppVersion(ctx, uv.requestPayload)
39+
if err != nil {
40+
log.Error("Failed to update application version:", err)
3341
return err
3442
}
35-
return uv.versionService.UpdateAppVersion(ctx, uv.applicationKey, uv.version, uv.requestPayload)
43+
44+
log.Info("Successfully updated application version:", uv.applicationKey, "version:", uv.version)
45+
return nil
3646
}
3747

3848
func (uv *updateAppVersionCommand) ServerDetails() (*coreConfig.ServerDetails, error) {
@@ -48,15 +58,11 @@ func (uv *updateAppVersionCommand) prepareAndRunCommand(ctx *components.Context)
4858
return pluginsCommon.WrongNumberOfArgumentsHandler(ctx)
4959
}
5060

51-
uv.applicationKey = ctx.Arguments[0]
52-
uv.version = ctx.Arguments[1]
53-
54-
serverDetails, err := utils.ServerDetailsByFlags(ctx)
55-
if err != nil {
61+
if err := uv.parseFlagsAndSetFields(ctx); err != nil {
5662
return err
5763
}
58-
uv.serverDetails = serverDetails
5964

65+
var err error
6066
uv.requestPayload, err = uv.buildRequestPayload(ctx)
6167
if errorutils.CheckError(err) != nil {
6268
return err
@@ -65,17 +71,32 @@ func (uv *updateAppVersionCommand) prepareAndRunCommand(ctx *components.Context)
6571
return commonCLiCommands.Exec(uv)
6672
}
6773

74+
// parseFlagsAndSetFields parses CLI flags and sets struct fields accordingly.
75+
func (uv *updateAppVersionCommand) parseFlagsAndSetFields(ctx *components.Context) error {
76+
uv.applicationKey = ctx.Arguments[0]
77+
uv.version = ctx.Arguments[1]
78+
79+
serverDetails, err := utils.ServerDetailsByFlags(ctx)
80+
if err != nil {
81+
return err
82+
}
83+
uv.serverDetails = serverDetails
84+
return nil
85+
}
86+
6887
func (uv *updateAppVersionCommand) buildRequestPayload(ctx *components.Context) (*model.UpdateAppVersionRequest, error) {
69-
request := &model.UpdateAppVersionRequest{}
88+
request := &model.UpdateAppVersionRequest{
89+
ApplicationKey: uv.applicationKey,
90+
Version: uv.version,
91+
}
7092

71-
// Handle tag - no validation, just pass through
7293
if ctx.IsFlagSet(commands.TagFlag) {
7394
request.Tag = ctx.GetStringFlagValue(commands.TagFlag)
7495
}
7596

76-
// Handle properties - support multiple values per key
97+
// Handle properties - use spec format: key=value1[,value2,...]
7798
if ctx.IsFlagSet(commands.PropertiesFlag) {
78-
properties, err := uv.parseProperties(ctx.GetStringFlagValue(commands.PropertiesFlag))
99+
properties, err := utils.ParsePropertiesFlag(ctx.GetStringFlagValue(commands.PropertiesFlag))
79100
if err != nil {
80101
return nil, err
81102
}
@@ -91,43 +112,6 @@ func (uv *updateAppVersionCommand) buildRequestPayload(ctx *components.Context)
91112
return request, nil
92113
}
93114

94-
func (uv *updateAppVersionCommand) parseProperties(propertiesStr string) (map[string][]string, error) {
95-
// Format: "key1=value1[,value2,...];key2=value3[,value4,...]"
96-
if propertiesStr == "" {
97-
return nil, nil
98-
}
99-
100-
result := make(map[string][]string)
101-
pairs := strings.Split(propertiesStr, ";")
102-
103-
for _, pair := range pairs {
104-
keyValue := strings.SplitN(strings.TrimSpace(pair), "=", 2)
105-
if len(keyValue) != 2 {
106-
return nil, errorutils.CheckErrorf("invalid property format: '%s' (expected key=value1[,value2,...])", pair)
107-
}
108-
109-
key := strings.TrimSpace(keyValue[0])
110-
valuesStr := strings.TrimSpace(keyValue[1])
111-
112-
if key == "" {
113-
return nil, errorutils.CheckErrorf("property key cannot be empty")
114-
}
115-
116-
var values []string
117-
if valuesStr != "" {
118-
values = strings.Split(valuesStr, ",")
119-
for i, v := range values {
120-
values[i] = strings.TrimSpace(v)
121-
}
122-
}
123-
// Always set the key, even with empty values (to clear values)
124-
125-
result[key] = values
126-
}
127-
128-
return result, nil
129-
}
130-
131115
func GetUpdateAppVersionCommand(appContext app.Context) components.Command {
132116
cmd := &updateAppVersionCommand{versionService: appContext.GetVersionService()}
133117
return components.Command{

0 commit comments

Comments
 (0)