github.com/jfrog/jfrog-cli-core/v2@v2.52.0/common/cliutils/utils.go (about)

     1  package cliutils
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/jfrog/jfrog-cli-core/v2/common/commands"
    12  	"github.com/jfrog/jfrog-cli-core/v2/common/spec"
    13  	"github.com/jfrog/jfrog-cli-core/v2/utils/config"
    14  	"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
    15  	"github.com/jfrog/jfrog-cli-core/v2/utils/ioutils"
    16  	clientUtils "github.com/jfrog/jfrog-client-go/utils"
    17  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    18  	"github.com/jfrog/jfrog-client-go/utils/log"
    19  )
    20  
    21  func FixWinPathBySource(path string, fromSpec bool) string {
    22  	if strings.Count(path, "/") > 0 {
    23  		// Assuming forward slashes - not doubling backslash to allow regexp escaping
    24  		return ioutils.UnixToWinPathSeparator(path)
    25  	}
    26  	if fromSpec {
    27  		// Doubling backslash only for paths from spec files (that aren't forward slashed)
    28  		return ioutils.DoubleWinPathSeparator(path)
    29  	}
    30  	return path
    31  }
    32  
    33  func LogNonNativeCommandDeprecation(cmdName, oldSubcommand string) {
    34  	if ShouldLogWarning() {
    35  		log.Warn(
    36  			`You are using a deprecated syntax of the command.
    37  	Instead of:
    38  	$ ` + coreutils.GetCliExecutableName() + ` ` + oldSubcommand + ` ` + cmdName + ` ...
    39  	Use:
    40  	$ ` + coreutils.GetCliExecutableName() + ` ` + cmdName + ` ...`)
    41  	}
    42  }
    43  
    44  func LogNonGenericAuditCommandDeprecation(cmdName string) {
    45  	if ShouldLogWarning() {
    46  		log.Warn(
    47  			`You are using a deprecated syntax of the command.
    48  	Instead of:
    49  	$ ` + coreutils.GetCliExecutableName() + ` ` + cmdName + ` ...
    50  	Use:
    51  	$ ` + coreutils.GetCliExecutableName() + ` audit ...`)
    52  	}
    53  }
    54  
    55  func ShouldLogWarning() bool {
    56  	return strings.ToLower(os.Getenv(JfrogCliAvoidDeprecationWarnings)) != "true"
    57  }
    58  
    59  func GetCLIDocumentationMessage() string {
    60  	return "You can read the documentation at " + coreutils.JFrogHelpUrl + "jfrog-cli"
    61  }
    62  
    63  func PrintHelpAndReturnError(msg string, printHelp func() error) error {
    64  	log.Error(msg + " " + GetCLIDocumentationMessage())
    65  	err := printHelp()
    66  	if err != nil {
    67  		msg = msg + ". " + err.Error()
    68  	}
    69  	return errors.New(msg)
    70  }
    71  
    72  // This function checks whether the command received --help as a single option.
    73  // This function should be used iff the SkipFlagParsing option is used.
    74  // Generic commands such as docker, don't have dedicated subcommands. As a workaround, printing the help of their subcommands,
    75  // we use a dummy command with no logic but the help message. to trigger the print of those dummy commands,
    76  // each generic command must decide what cmdName it needs to pass to this function.
    77  // For example, 'jf docker scan --help' passes cmdName='dockerscanhelp' to print our help and not the origin from docker client/cli.
    78  func ShowGenericCmdHelpIfNeeded(args []string, printHelp func() error) (bool, error) {
    79  	for _, arg := range args {
    80  		if arg == "--help" || arg == "-h" {
    81  			err := printHelp()
    82  			return true, err
    83  		}
    84  	}
    85  	return false, nil
    86  }
    87  
    88  // This function checks whether the command received --help as a single option.
    89  // If it did, the command's help is shown and true is returned.
    90  // This function should be used iff the SkipFlagParsing option is used.
    91  func ShowCmdHelpIfNeeded(args []string, printHelp func() error) (bool, error) {
    92  	if len(args) != 1 {
    93  		return false, nil
    94  	}
    95  	if args[0] == "--help" || args[0] == "-h" {
    96  		err := printHelp()
    97  		return true, err
    98  	}
    99  	return false, nil
   100  }
   101  
   102  func WrongNumberOfArgumentsHandler(argCount int, printHelp func() error) error {
   103  	return PrintHelpAndReturnError(fmt.Sprintf("Wrong number of arguments (%d).", argCount), printHelp)
   104  }
   105  
   106  func GetThreadsCount(threadCountStrVal string) (threads int, err error) {
   107  	threads = Threads
   108  	if threadCountStrVal != "" {
   109  		threads, err = strconv.Atoi(threadCountStrVal)
   110  		if err != nil || threads < 1 {
   111  			err = errors.New("the '--threads' option should have a numeric positive value")
   112  			return 0, err
   113  		}
   114  	}
   115  	return threads, nil
   116  }
   117  
   118  // Get a secret value from a flag or from stdin.
   119  func HandleSecretInput(stringFlag, secretRaw, stdinFlag string, isStdin bool) (secret string, err error) {
   120  	secret = secretRaw
   121  	isStdinSecret := isStdin
   122  	if isStdinSecret && secret != "" {
   123  		err = errorutils.CheckErrorf("providing both %s and %s flags is not supported", stringFlag, stdinFlag)
   124  		return
   125  	}
   126  
   127  	if isStdinSecret {
   128  		var stat os.FileInfo
   129  		stat, err = os.Stdin.Stat()
   130  		if err != nil {
   131  			return
   132  		}
   133  		if (stat.Mode() & os.ModeCharDevice) == 0 {
   134  			var rawSecret []byte
   135  			rawSecret, err = io.ReadAll(os.Stdin)
   136  			if err != nil {
   137  				return
   138  			}
   139  			secret = strings.TrimSpace(string(rawSecret))
   140  			if secret != "" {
   141  				log.Debug("Using", stringFlag, "provided via Stdin")
   142  				return
   143  			}
   144  		}
   145  		err = errorutils.CheckErrorf("no %s provided via Stdin", stringFlag)
   146  	}
   147  	return
   148  }
   149  
   150  func OfferConfig(createServerDetails func() (*config.ServerDetails, error)) (*config.ServerDetails, error) {
   151  	confirmed, err := ShouldOfferConfig()
   152  	if !confirmed || err != nil {
   153  		return nil, err
   154  	}
   155  	details, err := createServerDetails()
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	configCmd := commands.NewConfigCommand(commands.AddOrEdit, details.ServerId).SetDefaultDetails(details).SetInteractive(true).SetEncPassword(true)
   160  	err = configCmd.Run()
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	return configCmd.ServerDetails()
   166  }
   167  
   168  func ShouldOfferConfig() (bool, error) {
   169  	exists, err := config.IsServerConfExists()
   170  	if err != nil || exists {
   171  		return false, err
   172  	}
   173  	var ci bool
   174  	if ci, err = clientUtils.GetBoolEnvValue(coreutils.CI, false); err != nil {
   175  		return false, err
   176  	}
   177  	if ci {
   178  		return false, nil
   179  	}
   180  
   181  	msg := fmt.Sprintf("To avoid this message in the future, set the %s environment variable to true.\n"+
   182  		"The CLI commands require the URL and authentication details\n"+
   183  		"Configuring JFrog CLI with these parameters now will save you having to include them as command options.\n"+
   184  		"You can also configure these parameters later using the 'jfrog c' command.\n"+
   185  		"Configure now?", coreutils.CI)
   186  	confirmed := coreutils.AskYesNo(msg, false)
   187  	if !confirmed {
   188  		return false, nil
   189  	}
   190  	return true, nil
   191  }
   192  
   193  // Exclude refreshable tokens parameter should be true when working with external tools (build tools, curl, etc)
   194  // or when sending requests not via ArtifactoryHttpClient.
   195  func CreateServerDetailsWithConfigOffer(createServerDetails func() (*config.ServerDetails, error), excludeRefreshableTokens bool) (*config.ServerDetails, error) {
   196  	createdDetails, err := OfferConfig(createServerDetails)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	if createdDetails != nil {
   201  		return createdDetails, err
   202  	}
   203  
   204  	details, err := createServerDetails()
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  	// If urls or credentials were passed as options, use options as they are.
   209  	// For security reasons, we'd like to avoid using part of the connection details from command options and the rest from the config.
   210  	// Either use command options only or config only.
   211  	if credentialsChanged(details) {
   212  		return details, nil
   213  	}
   214  
   215  	// Else, use details from config for requested serverId, or for default server if empty.
   216  	confDetails, err := commands.GetConfig(details.ServerId, excludeRefreshableTokens)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	// Take insecureTls value from options since it is not saved in config.
   222  	confDetails.InsecureTls = details.InsecureTls
   223  	confDetails.Url = clientUtils.AddTrailingSlashIfNeeded(confDetails.Url)
   224  	confDetails.DistributionUrl = clientUtils.AddTrailingSlashIfNeeded(confDetails.DistributionUrl)
   225  
   226  	// Create initial access token if needed.
   227  	if !excludeRefreshableTokens {
   228  		err = config.CreateInitialRefreshableTokensIfNeeded(confDetails)
   229  		if err != nil {
   230  			return nil, err
   231  		}
   232  	}
   233  
   234  	return confDetails, nil
   235  }
   236  
   237  func credentialsChanged(details *config.ServerDetails) bool {
   238  	return details.Url != "" || details.ArtifactoryUrl != "" || details.DistributionUrl != "" || details.XrayUrl != "" ||
   239  		details.User != "" || details.Password != "" || details.SshKeyPath != "" || details.SshPassphrase != "" || details.AccessToken != "" ||
   240  		details.ClientCertKeyPath != "" || details.ClientCertPath != ""
   241  }
   242  
   243  func FixWinPathsForFileSystemSourcedCmds(uploadSpec *spec.SpecFiles, specFlag, exclusionsFlag bool) {
   244  	if coreutils.IsWindows() {
   245  		for i, file := range uploadSpec.Files {
   246  			uploadSpec.Files[i].Pattern = FixWinPathBySource(file.Pattern, specFlag)
   247  			for j, exclusion := range uploadSpec.Files[i].Exclusions {
   248  				// If exclusions are set, they override the spec value
   249  				uploadSpec.Files[i].Exclusions[j] = FixWinPathBySource(exclusion, specFlag && !exclusionsFlag)
   250  			}
   251  		}
   252  	}
   253  }