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 }