Skip to content
Open
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
3 changes: 3 additions & 0 deletions apptrust/commands/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const (
OverwriteStrategyFlag = "overwrite-strategy"
TagFlag = "tag"
DraftFlag = "draft"
SkipUnassignedFlag = "skip-unassigned"
SourceTypeBuildsFlag = "source-type-builds"
SourceTypeReleaseBundlesFlag = "source-type-release-bundles"
SourceTypeApplicationVersionsFlag = "source-type-application-versions"
Expand Down Expand Up @@ -94,6 +95,7 @@ var flagsMap = map[string]components.Flag{
OverwriteStrategyFlag: components.NewStringFlag(OverwriteStrategyFlag, "Strategy for handling target artifacts with the same path but different checksum. Supported values: "+coreutils.ListToText(model.OverwriteStrategyValues)+".", 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 }),
DraftFlag: components.NewBoolFlag(DraftFlag, "Create the application version as a draft.", components.WithBoolDefaultValueFalse()),
SkipUnassignedFlag: components.NewBoolFlag(SkipUnassignedFlag, "Automatically promote the new version to the first lifecycle stage when all of its source artifacts reside in repositories mapped to that stage. Otherwise the version is left unassigned and a message explaining why is returned.", components.WithBoolDefaultValueFalse()),
SourceTypeBuildsFlag: components.NewStringFlag(SourceTypeBuildsFlag, "List of semicolon-separated (;) builds in the form of 'name=buildName1, id=runID1[, include-deps=true][, repo-key=repo1][, started=2023-01-01T12:34:56.789+0100]; name=buildName2, id=runID2[, include-deps=true][, repo-key=repo2][, started=2023-01-01T12:34:56.789+0100]' 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[, project-key=project1][, repo-key=repo1]; name=releaseBundleName2, version=version2[, project-key=project2][, repo-key=repo2]' 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 }),
Expand All @@ -114,6 +116,7 @@ var commandFlags = map[string][]string{
SyncFlag,
TagFlag,
DraftFlag,
SkipUnassignedFlag,
SourceTypeBuildsFlag,
SourceTypeReleaseBundlesFlag,
SourceTypeApplicationVersionsFlag,
Expand Down
1 change: 1 addition & 0 deletions apptrust/commands/version/create_app_version_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func (cv *createAppVersionCommand) buildRequestPayload(ctx *components.Context)
Sources: sources,
Tag: ctx.GetStringFlagValue(commands.TagFlag),
Draft: ctx.GetBoolFlagValue(commands.DraftFlag),
SkipUnassigned: ctx.GetBoolFlagValue(commands.SkipUnassignedFlag),
Filters: filters,
}, nil
}
Expand Down
18 changes: 18 additions & 0 deletions apptrust/commands/version/create_app_version_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,24 @@ func TestCreateAppVersionCommand_FlagsSuite(t *testing.T) {
},
},
},
{
name: "skip-unassigned flag",
ctxSetup: func(ctx *components.Context) {
ctx.Arguments = []string{"app-key", "1.0.0"}
ctx.AddBoolFlag(commands.SkipUnassignedFlag, true)
ctx.AddStringFlag(commands.SourceTypePackagesFlag, "type=npm,name=pkg1,version=1.0.0,repo-key=repo1")
},
expectsPayload: &model.CreateAppVersionRequest{
ApplicationKey: "app-key",
Version: "1.0.0",
SkipUnassigned: true,
Sources: &model.CreateVersionSources{
Packages: []model.CreateVersionPackage{
{Type: "npm", Name: "pkg1", Version: "1.0.0", Repository: "repo1"},
},
},
},
},
{
name: "spec only",
ctxSetup: func(ctx *components.Context) {
Expand Down
1 change: 1 addition & 0 deletions apptrust/common/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ var OrderedAppVersionKeys = []string{
"status",
"current_stage",
"tag",
"message",
}

// OrderedAppKeys defines the display order for application table output
Expand Down
1 change: 1 addition & 0 deletions apptrust/model/create_app_version_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type CreateAppVersionRequest struct {
Sources *CreateVersionSources `json:"sources,omitempty"`
Tag string `json:"tag,omitempty"`
Draft bool `json:"draft,omitempty"`
SkipUnassigned bool `json:"skip_unassigned,omitempty"`
Filters *CreateVersionFilters `json:"filters,omitempty"`
}

Expand Down
93 changes: 93 additions & 0 deletions e2e/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
package e2e

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -196,6 +198,97 @@ func TestCreateVersion_Draft(t *testing.T) {
assert.Equal(t, utils.StatusDraft, versionContent.Status)
}

func TestCreateVersion_SkipUnassigned(t *testing.T) {
appKey := utils.GenerateUniqueKey("app-version-skip-unassigned")
utils.CreateBasicApplication(t, appKey)
defer utils.DeleteApplication(t, appKey)

testPackage := utils.GetTestPackage(t)

t.Run("auto-promotes when source repo is mapped to first stage", func(t *testing.T) {
version := utils.GenerateUniqueKey("skip-ua-ok")

packageFlag := fmt.Sprintf("--source-type-packages=type=%s, name=%s, version=%s, repo-key=%s",
testPackage.PackageType, testPackage.PackageName, testPackage.PackageVersion, testPackage.RepoKey)
output := utils.AppTrustCli.RunCliCmdWithOutput(t, "version-create", appKey, version, packageFlag, "--skip-unassigned")
defer utils.DeleteApplicationVersion(t, appKey, version)

require.NotEmpty(t, output)

var response struct {
ApplicationKey string `json:"application_key"`
Version string `json:"version"`
Status string `json:"status"`
CurrentStage string `json:"current_stage"`
}
err := json.Unmarshal([]byte(output), &response)
require.NoError(t, err, "failed to parse CLI output as JSON: %s", output)
assert.Equal(t, appKey, response.ApplicationKey)
assert.Equal(t, version, response.Version)

versionContent, statusCode, err := utils.GetApplicationVersion(appKey, version)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, statusCode)
require.NotNil(t, versionContent)
assert.Equal(t, utils.StatusCompleted, versionContent.Status)
assert.Equal(t, "DEV", versionContent.CurrentStage, "Version should be auto-promoted to DEV stage")
})

t.Run("stays unassigned with message when artifact not in first stage", func(t *testing.T) {
version := utils.GenerateUniqueKey("skip-ua-fail")

baseURL := os.Getenv("JFROG_APPTRUST_CLI_TESTS_JFROG_URL")
token := os.Getenv("JFROG_APPTRUST_CLI_TESTS_JFROG_ACCESS_TOKEN")
projectKey := utils.GetTestProjectKey(t)
repoKey := projectKey + "-prod-only-local"

repoPayload := fmt.Sprintf(`{"key":"%s","rclass":"local","packageType":"generic","environments":["PROD"],"projectKey":"%s"}`, repoKey, projectKey)
req, err := http.NewRequest(http.MethodPut, baseURL+"artifactory/api/repositories/"+repoKey, bytes.NewBufferString(repoPayload))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
resp.Body.Close()
require.True(t, resp.StatusCode >= 200 && resp.StatusCode < 300, "Failed to create PROD-only repo: %d", resp.StatusCode)
t.Cleanup(func() {
delReq, _ := http.NewRequest(http.MethodDelete, baseURL+"artifactory/api/repositories/"+repoKey, nil)
delReq.Header.Set("Authorization", "Bearer "+token)
delResp, _ := http.DefaultClient.Do(delReq)
if delResp != nil {
delResp.Body.Close()
}
})

artifactPath := repoKey + "/mismatch-artifact.txt"
uploadReq, err := http.NewRequest(http.MethodPut, baseURL+"artifactory/"+artifactPath, bytes.NewBufferString("mismatch-content"))
require.NoError(t, err)
uploadReq.Header.Set("Authorization", "Bearer "+token)
uploadResp, err := http.DefaultClient.Do(uploadReq)
require.NoError(t, err)
uploadResp.Body.Close()
require.True(t, uploadResp.StatusCode >= 200 && uploadResp.StatusCode < 300, "Failed to upload artifact: %d", uploadResp.StatusCode)

artifactFlag := fmt.Sprintf("--source-type-artifacts=path=%s", artifactPath)
output := utils.AppTrustCli.RunCliCmdWithOutput(t, "version-create", appKey, version, artifactFlag, "--skip-unassigned")
defer utils.DeleteApplicationVersion(t, appKey, version)

require.NotEmpty(t, output)

var response struct {
ApplicationKey string `json:"application_key"`
Version string `json:"version"`
Status string `json:"status"`
Message string `json:"message"`
}
err = json.Unmarshal([]byte(output), &response)
require.NoError(t, err, "failed to parse CLI output as JSON: %s", output)
assert.Equal(t, appKey, response.ApplicationKey)
assert.Equal(t, version, response.Version)
assert.NotEmpty(t, response.Message, "A message should explain why auto-promotion did not occur")
})
}

func TestCreateVersion_Async(t *testing.T) {
appKey := utils.GenerateUniqueKey("app-version-create-async")
utils.CreateBasicApplication(t, appKey)
Expand Down
Loading