diff --git a/artifactory/cli.go b/artifactory/cli.go index a2a328e71..11e7474c2 100644 --- a/artifactory/cli.go +++ b/artifactory/cli.go @@ -2412,7 +2412,7 @@ func createDefaultCopyMoveSpec(c *cli.Context) (*spec.SpecFiles, error) { Props(c.String("props")). ExcludeProps(c.String("exclude-props")). Build(c.String("build")). - Project(getProject(c)). + Project(cliutils.GetProject(c)). ExcludeArtifacts(c.Bool("exclude-artifacts")). IncludeDeps(c.Bool("include-deps")). Bundle(c.String("bundle")). @@ -2439,7 +2439,7 @@ func createDefaultDeleteSpec(c *cli.Context) (*spec.SpecFiles, error) { Props(c.String("props")). ExcludeProps(c.String("exclude-props")). Build(c.String("build")). - Project(getProject(c)). + Project(cliutils.GetProject(c)). ExcludeArtifacts(c.Bool("exclude-artifacts")). IncludeDeps(c.Bool("include-deps")). Bundle(c.String("bundle")). @@ -2463,7 +2463,7 @@ func createDefaultSearchSpec(c *cli.Context) (*spec.SpecFiles, error) { Props(c.String("props")). ExcludeProps(c.String("exclude-props")). Build(c.String("build")). - Project(getProject(c)). + Project(cliutils.GetProject(c)). ExcludeArtifacts(c.Bool("exclude-artifacts")). IncludeDeps(c.Bool("include-deps")). Bundle(c.String("bundle")). @@ -2490,7 +2490,7 @@ func createDefaultPropertiesSpec(c *cli.Context) (*spec.SpecFiles, error) { Props(c.String("props")). ExcludeProps(c.String("exclude-props")). Build(c.String("build")). - Project(getProject(c)). + Project(cliutils.GetProject(c)). ExcludeArtifacts(c.Bool("exclude-artifacts")). IncludeDeps(c.Bool("include-deps")). Bundle(c.String("bundle")). @@ -2586,7 +2586,7 @@ func createDefaultDownloadSpec(c *cli.Context) (*spec.SpecFiles, error) { Props(c.String("props")). ExcludeProps(c.String("exclude-props")). Build(c.String("build")). - Project(getProject(c)). + Project(cliutils.GetProject(c)). ExcludeArtifacts(c.Bool("exclude-artifacts")). IncludeDeps(c.Bool("include-deps")). Bundle(c.String("bundle")). @@ -2722,12 +2722,3 @@ func getOffsetAndLimitValues(c *cli.Context) (offset, limit int, err error) { return } - -// Get project key from flag or environment variable -func getProject(c *cli.Context) string { - project := c.String("project") - if project == "" { - project = os.Getenv(coreutils.Project) - } - return project -} diff --git a/artifactory/cli_test.go b/artifactory/cli_test.go index fb848c0d4..e951be591 100644 --- a/artifactory/cli_test.go +++ b/artifactory/cli_test.go @@ -2,15 +2,12 @@ package artifactory import ( "bytes" - "flag" "github.com/jfrog/jfrog-cli-core/v2/common/spec" - "path/filepath" - "strings" - "testing" - "github.com/jfrog/jfrog-cli/utils/tests" "github.com/stretchr/testify/assert" "github.com/urfave/cli" + "path/filepath" + "testing" ) func TestPrepareSearchDownloadDeleteCommands(t *testing.T) { @@ -34,7 +31,7 @@ func TestPrepareSearchDownloadDeleteCommands(t *testing.T) { for _, test := range testRuns { t.Run(test.name, func(t *testing.T) { - context, buffer := createContext(t, test.flags, test.args) + context, buffer := tests.CreateContext(t, test.flags, test.args) funcArray := []func(c *cli.Context) (*spec.SpecFiles, error){ prepareSearchCommand, prepareDownloadCommand, prepareDeleteCommand, } @@ -67,7 +64,7 @@ func TestPrepareCopyMoveCommand(t *testing.T) { for _, test := range testRuns { t.Run(test.name, func(t *testing.T) { - context, buffer := createContext(t, test.flags, test.args) + context, buffer := tests.CreateContext(t, test.flags, test.args) specFiles, err := prepareCopyMoveCommand(context) assertGenericCommand(t, err, buffer, test.expectError, test.expectedPattern, test.expectedBuild, test.expectedBundle, specFiles) }) @@ -96,7 +93,7 @@ func TestPreparePropsCmd(t *testing.T) { for _, test := range testRuns { t.Run(test.name, func(t *testing.T) { - context, buffer := createContext(t, test.flags, test.args) + context, buffer := tests.CreateContext(t, test.flags, test.args) propsCommand, err := preparePropsCmd(context) var actualSpec *spec.SpecFiles if propsCommand != nil { @@ -119,27 +116,6 @@ func assertGenericCommand(t *testing.T, err error, buffer *bytes.Buffer, expectE } } -func createContext(t *testing.T, testFlags, testArgs []string) (*cli.Context, *bytes.Buffer) { - flagSet := createFlagSet(t, testFlags, testArgs) - app := cli.NewApp() - app.Writer = &bytes.Buffer{} - return cli.NewContext(app, flagSet, nil), &bytes.Buffer{} -} - func getSpecPath(spec string) string { return filepath.Join("..", "testdata", "filespecs", spec) } - -// Create flag set with input flags and arguments. -func createFlagSet(t *testing.T, flags []string, args []string) *flag.FlagSet { - flagSet := flag.NewFlagSet("TestFlagSet", flag.ContinueOnError) - flags = append(flags, "url=http://127.0.0.1:8081/artifactory") - var cmdFlags []string - for _, curFlag := range flags { - flagSet.String(strings.Split(curFlag, "=")[0], "", "") - cmdFlags = append(cmdFlags, "--"+curFlag) - } - cmdFlags = append(cmdFlags, args...) - assert.NoError(t, flagSet.Parse(cmdFlags)) - return flagSet -} diff --git a/artifactory_test.go b/artifactory_test.go index 250545b05..9a5fbe194 100644 --- a/artifactory_test.go +++ b/artifactory_test.go @@ -4549,7 +4549,7 @@ func validateArtifactoryVersion(t *testing.T, minVersion string) { return } if !rtVersion.AtLeast(minVersion) { - t.Skip("Skipping artifactory project test. Artifactory version not supported.") + t.Skip("Skipping test. Artifactory version not supported.") } } diff --git a/distribution/cli.go b/distribution/cli.go index 81d04c537..682156767 100644 --- a/distribution/cli.go +++ b/distribution/cli.go @@ -28,7 +28,7 @@ func GetCommands() []cli.Command { return cliutils.GetSortedCommands(cli.CommandsByName{ { Name: "release-bundle-create", - Flags: cliutils.GetCommandFlags(cliutils.ReleaseBundleCreate), + Flags: cliutils.GetCommandFlags(cliutils.ReleaseBundleV1Create), Aliases: []string{"rbc"}, Usage: releasebundlecreate.GetDescription(), HelpName: coreCommonDocs.CreateUsage("ds rbc", releasebundlecreate.GetDescription(), releasebundlecreate.Usage), @@ -39,7 +39,7 @@ func GetCommands() []cli.Command { }, { Name: "release-bundle-update", - Flags: cliutils.GetCommandFlags(cliutils.ReleaseBundleUpdate), + Flags: cliutils.GetCommandFlags(cliutils.ReleaseBundleV1Update), Aliases: []string{"rbu"}, Usage: releasebundleupdate.GetDescription(), HelpName: coreCommonDocs.CreateUsage("ds rbu", releasebundleupdate.GetDescription(), releasebundleupdate.Usage), @@ -50,7 +50,7 @@ func GetCommands() []cli.Command { }, { Name: "release-bundle-sign", - Flags: cliutils.GetCommandFlags(cliutils.ReleaseBundleSign), + Flags: cliutils.GetCommandFlags(cliutils.ReleaseBundleV1Sign), Aliases: []string{"rbs"}, Usage: releasebundlesign.GetDescription(), HelpName: coreCommonDocs.CreateUsage("ds rbs", releasebundlesign.GetDescription(), releasebundlesign.Usage), @@ -61,7 +61,7 @@ func GetCommands() []cli.Command { }, { Name: "release-bundle-distribute", - Flags: cliutils.GetCommandFlags(cliutils.ReleaseBundleDistribute), + Flags: cliutils.GetCommandFlags(cliutils.ReleaseBundleV1Distribute), Aliases: []string{"rbd"}, Usage: releasebundledistribute.GetDescription(), HelpName: coreCommonDocs.CreateUsage("ds rbd", releasebundledistribute.GetDescription(), releasebundledistribute.Usage), @@ -72,7 +72,7 @@ func GetCommands() []cli.Command { }, { Name: "release-bundle-delete", - Flags: cliutils.GetCommandFlags(cliutils.ReleaseBundleDelete), + Flags: cliutils.GetCommandFlags(cliutils.ReleaseBundleV1Delete), Aliases: []string{"rbdel"}, Usage: releasebundledelete.GetDescription(), HelpName: coreCommonDocs.CreateUsage("ds rbdel", releasebundledelete.GetDescription(), releasebundledelete.Usage), @@ -328,7 +328,7 @@ func populateReleaseNotesSyntax(c *cli.Context) (distributionServicesUtils.Relea return distributionServicesUtils.PlainText, errorutils.CheckErrorf("--release-notes-syntax must be one of: markdown, asciidoc or plain_text.") } } - // If the file extension is ".md" or ".markdown", use the markdown syntax + // If the file extension is ".md" or ".markdown", use the Markdown syntax extension := strings.ToLower(filepath.Ext(c.String("release-notes-path"))) if extension == ".md" || extension == ".markdown" { return distributionServicesUtils.Markdown, nil diff --git a/docs/artifactory/releasebundlecreate/help.go b/docs/artifactory/releasebundlecreate/help.go index 5448b96ed..91df44ceb 100644 --- a/docs/artifactory/releasebundlecreate/help.go +++ b/docs/artifactory/releasebundlecreate/help.go @@ -4,7 +4,7 @@ var Usage = []string{"ds rbc [command options] [command options] "} func GetDescription() string { - return "Create a release bundle." + return "Create a release bundle v1." } func GetArguments() string { diff --git a/docs/artifactory/releasebundledelete/help.go b/docs/artifactory/releasebundledelete/help.go index ce377d18b..ec9e1fe7a 100644 --- a/docs/artifactory/releasebundledelete/help.go +++ b/docs/artifactory/releasebundledelete/help.go @@ -3,7 +3,7 @@ package releasebundledelete var Usage = []string{"ds rbdel [command options] "} func GetDescription() string { - return "Delete a release bundle." + return "Delete a release bundle v1." } func GetArguments() string { diff --git a/docs/artifactory/releasebundledistribute/help.go b/docs/artifactory/releasebundledistribute/help.go index 0fe5459ec..b277a84d3 100644 --- a/docs/artifactory/releasebundledistribute/help.go +++ b/docs/artifactory/releasebundledistribute/help.go @@ -3,7 +3,7 @@ package releasebundledistribute var Usage = []string{"ds rbd [command options] "} func GetDescription() string { - return "Distribute a release bundle." + return "Distribute a release bundle v1." } func GetArguments() string { diff --git a/docs/artifactory/releasebundlesign/help.go b/docs/artifactory/releasebundlesign/help.go index d576353a6..977874bd8 100644 --- a/docs/artifactory/releasebundlesign/help.go +++ b/docs/artifactory/releasebundlesign/help.go @@ -3,7 +3,7 @@ package releasebundlesign var Usage = []string{"ds rbs [command options] "} func GetDescription() string { - return "Sign a release bundle." + return "Sign a release bundle v1." } func GetArguments() string { diff --git a/docs/artifactory/releasebundleupdate/help.go b/docs/artifactory/releasebundleupdate/help.go index 0f80fc4af..d7cd5541f 100644 --- a/docs/artifactory/releasebundleupdate/help.go +++ b/docs/artifactory/releasebundleupdate/help.go @@ -4,7 +4,7 @@ var Usage = []string{"ds rbu [command options] [command options] "} func GetDescription() string { - return "Updates an existing unsigned release bundle version." + return "Updates an existing unsigned release bundle v1 version." } func GetArguments() string { diff --git a/docs/lifecycle/create/help.go b/docs/lifecycle/create/help.go new file mode 100644 index 000000000..c7079e3f2 --- /dev/null +++ b/docs/lifecycle/create/help.go @@ -0,0 +1,15 @@ +package create + +var Usage = []string{"rbc [command options] "} + +func GetDescription() string { + return "Create a release bundle from builds or from existing release bundles" +} + +func GetArguments() string { + return ` release bundle name + Name of the newly created Release Bundle. + + release bundle version + Version of the newly created Release Bundle.` +} diff --git a/docs/lifecycle/promote/help.go b/docs/lifecycle/promote/help.go new file mode 100644 index 000000000..b4954a8cf --- /dev/null +++ b/docs/lifecycle/promote/help.go @@ -0,0 +1,18 @@ +package promote + +var Usage = []string{"rbp [command options] "} + +func GetDescription() string { + return "Promote a release bundle" +} + +func GetArguments() string { + return ` release bundle name + Name of the Release Bundle to promote. + + release bundle version + Version of the Release Bundle to promote. + + environment + Name of the target environment for the promotion.` +} diff --git a/documentation/CLI-for-JFrog-Lifecycle.md b/documentation/CLI-for-JFrog-Lifecycle.md new file mode 100644 index 000000000..f81b7c817 --- /dev/null +++ b/documentation/CLI-for-JFrog-Lifecycle.md @@ -0,0 +1,133 @@ +JFrog CLI : CLI for JFrog Release Lifecycle Management +====================================== + + +Overview +-------- + +This page describes how to use JFrog CLI with [JFrog Release Lifecycle Management](https://jfrog.com/help/r/jfrog-artifactory-documentation/jfrog-release-lifecycle-management-solution). + +Read more about JFrog CLI [here](https://jfrog.com/help/r/jfrog-cli). + +--- +**Note** +> JFrog Release Lifecycle Management is only available since [Artifactory 7.63.2](https://jfrog.com/help/r/jfrog-release-information/artifactory-7.63.2-cloud). +--- + +### Syntax + +When used with JFrog Release Lifecycle Management, JFrog CLI uses the following syntax: + + $ jf command-name global-options command-options arguments + +### Commands + +The following sections describe the commands available in JFrog CLI for use with JFrog Release Lifecycle Management. + +### Creating a release bundle from builds or from existing release bundles + +This commands allows creating a release bundle from one of two sources: +1. Published build infos. To use, provide the `--builds` option, which accepts a path to a JSON file of the following syntax: + ```json + { + "builds": [ + { + "name": "build name", + "number": "build number", + "project": "project" + } + ] + } + ``` + `number` is optional, latest build will be used if empty. + + `project` is optional, default project will be used if empty. + +2. Existing release bundles. To use, provide the `--release-bundles` option, which accepts a path to a JSON file of the following syntax: + ```json + { + "releaseBundles": [ + { + "name": "release bundle name", + "version": "release bundle version", + "project": "project" + } + ] + } + ``` + `project` is optional, default project will be used if empty. + +| | | +|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Command-name | release-bundle-create | +| Abbreviation | rbc | +| Command options | | +| --builds | \[Optional\]

Path to a JSON file containing information of the source builds from which to create a release bundle. | +| --project | \[Optional\]

Project key associated with the Release Bundle version. | +| --release-bundles | \[Optional\]

Path to a JSON file containing information of the source release bundles from which to create a release bundle. | +| --server-id | \[Optional\]

Platform server ID configured using the config command. | +| --signing-key | \[Mandatory\]

The GPG/RSA key-pair name given in Artifactory. | +| --sync | \[Default: false\]

Set to true to run synchronously. | +| Command arguments | | +| release bundle name | Name of the newly created Release Bundle. | +| release bundle version | Version of the newly created Release Bundle. | + +##### Examples + +##### Example 1 + +Create a release bundle with name "myApp" and version "1.0.0", with signing key pair "myKeyPair". +The release bundle will include artifacts of the builds that were provided in the builds spec. + + jf rbc --builds=/path/to/builds-spec.json --signing-key=myKeyPair myApp 1.0.0 + +##### Example 2 + +Create a release bundle with name "myApp" and version "1.0.0", with signing key pair "myKeyPair". +The release bundle will include artifacts of the release bundles that were provided in the release bundles spec. + + jf ds rbc --spec=/path/to/release-bundles-spec.json --signing-key=myKeyPair myApp 1.0.0 + +##### Example 3 + +Create a release bundle synchronously with name "myApp" and version "1.0.0", in project "project0", with signing key pair "myKeyPair". +The release bundle will include artifacts of the release bundles that were provided in the release bundles spec. + + jf ds rbc --spec=/path/to/release-bundles-spec.json --signing-key=myKeyPair --sync=true --project=project0 myApp 1.0.0 + +### Promoting a release bundle + +This commands allows promoting a release bundle to a target environment. + +| | | +|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Command-name | release-bundle-promote | +| Abbreviation | rbp | +| Command options | | +| --overwrite | \[Default: false\]

Set to true to replace artifacts with the same name but a different checksum if such already exist at the promotion targets. By default, the promotion is stopped in a case of such conflict | +| --project | \[Optional\]

Project key associated with the Release Bundle version. | +| --server-id | \[Optional\]

Platform server ID configured using the config command. | +| --signing-key | \[Mandatory\]

The GPG/RSA key-pair name given in Artifactory. | +| --sync | \[Default: false\]

Set to true to run synchronously. | +| Command arguments | | +| release bundle name | Name of the Release Bundle to promote. | +| release bundle version | Version of the Release Bundle to promote. | +| environment | Name of the target environment for the promotion. | + + +##### Examples + +##### Example 1 + +Promote a release bundle named "myApp" version "1.0.0" to environment "PROD". +Use signing key pair "myKeyPair". + + jf rbp --signing-key=myKeyPair myApp 1.0.0 PROD + +##### Example 2 + +Promote a release bundle synchronously to environment "PROD". +The release bundle is named "myApp", version "1.0.0", of project "project0". +Use signing key pair "myKeyPair" and overwrite at conflict. + + jf rbp --signing-key=myKeyPair --project=project0 --overwrite=true --sync=true myApp 1.0.0 PROD diff --git a/documentation/cli.ftmap b/documentation/cli.ftmap index 8a14c4de4..2a967949b 100644 --- a/documentation/cli.ftmap +++ b/documentation/cli.ftmap @@ -13,6 +13,8 @@ - + + + diff --git a/go.mod b/go.mod index f93d7a975..c516a946e 100644 --- a/go.mod +++ b/go.mod @@ -123,8 +123,8 @@ require ( // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go -// replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20230613104333-33061fa53a01 +replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20230629184314-ff61ffffba34 // replace github.com/jfrog/gofrog => github.com/jfrog/gofrog v1.2.6-0.20230418122323-2bf299dd6d27 -// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20230618140310-d7dc9bc462c2 +replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20230629174113-81715f46ec0f diff --git a/go.sum b/go.sum index e6454b94f..6e5785091 100644 --- a/go.sum +++ b/go.sum @@ -238,10 +238,10 @@ github.com/jfrog/build-info-go v1.9.6 h1:lCJ2j5uXAlJsSwDe5J8WD7Co1f/hUlZvMfwfb5A github.com/jfrog/build-info-go v1.9.6/go.mod h1:GbuFS+viHCKZYx9nWHYu7ab1DgQkFdtVN3BJPUNb2D4= github.com/jfrog/gofrog v1.3.0 h1:o4zgsBZE4QyDbz2M7D4K6fXPTBJht+8lE87mS9bw7Gk= github.com/jfrog/gofrog v1.3.0/go.mod h1:IFMc+V/yf7rA5WZ74CSbXe+Lgf0iApEQLxRZVzKRUR0= -github.com/jfrog/jfrog-cli-core/v2 v2.36.1 h1:O67ovxouqXYljE2Lo890ytM9fDiYsMCZJ8F/DHdSY7w= -github.com/jfrog/jfrog-cli-core/v2 v2.36.1/go.mod h1:Wvf/XWVcRSu1ZuloLOofkifuM8BLZZ2LiYYzmdUn80Y= -github.com/jfrog/jfrog-client-go v1.30.1 h1:wASYBrFkpWzQHTNnCIIfqpDLtQF5oNcwQK9rrv8I8AA= -github.com/jfrog/jfrog-client-go v1.30.1/go.mod h1:qEJxoe68sUtqHJ1YhXv/7pKYP/9p1D5tJrruzJKYeoI= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20230629184314-ff61ffffba34 h1:i1nENbEtG6uv5V92UacU6okTW3zcH1VfzmkvJn9nK7Y= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20230629184314-ff61ffffba34/go.mod h1:zc3la+URXmlEvrDhcY2g4AnnAQPOsmJisVfJZM06o+g= +github.com/jfrog/jfrog-client-go v1.28.1-0.20230629174113-81715f46ec0f h1:FzagM0DDnwhcOCR+/grBwrg625a8mwOdytnJTlGCkAI= +github.com/jfrog/jfrog-client-go v1.28.1-0.20230629174113-81715f46ec0f/go.mod h1:qEJxoe68sUtqHJ1YhXv/7pKYP/9p1D5tJrruzJKYeoI= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jszwec/csvutil v1.8.0 h1:G7vS2LGdpZZDH1HmHeNbxOaJ/ZnJlpwGFvOkTkJzzNk= diff --git a/lifecycle/cli.go b/lifecycle/cli.go new file mode 100644 index 000000000..8caa332d2 --- /dev/null +++ b/lifecycle/cli.go @@ -0,0 +1,134 @@ +package lifecycle + +import ( + "errors" + "github.com/jfrog/jfrog-cli-core/v2/common/commands" + coreCommon "github.com/jfrog/jfrog-cli-core/v2/docs/common" + "github.com/jfrog/jfrog-cli-core/v2/lifecycle" + coreConfig "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli/docs/common" + rbCreate "github.com/jfrog/jfrog-cli/docs/lifecycle/create" + rbPromote "github.com/jfrog/jfrog-cli/docs/lifecycle/promote" + "github.com/jfrog/jfrog-cli/utils/cliutils" + "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/urfave/cli" +) + +const lcCategory = "Lifecycle" + +func GetCommands() []cli.Command { + return cliutils.GetSortedCommands(cli.CommandsByName{ + { + Name: "release-bundle-create", + Aliases: []string{"rbc"}, + Flags: cliutils.GetCommandFlags(cliutils.ReleaseBundleCreate), + Usage: rbCreate.GetDescription(), + HelpName: coreCommon.CreateUsage("release-bundle-create", rbCreate.GetDescription(), rbCreate.Usage), + UsageText: rbCreate.GetArguments(), + ArgsUsage: common.CreateEnvVars(), + BashComplete: coreCommon.CreateBashCompletionFunc(), + Category: lcCategory, + Action: create, + }, + { + Name: "release-bundle-promote", + Aliases: []string{"rbp"}, + Flags: cliutils.GetCommandFlags(cliutils.ReleaseBundlePromote), + Usage: rbPromote.GetDescription(), + HelpName: coreCommon.CreateUsage("release-bundle-promote", rbPromote.GetDescription(), rbPromote.Usage), + UsageText: rbPromote.GetArguments(), + ArgsUsage: common.CreateEnvVars(), + BashComplete: coreCommon.CreateBashCompletionFunc(), + Category: lcCategory, + Action: promote, + }, + }) +} + +func validateCreateReleaseBundleContext(c *cli.Context) error { + if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil { + return err + } + + if c.NArg() != 2 { + return cliutils.WrongNumberOfArgumentsHandler(c) + } + + if err := assertSigningKeyProvided(c); err != nil { + return err + } + + bothProvided := c.IsSet(cliutils.Builds) && c.IsSet(cliutils.ReleaseBundles) + noneProvided := !c.IsSet(cliutils.Builds) && !c.IsSet(cliutils.ReleaseBundles) + if bothProvided || noneProvided { + return errorutils.CheckErrorf("exactly one of the following options must be supplied: --%s or --%s", cliutils.Builds, cliutils.ReleaseBundles) + } + return nil +} + +func create(c *cli.Context) (err error) { + if err = validateCreateReleaseBundleContext(c); err != nil { + return err + } + + lcDetails, err := createLifecycleDetailsByFlags(c) + if err != nil { + return + } + + createCmd := lifecycle.NewReleaseBundleCreate().SetServerDetails(lcDetails).SetReleaseBundleName(c.Args().Get(0)). + SetReleaseBundleVersion(c.Args().Get(1)).SetSigningKeyName(c.String(cliutils.SigningKey)).SetSync(c.Bool(cliutils.Sync)). + SetReleaseBundleProject(cliutils.GetProject(c)).SetBuildsSpecPath(c.String(cliutils.Builds)). + SetReleaseBundlesSpecPath(c.String(cliutils.ReleaseBundles)) + return commands.Exec(createCmd) +} + +func promote(c *cli.Context) error { + if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil { + return err + } + + if c.NArg() != 3 { + return cliutils.WrongNumberOfArgumentsHandler(c) + } + + if err := assertSigningKeyProvided(c); err != nil { + return err + } + + lcDetails, err := createLifecycleDetailsByFlags(c) + if err != nil { + return err + } + + createCmd := lifecycle.NewReleaseBundlePromote().SetServerDetails(lcDetails).SetReleaseBundleName(c.Args().Get(0)). + SetReleaseBundleVersion(c.Args().Get(1)).SetEnvironment(c.Args().Get(2)).SetSigningKeyName(c.String(cliutils.SigningKey)). + SetSync(c.Bool(cliutils.Sync)).SetReleaseBundleProject(cliutils.GetProject(c)).SetOverwrite(c.Bool(cliutils.Overwrite)) + return commands.Exec(createCmd) +} + +func assertSigningKeyProvided(c *cli.Context) error { + if c.String(cliutils.SigningKey) == "" { + return errorutils.CheckErrorf("the --%s option is mandatory", cliutils.SigningKey) + } + return nil +} + +func createLifecycleDetailsByFlags(c *cli.Context) (*coreConfig.ServerDetails, error) { + lcDetails, err := cliutils.CreateServerDetailsWithConfigOffer(c, false, cliutils.Platform) + if err != nil { + return nil, err + } + if lcDetails.Url == "" { + return nil, errors.New("platform URL is mandatory for lifecycle commands") + } + PlatformToLifecycleUrls(lcDetails) + return lcDetails, nil +} + +func PlatformToLifecycleUrls(lcDetails *coreConfig.ServerDetails) { + lcDetails.ArtifactoryUrl = utils.AddTrailingSlashIfNeeded(lcDetails.Url) + "artifactory/" + lcDetails.LifecycleUrl = utils.AddTrailingSlashIfNeeded(lcDetails.Url) + "lifecycle/" + lcDetails.Url = "" +} diff --git a/lifecycle/cli_test.go b/lifecycle/cli_test.go new file mode 100644 index 000000000..9da70f718 --- /dev/null +++ b/lifecycle/cli_test.go @@ -0,0 +1,42 @@ +package lifecycle + +import ( + "github.com/jfrog/jfrog-cli/utils/cliutils" + "github.com/jfrog/jfrog-cli/utils/tests" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestValidateCreateReleaseBundleContext(t *testing.T) { + testRuns := []struct { + name string + args []string + flags []string + expectError bool + }{ + {"withoutArgs", []string{}, []string{}, true}, + {"oneArg", []string{"one"}, []string{}, true}, + {"twoArgs", []string{"one", "two"}, []string{}, true}, + {"extraArgs", []string{"one", "two", "three", "four"}, []string{}, true}, + {"bothSources", []string{"one", "two", "three"}, []string{cliutils.Builds + "=/path/to/file", cliutils.ReleaseBundles + "=/path/to/file"}, true}, + {"noSources", []string{"one", "two", "three"}, []string{}, true}, + {"builds without signing key", []string{"name", "version"}, []string{cliutils.Builds + "=/path/to/file"}, true}, + {"builds correct", []string{"name", "version"}, []string{ + cliutils.Builds + "=/path/to/file", cliutils.SigningKey + "=key"}, false}, + {"releaseBundles without signing key", []string{"name", "version", "env"}, []string{cliutils.ReleaseBundles + "=/path/to/file"}, true}, + {"releaseBundles", []string{"name", "version"}, []string{ + cliutils.ReleaseBundles + "=/path/to/file", cliutils.SigningKey + "=key"}, false}, + } + + for _, test := range testRuns { + t.Run(test.name, func(t *testing.T) { + context, buffer := tests.CreateContext(t, test.flags, test.args) + err := validateCreateReleaseBundleContext(context) + if test.expectError { + assert.Error(t, err, buffer) + } else { + assert.NoError(t, err, buffer) + } + }) + } +} diff --git a/lifecycle_test.go b/lifecycle_test.go new file mode 100644 index 000000000..24980c734 --- /dev/null +++ b/lifecycle_test.go @@ -0,0 +1,275 @@ +package main + +import ( + "encoding/json" + "fmt" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + configUtils "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-cli/inttestutils" + lifecycleCli "github.com/jfrog/jfrog-cli/lifecycle" + "github.com/jfrog/jfrog-cli/utils/cliutils" + "github.com/jfrog/jfrog-cli/utils/tests" + "github.com/jfrog/jfrog-client-go/http/httpclient" + "github.com/jfrog/jfrog-client-go/lifecycle" + "github.com/jfrog/jfrog-client-go/lifecycle/services" + clientUtils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/stretchr/testify/assert" + "net/http" + "os" + "path/filepath" + "testing" +) + +const ( + rbMinVersion = "7.45.0" + gpgKeyPairName = "lc-tests-key-pair" + lcTestdataPath = "lifecycle" + releaseBundlesSpec = "release-bundles-spec.json" + buildsSpec12 = "builds-spec-1-2.json" + buildsSpec3 = "builds-spec-3.json" + prodEnvironment = "PROD" + number1, number2, number3 = "111", "222", "333" +) + +var ( + lcDetails *configUtils.ServerDetails + lcCli *tests.JfrogCli +) + +func TestLifecycle(t *testing.T) { + initLifecycleTest(t) + defer cleanLifecycleTests(t) + lcManager := getLcServiceManager(t) + + // Upload builds to create release bundles from. + deleteBuilds := uploadBuilds(t) + defer deleteBuilds() + + // Create release bundles from builds synchronously. + createRb(t, buildsSpec12, cliutils.Builds, tests.LcRbName1, number1, true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName1, number1) + + // Create release bundles from builds asynchronously and assert status. + createRb(t, buildsSpec3, cliutils.Builds, tests.LcRbName2, number2, false) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName2, number2) + assertStatusCompleted(t, lcManager, tests.LcRbName2, number2, "") + + // Create a combined release bundle from the two previous release bundle. + createRb(t, releaseBundlesSpec, cliutils.ReleaseBundles, tests.LcRbName3, number3, true) + defer deleteReleaseBundle(t, lcManager, tests.LcRbName3, number3) + + // Promote the last release bundle. + promoteRb(t, lcManager, number3) + + // Verify the artifacts of both the initial release bundles made it to the prod repo. + searchSpec, err := tests.CreateSpec(tests.SearchAllProdRepo) + assert.NoError(t, err) + inttestutils.VerifyExistInArtifactory(tests.GetExpectedLifecycleArtifacts(), searchSpec, serverDetails, t) +} + +func uploadBuilds(t *testing.T) func() { + uploadBuild(t, tests.UploadDevSpecA, tests.LcBuildName1, number1) + uploadBuild(t, tests.UploadDevSpecB, tests.LcBuildName2, number2) + uploadBuild(t, tests.UploadDevSpecC, tests.LcBuildName3, number3) + return func() { + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, tests.LcBuildName1, artHttpDetails) + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, tests.LcBuildName2, artHttpDetails) + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, tests.LcBuildName3, artHttpDetails) + } +} + +func createRb(t *testing.T, specName, sourceOption, buildName, buildNumber string, sync bool) { + specFile, err := getSpecFile(specName) + assert.NoError(t, err) + argsAndOptions := []string{ + "rbc", + buildName, + buildNumber, + getOption(sourceOption, specFile), + getOption(cliutils.SigningKey, gpgKeyPairName), + } + // Add the --sync option only if requested, to test the default value. + if sync { + argsAndOptions = append(argsAndOptions, getOption(cliutils.Sync, "true")) + } + assert.NoError(t, lcCli.Exec(argsAndOptions...)) +} + +func getOption(option, value string) string { + return fmt.Sprintf("--%s=%s", option, value) +} + +func promoteRb(t *testing.T, lcManager *lifecycle.LifecycleServicesManager, rbVersion string) { + output := lcCli.RunCliCmdWithOutput(t, "rbp", tests.LcRbName3, rbVersion, prodEnvironment, + getOption(cliutils.SigningKey, gpgKeyPairName), + getOption(cliutils.Overwrite, "true"), + "--project=default") + var promotionResp services.RbPromotionResp + if !assert.NoError(t, json.Unmarshal([]byte(output), &promotionResp)) { + return + } + assertStatusCompleted(t, lcManager, tests.LcRbName3, rbVersion, promotionResp.CreatedMillis.String()) +} + +func getSpecFile(fileName string) (string, error) { + source := filepath.Join(tests.GetTestResourcesPath(), lcTestdataPath, fileName) + return tests.ReplaceTemplateVariables(source, "") +} + +// If createdMillis is provided, assert status for promotion. If blank, assert for creation. +func assertStatusCompleted(t *testing.T, lcManager *lifecycle.LifecycleServicesManager, rbName, rbVersion, createdMillis string) { + resp, err := getStatus(lcManager, rbName, rbVersion, createdMillis) + if !assert.NoError(t, err) { + return + } + assert.Equal(t, services.Completed, resp.Status) +} + +func getLcServiceManager(t *testing.T) *lifecycle.LifecycleServicesManager { + lcManager, err := utils.CreateLifecycleServiceManager(lcDetails, false) + assert.NoError(t, err) + return lcManager +} + +func authenticateLifecycle() string { + *tests.JfrogUrl = clientUtils.AddTrailingSlashIfNeeded(*tests.JfrogUrl) + lcDetails = &configUtils.ServerDetails{ + Url: *tests.JfrogUrl} + lifecycleCli.PlatformToLifecycleUrls(lcDetails) + + cred := fmt.Sprintf("--url=%s", *tests.JfrogUrl) + if *tests.JfrogAccessToken != "" { + lcDetails.AccessToken = *tests.JfrogAccessToken + cred += fmt.Sprintf(" --access-token=%s", lcDetails.AccessToken) + } else { + lcDetails.User = *tests.JfrogUser + lcDetails.Password = *tests.JfrogPassword + cred += fmt.Sprintf(" --user=%s --password=%s", lcDetails.User, lcDetails.Password) + } + return cred +} + +func getStatus(lcManager *lifecycle.LifecycleServicesManager, rbName, rbVersion, createdMillis string) (services.ReleaseBundleStatusResponse, error) { + rbDetails := services.ReleaseBundleDetails{ + ReleaseBundleName: rbName, + ReleaseBundleVersion: rbVersion, + } + + if createdMillis == "" { + return lcManager.GetReleaseBundleCreationStatus(rbDetails, "", true) + } + return lcManager.GetReleaseBundlePromotionStatus(rbDetails, "", createdMillis, true) +} + +func deleteReleaseBundle(t *testing.T, lcManager *lifecycle.LifecycleServicesManager, rbName, rbVersion string) { + rbDetails := services.ReleaseBundleDetails{ + ReleaseBundleName: rbName, + ReleaseBundleVersion: rbVersion, + } + + assert.NoError(t, lcManager.DeleteReleaseBundle(rbDetails, services.ReleaseBundleQueryParams{Async: false})) +} + +func uploadBuild(t *testing.T, specFileName, buildName, buildNumber string) { + specFile, err := tests.CreateSpec(specFileName) + assert.NoError(t, err) + runRt(t, "upload", "--spec="+specFile, "--build-name="+buildName, "--build-number="+buildNumber) + runRt(t, "build-publish", buildName, buildNumber) +} + +func initLifecycleTest(t *testing.T) { + if !*tests.TestLifecycle { + t.Skip("Skipping lifecycle test. To run release bundle test add the '-test.lc=true' option.") + } + validateArtifactoryVersion(t, rbMinVersion) + + if !isLifecycleSupported(t) { + t.Skip("Skipping lifecycle test because the functionality is not enabled on the provided JPD.") + } +} + +func isLifecycleSupported(t *testing.T) (skip bool) { + client, err := httpclient.ClientBuilder().Build() + assert.NoError(t, err) + + resp, _, _, err := client.SendGet(serverDetails.ArtifactoryUrl+"api/release_bundles/records/non-existing-rb", true, artHttpDetails, "") + if !assert.NoError(t, err) { + return + } + return resp.StatusCode != http.StatusNotImplemented +} + +func InitLifecycleTests() { + initArtifactoryCli() + initLifecycleCli() + cleanUpOldBuilds() + cleanUpOldRepositories() + cleanUpOldUsers() + tests.AddTimestampToGlobalVars() + createRequiredRepos() + sendGpgKeyPair() +} + +func initLifecycleCli() { + if lcCli != nil { + return + } + lcCli = tests.NewJfrogCli(execMain, "jfrog", authenticateLifecycle()) +} + +func CleanLifecycleTests() { + deleteCreatedRepos() +} + +func cleanLifecycleTests(t *testing.T) { + deleteFilesFromRepo(t, tests.RtDevRepo) + deleteFilesFromRepo(t, tests.RtProdRepo) + tests.CleanFileSystem() +} + +func sendGpgKeyPair() { + // Create http client + client, err := httpclient.ClientBuilder().Build() + coreutils.ExitOnErr(err) + + // Check if one already exists + resp, body, _, err := client.SendGet(*tests.JfrogUrl+"artifactory/api/security/keypair/"+gpgKeyPairName, true, artHttpDetails, "") + coreutils.ExitOnErr(err) + if resp.StatusCode == http.StatusOK { + return + } + coreutils.ExitOnErr(errorutils.CheckResponseStatusWithBody(resp, body, http.StatusNotFound)) + + // Read gpg public and private keys + keysDir := filepath.Join(tests.GetTestResourcesPath(), lcTestdataPath, "keys") + publicKey, err := os.ReadFile(filepath.Join(keysDir, "public.txt")) + coreutils.ExitOnErr(err) + privateKey, err := os.ReadFile(filepath.Join(keysDir, "private.txt")) + coreutils.ExitOnErr(err) + + // Send keys to Artifactory + payload := KeyPairPayload{ + PairName: gpgKeyPairName, + PairType: "GPG", + Alias: gpgKeyPairName + "-alias", + Passphrase: "password", + PublicKey: string(publicKey), + PrivateKey: string(privateKey), + } + content, err := json.Marshal(payload) + coreutils.ExitOnErr(err) + resp, body, err = client.SendPost(*tests.JfrogUrl+"artifactory/api/security/keypair", content, artHttpDetails, "") + coreutils.ExitOnErr(err) + coreutils.ExitOnErr(errorutils.CheckResponseStatusWithBody(resp, body, http.StatusCreated)) +} + +type KeyPairPayload struct { + PairName string `json:"pairName,omitempty"` + PairType string `json:"pairType,omitempty"` + Alias string `json:"alias,omitempty"` + Passphrase string `json:"passphrase,omitempty"` + PublicKey string `json:"publicKey,omitempty"` + PrivateKey string `json:"privateKey,omitempty"` +} diff --git a/main.go b/main.go index d7d904495..42c3c804c 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/jfrog/jfrog-cli/lifecycle" "golang.org/x/exp/slices" "os" "runtime" @@ -269,6 +270,7 @@ func getCommands() []cli.Command { allCommands := append(slices.Clone(cliNameSpaces), utils.GetPlugins()...) allCommands = append(allCommands, scan.GetCommands()...) allCommands = append(allCommands, buildtools.GetCommands()...) + allCommands = append(allCommands, lifecycle.GetCommands()...) return append(allCommands, buildtools.GetBuildToolsHelpCommands()...) } diff --git a/main_test.go b/main_test.go index 7bab29827..bc6684073 100644 --- a/main_test.go +++ b/main_test.go @@ -4,34 +4,30 @@ import ( "errors" "flag" "fmt" - "os" - "path/filepath" - "strconv" - "strings" - "testing" - - "github.com/jfrog/jfrog-cli-core/v2/common/spec" - "github.com/jfrog/jfrog-cli-core/v2/utils/config" - coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" - xrayutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils" - "github.com/urfave/cli" - buildinfo "github.com/jfrog/build-info-go/entities" - clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" - + commandUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" + artifactoryUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/common/commands" + "github.com/jfrog/jfrog-cli-core/v2/common/spec" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/utils/log" - clientlog "github.com/jfrog/jfrog-client-go/utils/log" - - commandUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils" - artifactoryUtils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" + xrayutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils" "github.com/jfrog/jfrog-cli/artifactory" "github.com/jfrog/jfrog-cli/inttestutils" "github.com/jfrog/jfrog-cli/utils/tests" "github.com/jfrog/jfrog-client-go/utils" + clientlog "github.com/jfrog/jfrog-client-go/utils/log" + clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/stretchr/testify/assert" + "github.com/urfave/cli" "gopkg.in/yaml.v2" + "os" + "path/filepath" + "strconv" + "strings" + "testing" ) func TestMain(m *testing.M) { @@ -80,6 +76,9 @@ func setupIntegrationTests() { if *tests.TestTransfer { InitTransferTests() } + if *tests.TestLifecycle { + InitLifecycleTests() + } } func tearDownIntegrationTests() { @@ -98,6 +97,9 @@ func tearDownIntegrationTests() { if *tests.TestTransfer { CleanTransferTests() } + if *tests.TestLifecycle { + CleanLifecycleTests() + } } func InitBuildToolsTests() { @@ -180,7 +182,8 @@ func initArtifactoryCli() { } *tests.JfrogUrl = utils.AddTrailingSlashIfNeeded(*tests.JfrogUrl) artifactoryCli = tests.NewJfrogCli(execMain, "jfrog rt", authenticate(false)) - if (*tests.TestArtifactory && !*tests.TestArtifactoryProxy) || *tests.TestPlugins || *tests.TestArtifactoryProject || *tests.TestAccess || *tests.TestTransfer { + if (*tests.TestArtifactory && !*tests.TestArtifactoryProxy) || *tests.TestPlugins || *tests.TestArtifactoryProject || + *tests.TestAccess || *tests.TestTransfer || *tests.TestLifecycle { configCli = createConfigJfrogCLI(authenticate(true)) platformCli = tests.NewJfrogCli(execMain, "jfrog", authenticate(false)) } @@ -331,6 +334,12 @@ func initDeploymentViewTest(t *testing.T) (assertDeploymentViewFunc func(), clea return } +func deleteFilesFromRepo(t *testing.T, repoName string) { + deleteSpec := spec.NewBuilder().Pattern(repoName).BuildSpec() + _, _, err := tests.DeleteFiles(deleteSpec, serverDetails) + assert.NoError(t, err) +} + func TestIntro(t *testing.T) { buffer, _, previousLog := coreTests.RedirectLogOutputToBuffer() defer clientlog.SetLogger(previousLog) diff --git a/maven_test.go b/maven_test.go index a41cfc498..e7de3a582 100644 --- a/maven_test.go +++ b/maven_test.go @@ -2,29 +2,25 @@ package main import ( "fmt" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/jfrog/jfrog-cli/inttestutils" - "github.com/jfrog/jfrog-cli/utils/tests/proxy/server/certificate" - "github.com/jfrog/jfrog-client-go/utils/log" - clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" - buildinfo "github.com/jfrog/build-info-go/entities" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/mvn" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/common/commands" "github.com/jfrog/jfrog-cli-core/v2/common/spec" - "github.com/jfrog/jfrog-client-go/utils/io/fileutils" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" + "github.com/jfrog/jfrog-cli/inttestutils" "github.com/jfrog/jfrog-cli/utils/tests" cliproxy "github.com/jfrog/jfrog-cli/utils/tests/proxy/server" + "github.com/jfrog/jfrog-cli/utils/tests/proxy/server/certificate" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" + "github.com/jfrog/jfrog-client-go/utils/log" + clientTestUtils "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "strings" + "testing" ) const mavenTestsProxyPort = "1028" @@ -34,12 +30,8 @@ var localRepoDir string func cleanMavenTest(t *testing.T) { clientTestUtils.UnSetEnvAndAssert(t, coreutils.HomeDir) - deleteSpec := spec.NewBuilder().Pattern(tests.MvnRepo1).BuildSpec() - _, _, err := tests.DeleteFiles(deleteSpec, serverDetails) - assert.NoError(t, err) - deleteSpec = spec.NewBuilder().Pattern(tests.MvnRepo2).BuildSpec() - _, _, err = tests.DeleteFiles(deleteSpec, serverDetails) - assert.NoError(t, err) + deleteFilesFromRepo(t, tests.MvnRepo1) + deleteFilesFromRepo(t, tests.MvnRepo2) tests.CleanFileSystem() } diff --git a/testdata/dev_repo_repository_config.json b/testdata/dev_repo_repository_config.json new file mode 100644 index 000000000..de2297cfd --- /dev/null +++ b/testdata/dev_repo_repository_config.json @@ -0,0 +1,6 @@ +{ + "key": "${DEV_REPO}", + "rclass": "local", + "packageType": "generic", + "environments":["DEV"] +} diff --git a/testdata/filespecs/search_all_prod_repo.json b/testdata/filespecs/search_all_prod_repo.json new file mode 100644 index 000000000..2930acf50 --- /dev/null +++ b/testdata/filespecs/search_all_prod_repo.json @@ -0,0 +1,7 @@ +{ + "files": [ + { + "pattern": "${PROD_REPO}/*" + } + ] +} \ No newline at end of file diff --git a/testdata/filespecs/upload_dev_spec_a.json b/testdata/filespecs/upload_dev_spec_a.json new file mode 100644 index 000000000..d816a4499 --- /dev/null +++ b/testdata/filespecs/upload_dev_spec_a.json @@ -0,0 +1,10 @@ +{ + "files": [ + { + "pattern": "testdata/a/*", + "target": "${DEV_REPO}/", + "flat": "true", + "recursive": "false" + } + ] +} \ No newline at end of file diff --git a/testdata/filespecs/upload_dev_spec_b.json b/testdata/filespecs/upload_dev_spec_b.json new file mode 100644 index 000000000..e5419e101 --- /dev/null +++ b/testdata/filespecs/upload_dev_spec_b.json @@ -0,0 +1,10 @@ +{ + "files": [ + { + "pattern": "testdata/a/b/*", + "target": "${DEV_REPO}/", + "flat": "true", + "recursive": "false" + } + ] +} \ No newline at end of file diff --git a/testdata/filespecs/upload_dev_spec_c.json b/testdata/filespecs/upload_dev_spec_c.json new file mode 100644 index 000000000..db8e8177f --- /dev/null +++ b/testdata/filespecs/upload_dev_spec_c.json @@ -0,0 +1,10 @@ +{ + "files": [ + { + "pattern": "testdata/a/b/c/*", + "target": "${DEV_REPO}/", + "flat": "true", + "recursive": "false" + } + ] +} \ No newline at end of file diff --git a/testdata/lifecycle/builds-spec-1-2.json b/testdata/lifecycle/builds-spec-1-2.json new file mode 100644 index 000000000..1d31239ca --- /dev/null +++ b/testdata/lifecycle/builds-spec-1-2.json @@ -0,0 +1,13 @@ +{ + "builds": [ + { + "name": "${LC_BUILD_NAME1}", + "number": "111" + }, + { + "name": "${LC_BUILD_NAME2}", + "number": "", + "project": "default" + } + ] +} diff --git a/testdata/lifecycle/builds-spec-3.json b/testdata/lifecycle/builds-spec-3.json new file mode 100644 index 000000000..b188d7eb6 --- /dev/null +++ b/testdata/lifecycle/builds-spec-3.json @@ -0,0 +1,8 @@ +{ + "builds": [ + { + "name": "${LC_BUILD_NAME3}", + "number": "" + } + ] +} diff --git a/testdata/lifecycle/keys/private.txt b/testdata/lifecycle/keys/private.txt new file mode 100644 index 000000000..302ccf64a --- /dev/null +++ b/testdata/lifecycle/keys/private.txt @@ -0,0 +1,83 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +=lBLn +-----END PGP PRIVATE KEY BLOCK----- diff --git a/testdata/lifecycle/keys/public.txt b/testdata/lifecycle/keys/public.txt new file mode 100644 index 000000000..00e214e35 --- /dev/null +++ b/testdata/lifecycle/keys/public.txt @@ -0,0 +1,41 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +m0maxaPoW55LRqmm +=S8pT +-----END PGP PUBLIC KEY BLOCK----- diff --git a/testdata/lifecycle/release-bundles-spec.json b/testdata/lifecycle/release-bundles-spec.json new file mode 100644 index 000000000..36fac6eea --- /dev/null +++ b/testdata/lifecycle/release-bundles-spec.json @@ -0,0 +1,12 @@ +{ + "releaseBundles": [ + { + "name": "${RB_NAME1}", + "version": "111" + }, + { + "name": "${RB_NAME2}", + "version": "222" + } + ] +} diff --git a/testdata/prod_repo_repository_config.json b/testdata/prod_repo_repository_config.json new file mode 100644 index 000000000..6187c443e --- /dev/null +++ b/testdata/prod_repo_repository_config.json @@ -0,0 +1,6 @@ +{ + "key": "${PROD_REPO}", + "rclass": "local", + "packageType": "generic", + "environments":["PROD"] +} diff --git a/utils/cliutils/commandsflags.go b/utils/cliutils/commandsflags.go index 68d9a2a7e..080fea61f 100644 --- a/utils/cliutils/commandsflags.go +++ b/utils/cliutils/commandsflags.go @@ -88,11 +88,11 @@ const ( passphrase = "passphrase" // Distribution's Command Keys - ReleaseBundleCreate = "release-bundle-create" - ReleaseBundleUpdate = "release-bundle-update" - ReleaseBundleSign = "release-bundle-sign" - ReleaseBundleDistribute = "release-bundle-distribute" - ReleaseBundleDelete = "release-bundle-delete" + ReleaseBundleV1Create = "release-bundle-v1-create" + ReleaseBundleV1Update = "release-bundle-v1-update" + ReleaseBundleV1Sign = "release-bundle-v1-sign" + ReleaseBundleV1Distribute = "release-bundle-v1-distribute" + ReleaseBundleV1Delete = "release-bundle-v1-delete" // MC's Commands Keys McConfig = "mc-config" @@ -130,6 +130,10 @@ const ( // TransferInstall commands keys TransferInstall = "transfer-plugin-install" + // Lifecycle commands keys + ReleaseBundleCreate = "release-bundle-create" + ReleaseBundlePromote = "release-bundle-promote" + // *** Artifactory Commands' flags *** // Base flags url = "url" @@ -299,11 +303,11 @@ const ( copyFlag = "copy" failFast = "fail-fast" - async = "async" + Async = "async" // Unique build-discard flags buildDiscardPrefix = "bdi-" - bdiAsync = buildDiscardPrefix + async + bdiAsync = buildDiscardPrefix + Async maxDays = "max-days" maxBuilds = "max-builds" excludeBuilds = "exclude-builds" @@ -405,25 +409,25 @@ const ( // Base flags distUrl = "dist-url" - // Unique release-bundle-* flags - releaseBundlePrefix = "rb-" - rbDryRun = releaseBundlePrefix + dryRun - rbRepo = releaseBundlePrefix + repo - rbPassphrase = releaseBundlePrefix + passphrase - distTarget = releaseBundlePrefix + target - rbDetailedSummary = releaseBundlePrefix + detailedSummary - sign = "sign" - desc = "desc" - releaseNotesPath = "release-notes-path" - releaseNotesSyntax = "release-notes-syntax" - distRules = "dist-rules" - site = "site" - city = "city" - countryCodes = "country-codes" - sync = "sync" - maxWaitMinutes = "max-wait-minutes" - deleteFromDist = "delete-from-dist" - createRepo = "create-repo" + // Unique release-bundle-* v1 flags + releaseBundleV1Prefix = "rbv1-" + rbDryRun = releaseBundleV1Prefix + dryRun + rbRepo = releaseBundleV1Prefix + repo + rbPassphrase = releaseBundleV1Prefix + passphrase + distTarget = releaseBundleV1Prefix + target + rbDetailedSummary = releaseBundleV1Prefix + detailedSummary + sign = "sign" + desc = "desc" + releaseNotesPath = "release-notes-path" + releaseNotesSyntax = "release-notes-syntax" + distRules = "dist-rules" + site = "site" + city = "city" + countryCodes = "country-codes" + sync = "sync" + maxWaitMinutes = "max-wait-minutes" + deleteFromDist = "delete-from-dist" + createRepo = "create-repo" // *** Xray Commands' flags *** // Base flags @@ -532,6 +536,19 @@ const ( installPluginVersion = installPluginPrefix + Version InstallPluginSrcDir = "dir" InstallPluginHomeDir = "home-dir" + + // Unique lifecycle flags + lifecyclePrefix = "lc-" + lcUrl = lifecyclePrefix + url + lcSync = lifecyclePrefix + Sync + lcProject = lifecyclePrefix + project + Builds = "builds" + lcBuilds = lifecyclePrefix + Builds + ReleaseBundles = "release-bundles" + lcReleaseBundles = lifecyclePrefix + ReleaseBundles + SigningKey = "signing-key" + lcSigningKey = lifecyclePrefix + SigningKey + lcOverwrite = lifecyclePrefix + Overwrite ) var flagsMap = map[string]cli.Flag{ @@ -987,7 +1004,7 @@ var flagsMap = map[string]cli.Flag{ Usage: "[Default: false] If set to true, automatically removes build artifacts stored in Artifactory.` `", }, bdiAsync: cli.BoolFlag{ - Name: async, + Name: Async, Usage: "[Default: false] If set to true, build discard will run asynchronously and will not wait for response.` `", }, refs: cli.StringFlag{ @@ -1348,7 +1365,7 @@ var flagsMap = map[string]cli.Flag{ }, xrOutput: cli.StringFlag{ Name: xrOutput, - Usage: "[Default: table] Defines the output format of the command. Acceptable values are: table, json, simple-json and sarif. Note: the json format doesn’t include information about scans that are included as part of the Advanced Security package.` `", + Usage: "[Default: table] Defines the output format of the command. Acceptable values are: table, json, simple-json and sarif. Note: the json format doesn't include information about scans that are included as part of the Advanced Security package.` `", }, BypassArchiveLimits: cli.BoolFlag{ Name: BypassArchiveLimits, @@ -1556,6 +1573,34 @@ var flagsMap = map[string]cli.Flag{ Name: PreChecks, Usage: "[Default: false] Set to true to run pre transfer checks.` `", }, + lcUrl: cli.StringFlag{ + Name: url, + Usage: "[Optional] JFrog platform URL.` `", + }, + lcSync: cli.BoolFlag{ + Name: Sync, + Usage: "[Default: false] Set to true to run synchronously.` `", + }, + lcProject: cli.StringFlag{ + Name: project, + Usage: "[Optional] Project key associated with the Release Bundle version.` `", + }, + lcBuilds: cli.StringFlag{ + Name: Builds, + Usage: "[Optional] Path to a JSON file containing information of the source builds from which to create a release bundle.` `", + }, + lcReleaseBundles: cli.StringFlag{ + Name: ReleaseBundles, + Usage: "[Optional] Path to a JSON file containing information of the source release bundles from which to create a release bundle.` `", + }, + lcSigningKey: cli.StringFlag{ + Name: SigningKey, + Usage: "[Mandatory] The GPG/RSA key-pair name given in Artifactory.` `", + }, + lcOverwrite: cli.BoolFlag{ + Name: Overwrite, + Usage: "[Default: false] Set to true to replace artifacts with the same name but a different checksum if such already exist at the promotion targets. By default, the promotion is stopped in a case of such conflict.` `", + }, } var commandFlags = map[string][]string{ @@ -1766,23 +1811,23 @@ var commandFlags = map[string][]string{ Poetry: { buildName, buildNumber, module, project, }, - ReleaseBundleCreate: { + ReleaseBundleV1Create: { distUrl, user, password, accessToken, serverId, specFlag, specVars, targetProps, rbDryRun, sign, desc, exclusions, releaseNotesPath, releaseNotesSyntax, rbPassphrase, rbRepo, InsecureTls, distTarget, rbDetailedSummary, }, - ReleaseBundleUpdate: { + ReleaseBundleV1Update: { distUrl, user, password, accessToken, serverId, specFlag, specVars, targetProps, rbDryRun, sign, desc, exclusions, releaseNotesPath, releaseNotesSyntax, rbPassphrase, rbRepo, InsecureTls, distTarget, rbDetailedSummary, }, - ReleaseBundleSign: { + ReleaseBundleV1Sign: { distUrl, user, password, accessToken, serverId, rbPassphrase, rbRepo, InsecureTls, rbDetailedSummary, }, - ReleaseBundleDistribute: { + ReleaseBundleV1Distribute: { distUrl, user, password, accessToken, serverId, rbDryRun, distRules, site, city, countryCodes, sync, maxWaitMinutes, InsecureTls, createRepo, }, - ReleaseBundleDelete: { + ReleaseBundleV1Delete: { distUrl, user, password, accessToken, serverId, rbDryRun, distRules, site, city, countryCodes, sync, maxWaitMinutes, InsecureTls, deleteFromDist, deleteQuiet, }, @@ -1834,6 +1879,12 @@ var commandFlags = map[string][]string{ TransferInstall: { installPluginVersion, InstallPluginSrcDir, InstallPluginHomeDir, }, + ReleaseBundleCreate: { + lcUrl, user, password, accessToken, serverId, lcSigningKey, lcSync, lcProject, lcBuilds, lcReleaseBundles, + }, + ReleaseBundlePromote: { + lcUrl, user, password, accessToken, serverId, lcSigningKey, lcSync, lcProject, lcOverwrite, + }, // Xray's commands OfflineUpdate: { licenseId, from, to, Version, target, Stream, Periodic, diff --git a/utils/cliutils/utils.go b/utils/cliutils/utils.go index 887af601b..ffcfe7558 100644 --- a/utils/cliutils/utils.go +++ b/utils/cliutils/utils.go @@ -33,9 +33,10 @@ import ( type CommandDomain string const ( - Rt CommandDomain = "rt" - Ds CommandDomain = "ds" - Xr CommandDomain = "xr" + Rt CommandDomain = "rt" + Ds CommandDomain = "ds" + Xr CommandDomain = "xr" + Platform CommandDomain = "platform" ) // Error modes (how should the application behave when the CheckError function is invoked): @@ -530,6 +531,8 @@ func createServerDetailsFromFlags(c *cli.Context, domain CommandDomain) (details details.XrayUrl = details.Url case Ds: details.DistributionUrl = details.Url + case Platform: + return } details.Url = "" @@ -853,3 +856,12 @@ func doHttpRequest(client *http.Client, req *http.Request) (resp *http.Response, body, err = io.ReadAll(resp.Body) return resp, body, errorutils.CheckError(err) } + +// Get project key from flag or environment variable +func GetProject(c *cli.Context) string { + projectKey := c.String("project") + if projectKey == "" { + projectKey = os.Getenv(coreutils.Project) + } + return projectKey +} diff --git a/utils/tests/consts.go b/utils/tests/consts.go index 23c5202c9..a4c52191c 100644 --- a/utils/tests/consts.go +++ b/utils/tests/consts.go @@ -1,13 +1,11 @@ package tests import ( - "path/filepath" - - "github.com/jfrog/jfrog-client-go/artifactory/services" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" - servicesutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" - clientutils "github.com/jfrog/jfrog-client-go/utils" + "github.com/jfrog/jfrog-client-go/artifactory/services" + servicesUtils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" + clientUtils "github.com/jfrog/jfrog-client-go/utils" + "path/filepath" ) const ( @@ -93,6 +91,7 @@ const ( SearchAllMaven = "search_all_maven.json" SearchAllNpm = "search_all_npm.json" SearchAllRepo1 = "search_all_repo1.json" + SearchAllProdRepo = "search_all_prod_repo.json" SearchDistRepoByInSuffix = "search_dist_repo_by_in_suffix.json" SearchRepo1ByInSuffix = "search_repo1_by_in_suffix.json" SearchRepo1IncludeDirs = "search_repo1_include_dirs.json" @@ -148,6 +147,11 @@ const ( DockerRemoteRepositoryConfig = "docker_remote_repository_config.json" DockerVirtualRepositoryConfig = "docker_virtual_repository_config.json" XrayEndpoint = "xray/" + DevRepoRepositoryConfig = "dev_repo_repository_config.json" + ProdRepoRepositoryConfig = "prod_repo_repository_config.json" + UploadDevSpecA = "upload_dev_spec_a.json" + UploadDevSpecB = "upload_dev_spec_b.json" + UploadDevSpecC = "upload_dev_spec_c.json" ) var ( @@ -181,6 +185,9 @@ var ( RtRepo1 = "cli-rt1" RtRepo2 = "cli-rt2" RtVirtualRepo = "cli-rt-virtual" + // Repositories that are assigned to an environment. + RtDevRepo = "cli-rt-dev" + RtProdRepo = "cli-rt-prod" // These are not actual repositories. These patterns are meant to be used in both Repo1 and Repo2. RtRepo1And2 = "cli-rt*" RtRepo1And2Placeholder = "cli-rt(*)" @@ -202,6 +209,12 @@ var ( RtBuildName2 = "cli-rt-build2" RtBuildNameWithSpecialChars = "cli-rt-a$+~&^a#-build3" RtPermissionTargetName = "cli-rt-pt" + LcBuildName1 = "cli-lc-build1" + LcBuildName2 = "cli-lc-build2" + LcBuildName3 = "cli-lc-build3" + LcRbName1 = "cli-lc-rb1" + LcRbName2 = "cli-lc-rb2" + LcRbName3 = "cli-lc-rb3" // Users UserName1 = "alice" @@ -249,16 +262,6 @@ func GetExpectedExcludeUploadPart2() []string { RtRepo1 + "/", } } -func GetExpectedExcludeUpload2() []string { - return []string{ - RtRepo1 + "/b3.in", - RtRepo1 + "/a2.in", - RtRepo1 + "/a3.in", - RtRepo1 + "/a1.in", - RtRepo1 + "/c", - RtRepo1 + "/", - } -} func GetExpectedExcludeUploadIncludeDir() []string { return []string{ RtRepo1 + "/a2.in", @@ -269,14 +272,6 @@ func GetExpectedExcludeUploadIncludeDir() []string { } } -func GetUploadLegacyPropsExpected() []string { - return []string{ - RtRepo1 + "/data/a1.in", - RtRepo1 + "/data/a2.in", - RtRepo1 + "/data/a3.in", - } -} - func GetSearchAppendedBuildNoPatternExpected() []string { return []string{ RtRepo1 + "/data/a1.in", @@ -1739,12 +1734,6 @@ func GetSearchResultAfterDeleteByPropsStep3() []utils.SearchResult { } } -func GetDockerSourceManifest() []string { - return []string{ - DockerLocalRepo + "/" + DockerImageName + "/1/manifest.json", - } -} - func GetDockerDeployedManifest() []string { return []string{ DockerLocalPromoteRepo + "/" + DockerImageName + "promotion" + "/2/manifest.json", @@ -1972,17 +1961,17 @@ func GetFileWithDownloadedPlaceHolderSlashSuffix() []string { } } -func GetExpectedUploadSummaryDetails(rtUrl string) []clientutils.FileTransferDetails { +func GetExpectedUploadSummaryDetails(rtUrl string) []clientUtils.FileTransferDetails { path1, path2, path3 := filepath.Join("testdata", "a", "a1.in"), filepath.Join("testdata", "a", "a2.in"), filepath.Join("testdata", "a", "a3.in") - return []clientutils.FileTransferDetails{ + return []clientUtils.FileTransferDetails{ {SourcePath: path1, RtUrl: rtUrl, TargetPath: RtRepo1 + "/testdata/a/a1.in", Sha256: "4eb341b5d2762a853d79cc25e622aa8b978eb6e12c3259e2d99dc9dc60d82c5d"}, {SourcePath: path2, RtUrl: rtUrl, TargetPath: RtRepo1 + "/testdata/a/a2.in", Sha256: "3e3deb6628658a48cf0d280a2210211f9d977ec2e10a4619b95d5fb85cb10450"}, {SourcePath: path3, RtUrl: rtUrl, TargetPath: RtRepo1 + "/testdata/a/a3.in", Sha256: "14e3dc4749bf42df13a67a271065b0f334d0ad36bb34a74cc57c6e137f9af09e"}, } } -func GetReplicationConfig() []servicesutils.ReplicationParams { - return []servicesutils.ReplicationParams{ +func GetReplicationConfig() []servicesUtils.ReplicationParams { + return []servicesUtils.ReplicationParams{ { Url: *JfrogUrl + ArtifactoryEndpoint + "targetRepo", Username: "admin", @@ -2075,3 +2064,17 @@ func GetTransferExpectedRepoSnapshot() []string { RtRepo1 + "/testdata/a/b/b3.in", } } + +func GetExpectedLifecycleArtifacts() []string { + return []string{ + RtProdRepo + "/a1.in", + RtProdRepo + "/a2.in", + RtProdRepo + "/a3.in", + RtProdRepo + "/b1.in", + RtProdRepo + "/b2.in", + RtProdRepo + "/b3.in", + RtProdRepo + "/c1.in", + RtProdRepo + "/c2.in", + RtProdRepo + "/c3.in", + } +} diff --git a/utils/tests/utils.go b/utils/tests/utils.go index 5f245f63e..bfd9d0a6d 100644 --- a/utils/tests/utils.go +++ b/utils/tests/utils.go @@ -7,6 +7,7 @@ import ( "errors" "flag" "fmt" + "github.com/urfave/cli" "io" "math/rand" "os" @@ -68,6 +69,7 @@ var ( TestXray *bool TestAccess *bool TestTransfer *bool + TestLifecycle *bool HideUnitTestLog *bool ciRunId *string InstallDataTransferPlugin *bool @@ -103,6 +105,7 @@ func init() { TestXray = flag.Bool("test.xray", false, "Test Xray") TestAccess = flag.Bool("test.access", false, "Test Access") TestTransfer = flag.Bool("test.transfer", false, "Test files transfer") + TestLifecycle = flag.Bool("test.lc", false, "Test lifecycle") ContainerRegistry = flag.String("test.containerRegistry", "localhost:8082", "Container registry") HideUnitTestLog = flag.Bool("test.hideUnitTestLog", false, "Hide unit tests logs and print it in a file") InstallDataTransferPlugin = flag.Bool("test.installDataTransferPlugin", false, "Install data-transfer plugin on the source Artifactory server") @@ -349,6 +352,8 @@ var reposConfigMap = map[*string]string{ &DockerLocalPromoteRepo: DockerLocalPromoteRepositoryConfig, &DockerRemoteRepo: DockerRemoteRepositoryConfig, &DockerVirtualRepo: DockerVirtualRepositoryConfig, + &RtDevRepo: DevRepoRepositoryConfig, + &RtProdRepo: ProdRepoRepositoryConfig, } var CreatedNonVirtualRepositories map[*string]string @@ -399,6 +404,7 @@ func GetNonVirtualRepositories() map[*string]string { TestXray: {}, TestAccess: {&RtRepo1}, TestTransfer: {&RtRepo1, &RtRepo2, &MvnRepo1, &MvnRemoteRepo, &DockerRemoteRepo}, + TestLifecycle: {&RtDevRepo, &RtProdRepo}, } return getNeededRepositories(nonVirtualReposMap) } @@ -459,6 +465,7 @@ func GetBuildNames() []string { TestXray: {}, TestAccess: {}, TestTransfer: {&MvnBuildName}, + TestLifecycle: {&LcBuildName1, &LcBuildName2, &LcBuildName3}, } return getNeededBuildNames(buildNamesMap) } @@ -512,6 +519,13 @@ func getSubstitutionMap() map[string]string { "{PASSWORD_1}": Password1, "{USER_NAME_2}": UserName2, "{PASSWORD_2}": Password2, + "${LC_BUILD_NAME1}": LcBuildName1, + "${LC_BUILD_NAME2}": LcBuildName2, + "${LC_BUILD_NAME3}": LcBuildName3, + "${RB_NAME1}": LcRbName1, + "${RB_NAME2}": LcRbName2, + "${DEV_REPO}": RtDevRepo, + "${PROD_REPO}": RtProdRepo, } } @@ -561,6 +575,8 @@ func AddTimestampToGlobalVars() { RtRepo1And2Placeholder += uniqueSuffix RtRepo2 += uniqueSuffix RtVirtualRepo += uniqueSuffix + RtDevRepo += uniqueSuffix + RtProdRepo += uniqueSuffix // Builds/bundles/images BundleName += uniqueSuffix @@ -580,6 +596,12 @@ func AddTimestampToGlobalVars() { RtBuildName2 += uniqueSuffix RtBuildNameWithSpecialChars += uniqueSuffix RtPermissionTargetName += uniqueSuffix + LcBuildName1 += uniqueSuffix + LcBuildName2 += uniqueSuffix + LcBuildName3 += uniqueSuffix + LcRbName1 += uniqueSuffix + LcRbName2 += uniqueSuffix + LcRbName3 += uniqueSuffix // Users UserName1 += uniqueSuffix @@ -778,3 +800,24 @@ func SkipKnownFailingTest(t *testing.T) { t.Error("Not skipping test. Please fix the test or delay the skipMonth") } } + +func CreateContext(t *testing.T, testFlags, testArgs []string) (*cli.Context, *bytes.Buffer) { + flagSet := createFlagSet(t, testFlags, testArgs) + app := cli.NewApp() + app.Writer = &bytes.Buffer{} + return cli.NewContext(app, flagSet, nil), &bytes.Buffer{} +} + +// Create flag set with input flags and arguments. +func createFlagSet(t *testing.T, flags []string, args []string) *flag.FlagSet { + flagSet := flag.NewFlagSet("TestFlagSet", flag.ContinueOnError) + flags = append(flags, "url=http://127.0.0.1:8081/artifactory") + var cmdFlags []string + for _, curFlag := range flags { + flagSet.String(strings.Split(curFlag, "=")[0], "", "") + cmdFlags = append(cmdFlags, "--"+curFlag) + } + cmdFlags = append(cmdFlags, args...) + assert.NoError(t, flagSet.Parse(cmdFlags)) + return flagSet +}