Skip to content

Commit dc5241b

Browse files
committed
Create app version align
1 parent 6d7e672 commit dc5241b

File tree

6 files changed

+499
-58
lines changed

6 files changed

+499
-58
lines changed

apptrust/commands/flags.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ const (
4747
DryRunFlag = "dry-run"
4848
ExcludeReposFlag = "exclude-repos"
4949
IncludeReposFlag = "include-repos"
50+
PropsFlag = "props"
51+
TagFlag = "tag"
52+
BuildsFlag = "builds"
53+
ReleaseBundlesFlag = "release-bundles"
54+
SourceVersionFlag = "source-version"
55+
ExcludeFlag = "exclude"
56+
PromoteFlag = "promote"
5057
)
5158

5259
// Flag keys mapped to their corresponding components.Flag definition.
@@ -64,7 +71,7 @@ var flagsMap = map[string]components.Flag{
6471
PackageVersionFlag: components.NewStringFlag(PackageVersionFlag, "Package version.", func(f *components.StringFlag) { f.Mandatory = false }),
6572
PackageRepositoryFlag: components.NewStringFlag(PackageRepositoryFlag, "Package storing repository.", func(f *components.StringFlag) { f.Mandatory = false }),
6673
SpecFlag: components.NewStringFlag(SpecFlag, "A path to the specification file.", func(f *components.StringFlag) { f.Mandatory = false }),
67-
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 }),
74+
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 }),
6875
StageVarsFlag: components.NewStringFlag(StageVarsFlag, "Promotion stage.", func(f *components.StringFlag) { f.Mandatory = true }),
6976
ApplicationNameFlag: components.NewStringFlag(ApplicationNameFlag, "The display name of the application.", func(f *components.StringFlag) { f.Mandatory = false }),
7077
DescriptionFlag: components.NewStringFlag(DescriptionFlag, "The description of the application.", func(f *components.StringFlag) { f.Mandatory = false }),
@@ -79,6 +86,13 @@ var flagsMap = map[string]components.Flag{
7986
DryRunFlag: components.NewBoolFlag(DryRunFlag, "Perform a simulation of the operation.", components.WithBoolDefaultValueFalse()),
8087
ExcludeReposFlag: components.NewStringFlag(ExcludeReposFlag, "Semicolon-separated list of repositories to exclude.", func(f *components.StringFlag) { f.Mandatory = false }),
8188
IncludeReposFlag: components.NewStringFlag(IncludeReposFlag, "Semicolon-separated list of repositories to include.", func(f *components.StringFlag) { f.Mandatory = false }),
89+
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+
ExcludeFlag: components.NewStringFlag(ExcludeFlag, "Excluded packages in format 'package1:version1;package2:version2'", func(f *components.StringFlag) { f.Mandatory = false }),
95+
PromoteFlag: components.NewStringFlag(PromoteFlag, "Promotion stage for immediate promotion after creation", func(f *components.StringFlag) { f.Mandatory = false }),
8296
}
8397

8498
var commandFlags = map[string][]string{
@@ -88,12 +102,20 @@ var commandFlags = map[string][]string{
88102
accessToken,
89103
serverId,
90104
ApplicationKeyFlag,
105+
TagFlag,
91106
PackageTypeFlag,
92107
PackageNameFlag,
93108
PackageVersionFlag,
94109
PackageRepositoryFlag,
110+
BuildsFlag,
111+
ReleaseBundlesFlag,
112+
SourceVersionFlag,
113+
ExcludeFlag,
114+
PromoteFlag,
115+
SigningKeyFlag,
95116
SpecFlag,
96117
SpecVarsFlag,
118+
SyncFlag,
97119
},
98120
VersionPromote: {
99121
url,

apptrust/commands/version/create_app_version_cmd.go

Lines changed: 200 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package version
22

33
import (
44
"encoding/json"
5+
"strings"
6+
"time"
57

68
"github.com/jfrog/jfrog-cli-application/apptrust/service/versions"
79

@@ -24,10 +26,16 @@ type createAppVersionCommand struct {
2426
versionService versions.VersionService
2527
serverDetails *coreConfig.ServerDetails
2628
requestPayload *model.CreateAppVersionRequest
29+
signingKey string
30+
sync bool
2731
}
2832

2933
type createVersionSpec struct {
30-
Packages []model.CreateVersionPackage `json:"packages"`
34+
Packages []model.CreateVersionPackage `json:"packages,omitempty"`
35+
Builds []model.CreateVersionBuild `json:"builds,omitempty"`
36+
ReleaseBundles []model.CreateVersionReleaseBundle `json:"release_bundles,omitempty"`
37+
Versions []model.CreateVersionReference `json:"versions,omitempty"`
38+
Exclude []model.ExcludePackage `json:"exclude,omitempty"`
3139
}
3240

3341
func (cv *createAppVersionCommand) Run() error {
@@ -36,7 +44,7 @@ func (cv *createAppVersionCommand) Run() error {
3644
return err
3745
}
3846

39-
return cv.versionService.CreateAppVersion(ctx, cv.requestPayload)
47+
return cv.versionService.CreateAppVersion(ctx, cv.requestPayload, cv.signingKey, cv.sync)
4048
}
4149

4250
func (cv *createAppVersionCommand) ServerDetails() (*coreConfig.ServerDetails, error) {
@@ -51,6 +59,10 @@ func (cv *createAppVersionCommand) prepareAndRunCommand(ctx *components.Context)
5159
if err := validateCreateAppVersionContext(ctx); err != nil {
5260
return err
5361
}
62+
63+
cv.signingKey = ctx.GetStringFlagValue(commands.SigningKeyFlag)
64+
cv.sync = ctx.GetBoolFlagValue(commands.SyncFlag)
65+
5466
serverDetails, err := utils.ServerDetailsByFlags(ctx)
5567
if err != nil {
5668
return err
@@ -64,35 +76,76 @@ func (cv *createAppVersionCommand) prepareAndRunCommand(ctx *components.Context)
6476
}
6577

6678
func (cv *createAppVersionCommand) buildRequestPayload(ctx *components.Context) (*model.CreateAppVersionRequest, error) {
67-
var packages []model.CreateVersionPackage
79+
sources := &model.CreateVersionSources{}
80+
var err error
81+
82+
// Handle spec file if provided
6883
if ctx.IsFlagSet(commands.SpecFlag) {
69-
err := loadPackagesFromSpec(ctx, &packages)
84+
sources, err = cv.loadFromSpec(ctx)
7085
if errorutils.CheckError(err) != nil {
7186
return nil, err
7287
}
7388
} else {
74-
packages = append(packages, model.CreateVersionPackage{
75-
Type: ctx.GetStringFlagValue(commands.PackageTypeFlag),
76-
Name: ctx.GetStringFlagValue(commands.PackageNameFlag),
77-
Version: ctx.GetStringFlagValue(commands.PackageVersionFlag),
78-
Repository: ctx.GetStringFlagValue(commands.PackageRepositoryFlag),
79-
})
89+
if ctx.IsFlagSet(commands.PackageNameFlag) {
90+
sources.Packages = append(sources.Packages, model.CreateVersionPackage{
91+
Type: ctx.GetStringFlagValue(commands.PackageTypeFlag),
92+
Name: ctx.GetStringFlagValue(commands.PackageNameFlag),
93+
Version: ctx.GetStringFlagValue(commands.PackageVersionFlag),
94+
Repository: ctx.GetStringFlagValue(commands.PackageRepositoryFlag),
95+
})
96+
}
97+
if buildsStr := ctx.GetStringFlagValue(commands.BuildsFlag); buildsStr != "" {
98+
builds, err := cv.parseBuilds(buildsStr)
99+
if err != nil {
100+
return nil, err
101+
}
102+
sources.Builds = builds
103+
}
104+
if rbStr := ctx.GetStringFlagValue(commands.ReleaseBundlesFlag); rbStr != "" {
105+
releaseBundles, err := cv.parseReleaseBundles(rbStr)
106+
if err != nil {
107+
return nil, err
108+
}
109+
sources.ReleaseBundles = releaseBundles
110+
}
111+
if srcVersionsStr := ctx.GetStringFlagValue(commands.SourceVersionFlag); srcVersionsStr != "" {
112+
sourceVersions, err := cv.parseSourceVersions(srcVersionsStr)
113+
if err != nil {
114+
return nil, err
115+
}
116+
sources.Versions = sourceVersions
117+
}
118+
if excludeStr := ctx.GetStringFlagValue(commands.ExcludeFlag); excludeStr != "" {
119+
excludedPackages, err := cv.parseExcludedPackages(excludeStr)
120+
if err != nil {
121+
return nil, err
122+
}
123+
sources.Exclude = excludedPackages
124+
}
125+
}
126+
127+
// Only include sources in the request if any sources were provided
128+
var sourcesPointer *model.CreateVersionSources
129+
if len(sources.Packages) > 0 || len(sources.Builds) > 0 ||
130+
len(sources.ReleaseBundles) > 0 || len(sources.Versions) > 0 || len(sources.Exclude) > 0 {
131+
sourcesPointer = sources
80132
}
81133

82134
return &model.CreateAppVersionRequest{
83135
ApplicationKey: ctx.GetStringFlagValue(commands.ApplicationKeyFlag),
84-
Version: ctx.Arguments[0],
85-
Packages: packages,
136+
Version: ctx.Arguments[1],
137+
Sources: sourcesPointer,
138+
Tag: ctx.GetStringFlagValue(commands.TagFlag),
86139
}, nil
87140
}
88141

89-
func loadPackagesFromSpec(ctx *components.Context, packages *[]model.CreateVersionPackage) error {
142+
func (cv *createAppVersionCommand) loadFromSpec(ctx *components.Context) (*model.CreateVersionSources, error) {
90143
specFilePath := ctx.GetStringFlagValue(commands.SpecFlag)
91144
spec := new(createVersionSpec)
92-
specVars := coreutils.SpecVarsStringToMap(ctx.GetStringFlagValue("spec-vars"))
145+
specVars := coreutils.SpecVarsStringToMap(ctx.GetStringFlagValue(commands.SpecVarsFlag))
93146
content, err := fileutils.ReadFile(specFilePath)
94147
if errorutils.CheckError(err) != nil {
95-
return err
148+
return nil, err
96149
}
97150

98151
if len(specVars) > 0 {
@@ -101,32 +154,139 @@ func loadPackagesFromSpec(ctx *components.Context, packages *[]model.CreateVersi
101154

102155
err = json.Unmarshal(content, spec)
103156
if errorutils.CheckError(err) != nil {
104-
return err
157+
return nil, err
105158
}
106159

107-
// add spec packages to the packages list
108-
*packages = append(*packages, spec.Packages...)
109-
return nil
160+
sources := &model.CreateVersionSources{
161+
Packages: spec.Packages,
162+
Builds: spec.Builds,
163+
ReleaseBundles: spec.ReleaseBundles,
164+
Versions: spec.Versions,
165+
Exclude: spec.Exclude,
166+
}
167+
168+
return sources, nil
110169
}
111170

112-
func validateCreateAppVersionContext(ctx *components.Context) error {
113-
if len(ctx.Arguments) != 1 {
114-
return pluginsCommon.WrongNumberOfArgumentsHandler(ctx)
171+
// Helper method to parse builds string format: "name1:number1[:timestamp1];name2:number2[:timestamp2]"
172+
func (cv *createAppVersionCommand) parseBuilds(buildsStr string) ([]model.CreateVersionBuild, error) {
173+
var builds []model.CreateVersionBuild
174+
buildEntries := strings.Split(buildsStr, ";")
175+
176+
for _, entry := range buildEntries {
177+
parts := strings.Split(entry, ":")
178+
if len(parts) < 2 || len(parts) > 3 {
179+
return nil, errorutils.CheckErrorf("invalid build format: '%s'. Expected format: name:number[:timestamp]", entry)
180+
}
181+
182+
build := model.CreateVersionBuild{
183+
Name: parts[0],
184+
Number: parts[1],
185+
}
186+
187+
// Add timestamp if provided (optional)
188+
if len(parts) == 3 {
189+
build.Started = parts[2]
190+
191+
// Validate timestamp format
192+
_, err := time.Parse(time.RFC3339, build.Started)
193+
if err != nil {
194+
return nil, errorutils.CheckErrorf("invalid timestamp format for build '%s': %s. Expected RFC3339 format (e.g., 2006-01-02T15:04:05Z)", build.Name, err.Error())
195+
}
196+
}
197+
198+
builds = append(builds, build)
115199
}
116200

117-
// Use spec flag if provided, if not check for package flags
118-
err := utils.AssertValueProvided(ctx, commands.SpecFlag)
119-
if err != nil {
120-
err = utils.AssertValueProvided(ctx, commands.PackageNameFlag)
121-
if err != nil {
122-
return handleMissingPackageDetailsError()
201+
return builds, nil
202+
}
203+
204+
// Helper method to parse release bundles string format: "name1:version1;name2:version2"
205+
func (cv *createAppVersionCommand) parseReleaseBundles(rbStr string) ([]model.CreateVersionReleaseBundle, error) {
206+
var releaseBundles []model.CreateVersionReleaseBundle
207+
rbEntries := strings.Split(rbStr, ";")
208+
209+
for _, entry := range rbEntries {
210+
parts := strings.Split(entry, ":")
211+
if len(parts) != 2 {
212+
return nil, errorutils.CheckErrorf("invalid release bundle format: '%s'. Expected format: name:version", entry)
123213
}
124-
err = utils.AssertValueProvided(ctx, commands.PackageVersionFlag)
125-
if err != nil {
126-
return handleMissingPackageDetailsError()
214+
215+
rb := model.CreateVersionReleaseBundle{
216+
Name: parts[0],
217+
Version: parts[1],
127218
}
128-
err = utils.AssertValueProvided(ctx, commands.PackageRepositoryFlag)
129-
if err != nil {
219+
220+
releaseBundles = append(releaseBundles, rb)
221+
}
222+
223+
return releaseBundles, nil
224+
}
225+
226+
// Helper method to parse source versions string format: "app1:version1;app2:version2"
227+
func (cv *createAppVersionCommand) parseSourceVersions(sourceVersionsStr string) ([]model.CreateVersionReference, error) {
228+
var sourceVersions []model.CreateVersionReference
229+
versionEntries := strings.Split(sourceVersionsStr, ";")
230+
231+
for _, entry := range versionEntries {
232+
parts := strings.Split(entry, ":")
233+
if len(parts) != 2 {
234+
return nil, errorutils.CheckErrorf("invalid source version format: '%s'. Expected format: app-key:version", entry)
235+
}
236+
237+
sv := model.CreateVersionReference{
238+
ApplicationKey: parts[0],
239+
Version: parts[1],
240+
}
241+
242+
sourceVersions = append(sourceVersions, sv)
243+
}
244+
245+
return sourceVersions, nil
246+
}
247+
248+
// Helper method to parse excluded packages string format: "name1:version1;name2:version2"
249+
func (cv *createAppVersionCommand) parseExcludedPackages(excludeStr string) ([]model.ExcludePackage, error) {
250+
var excludedPackages []model.ExcludePackage
251+
packageEntries := strings.Split(excludeStr, ";")
252+
253+
for _, entry := range packageEntries {
254+
parts := strings.Split(entry, ":")
255+
if len(parts) != 2 {
256+
return nil, errorutils.CheckErrorf("invalid exclude package format: '%s'. Expected format: name:version", entry)
257+
}
258+
259+
pkg := model.ExcludePackage{
260+
Name: parts[0],
261+
Version: parts[1],
262+
}
263+
264+
excludedPackages = append(excludedPackages, pkg)
265+
}
266+
267+
return excludedPackages, nil
268+
}
269+
270+
// Updated validation logic to support multiple source types
271+
func validateCreateAppVersionContext(ctx *components.Context) error {
272+
if len(ctx.Arguments) != 2 {
273+
return pluginsCommon.WrongNumberOfArgumentsHandler(ctx)
274+
}
275+
276+
hasSource := ctx.IsFlagSet(commands.SpecFlag) ||
277+
ctx.IsFlagSet(commands.PackageNameFlag) ||
278+
ctx.IsFlagSet(commands.BuildsFlag) ||
279+
ctx.IsFlagSet(commands.ReleaseBundlesFlag) ||
280+
ctx.IsFlagSet(commands.SourceVersionFlag)
281+
282+
if !hasSource {
283+
return errorutils.CheckErrorf("Missing source information. Please provide at least one source: --%s, --%s, --%s, --%s, or --%s",
284+
commands.SpecFlag, commands.PackageNameFlag, commands.BuildsFlag, commands.ReleaseBundlesFlag, commands.SourceVersionFlag)
285+
}
286+
287+
// Validate package details if used
288+
if ctx.IsFlagSet(commands.PackageNameFlag) {
289+
if !ctx.IsFlagSet(commands.PackageVersionFlag) || !ctx.IsFlagSet(commands.PackageRepositoryFlag) {
130290
return handleMissingPackageDetailsError()
131291
}
132292
}
@@ -143,8 +303,13 @@ func GetCreateAppVersionCommand(appContext app.Context) components.Command {
143303
Aliases: []string{"vc"},
144304
Arguments: []components.Argument{
145305
{
146-
Name: "version-name",
147-
Description: "The name of the version.",
306+
Name: "application-key",
307+
Description: "The application key.",
308+
Optional: false,
309+
},
310+
{
311+
Name: "version",
312+
Description: "The version to create (must follow SemVer format).",
148313
Optional: false,
149314
},
150315
},

0 commit comments

Comments
 (0)