diff --git a/.github/workflows/pythonTests.yml b/.github/workflows/pythonTests.yml index ccedde64d..926dc4444 100644 --- a/.github/workflows/pythonTests.yml +++ b/.github/workflows/pythonTests.yml @@ -38,6 +38,10 @@ jobs: if: ${{ matrix.suite == 'pipenv' }} run: python -m pip install pipenv + - name: Setup Twine + if: ${{ matrix.suite == 'pip' }} + run: python -m pip install twine + - name: Setup Go with cache uses: jfrog/.github/actions/install-go-with-cache@main diff --git a/buildtools/cli.go b/buildtools/cli.go index 155717b49..80404a382 100644 --- a/buildtools/cli.go +++ b/buildtools/cli.go @@ -33,6 +33,7 @@ import ( "github.com/jfrog/jfrog-cli-security/commands/scan" terraformdocs "github.com/jfrog/jfrog-cli/docs/artifactory/terraform" "github.com/jfrog/jfrog-cli/docs/artifactory/terraformconfig" + twinedocs "github.com/jfrog/jfrog-cli/docs/artifactory/twine" "github.com/jfrog/jfrog-cli/docs/buildtools/docker" dotnetdocs "github.com/jfrog/jfrog-cli/docs/buildtools/dotnet" "github.com/jfrog/jfrog-cli/docs/buildtools/dotnetconfig" @@ -402,6 +403,18 @@ func GetCommands() []cli.Command { Category: buildToolsCategory, Action: terraformCmd, }, + { + Name: "twine", + Flags: cliutils.GetCommandFlags(cliutils.Twine), + Usage: twinedocs.GetDescription(), + HelpName: corecommon.CreateUsage("twine", twinedocs.GetDescription(), twinedocs.Usage), + UsageText: twinedocs.GetArguments(), + ArgsUsage: common.CreateEnvVars(), + SkipFlagParsing: true, + BashComplete: corecommon.CreateBashCompletionFunc(), + Category: buildToolsCategory, + Action: twineCmd, + }, }) } @@ -410,13 +423,11 @@ func MvnCmd(c *cli.Context) (err error) { return err } - configFilePath, exists, err := project.GetProjectConfFilePath(project.Maven) + configFilePath, err := getProjectConfigPathOrThrow(project.Maven, "mvn", "mvn-config") if err != nil { return err } - if !exists { - return errors.New("no config file was found! Before running the mvn command on a project for the first time, the project should be configured with the mvn-config command") - } + if c.NArg() < 1 { return cliutils.WrongNumberOfArgumentsHandler(c) } @@ -469,13 +480,11 @@ func GradleCmd(c *cli.Context) (err error) { return err } - configFilePath, exists, err := project.GetProjectConfFilePath(project.Gradle) + configFilePath, err := getProjectConfigPathOrThrow(project.Gradle, "gradle", "gradle-config") if err != nil { return err } - if !exists { - return errors.New("no config file was found! Before running the gradle command on a project for the first time, the project should be configured with the gradle-config command") - } + // Found a config file. Continue as native command. if c.NArg() < 1 { return cliutils.WrongNumberOfArgumentsHandler(c) @@ -525,13 +534,10 @@ func YarnCmd(c *cli.Context) error { return err } - configFilePath, exists, err := project.GetProjectConfFilePath(project.Yarn) + configFilePath, err := getProjectConfigPathOrThrow(project.Yarn, "yarn", "yarn-config") if err != nil { return err } - if !exists { - return fmt.Errorf("no config file was found! Before running the yarn command on a project for the first time, the project should be configured using the yarn-config command") - } yarnCmd := yarn.NewYarnCommand().SetConfigFilePath(configFilePath).SetArgs(c.Args()) return commands.Exec(yarnCmd) @@ -544,15 +550,12 @@ func NugetCmd(c *cli.Context) error { if c.NArg() < 1 { return cliutils.WrongNumberOfArgumentsHandler(c) } - configFilePath, exists, err := project.GetProjectConfFilePath(project.Nuget) + + configFilePath, err := getProjectConfigPathOrThrow(project.Nuget, "nuget", "nuget-config") if err != nil { return err } - if !exists { - return fmt.Errorf("no config file was found! Before running the nuget command on a project for the first time, the project should be configured using the nuget-config command") - } - rtDetails, targetRepo, useNugetV2, err := getNugetAndDotnetConfigFields(configFilePath) if err != nil { return err @@ -584,13 +587,10 @@ func DotnetCmd(c *cli.Context) error { } // Get configuration file path. - configFilePath, exists, err := project.GetProjectConfFilePath(project.Dotnet) + configFilePath, err := getProjectConfigPathOrThrow(project.Dotnet, "dotnet", "dotnet-config") if err != nil { return err } - if !exists { - return fmt.Errorf("no config file was found! Before running the dotnet command on a project for the first time, the project should be configured using the dotnet-config command") - } rtDetails, targetRepo, useNugetV2, err := getNugetAndDotnetConfigFields(configFilePath) if err != nil { @@ -690,14 +690,12 @@ func goCmdVerification(c *cli.Context) (string, error) { if c.NArg() < 1 { return "", cliutils.WrongNumberOfArgumentsHandler(c) } - configFilePath, exists, err := project.GetProjectConfFilePath(project.Go) + + configFilePath, err := getProjectConfigPathOrThrow(project.Go, "go", "go-config") if err != nil { return "", err } - // Verify config file is found. - if !exists { - return "", fmt.Errorf("no config file was found! Before running the go command on a project for the first time, the project should be configured using the go-config command") - } + log.Debug("Go config file was found in:", configFilePath) return configFilePath, nil } @@ -887,13 +885,9 @@ func NpmPublishCmd(c *cli.Context) (err error) { } func GetNpmConfigAndArgs(c *cli.Context) (configFilePath string, args []string, err error) { - configFilePath, exists, err := project.GetProjectConfFilePath(project.Npm) + configFilePath, err = getProjectConfigPathOrThrow(project.Npm, "npm", "npm-config") if err != nil { - return "", nil, err - } - - if !exists { - return "", nil, errorutils.CheckErrorf("no config file was found! Before running the npm command on a project for the first time, the project should be configured using the npm-config command") + return } _, args = getCommandName(c.Args()) return @@ -970,13 +964,9 @@ func terraformCmd(c *cli.Context) error { } func getTerraformConfigAndArgs(c *cli.Context) (configFilePath string, args []string, err error) { - configFilePath, exists, err := project.GetProjectConfFilePath(project.Terraform) + configFilePath, err = getProjectConfigPathOrThrow(project.Terraform, "terraform", "terraform-config") if err != nil { - return "", nil, err - } - - if !exists { - return "", nil, errors.New("no config file was found! Before running the terraform command on a project for the first time, the project should be configured using the terraform-config command") + return } args = cliutils.ExtractCommand(c) return @@ -992,3 +982,63 @@ func terraformPublishCmd(configFilePath string, args []string, c *cli.Context) e result := terraformCmd.Result() return cliutils.PrintBriefSummaryReport(result.SuccessCount(), result.FailCount(), cliutils.IsFailNoOp(c), err) } + +func getProjectConfigPathOrThrow(projectType project.ProjectType, cmdName, configCmdName string) (configFilePath string, err error) { + configFilePath, exists, err := project.GetProjectConfFilePath(projectType) + if err != nil { + return + } + if !exists { + return "", errorutils.CheckErrorf(getMissingConfigErrMsg(cmdName, configCmdName)) + } + return +} + +func getMissingConfigErrMsg(cmdName, configCmdName string) string { + return fmt.Sprintf("no config file was found! Before running the 'jf %s' command on a project for the first time, the project should be configured with the 'jf %s' command", cmdName, configCmdName) +} + +func twineCmd(c *cli.Context) error { + if show, err := cliutils.ShowCmdHelpIfNeeded(c, c.Args()); show || err != nil { + return err + } + serverDetails, targetRepo, err := getTwineConfigAndArgs() + if err != nil { + return err + } + cmdName, filteredArgs := getCommandName(cliutils.ExtractCommand(c)) + return python.NewTwineCommand(cmdName).SetServerDetails(serverDetails).SetTargetRepo(targetRepo).SetArgs(filteredArgs).Run() +} + +func getTwineConfigAndArgs() (serverDetails *coreConfig.ServerDetails, targetRepo string, err error) { + configFilePath, err := getTwineConfigPath() + if err != nil { + return + } + + vConfig, err := project.ReadConfigFile(configFilePath, project.YAML) + if err != nil { + return nil, "", fmt.Errorf("failed while reading configuration file '%s'. Error: %s", configFilePath, err.Error()) + } + projectConfig, err := project.GetRepoConfigByPrefix(configFilePath, project.ProjectConfigDeployerPrefix, vConfig) + if err != nil { + return nil, "", err + } + serverDetails, err = projectConfig.ServerDetails() + if err != nil { + return nil, "", err + } + targetRepo = projectConfig.TargetRepo() + return +} + +func getTwineConfigPath() (configFilePath string, err error) { + var exists bool + for _, projectType := range []project.ProjectType{project.Pip, project.Pipenv} { + configFilePath, exists, err = project.GetProjectConfFilePath(projectType) + if err != nil || exists { + return + } + } + return "", errorutils.CheckErrorf(getMissingConfigErrMsg("twine", "pip-config OR pipenv-config")) +} diff --git a/docs/artifactory/twine/help.go b/docs/artifactory/twine/help.go new file mode 100644 index 000000000..9fb5af93d --- /dev/null +++ b/docs/artifactory/twine/help.go @@ -0,0 +1,12 @@ +package twinedocs + +var Usage = []string{"twine [command options]"} + +func GetDescription() string { + return "Runs twine " +} + +func GetArguments() string { + return ` twine commands + Arguments and options for the twine command.` +} diff --git a/go.mod b/go.mod index 24edd3a34..7508d2390 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/testcontainers/testcontainers-go v0.33.0 github.com/urfave/cli v1.22.15 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 gopkg.in/yaml.v2 v2.4.0 ) @@ -156,7 +156,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect @@ -170,12 +170,12 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240909132328-33f3a1ec52c1 +replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240918150651-0d4ff6b567ce // replace github.com/jfrog/jfrog-cli-security => github.com/attiasas/jfrog-cli-security v0.0.0-20240904061406-f368939ce3a0 -// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240806162439-01bb7dcd43fc +replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240918081224-1c584cc334c7 -// replace github.com/jfrog/build-info-go => github.com/asafambar/build-info-go v1.8.9-0.20240819133117-c3f52700927d +replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20240918150101-ad5b10435a12 // replace github.com/jfrog/gofrog => github.com/jfrog/gofrog dev diff --git a/go.sum b/go.sum index a4e857665..0d99a55b2 100644 --- a/go.sum +++ b/go.sum @@ -931,8 +931,8 @@ github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+ github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI= github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= -github.com/jfrog/build-info-go v1.9.36 h1:bKoYW3o+U70Zbz2kt5NT84N5JWNxdDXHOf+kVdzK+j4= -github.com/jfrog/build-info-go v1.9.36/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= +github.com/jfrog/build-info-go v1.8.9-0.20240918150101-ad5b10435a12 h1:OWxdvdurW6LRRBDEgVl8WFcjJbk9TyBcVGgXX4k5atc= +github.com/jfrog/build-info-go v1.8.9-0.20240918150101-ad5b10435a12/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= github.com/jfrog/froggit-go v1.16.1 h1:FBIM1qevX/ag9unfmpGzfmZ36D8ulOJ+DPTSFUk3l5U= github.com/jfrog/froggit-go v1.16.1/go.mod h1:TEJSzgiV+3D/GVGE8Y6j46ut1jrBLD1FL6WdMdKwwCE= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= @@ -941,14 +941,14 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= github.com/jfrog/jfrog-cli-artifactory v0.1.6 h1:bMfJsrLQJw0dZp4nqUf1xOmtY0rpCatW/I5q88x+fhQ= github.com/jfrog/jfrog-cli-artifactory v0.1.6/go.mod h1:jbNb22ebtupcjdhrdGq0VBew2vWG6VUK04xxGNDfynE= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240909132328-33f3a1ec52c1 h1:+eqMs31Ms+O9nm4gondTNIj3scd69Kmag6zzuF1Bi3k= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240909132328-33f3a1ec52c1/go.mod h1:24P424ysZzA0kVw/xESKELRJ+rFybRVf5zJiH18p72k= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240918150651-0d4ff6b567ce h1:j3qKnp+YdB5MO44/hD2EUpfJEyLmP56tMEwL8StFO/E= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240918150651-0d4ff6b567ce/go.mod h1:bEnACPygif5dz9d0Ve0/Apnek7ZohyNkI6OH0fbICVo= github.com/jfrog/jfrog-cli-platform-services v1.3.0 h1:IblSDZFBjL7WLRi37Ni2DmHrXJJ6ysSMxx7t41AvyDA= github.com/jfrog/jfrog-cli-platform-services v1.3.0/go.mod h1:Ky4SDXuMeaiNP/5zMT1YSzIuXG+cNYYOl8BaEA7Awbc= github.com/jfrog/jfrog-cli-security v1.8.0 h1:jp/AVaQcItUNXRCud5PMyl8VVjPuzfrNHJWQvWAMnms= github.com/jfrog/jfrog-cli-security v1.8.0/go.mod h1:DjufYZpsTwILOFJlx7tR/y63oLBRmtPtFIz1WgiP/X4= -github.com/jfrog/jfrog-client-go v1.46.2 h1:1rk7PliYGc7zVSFVE2/RO77JOR1KdEtr28os8GQiLyI= -github.com/jfrog/jfrog-client-go v1.46.2/go.mod h1:qtQ9ML8xrRJmUwU/t6QRsov7C5mIZndTDY3qulgB5hA= +github.com/jfrog/jfrog-client-go v1.28.1-0.20240918081224-1c584cc334c7 h1:h/bLASJGFaI3QOow1Ix63RZB8kZpAClkA/NpAVWRroc= +github.com/jfrog/jfrog-client-go v1.28.1-0.20240918081224-1c584cc334c7/go.mod h1:kk0lbMJbZF9961lGLw1aKKH4PNcXZFB0pAwvZHGwYSw= 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.10.0 h1:upMDUxhQKqZ5ZDCs/wy+8Kib8rZR8I8lOR34yJkdqhI= @@ -1249,8 +1249,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= -golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1356,8 +1356,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1608,8 +1608,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pip_test.go b/pip_test.go index 34267df70..c4199eded 100644 --- a/pip_test.go +++ b/pip_test.go @@ -166,7 +166,11 @@ func assertDependencyChecksums(t *testing.T, checksum buildinfo.Checksum) { } func createPipProject(t *testing.T, outFolder, projectName string) string { - projectSrc := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), "pip", projectName) + return createPypiProject(t, outFolder, projectName, "pip") +} + +func createPypiProject(t *testing.T, outFolder, projectName, projectSrcDir string) string { + projectSrc := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), projectSrcDir, projectName) projectTarget := filepath.Join(tests.Out, outFolder+"-"+projectName) err := fileutils.CreateDirIfNotExist(projectTarget) assert.NoError(t, err) @@ -176,7 +180,7 @@ func createPipProject(t *testing.T, outFolder, projectName string) string { assert.NoError(t, err) // Copy pip-config file. - configSrc := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), "pip", "pip.yaml") + configSrc := filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), projectSrcDir, "pip.yaml") configTarget := filepath.Join(projectTarget, ".jfrog", "projects") _, err = tests.ReplaceTemplateVariables(configSrc, configTarget) assert.NoError(t, err) @@ -187,6 +191,80 @@ func initPipTest(t *testing.T) { if !*tests.TestPip { t.Skip("Skipping Pip test. To run Pip test add the '-test.pip=true' option.") } + require.True(t, isRepoExist(tests.PypiLocalRepo), "Pypi test local repository doesn't exist.") require.True(t, isRepoExist(tests.PypiRemoteRepo), "Pypi test remote repository doesn't exist.") require.True(t, isRepoExist(tests.PypiVirtualRepo), "Pypi test virtual repository doesn't exist.") } + +func TestTwine(t *testing.T) { + // Init pip. + initPipTest(t) + + // Populate cli config with 'default' server. + oldHomeDir, newHomeDir := prepareHomeDir(t) + defer func() { + clientTestUtils.SetEnvAndAssert(t, coreutils.HomeDir, oldHomeDir) + clientTestUtils.RemoveAllAndAssert(t, newHomeDir) + }() + + // Create test cases. + allTests := []struct { + name string + project string + outputFolder string + expectedModuleId string + args []string + expectedArtifacts int + }{ + {"twine", "pyproject", "twine", "jfrog-python-example:1.0", []string{}, 2}, + {"twine-with-module", "pyproject", "twine-with-module", "twine-with-module", []string{"--module=twine-with-module"}, 2}, + } + + // Run test cases. + for testNumber, test := range allTests { + t.Run(test.name, func(t *testing.T) { + cleanVirtualEnv, err := prepareVirtualEnv(t) + assert.NoError(t, err) + + buildNumber := strconv.Itoa(100 + testNumber) + test.args = append([]string{"twine", "upload", "dist/*", "--build-name=" + tests.PipBuildName, "--build-number=" + buildNumber}, test.args...) + testTwineCmd(t, createPypiProject(t, test.outputFolder, test.project, "twine"), buildNumber, test.expectedModuleId, test.expectedArtifacts, test.args) + + // cleanup + cleanVirtualEnv() + inttestutils.DeleteBuild(serverDetails.ArtifactoryUrl, tests.PipBuildName, artHttpDetails) + }) + } +} + +func testTwineCmd(t *testing.T, projectPath, buildNumber, expectedModuleId string, expectedArtifacts int, args []string) { + wd, err := os.Getwd() + assert.NoError(t, err, "Failed to get current dir") + chdirCallback := clientTestUtils.ChangeDirWithCallback(t, wd, projectPath) + defer chdirCallback() + + jfrogCli := coretests.NewJfrogCli(execMain, "jfrog", "") + err = jfrogCli.Exec(args...) + if err != nil { + assert.Fail(t, "Failed executing twine upload command", err.Error()) + return + } + + assert.NoError(t, artifactoryCli.Exec("bp", tests.PipBuildName, buildNumber)) + + publishedBuildInfo, found, err := tests.GetBuildInfo(serverDetails, tests.PipBuildName, buildNumber) + if err != nil { + assert.NoError(t, err) + return + } + if !found { + assert.True(t, found, "build info was expected to be found") + return + } + buildInfo := publishedBuildInfo.BuildInfo + require.Len(t, buildInfo.Modules, 1) + twineModule := buildInfo.Modules[0] + assert.Equal(t, buildinfo.Python, twineModule.Type) + assert.Len(t, twineModule.Artifacts, expectedArtifacts) + assert.Equal(t, expectedModuleId, twineModule.Id) +} diff --git a/testdata/pypi_local_repository_config.json b/testdata/pypi_local_repository_config.json new file mode 100644 index 000000000..ab4952816 --- /dev/null +++ b/testdata/pypi_local_repository_config.json @@ -0,0 +1,5 @@ +{ + "key": "${PYPI_LOCAL_REPO}", + "rclass": "local", + "packageType": "pypi" +} diff --git a/testdata/pypi_virtual_repository_config.json b/testdata/pypi_virtual_repository_config.json index 574fad282..0c8a751d0 100644 --- a/testdata/pypi_virtual_repository_config.json +++ b/testdata/pypi_virtual_repository_config.json @@ -2,5 +2,6 @@ "key": "${PYPI_VIRTUAL_REPO}", "rclass": "virtual", "packageType": "pypi", - "repositories": ["${PYPI_REMOTE_REPO}"] + "repositories": ["${PYPI_REMOTE_REPO}", "${PYPI_LOCAL_REPO}"], + "defaultDeploymentRepo": "${PYPI_LOCAL_REPO}" } diff --git a/testdata/twine/pip.yaml b/testdata/twine/pip.yaml new file mode 100644 index 000000000..226862f66 --- /dev/null +++ b/testdata/twine/pip.yaml @@ -0,0 +1,8 @@ +version: 1 +type: pip +resolver: + repo: ${PYPI_VIRTUAL_REPO} + serverId: default +deployer: + repo: ${PYPI_VIRTUAL_REPO} + serverId: default \ No newline at end of file diff --git a/testdata/twine/pyproject/dist/jfrog_python_example-1.0-py3-none-any.whl b/testdata/twine/pyproject/dist/jfrog_python_example-1.0-py3-none-any.whl new file mode 100644 index 000000000..be3eea646 Binary files /dev/null and b/testdata/twine/pyproject/dist/jfrog_python_example-1.0-py3-none-any.whl differ diff --git a/testdata/twine/pyproject/dist/jfrog_python_example-1.0.tar.gz b/testdata/twine/pyproject/dist/jfrog_python_example-1.0.tar.gz new file mode 100644 index 000000000..657cf23e5 Binary files /dev/null and b/testdata/twine/pyproject/dist/jfrog_python_example-1.0.tar.gz differ diff --git a/testdata/twine/pyproject/pyproject.toml b/testdata/twine/pyproject/pyproject.toml new file mode 100644 index 000000000..792971df4 --- /dev/null +++ b/testdata/twine/pyproject/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "jfrog-python-example" +version = "1.0" +description = "Project example for building Python project with JFrog products" +authors = [ + { name="JFrog", email="jfrog@jfrog.com" } +] +dependencies = [ + "PyYAML>3.11", + "nltk" +] diff --git a/utils/cliutils/commandsflags.go b/utils/cliutils/commandsflags.go index 2c656a237..787068a53 100644 --- a/utils/cliutils/commandsflags.go +++ b/utils/cliutils/commandsflags.go @@ -66,6 +66,7 @@ const ( PipConfig = "pip-config" TerraformConfig = "terraform-config" Terraform = "terraform" + Twine = "twine" Pipenv = "pipenv" PipenvConfig = "pipenv-config" PipenvInstall = "pipenv-install" @@ -1897,6 +1898,9 @@ var commandFlags = map[string][]string{ namespace, provider, tag, exclusions, buildName, buildNumber, module, Project, }, + Twine: { + buildName, buildNumber, module, Project, + }, TransferConfig: { Force, Verbose, IncludeRepos, ExcludeRepos, SourceWorkingDir, TargetWorkingDir, PreChecks, }, @@ -1911,13 +1915,13 @@ var commandFlags = map[string][]string{ serverId, }, PipConfig: { - global, serverIdResolve, repoResolve, + global, serverIdResolve, serverIdDeploy, repoResolve, repoDeploy, }, PipInstall: { buildName, buildNumber, module, Project, }, PipenvConfig: { - global, serverIdResolve, repoResolve, + global, serverIdResolve, serverIdDeploy, repoResolve, repoDeploy, }, PipenvInstall: { buildName, buildNumber, module, Project, diff --git a/utils/tests/consts.go b/utils/tests/consts.go index de4152d01..e9d88bab9 100644 --- a/utils/tests/consts.go +++ b/utils/tests/consts.go @@ -94,6 +94,7 @@ const ( PipenvVirtualRepositoryConfig = "pipenv_virtual_repository_config.json" ProdRepo1RepositoryConfig = "prod_repo1_repository_config.json" ProdRepo2RepositoryConfig = "prod_repo2_repository_config.json" + PypiLocalRepositoryConfig = "pypi_local_repository_config.json" PypiRemoteRepositoryConfig = "pypi_remote_repository_config.json" PypiVirtualRepositoryConfig = "pypi_virtual_repository_config.json" ReplicationTempCreate = "replication_push_create.json" @@ -182,6 +183,7 @@ var ( NpmRemoteRepo = "cli-npm-remote" NugetRemoteRepo = "cli-nuget-remote" YarnRemoteRepo = "cli-yarn-remote" + PypiLocalRepo = "cli-pypi-local" PypiRemoteRepo = "cli-pypi-remote" PypiVirtualRepo = "cli-pypi-virtual" PipenvRemoteRepo = "cli-pipenv-pypi-remote" diff --git a/utils/tests/utils.go b/utils/tests/utils.go index e1e6b6396..91f3cdd6b 100644 --- a/utils/tests/utils.go +++ b/utils/tests/utils.go @@ -255,6 +255,7 @@ var reposConfigMap = map[*string]string{ &NpmRemoteRepo: NpmRemoteRepositoryConfig, &NugetRemoteRepo: NugetRemoteRepositoryConfig, &YarnRemoteRepo: YarnRemoteRepositoryConfig, + &PypiLocalRepo: PypiLocalRepositoryConfig, &PypiRemoteRepo: PypiRemoteRepositoryConfig, &PypiVirtualRepo: PypiVirtualRepositoryConfig, &PipenvRemoteRepo: PipenvRemoteRepositoryConfig, @@ -316,7 +317,7 @@ func GetNonVirtualRepositories() map[*string]string { TestMaven: {&MvnRepo1, &MvnRepo2, &MvnRemoteRepo}, TestNpm: {&NpmRepo, &NpmRemoteRepo}, TestNuget: {&NugetRemoteRepo}, - TestPip: {&PypiRemoteRepo}, + TestPip: {&PypiLocalRepo, &PypiRemoteRepo}, TestPipenv: {&PipenvRemoteRepo}, TestPlugins: {&RtRepo1}, TestXray: {&NpmRemoteRepo, &NugetRemoteRepo, &YarnRemoteRepo, &GradleRemoteRepo, &MvnRemoteRepo, &GoRepo, &GoRemoteRepo, &PypiRemoteRepo}, @@ -421,6 +422,7 @@ func getSubstitutionMap() map[string]string { "${PASSWORD}": *JfrogPassword, "${RT_CREDENTIALS_BASIC_AUTH}": base64.StdEncoding.EncodeToString([]byte(*JfrogUser + ":" + *JfrogPassword)), "${ACCESS_TOKEN}": *JfrogAccessToken, + "${PYPI_LOCAL_REPO}": PypiLocalRepo, "${PYPI_REMOTE_REPO}": PypiRemoteRepo, "${PYPI_VIRTUAL_REPO}": PypiVirtualRepo, "${PIPENV_REMOTE_REPO}": PipenvRemoteRepo, @@ -479,6 +481,7 @@ func AddTimestampToGlobalVars() { NpmRemoteRepo += uniqueSuffix NugetRemoteRepo += uniqueSuffix YarnRemoteRepo += uniqueSuffix + PypiLocalRepo += uniqueSuffix PypiRemoteRepo += uniqueSuffix PypiVirtualRepo += uniqueSuffix PipenvRemoteRepo += uniqueSuffix