Skip to content

Commit

Permalink
Add twine support (#1267)
Browse files Browse the repository at this point in the history
  • Loading branch information
RobiNino authored Sep 18, 2024
1 parent 9310080 commit 0d4ff6b
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 11 deletions.
195 changes: 195 additions & 0 deletions artifactory/commands/python/twine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package python

import (
"errors"
"fmt"
"github.com/jfrog/build-info-go/build"
"github.com/jfrog/build-info-go/utils/pythonutils"
buildUtils "github.com/jfrog/jfrog-cli-core/v2/common/build"
"github.com/jfrog/jfrog-cli-core/v2/utils/config"
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
"github.com/jfrog/jfrog-client-go/auth"
"github.com/jfrog/jfrog-client-go/utils"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
"github.com/jfrog/jfrog-client-go/utils/log"
"os"
"os/exec"
"strings"
)

const (
_configFileOptionKey = "--config-file"
_repositoryUrlOptionKey = "--repository-url"
_usernameOptionKey = "--username"
_passwordOptionKey = "--password"
_usernamePrefixOptionKey = "-u"
_passwordPrefixOptionKey = "-p"
_repositoryUrlEnvKey = "TWINE_REPOSITORY_URL"
_usernameEnvKey = "TWINE_USERNAME"
_passwordEnvKey = "TWINE_PASSWORD"
// Artifactory endpoint for pypi deployment.
_apiPypi = "api/pypi/"
_twineExecName = "twine"
_uploadCmdName = "upload"
)

var twineRepoConfigFlags = []string{_configFileOptionKey, _repositoryUrlOptionKey, _usernameOptionKey, _passwordOptionKey, _usernamePrefixOptionKey, _passwordPrefixOptionKey}

type TwineCommand struct {
serverDetails *config.ServerDetails
commandName string
args []string
targetRepo string
buildConfiguration *buildUtils.BuildConfiguration
}

func NewTwineCommand(commandName string) *TwineCommand {
return &TwineCommand{
commandName: commandName,
}
}

func (tc *TwineCommand) CommandName() string {
return "twine_" + tc.commandName
}

func (tc *TwineCommand) ServerDetails() (*config.ServerDetails, error) {
return tc.serverDetails, nil
}

func (tc *TwineCommand) SetServerDetails(serverDetails *config.ServerDetails) *TwineCommand {
tc.serverDetails = serverDetails
return tc
}

func (tc *TwineCommand) SetTargetRepo(targetRepo string) *TwineCommand {
tc.targetRepo = targetRepo
return tc
}

func (tc *TwineCommand) SetArgs(args []string) *TwineCommand {
tc.args = args
return tc
}

func (tc *TwineCommand) Run() (err error) {
// Assert no forbidden flags were provided.
if tc.isRepoConfigFlagProvided() {
return errorutils.CheckErrorf(tc.getRepoConfigFlagProvidedErr())
}
if err = tc.extractAndFilterArgs(tc.args); err != nil {
return err
}
callbackFunc, err := tc.setAuthEnvVars()
defer func() {
err = errors.Join(err, callbackFunc())
}()

collectBuild, err := tc.buildConfiguration.IsCollectBuildInfo()
if err != nil {
return err
}
// If build info is not collected, or this is not an upload command, run the twine command directly.
if !collectBuild || tc.commandName != _uploadCmdName {
return tc.runPlainTwineCommand()
}
return tc.uploadAndCollectBuildInfo()
}

func (tc *TwineCommand) extractAndFilterArgs(args []string) (err error) {
cleanArgs := append([]string(nil), args...)
cleanArgs, tc.buildConfiguration, err = buildUtils.ExtractBuildDetailsFromArgs(cleanArgs)
if err != nil {
return
}
tc.args = cleanArgs
return
}

func (tc *TwineCommand) setAuthEnvVars() (callbackFunc func() error, err error) {
oldRepoUrl := os.Getenv(_repositoryUrlEnvKey)
oldUsername := os.Getenv(_usernameEnvKey)
oldPassword := os.Getenv(_passwordEnvKey)
callbackFunc = func() error {
return errors.Join(os.Setenv(_repositoryUrlOptionKey, oldRepoUrl), os.Setenv(_usernameEnvKey, oldUsername), os.Setenv(_passwordEnvKey, oldPassword))
}

if err = os.Setenv(_repositoryUrlEnvKey, utils.AddTrailingSlashIfNeeded(tc.serverDetails.ArtifactoryUrl)+_apiPypi+tc.targetRepo); err != nil {
return
}

username := tc.serverDetails.User
password := tc.serverDetails.Password
// Get credentials from access-token if exists.
if tc.serverDetails.GetAccessToken() != "" {
if username == "" {
username = auth.ExtractUsernameFromAccessToken(tc.serverDetails.GetAccessToken())
}
password = tc.serverDetails.GetAccessToken()
}

if err = os.Setenv(_usernameEnvKey, username); err != nil {
return
}
err = os.Setenv(_passwordEnvKey, password)
return
}

func (tc *TwineCommand) runPlainTwineCommand() error {
log.Debug("Running twine command:", tc.commandName, strings.Join(tc.args, " "))
args := append([]string{tc.commandName}, tc.args...)
cmd := exec.Command(_twineExecName, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}

func (tc *TwineCommand) uploadAndCollectBuildInfo() error {
buildInfo, err := buildUtils.PrepareBuildPrerequisites(tc.buildConfiguration)
if err != nil {
return err
}

defer func() {
if buildInfo != nil && err != nil {
err = errors.Join(err, buildInfo.Clean())
}
}()

var pythonModule *build.PythonModule
pythonModule, err = buildInfo.AddPythonModule("", pythonutils.Twine)
if err != nil {
return err
}
if tc.buildConfiguration.GetModule() != "" {
pythonModule.SetName(tc.buildConfiguration.GetModule())
}

artifacts, err := pythonModule.TwineUploadWithLogParsing(tc.args)
if err != nil {
return err
}
for i := range artifacts {
artifacts[i].OriginalDeploymentRepo = tc.targetRepo
}
if err = pythonModule.AddArtifacts(artifacts); err != nil {
return err
}
log.Debug(fmt.Sprintf("Command finished successfully. %d artifacs were added to build info.", len(artifacts)))
return nil
}

func (tc *TwineCommand) isRepoConfigFlagProvided() bool {
for _, arg := range tc.args {
for _, flag := range twineRepoConfigFlags {
if strings.HasPrefix(arg, flag) {
return true
}
}
}
return false
}

func (tc *TwineCommand) getRepoConfigFlagProvidedErr() string {
return "twine command must not be executed with the following flags: " + coreutils.ListToText(twineRepoConfigFlags)
}
1 change: 1 addition & 0 deletions artifactory/utils/commandsummary/buildinfosummary.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var (
buildInfo.Generic: true,
buildInfo.Terraform: true,
buildInfo.Docker: true,
buildInfo.Python: true,
}
)

Expand Down
4 changes: 3 additions & 1 deletion common/commands/configfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@ func handleInteractiveConfigCreation(configFile *ConfigFile, confType project.Pr
switch confType {
case project.Go:
return configFile.setDeployerResolver()
case project.Pip, project.Pipenv, project.Poetry:
case project.Pip, project.Pipenv:
return configFile.setDeployerResolver()
case project.Poetry:
return configFile.setResolver(false)
case project.Yarn:
return configFile.setResolver(false)
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,6 @@ require (

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/jfrog/build-info-go v1.8.9-0.20240909072259-13bf8722d051
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 v1.7.6-0.20240909061051-2d36ae4bd05a
// replace github.com/jfrog/gofrog => github.com/jfrog/gofrog v1.3.3-0.20231223133729-ef57bd08cedc
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,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/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s=
github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4=
github.com/jfrog/jfrog-client-go v1.28.1-0.20240918081224-1c584cc334c7 h1:h/bLASJGFaI3QOow1Ix63RZB8kZpAClkA/NpAVWRroc=
Expand Down
12 changes: 6 additions & 6 deletions utils/coreutils/cmdutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func FindFlagFirstMatch(flags, args []string) (flagIndex, flagValueIndex int, fl
}

func ExtractServerIdFromCommand(args []string) (cleanArgs []string, serverId string, err error) {
return extractStringOptionFromArgs(args, "server-id")
return ExtractStringOptionFromArgs(args, "server-id")
}

func ExtractThreadsFromArgs(args []string, defaultValue int) (cleanArgs []string, threads int, err error) {
Expand Down Expand Up @@ -181,12 +181,12 @@ func ExtractLicensesFromArgs(args []string) (cleanArgs []string, licenses bool,

// Used by docker scan (Xray)
func ExtractRepoPathFromArgs(args []string) (cleanArgs []string, repoPath string, err error) {
return extractStringOptionFromArgs(args, "repo-path")
return ExtractStringOptionFromArgs(args, "repo-path")
}

// Used by docker scan (Xray)
func ExtractWatchesFromArgs(args []string) (cleanArgs []string, watches string, err error) {
return extractStringOptionFromArgs(args, "watches")
return ExtractStringOptionFromArgs(args, "watches")
}

func ExtractDetailedSummaryFromArgs(args []string) (cleanArgs []string, detailedSummary bool, err error) {
Expand All @@ -198,14 +198,14 @@ func ExtractXrayScanFromArgs(args []string) (cleanArgs []string, xrayScan bool,
}

func ExtractXrayOutputFormatFromArgs(args []string) (cleanArgs []string, format string, err error) {
return extractStringOptionFromArgs(args, "format")
return ExtractStringOptionFromArgs(args, "format")
}

func ExtractTagFromArgs(args []string) (cleanArgs []string, tag string, err error) {
return extractStringOptionFromArgs(args, "tag")
return ExtractStringOptionFromArgs(args, "tag")
}

func extractStringOptionFromArgs(args []string, optionName string) (cleanArgs []string, value string, err error) {
func ExtractStringOptionFromArgs(args []string, optionName string) (cleanArgs []string, value string, err error) {
cleanArgs = append([]string(nil), args...)

flagIndex, valIndex, value, err := FindFlag("--"+optionName, cleanArgs)
Expand Down

0 comments on commit 0d4ff6b

Please sign in to comment.