github.com/ali-iotechsys/cli@v20.10.0+incompatible/cli/command/utils.go (about) 1 package command 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "runtime" 10 "strings" 11 12 "github.com/docker/cli/cli/streams" 13 "github.com/docker/docker/api/types/filters" 14 "github.com/docker/docker/pkg/system" 15 "github.com/pkg/errors" 16 "github.com/spf13/pflag" 17 ) 18 19 // CopyToFile writes the content of the reader to the specified file 20 func CopyToFile(outfile string, r io.Reader) error { 21 // We use sequential file access here to avoid depleting the standby list 22 // on Windows. On Linux, this is a call directly to ioutil.TempFile 23 tmpFile, err := system.TempFileSequential(filepath.Dir(outfile), ".docker_temp_") 24 if err != nil { 25 return err 26 } 27 28 tmpPath := tmpFile.Name() 29 30 _, err = io.Copy(tmpFile, r) 31 tmpFile.Close() 32 33 if err != nil { 34 os.Remove(tmpPath) 35 return err 36 } 37 38 if err = os.Rename(tmpPath, outfile); err != nil { 39 os.Remove(tmpPath) 40 return err 41 } 42 43 return nil 44 } 45 46 // capitalizeFirst capitalizes the first character of string 47 func capitalizeFirst(s string) string { 48 switch l := len(s); l { 49 case 0: 50 return s 51 case 1: 52 return strings.ToLower(s) 53 default: 54 return strings.ToUpper(string(s[0])) + strings.ToLower(s[1:]) 55 } 56 } 57 58 // PrettyPrint outputs arbitrary data for human formatted output by uppercasing the first letter. 59 func PrettyPrint(i interface{}) string { 60 switch t := i.(type) { 61 case nil: 62 return "None" 63 case string: 64 return capitalizeFirst(t) 65 default: 66 return capitalizeFirst(fmt.Sprintf("%s", t)) 67 } 68 } 69 70 // PromptForConfirmation requests and checks confirmation from user. 71 // This will display the provided message followed by ' [y/N] '. If 72 // the user input 'y' or 'Y' it returns true other false. If no 73 // message is provided "Are you sure you want to proceed? [y/N] " 74 // will be used instead. 75 func PromptForConfirmation(ins io.Reader, outs io.Writer, message string) bool { 76 if message == "" { 77 message = "Are you sure you want to proceed?" 78 } 79 message += " [y/N] " 80 81 _, _ = fmt.Fprint(outs, message) 82 83 // On Windows, force the use of the regular OS stdin stream. 84 if runtime.GOOS == "windows" { 85 ins = streams.NewIn(os.Stdin) 86 } 87 88 reader := bufio.NewReader(ins) 89 answer, _, _ := reader.ReadLine() 90 return strings.ToLower(string(answer)) == "y" 91 } 92 93 // PruneFilters returns consolidated prune filters obtained from config.json and cli 94 func PruneFilters(dockerCli Cli, pruneFilters filters.Args) filters.Args { 95 if dockerCli.ConfigFile() == nil { 96 return pruneFilters 97 } 98 for _, f := range dockerCli.ConfigFile().PruneFilters { 99 parts := strings.SplitN(f, "=", 2) 100 if len(parts) != 2 { 101 continue 102 } 103 if parts[0] == "label" { 104 // CLI label filter supersede config.json. 105 // If CLI label filter conflict with config.json, 106 // skip adding label! filter in config.json. 107 if pruneFilters.Contains("label!") && pruneFilters.ExactMatch("label!", parts[1]) { 108 continue 109 } 110 } else if parts[0] == "label!" { 111 // CLI label! filter supersede config.json. 112 // If CLI label! filter conflict with config.json, 113 // skip adding label filter in config.json. 114 if pruneFilters.Contains("label") && pruneFilters.ExactMatch("label", parts[1]) { 115 continue 116 } 117 } 118 pruneFilters.Add(parts[0], parts[1]) 119 } 120 121 return pruneFilters 122 } 123 124 // AddPlatformFlag adds `platform` to a set of flags for API version 1.32 and later. 125 func AddPlatformFlag(flags *pflag.FlagSet, target *string) { 126 flags.StringVar(target, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable") 127 flags.SetAnnotation("platform", "version", []string{"1.32"}) 128 flags.SetAnnotation("platform", "experimental", nil) 129 } 130 131 // ValidateOutputPath validates the output paths of the `export` and `save` commands. 132 func ValidateOutputPath(path string) error { 133 dir := filepath.Dir(filepath.Clean(path)) 134 if dir != "" && dir != "." { 135 if _, err := os.Stat(dir); os.IsNotExist(err) { 136 return errors.Errorf("invalid output path: directory %q does not exist", dir) 137 } 138 } 139 // check whether `path` points to a regular file 140 // (if the path exists and doesn't point to a directory) 141 if fileInfo, err := os.Stat(path); !os.IsNotExist(err) { 142 if err != nil { 143 return err 144 } 145 146 if fileInfo.Mode().IsDir() || fileInfo.Mode().IsRegular() { 147 return nil 148 } 149 150 if err := ValidateOutputPathFileMode(fileInfo.Mode()); err != nil { 151 return errors.Wrapf(err, fmt.Sprintf("invalid output path: %q must be a directory or a regular file", path)) 152 } 153 } 154 return nil 155 } 156 157 // ValidateOutputPathFileMode validates the output paths of the `cp` command and serves as a 158 // helper to `ValidateOutputPath` 159 func ValidateOutputPathFileMode(fileMode os.FileMode) error { 160 switch { 161 case fileMode&os.ModeDevice != 0: 162 return errors.New("got a device") 163 case fileMode&os.ModeIrregular != 0: 164 return errors.New("got an irregular file") 165 } 166 return nil 167 } 168 169 func stringSliceIndex(s, subs []string) int { 170 j := 0 171 if len(subs) > 0 { 172 for i, x := range s { 173 if j < len(subs) && subs[j] == x { 174 j++ 175 } else { 176 j = 0 177 } 178 if len(subs) == j { 179 return i + 1 - j 180 } 181 } 182 } 183 return -1 184 } 185 186 // StringSliceReplaceAt replaces the sub-slice old, with the sub-slice new, in the string 187 // slice s, returning a new slice and a boolean indicating if the replacement happened. 188 // requireIdx is the index at which old needs to be found at (or -1 to disregard that). 189 func StringSliceReplaceAt(s, old, new []string, requireIndex int) ([]string, bool) { 190 idx := stringSliceIndex(s, old) 191 if (requireIndex != -1 && requireIndex != idx) || idx == -1 { 192 return s, false 193 } 194 out := append([]string{}, s[:idx]...) 195 out = append(out, new...) 196 out = append(out, s[idx+len(old):]...) 197 return out, true 198 }