github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/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  }
   129  
   130  // ValidateOutputPath validates the output paths of the `export` and `save` commands.
   131  func ValidateOutputPath(path string) error {
   132  	dir := filepath.Dir(filepath.Clean(path))
   133  	if dir != "" && dir != "." {
   134  		if _, err := os.Stat(dir); os.IsNotExist(err) {
   135  			return errors.Errorf("invalid output path: directory %q does not exist", dir)
   136  		}
   137  	}
   138  	// check whether `path` points to a regular file
   139  	// (if the path exists and doesn't point to a directory)
   140  	if fileInfo, err := os.Stat(path); !os.IsNotExist(err) {
   141  		if err != nil {
   142  			return err
   143  		}
   144  
   145  		if fileInfo.Mode().IsDir() || fileInfo.Mode().IsRegular() {
   146  			return nil
   147  		}
   148  
   149  		if err := ValidateOutputPathFileMode(fileInfo.Mode()); err != nil {
   150  			return errors.Wrapf(err, fmt.Sprintf("invalid output path: %q must be a directory or a regular file", path))
   151  		}
   152  	}
   153  	return nil
   154  }
   155  
   156  // ValidateOutputPathFileMode validates the output paths of the `cp` command and serves as a
   157  // helper to `ValidateOutputPath`
   158  func ValidateOutputPathFileMode(fileMode os.FileMode) error {
   159  	switch {
   160  	case fileMode&os.ModeDevice != 0:
   161  		return errors.New("got a device")
   162  	case fileMode&os.ModeIrregular != 0:
   163  		return errors.New("got an irregular file")
   164  	}
   165  	return nil
   166  }
   167  
   168  func stringSliceIndex(s, subs []string) int {
   169  	j := 0
   170  	if len(subs) > 0 {
   171  		for i, x := range s {
   172  			if j < len(subs) && subs[j] == x {
   173  				j++
   174  			} else {
   175  				j = 0
   176  			}
   177  			if len(subs) == j {
   178  				return i + 1 - j
   179  			}
   180  		}
   181  	}
   182  	return -1
   183  }
   184  
   185  // StringSliceReplaceAt replaces the sub-slice old, with the sub-slice new, in the string
   186  // slice s, returning a new slice and a boolean indicating if the replacement happened.
   187  // requireIdx is the index at which old needs to be found at (or -1 to disregard that).
   188  func StringSliceReplaceAt(s, old, new []string, requireIndex int) ([]string, bool) {
   189  	idx := stringSliceIndex(s, old)
   190  	if (requireIndex != -1 && requireIndex != idx) || idx == -1 {
   191  		return s, false
   192  	}
   193  	out := append([]string{}, s[:idx]...)
   194  	out = append(out, new...)
   195  	out = append(out, s[idx+len(old):]...)
   196  	return out, true
   197  }