github.com/jfrog/jfrog-cli-go@v1.22.1-0.20200318093948-4826ef344ffd/artifactory/commands/curl/curl.go (about)

     1  package curl
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os/exec"
     8  	"strings"
     9  
    10  	gofrogcmd "github.com/jfrog/gofrog/io"
    11  	"github.com/jfrog/jfrog-cli-go/artifactory/utils"
    12  	"github.com/jfrog/jfrog-cli-go/utils/config"
    13  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    14  	"github.com/jfrog/jfrog-client-go/utils/log"
    15  )
    16  
    17  type CurlCommand struct {
    18  	arguments      []string
    19  	executablePath string
    20  	rtDetails      *config.ArtifactoryDetails
    21  }
    22  
    23  func NewCurlCommand() *CurlCommand {
    24  	return &CurlCommand{}
    25  }
    26  
    27  func (curlCmd *CurlCommand) SetArguments(arguments []string) *CurlCommand {
    28  	curlCmd.arguments = arguments
    29  	return curlCmd
    30  }
    31  
    32  func (curlCmd *CurlCommand) SetExecutablePath(executablePath string) *CurlCommand {
    33  	curlCmd.executablePath = executablePath
    34  	return curlCmd
    35  }
    36  
    37  func (curlCmd *CurlCommand) SetRtDetails(rtDetails *config.ArtifactoryDetails) *CurlCommand {
    38  	curlCmd.rtDetails = rtDetails
    39  	return curlCmd
    40  }
    41  
    42  func (curlCmd *CurlCommand) Run() error {
    43  	// Get curl execution path.
    44  	execPath, err := exec.LookPath("curl")
    45  	if err != nil {
    46  		return errorutils.CheckError(err)
    47  	}
    48  	curlCmd.SetExecutablePath(execPath)
    49  
    50  	// If the command already includes credentials flag, return an error.
    51  	if curlCmd.isCredentialsFlagExists() {
    52  		return errorutils.CheckError(errors.New("Curl command must not include credentials flag (-u or --user)."))
    53  	}
    54  
    55  	// If the command already includes certificates flag, return an error.
    56  	if curlCmd.rtDetails.ClientCertPath != "" && curlCmd.isCertificateFlagExists() {
    57  		return errorutils.CheckError(errors.New("Curl command must not include certificate flag (--cert or --key)."))
    58  	}
    59  
    60  	// Get target url for the curl command.
    61  	uriIndex, targetUri, err := curlCmd.buildCommandUrl(curlCmd.rtDetails.Url)
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	// Replace url argument with complete url.
    67  	curlCmd.arguments[uriIndex] = targetUri
    68  
    69  	cmdWithoutCreds := strings.Join(curlCmd.arguments, " ")
    70  	// Add credentials to curl command.
    71  	credentialsMessage, err := curlCmd.addCommandCredentials()
    72  
    73  	// Run curl.
    74  	log.Debug(fmt.Sprintf("Executing curl command: '%s %s'", cmdWithoutCreds, credentialsMessage))
    75  	return gofrogcmd.RunCmd(curlCmd)
    76  }
    77  
    78  func (curlCmd *CurlCommand) addCommandCredentials() (string, error) {
    79  	certificateHelpPrefix := ""
    80  
    81  	if curlCmd.rtDetails.ClientCertPath != "" {
    82  		curlCmd.arguments = append(curlCmd.arguments,
    83  			"--cert", curlCmd.rtDetails.ClientCertPath,
    84  			"--key", curlCmd.rtDetails.ClientCertKeyPath)
    85  		certificateHelpPrefix = "--cert *** --key *** "
    86  	}
    87  
    88  	if curlCmd.rtDetails.AccessToken != "" {
    89  		// Add access token header.
    90  		tokenHeader := fmt.Sprintf("Authorization: Bearer %s", curlCmd.rtDetails.AccessToken)
    91  		curlCmd.arguments = append(curlCmd.arguments, "-H", tokenHeader)
    92  
    93  		return certificateHelpPrefix + "-H \"Authorization: Bearer ***\"", nil
    94  	}
    95  
    96  	// Add credentials flag to Command. In case of flag duplication, the latter is used by Curl.
    97  	credFlag := fmt.Sprintf("-u%s:%s", curlCmd.rtDetails.User, curlCmd.rtDetails.Password)
    98  	curlCmd.arguments = append(curlCmd.arguments, credFlag)
    99  
   100  	return certificateHelpPrefix + "-u***:***", nil
   101  }
   102  
   103  func (curlCmd *CurlCommand) buildCommandUrl(artifactoryUrl string) (uriIndex int, uriValue string, err error) {
   104  	// Find command's URL argument.
   105  	// Representing the target API for the Curl command.
   106  	uriIndex, uriValue = curlCmd.findUriValueAndIndex()
   107  	if uriIndex == -1 {
   108  		err = errorutils.CheckError(errors.New("Could not find argument in curl command."))
   109  		return
   110  	}
   111  
   112  	// If user provided full-url, throw an error.
   113  	if strings.HasPrefix(uriValue, "http://") || strings.HasPrefix(uriValue, "https://") {
   114  		err = errorutils.CheckError(errors.New("Curl command must not include full-url, but only the REST API URI (e.g '/api/system/ping')."))
   115  		return
   116  	}
   117  
   118  	// Trim '/' prefix if exists.
   119  	if strings.HasPrefix(uriValue, "/") {
   120  		uriValue = strings.TrimPrefix(uriValue, "/")
   121  	}
   122  
   123  	// Attach Artifactory's url to the api.
   124  	uriValue = artifactoryUrl + uriValue
   125  
   126  	return
   127  }
   128  
   129  // Returns Artifactory details
   130  func (curlCmd *CurlCommand) GetArtifactoryDetails() (*config.ArtifactoryDetails, error) {
   131  	// Get --server-id flag value from the command, and remove it.
   132  	flagIndex, valueIndex, serverIdValue, err := utils.FindFlag("--server-id", curlCmd.arguments)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	utils.RemoveFlagFromCommand(&curlCmd.arguments, flagIndex, valueIndex)
   137  	return config.GetArtifactorySpecificConfig(serverIdValue)
   138  }
   139  
   140  // Find the URL argument in the Curl Command.
   141  // A command flag is prefixed by '-' or '--'.
   142  // Use this method ONLY after removing all JFrog-CLI flags, i.e flags in the form: '--my-flag=value' are not allowed.
   143  // An argument is any provided candidate which is not a flag or a flag value.
   144  func (curlCmd *CurlCommand) findUriValueAndIndex() (int, string) {
   145  	skipThisArg := false
   146  	for index, arg := range curlCmd.arguments {
   147  		// Check if shouldn't check current arg.
   148  		if skipThisArg {
   149  			skipThisArg = false
   150  			continue
   151  		}
   152  
   153  		// If starts with '--', meaning a flag which its value is at next slot.
   154  		if strings.HasPrefix(arg, "--") {
   155  			skipThisArg = true
   156  			continue
   157  		}
   158  
   159  		// Check if '-'.
   160  		if strings.HasPrefix(arg, "-") {
   161  			if len(arg) > 2 {
   162  				// Meaning that this flag also contains its value.
   163  				continue
   164  			}
   165  			// If reached here, means that the flag value is at the next arg.
   166  			skipThisArg = true
   167  			continue
   168  		}
   169  
   170  		// Found an argument
   171  		return index, arg
   172  	}
   173  
   174  	// If reached here, didn't find an argument.
   175  	return -1, ""
   176  }
   177  
   178  // Return true if the curl command includes credentials flag.
   179  // The searched flags are not CLI flags.
   180  func (curlCmd *CurlCommand) isCredentialsFlagExists() bool {
   181  	for _, arg := range curlCmd.arguments {
   182  		if strings.HasPrefix(arg, "-u") || arg == "--user" {
   183  			return true
   184  		}
   185  	}
   186  
   187  	return false
   188  }
   189  
   190  // Return true if the curl command includes certificates flag.
   191  // The searched flags are not CLI flags.
   192  func (curlCmd *CurlCommand) isCertificateFlagExists() bool {
   193  	for _, arg := range curlCmd.arguments {
   194  		if arg == "--cert" || arg == "--key" {
   195  			return true
   196  		}
   197  	}
   198  
   199  	return false
   200  }
   201  
   202  func (curlCmd *CurlCommand) GetCmd() *exec.Cmd {
   203  	var cmd []string
   204  	cmd = append(cmd, curlCmd.executablePath)
   205  	cmd = append(cmd, curlCmd.arguments...)
   206  	return exec.Command(cmd[0], cmd[1:]...)
   207  }
   208  
   209  func (curlCmd *CurlCommand) GetEnv() map[string]string {
   210  	return map[string]string{}
   211  }
   212  
   213  func (curlCmd *CurlCommand) GetStdWriter() io.WriteCloser {
   214  	return nil
   215  }
   216  
   217  func (curlCmd *CurlCommand) GetErrWriter() io.WriteCloser {
   218  	return nil
   219  }
   220  
   221  func (curlCmd *CurlCommand) RtDetails() (*config.ArtifactoryDetails, error) {
   222  	return curlCmd.rtDetails, nil
   223  }
   224  
   225  func (curlCmd *CurlCommand) CommandName() string {
   226  	return "rt_curl"
   227  }