diff options
Diffstat (limited to 'cmd')
129 files changed, 1507 insertions, 1909 deletions
diff --git a/cmd/podman-mac-helper/install.go b/cmd/podman-mac-helper/install.go index a1b99e66c..713bdfcdf 100644 --- a/cmd/podman-mac-helper/install.go +++ b/cmd/podman-mac-helper/install.go @@ -5,6 +5,7 @@ package main import ( "bytes" + "errors" "fmt" "io" "io/fs" @@ -14,7 +15,6 @@ import ( "syscall" "text/template" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -111,7 +111,7 @@ func install(cmd *cobra.Command, args []string) error { file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_EXCL, rw_r_r) if err != nil { - return errors.Wrap(err, "error creating helper plist file") + return fmt.Errorf("creating helper plist file: %w", err) } defer file.Close() _, err = buf.WriteTo(file) @@ -120,7 +120,7 @@ func install(cmd *cobra.Command, args []string) error { } if err = runDetectErr("launchctl", "load", fileName); err != nil { - return errors.Wrap(err, "launchctl failed loading service") + return fmt.Errorf("launchctl failed loading service: %w", err) } return nil @@ -133,13 +133,13 @@ func restrictRecursive(targetDir string, until string) error { return err } if info.Mode()&fs.ModeSymlink != 0 { - return errors.Errorf("symlinks not allowed in helper paths (remove them and rerun): %s", targetDir) + return fmt.Errorf("symlinks not allowed in helper paths (remove them and rerun): %s", targetDir) } if err = os.Chown(targetDir, 0, 0); err != nil { - return errors.Wrap(err, "could not update ownership of helper path") + return fmt.Errorf("could not update ownership of helper path: %w", err) } if err = os.Chmod(targetDir, rwx_rx_rx|fs.ModeSticky); err != nil { - return errors.Wrap(err, "could not update permissions of helper path") + return fmt.Errorf("could not update permissions of helper path: %w", err) } targetDir = filepath.Dir(targetDir) } @@ -162,7 +162,7 @@ func verifyRootDeep(path string) error { stat := info.Sys().(*syscall.Stat_t) if stat.Uid != 0 { - return errors.Errorf("installation target path must be solely owned by root: %s is not", current) + return fmt.Errorf("installation target path must be solely owned by root: %s is not", current) } if info.Mode()&fs.ModeSymlink != 0 { @@ -193,7 +193,7 @@ func verifyRootDeep(path string) error { func installExecutable(user string) (string, error) { // Since the installed executable runs as root, as a precaution verify root ownership of - // the entire installation path, and utilize sticky + read only perms for the helper path + // the entire installation path, and utilize sticky + read-only perms for the helper path // suffix. The goal is to help users harden against privilege escalation from loose // filesystem permissions. // @@ -206,7 +206,7 @@ func installExecutable(user string) (string, error) { targetDir := filepath.Join(installPrefix, "podman", "helper", user) if err := os.MkdirAll(targetDir, rwx_rx_rx); err != nil { - return "", errors.Wrap(err, "could not create helper directory structure") + return "", fmt.Errorf("could not create helper directory structure: %w", err) } // Correct any incorrect perms on previously existing directories and verify no symlinks diff --git a/cmd/podman-mac-helper/main.go b/cmd/podman-mac-helper/main.go index 8d995519f..937cb8433 100644 --- a/cmd/podman-mac-helper/main.go +++ b/cmd/podman-mac-helper/main.go @@ -4,6 +4,7 @@ package main import ( + "errors" "fmt" "io" "io/ioutil" @@ -13,7 +14,6 @@ import ( "strconv" "strings" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -73,7 +73,7 @@ func getUserInfo(name string) (string, string, string, error) { entry := readCapped(output) elements := strings.Split(entry, ":") if len(elements) < 9 || elements[0] != name { - return "", "", "", errors.New("Could not lookup user") + return "", "", "", errors.New("Could not look up user") } return elements[0], elements[2], elements[8], nil @@ -90,14 +90,14 @@ func getUser() (string, string, string, error) { _, uid, home, err := getUserInfo(name) if err != nil { - return "", "", "", fmt.Errorf("could not lookup user: %s", name) + return "", "", "", fmt.Errorf("could not look up user: %s", name) } id, err := strconv.Atoi(uid) if err != nil { return "", "", "", fmt.Errorf("invalid uid for user: %s", name) } if id == 0 { - return "", "", "", fmt.Errorf("unexpected root user") + return "", "", "", errors.New("unexpected root user") } return name, uid, home, nil diff --git a/cmd/podman-mac-helper/uninstall.go b/cmd/podman-mac-helper/uninstall.go index f72d0efd1..0c21b27e9 100644 --- a/cmd/podman-mac-helper/uninstall.go +++ b/cmd/podman-mac-helper/uninstall.go @@ -9,7 +9,6 @@ import ( "os/exec" "path/filepath" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -48,13 +47,13 @@ func uninstall(cmd *cobra.Command, args []string) error { if err := os.Remove(fileName); err != nil { if !os.IsNotExist(err) { - return errors.Errorf("could not remove plist file: %s", fileName) + return fmt.Errorf("could not remove plist file: %s", fileName) } } helperPath := filepath.Join(installPrefix, "podman", "helper", userName) if err := os.RemoveAll(helperPath); err != nil { - return errors.Errorf("could not remove helper binary path: %s", helperPath) + return fmt.Errorf("could not remove helper binary path: %s", helperPath) } return nil } diff --git a/cmd/podman/auto-update.go b/cmd/podman/auto-update.go index 1dc29530e..88ef0ec88 100644 --- a/cmd/podman/auto-update.go +++ b/cmd/podman/auto-update.go @@ -13,7 +13,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/errorhandling" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -63,7 +62,7 @@ func init() { func autoUpdate(cmd *cobra.Command, args []string) error { if len(args) > 0 { // Backwards compat. System tests expect this error string. - return errors.Errorf("`%s` takes no arguments", cmd.CommandPath()) + return fmt.Errorf("`%s` takes no arguments", cmd.CommandPath()) } allReports, failures := registry.ContainerEngine().AutoUpdate(registry.GetContext(), autoUpdateOptions.AutoUpdateOptions) diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 58dff3578..6e6c33f9b 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -4,9 +4,13 @@ import ( "bufio" "fmt" "os" + "path" "reflect" + "strconv" "strings" + "unicode" + libimageDefine "github.com/containers/common/libimage/define" "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/pkg/sysregistriesv2" @@ -15,8 +19,10 @@ import ( "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/rootless" + "github.com/containers/podman/v4/pkg/signal" systemdDefine "github.com/containers/podman/v4/pkg/systemd/define" "github.com/containers/podman/v4/pkg/util" + securejoin "github.com/cyphar/filepath-securejoin" "github.com/spf13/cobra" ) @@ -65,7 +71,7 @@ func setupImageEngine(cmd *cobra.Command) (entities.ImageEngine, error) { return nil, err } // we also need to set up the container engine since this - // is required to setup the rootless namespace + // is required to set up the rootless namespace if _, err = setupContainerEngine(cmd); err != nil { return nil, err } @@ -278,6 +284,90 @@ func getNetworks(cmd *cobra.Command, toComplete string, cType completeType) ([]s return suggestions, cobra.ShellCompDirectiveNoFileComp } +func fdIsNotDir(f *os.File) bool { + stat, err := f.Stat() + if err != nil { + cobra.CompErrorln(err.Error()) + return true + } + return !stat.IsDir() +} + +func getPathCompletion(root string, toComplete string) ([]string, cobra.ShellCompDirective) { + if toComplete == "" { + toComplete = "/" + } + // Important: securejoin is required to make sure we never leave the root mount point + userpath, err := securejoin.SecureJoin(root, toComplete) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + var base string + f, err := os.Open(userpath) + // when error or file is not dir get the parent path to stat + if err != nil || fdIsNotDir(f) { + // Do not use path.Dir() since this cleans the paths which + // then no longer matches the user input. + userpath, base = path.Split(userpath) + toComplete, _ = path.Split(toComplete) + f, err = os.Open(userpath) + if err != nil { + return nil, cobra.ShellCompDirectiveDefault + } + } + + if fdIsNotDir(f) { + // nothing to complete since it is no dir + return nil, cobra.ShellCompDirectiveDefault + } + + entries, err := f.ReadDir(-1) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + if len(entries) == 0 { + // path is empty dir, just add the trailing slash and no space + if !strings.HasSuffix(toComplete, "/") { + toComplete += "/" + } + return []string{toComplete}, cobra.ShellCompDirectiveDefault | cobra.ShellCompDirectiveNoSpace + } + completions := make([]string, 0, len(entries)) + count := 0 + for _, e := range entries { + if strings.HasPrefix(e.Name(), base) { + suf := "" + // When the entry is an directory we add the "/" as suffix and do not want to add space + // to match normal shell completion behavior. + // Just inc counter again to fake more than one entry in this case and thus get no space. + if e.IsDir() { + suf = "/" + count++ + } + completions = append(completions, simplePathJoinUnix(toComplete, e.Name()+suf)) + count++ + } + } + directive := cobra.ShellCompDirectiveDefault + if count > 1 { + // when we have more than one match we do not want to add a space after the completion + directive |= cobra.ShellCompDirectiveNoSpace + } + return completions, directive +} + +// simplePathJoinUnix joins to path components by adding a slash only if p1 doesn't end with one. +// We cannot use path.Join() for the completions logic because this one always calls Clean() on +// the path which changes it from the input. +func simplePathJoinUnix(p1, p2 string) string { + if p1[len(p1)-1] == '/' { + return p1 + p2 + } + return p1 + "/" + p2 +} + // validCurrentCmdLine validates the current cmd line // It utilizes the Args function from the cmd struct // In most cases the Args function validates the args length but it @@ -430,6 +520,16 @@ func AutocompletePodsRunning(cmd *cobra.Command, args []string, toComplete strin return getPods(cmd, toComplete, completeDefault, "running", "degraded") } +// AutoCompletePodsPause - Autocomplete only paused pod names +// When a pod has a few containers paused, that ends up in degraded state +// So autocomplete degraded pod names as well +func AutoCompletePodsPause(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return getPods(cmd, toComplete, completeDefault, "paused", "degraded") +} + // AutocompleteForKube - Autocomplete all Podman objects supported by kube generate. func AutocompleteForKube(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if !validCurrentCmdLine(cmd, args, toComplete) { @@ -495,6 +595,11 @@ func AutocompleteImages(cmd *cobra.Command, args []string, toComplete string) ([ return getImages(cmd, toComplete) } +// AutocompleteImageSearchFilters - Autocomplate `search --filter`. +func AutocompleteImageSearchFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return libimageDefine.SearchFilters, cobra.ShellCompDirectiveNoFileComp +} + // AutocompletePodExitPolicy - Autocomplete pod exit policy. func AutocompletePodExitPolicy(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return config.PodExitPolicies, cobra.ShellCompDirectiveNoFileComp @@ -514,8 +619,32 @@ func AutocompleteCreateRun(cmd *cobra.Command, args []string, toComplete string) } return getImages(cmd, toComplete) } - // TODO: add path completion for files in the image - return nil, cobra.ShellCompDirectiveDefault + // Mount the image and provide path completion + engine, err := setupImageEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + + resp, err := engine.Mount(registry.Context(), []string{args[0]}, entities.ImageMountOptions{}) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + defer func() { + _, err := engine.Unmount(registry.Context(), []string{args[0]}, entities.ImageUnmountOptions{}) + if err != nil { + cobra.CompErrorln(err.Error()) + } + }() + if len(resp) != 1 { + return nil, cobra.ShellCompDirectiveDefault + } + + // So this uses ShellCompDirectiveDefault to also still provide normal shell + // completion in case no path matches. This is useful if someone tries to get + // completion for paths that are not available in the image, e.g. /proc/... + return getPathCompletion(resp[0].Path, toComplete) } // AutocompleteRegistries - Autocomplete registries. @@ -563,14 +692,40 @@ func AutocompleteCpCommand(cmd *cobra.Command, args []string, toComplete string) return nil, cobra.ShellCompDirectiveNoFileComp } if len(args) < 2 { + if i := strings.IndexByte(toComplete, ':'); i > -1 { + // Looks like the user already set the container. + // Lets mount it and provide path completion for files in the container. + engine, err := setupContainerEngine(cmd) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + + resp, err := engine.ContainerMount(registry.Context(), []string{toComplete[:i]}, entities.ContainerMountOptions{}) + if err != nil { + cobra.CompErrorln(err.Error()) + return nil, cobra.ShellCompDirectiveDefault + } + defer func() { + _, err := engine.ContainerUnmount(registry.Context(), []string{toComplete[:i]}, entities.ContainerUnmountOptions{}) + if err != nil { + cobra.CompErrorln(err.Error()) + } + }() + if len(resp) != 1 { + return nil, cobra.ShellCompDirectiveDefault + } + comps, directive := getPathCompletion(resp[0].Path, toComplete[i+1:]) + return prefixSlice(toComplete[:i+1], comps), directive + } + // Suggest containers when they match the input otherwise normal shell completion is used containers, _ := getContainers(cmd, toComplete, completeDefault) for _, container := range containers { - // TODO: Add path completion for inside the container if possible if strings.HasPrefix(container, toComplete) { - return containers, cobra.ShellCompDirectiveNoSpace + return suffixCompSlice(":", containers), cobra.ShellCompDirectiveNoSpace } } - // else complete paths + // else complete paths on the host return nil, cobra.ShellCompDirectiveDefault } // don't complete more than 2 args @@ -594,7 +749,9 @@ func AutocompleteRunlabelCommand(cmd *cobra.Command, args []string, toComplete s return nil, cobra.ShellCompDirectiveNoFileComp } if len(args) == 0 { - // FIXME: What labels can we recommend here? + // This is unfortunate because the argument order is label followed by image. + // If it would be the other way around we could inspect the first arg and get + // all labels from it to suggest them. return nil, cobra.ShellCompDirectiveNoFileComp } if len(args) == 1 { @@ -799,8 +956,7 @@ func AutocompleteLogDriver(cmd *cobra.Command, args []string, toComplete string) // AutocompleteLogOpt - Autocomplete log-opt options. // -> "path=", "tag=" func AutocompleteLogOpt(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - // FIXME: are these the only one? the man page states these but in the current shell completion they are more options - logOptions := []string{"path=", "tag="} + logOptions := []string{"path=", "tag=", "max-size="} if strings.HasPrefix(toComplete, "path=") { return nil, cobra.ShellCompDirectiveDefault } @@ -810,7 +966,7 @@ func AutocompleteLogOpt(cmd *cobra.Command, args []string, toComplete string) ([ // AutocompletePullOption - Autocomplete pull options for create and run command. // -> "always", "missing", "never" func AutocompletePullOption(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - pullOptions := []string{"always", "missing", "never"} + pullOptions := []string{"always", "missing", "never", "newer"} return pullOptions, cobra.ShellCompDirectiveNoFileComp } @@ -839,10 +995,26 @@ func AutocompleteSecurityOption(cmd *cobra.Command, args []string, toComplete st } // AutocompleteStopSignal - Autocomplete stop signal options. -// -> "SIGHUP", "SIGINT", "SIGKILL", "SIGTERM" +// Autocompletes signals both lower or uppercase depending on the user input. func AutocompleteStopSignal(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - // FIXME: add more/different signals? - stopSignals := []string{"SIGHUP", "SIGINT", "SIGKILL", "SIGTERM"} + // convertCase will convert a string to lowercase only if the user input is lowercase + convertCase := func(s string) string { return s } + if len(toComplete) > 0 && unicode.IsLower(rune(toComplete[0])) { + convertCase = strings.ToLower + } + + prefix := "" + // if input starts with "SI" we have to add SIG in front + // since the signal map does not have this prefix but the option + // allows signals with and without SIG prefix + if strings.HasPrefix(toComplete, convertCase("SI")) { + prefix = "SIG" + } + + stopSignals := make([]string, 0, len(signal.SignalMap)) + for sig := range signal.SignalMap { + stopSignals = append(stopSignals, convertCase(prefix+sig)) + } return stopSignals, cobra.ShellCompDirectiveNoFileComp } @@ -963,9 +1135,22 @@ func AutocompleteNetworkFlag(cmd *cobra.Command, args []string, toComplete strin return append(networks, suggestions...), dir } +type formatSuggestion struct { + fieldname string + suffix string +} + +func convertFormatSuggestions(suggestions []formatSuggestion) []string { + completions := make([]string, 0, len(suggestions)) + for _, f := range suggestions { + completions = append(completions, f.fieldname+f.suffix) + } + return completions +} + // AutocompleteFormat - Autocomplete json or a given struct to use for a go template. // The input can be nil, In this case only json will be autocompleted. -// This function will only work for structs other types are not supported. +// This function will only work for pointer to structs other types are not supported. // When "{{." is typed the field and method names of the given struct will be completed. // This also works recursive for nested structs. func AutocompleteFormat(o interface{}) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -994,6 +1179,12 @@ func AutocompleteFormat(o interface{}) func(cmd *cobra.Command, args []string, t // split this into it struct field names fields := strings.Split(field[len(field)-1], ".") f := reflect.ValueOf(o) + if f.Kind() != reflect.Ptr { + // We panic here to make sure that all callers pass the value by reference. + // If someone passes a by value then all podman commands will panic since + // this function is run at init time. + panic("AutocompleteFormat: passed value must be a pointer to a struct") + } for i := 1; i < len(fields); i++ { // last field get all names to suggest if i == len(fields)-1 { @@ -1002,61 +1193,83 @@ func AutocompleteFormat(o interface{}) func(cmd *cobra.Command, args []string, t toCompArr := strings.Split(toComplete, ".") toCompArr[len(toCompArr)-1] = "" toComplete = strings.Join(toCompArr, ".") - return prefixSlice(toComplete, suggestions), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp + return prefixSlice(toComplete, convertFormatSuggestions(suggestions)), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp } - val := getActualStructType(f) - if val == nil { - // no struct return nothing to complete + // first follow pointer and create element when it is nil + f = actualReflectValue(f) + switch f.Kind() { + case reflect.Struct: + for j := 0; j < f.NumField(); j++ { + field := f.Type().Field(j) + // ok this is a bit weird but when we have an embedded nil struct + // calling FieldByName on a name which is present on this struct will panic + // Therefore we have to init them (non nil ptr), https://github.com/containers/podman/issues/14223 + if field.Anonymous && f.Field(j).Type().Kind() == reflect.Ptr { + f.Field(j).Set(reflect.New(f.Field(j).Type().Elem())) + } + } + // set the next struct field + f = f.FieldByName(fields[i]) + case reflect.Map: + rtype := f.Type().Elem() + if rtype.Kind() == reflect.Ptr { + rtype = rtype.Elem() + } + f = reflect.New(rtype) + case reflect.Func: + if f.Type().NumOut() != 1 { + // unsupported type return nothing + return nil, cobra.ShellCompDirectiveNoFileComp + } + f = reflect.New(f.Type().Out(0)) + default: + // unsupported type return nothing return nil, cobra.ShellCompDirectiveNoFileComp } - f = *val - - // set the next struct field - f = f.FieldByName(fields[i]) } return nil, cobra.ShellCompDirectiveNoFileComp } } -// getActualStructType take the value and check if it is a struct, +// actualReflectValue takes the value, // if it is pointer it will dereference it and when it is nil, -// it will create a new value from it to get the actual struct -// returns nil when type is not a struct -func getActualStructType(f reflect.Value) *reflect.Value { +// it will create a new value from it +func actualReflectValue(f reflect.Value) reflect.Value { // follow the pointer first if f.Kind() == reflect.Ptr { // if the pointer is nil we create a new value from the elements type - // this allows us to follow nil pointers and get the actual struct fields + // this allows us to follow nil pointers and get the actual type if f.IsNil() { f = reflect.New(f.Type().Elem()) } f = f.Elem() } - // we only support structs - if f.Kind() != reflect.Struct { - return nil - } - return &f + return f } // getStructFields reads all struct field names and method names and returns them. -func getStructFields(f reflect.Value, prefix string) []string { - var suggestions []string +func getStructFields(f reflect.Value, prefix string) []formatSuggestion { + var suggestions []formatSuggestion if f.IsValid() { suggestions = append(suggestions, getMethodNames(f, prefix)...) } - val := getActualStructType(f) - if val == nil { - // no struct return nothing to complete + f = actualReflectValue(f) + // we only support structs + if f.Kind() != reflect.Struct { return suggestions } - f = *val + var anonymous []formatSuggestion // loop over all field names for j := 0; j < f.NumField(); j++ { field := f.Type().Field(j) + // check if struct field is not exported, templates only use exported fields + // PkgPath is always empty for exported fields + if field.PkgPath != "" { + continue + } fname := field.Name suffix := "}}" kind := field.Type.Kind() @@ -1065,27 +1278,63 @@ func getStructFields(f reflect.Value, prefix string) []string { kind = field.Type.Elem().Kind() } // when we have a nested struct do not append braces instead append a dot - if kind == reflect.Struct { + if kind == reflect.Struct || kind == reflect.Map { suffix = "." } // if field is anonymous add the child fields as well if field.Anonymous { - suggestions = append(suggestions, getStructFields(f.Field(j), prefix)...) - } else if strings.HasPrefix(fname, prefix) { + anonymous = append(anonymous, getStructFields(f.Field(j), prefix)...) + } + if strings.HasPrefix(fname, prefix) { // add field name with suffix - suggestions = append(suggestions, fname+suffix) + suggestions = append(suggestions, formatSuggestion{fieldname: fname, suffix: suffix}) + } + } +outer: + for _, ano := range anonymous { + // we should only add anonymous child fields if they are not already present. + for _, sug := range suggestions { + if ano.fieldname == sug.fieldname { + continue outer + } } + suggestions = append(suggestions, ano) } return suggestions } -func getMethodNames(f reflect.Value, prefix string) []string { - suggestions := make([]string, 0, f.NumMethod()) +func getMethodNames(f reflect.Value, prefix string) []formatSuggestion { + suggestions := make([]formatSuggestion, 0, f.NumMethod()) for j := 0; j < f.NumMethod(); j++ { - fname := f.Type().Method(j).Name + method := f.Type().Method(j) + // in a template we can only run functions with one return value + if method.Func.Type().NumOut() != 1 { + continue + } + // when we have a nested struct do not append braces instead append a dot + kind := method.Func.Type().Out(0).Kind() + suffix := "}}" + if kind == reflect.Struct || kind == reflect.Map { + suffix = "." + } + // From a template users POV it is not important when the use a struct field or method. + // They only notice the difference when the function requires arguments. + // So lets be nice and let the user know that this method requires arguments via the help text. + // Note since this is actually a method on a type the first argument is always fix so we should skip it. + num := method.Func.Type().NumIn() - 1 + if num > 0 { + // everything after tab will the completion scripts show as help when enabled + // overwrite the suffix because it expects the args + suffix = "\tThis is a function and requires " + strconv.Itoa(num) + " argument" + if num > 1 { + // add plural s + suffix += "s" + } + } + fname := method.Name if strings.HasPrefix(fname, prefix) { // add method name with closing braces - suggestions = append(suggestions, fname+"}}") + suggestions = append(suggestions, formatSuggestion{fieldname: fname, suffix: suffix}) } } return suggestions @@ -1368,8 +1617,14 @@ func AutocompleteClone(cmd *cobra.Command, args []string, toComplete string) ([] } switch len(args) { case 0: + if cmd.Parent().Name() == "pod" { // needs to be " pod " to exclude 'podman' + return getPods(cmd, toComplete, completeDefault) + } return getContainers(cmd, toComplete, completeDefault) case 2: + if cmd.Parent().Name() == "pod" { + return nil, cobra.ShellCompDirectiveNoFileComp + } return getImages(cmd, toComplete) } return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/podman/common/completion_test.go b/cmd/podman/common/completion_test.go index 13f45a662..d8be48ad7 100644 --- a/cmd/podman/common/completion_test.go +++ b/cmd/podman/common/completion_test.go @@ -14,11 +14,29 @@ type Car struct { HP *int Displacement int } - Extras map[string]string + Extras map[string]Extra + // also ensure it will work with pointers + Extras2 map[string]*Extra +} + +type Extra struct { + Name1 string + Name2 string } type Anonymous struct { Hello string + // The name should match the testStruct Name below. This is used to make + // sure the logic uses the actual struct fields before the embedded ones. + Name struct { + Suffix string + Prefix string + } +} + +// The name should match the testStruct Age name below. +func (a Anonymous) Age() int { + return 0 } func (c Car) Type() string { @@ -31,6 +49,20 @@ func (c *Car) Color() string { return "" } +// This is for reflect testing required. +//nolint:unused +func (c Car) internal() int { + return 0 +} + +func (c Car) TwoOut() (string, string) { + return "", "" +} + +func (c Car) Struct() Car { + return Car{} +} + func TestAutocompleteFormat(t *testing.T) { testStruct := struct { Name string @@ -38,10 +70,10 @@ func TestAutocompleteFormat(t *testing.T) { Car *Car Car2 *Car *Anonymous + private int }{} testStruct.Car = &Car{} - testStruct.Car.Extras = map[string]string{"test": "1"} tests := []struct { name string @@ -76,17 +108,17 @@ func TestAutocompleteFormat(t *testing.T) { { "invalid completion", "{{ ..", - nil, + []string{}, }, { "fist level struct field name", "{{.", - []string{"{{.Name}}", "{{.Age}}", "{{.Car.", "{{.Car2.", "{{.Hello}}"}, + []string{"{{.Name}}", "{{.Age}}", "{{.Car.", "{{.Car2.", "{{.Anonymous.", "{{.Hello}}"}, }, { "fist level struct field name", "{{ .", - []string{"{{ .Name}}", "{{ .Age}}", "{{ .Car.", "{{ .Car2.", "{{ .Hello}}"}, + []string{"{{ .Name}}", "{{ .Age}}", "{{ .Car.", "{{ .Car2.", "{{ .Anonymous.", "{{ .Hello}}"}, }, { "fist level struct field name", @@ -96,7 +128,7 @@ func TestAutocompleteFormat(t *testing.T) { { "second level struct field name", "{{ .Car.", - []string{"{{ .Car.Color}}", "{{ .Car.Type}}", "{{ .Car.Brand}}", "{{ .Car.Stats.", "{{ .Car.Extras}}"}, + []string{"{{ .Car.Color}}", "{{ .Car.Struct.", "{{ .Car.Type}}", "{{ .Car.Brand}}", "{{ .Car.Stats.", "{{ .Car.Extras.", "{{ .Car.Extras2."}, }, { "second level struct field name", @@ -106,7 +138,7 @@ func TestAutocompleteFormat(t *testing.T) { { "second level nil struct field name", "{{ .Car2.", - []string{"{{ .Car2.Color}}", "{{ .Car2.Type}}", "{{ .Car2.Brand}}", "{{ .Car2.Stats.", "{{ .Car2.Extras}}"}, + []string{"{{ .Car2.Color}}", "{{ .Car2.Struct.", "{{ .Car2.Type}}", "{{ .Car2.Brand}}", "{{ .Car2.Stats.", "{{ .Car2.Extras.", "{{ .Car2.Extras2."}, }, { "three level struct field name", @@ -126,28 +158,44 @@ func TestAutocompleteFormat(t *testing.T) { { "invalid field name", "{{ .Ca.B", - nil, + []string{}, }, { "map key names don't work", "{{ .Car.Extras.", - nil, + []string{}, + }, + { + "map values work", + "{{ .Car.Extras.somekey.", + []string{"{{ .Car.Extras.somekey.Name1}}", "{{ .Car.Extras.somekey.Name2}}"}, + }, + { + "map values work with ptr", + "{{ .Car.Extras2.somekey.", + []string{"{{ .Car.Extras2.somekey.Name1}}", "{{ .Car.Extras2.somekey.Name2}}"}, }, { "two variables struct field name", "{{ .Car.Brand }} {{ .Car.", - []string{"{{ .Car.Brand }} {{ .Car.Color}}", "{{ .Car.Brand }} {{ .Car.Type}}", "{{ .Car.Brand }} {{ .Car.Brand}}", - "{{ .Car.Brand }} {{ .Car.Stats.", "{{ .Car.Brand }} {{ .Car.Extras}}"}, + []string{"{{ .Car.Brand }} {{ .Car.Color}}", "{{ .Car.Brand }} {{ .Car.Struct.", "{{ .Car.Brand }} {{ .Car.Type}}", + "{{ .Car.Brand }} {{ .Car.Brand}}", "{{ .Car.Brand }} {{ .Car.Stats.", "{{ .Car.Brand }} {{ .Car.Extras.", + "{{ .Car.Brand }} {{ .Car.Extras2."}, }, { "only dot without variable", ".", nil, }, + { + "access embedded nil struct field", + "{{.Hello.", + []string{}, + }, } for _, test := range tests { - completion, directive := common.AutocompleteFormat(testStruct)(nil, nil, test.toComplete) + completion, directive := common.AutocompleteFormat(&testStruct)(nil, nil, test.toComplete) // directive should always be greater than ShellCompDirectiveNoFileComp assert.GreaterOrEqual(t, directive, cobra.ShellCompDirectiveNoFileComp, "unexpected ShellCompDirective") assert.Equal(t, test.expected, completion, test.name) diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index d28becc8a..923d0517f 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/cobra" ) -const sizeWithUnitFormat = "(format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))" +const sizeWithUnitFormat = "(format: `<number>[<unit>]`, where unit = b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes))" var containerConfig = registry.PodmanConfig() @@ -98,7 +98,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, cgroupsFlagName := "cgroups" createFlags.StringVar( &cf.CgroupsMode, - cgroupsFlagName, cgroupConfig(), + cgroupsFlagName, cf.CgroupsMode, `control container cgroup configuration ("enabled"|"disabled"|"no-conmon"|"split")`, ) _ = cmd.RegisterFlagCompletionFunc(cgroupsFlagName, AutocompleteCgroupMode) @@ -150,7 +150,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, envFlagName := "env" createFlags.StringArrayP( - envFlagName, "e", env(), + envFlagName, "e", Env(), "Set environment variables in container", ) _ = cmd.RegisterFlagCompletionFunc(envFlagName, completion.AutocompleteNone) @@ -255,9 +255,8 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, _ = cmd.RegisterFlagCompletionFunc(hostUserFlagName, completion.AutocompleteNone) imageVolumeFlagName := "image-volume" - createFlags.StringVar( - &cf.ImageVolume, - imageVolumeFlagName, DefaultImageVolume, + createFlags.String( + imageVolumeFlagName, cf.ImageVolume, `Tells podman how to handle the builtin image volumes ("bind"|"tmpfs"|"ignore")`, ) _ = cmd.RegisterFlagCompletionFunc(imageVolumeFlagName, AutocompleteImageVolume) @@ -299,7 +298,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, logDriverFlagName := "log-driver" createFlags.StringVar( &cf.LogDriver, - logDriverFlagName, LogDriver(), + logDriverFlagName, cf.LogDriver, "Logging driver for the container", ) _ = cmd.RegisterFlagCompletionFunc(logDriverFlagName, AutocompleteLogDriver) @@ -390,8 +389,8 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, pullFlagName := "pull" createFlags.StringVar( &cf.Pull, - pullFlagName, policy(), - `Pull image before creating ("always"|"missing"|"never")`, + pullFlagName, cf.Pull, + `Pull image policy`, ) _ = cmd.RegisterFlagCompletionFunc(pullFlagName, AutocompletePullOption) @@ -407,7 +406,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) createFlags.BoolVar( &cf.ReadOnlyTmpFS, - "read-only-tmpfs", true, + "read-only-tmpfs", cf.ReadOnlyTmpFS, "When running containers in read-only mode mount a read-write tmpfs on /run, /tmp and /var/tmp", ) requiresFlagName := "requires" @@ -440,7 +439,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, sdnotifyFlagName := "sdnotify" createFlags.StringVar( &cf.SdNotifyMode, - sdnotifyFlagName, define.SdNotifyModeContainer, + sdnotifyFlagName, cf.SdNotifyMode, `control sd-notify behavior ("container"|"conmon"|"ignore")`, ) _ = cmd.RegisterFlagCompletionFunc(sdnotifyFlagName, AutocompleteSDNotify) @@ -453,13 +452,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(secretFlagName, AutocompleteSecrets) - shmSizeFlagName := "shm-size" - createFlags.String( - shmSizeFlagName, shmSize(), - "Size of /dev/shm "+sizeWithUnitFormat, - ) - _ = cmd.RegisterFlagCompletionFunc(shmSizeFlagName, completion.AutocompleteNone) - stopSignalFlagName := "stop-signal" createFlags.StringVar( &cf.StopSignal, @@ -471,7 +463,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, stopTimeoutFlagName := "stop-timeout" createFlags.UintVar( &cf.StopTimeout, - stopTimeoutFlagName, containerConfig.Engine.StopTimeout, + stopTimeoutFlagName, cf.StopTimeout, "Timeout (in seconds) that containers stopped by user command have to exit. If exceeded, the container will be forcibly stopped via SIGKILL.", ) _ = cmd.RegisterFlagCompletionFunc(stopTimeoutFlagName, completion.AutocompleteNone) @@ -479,7 +471,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, systemdFlagName := "systemd" createFlags.StringVar( &cf.Systemd, - systemdFlagName, "true", + systemdFlagName, cf.Systemd, `Run container in systemd mode ("true"|"false"|"always")`, ) _ = cmd.RegisterFlagCompletionFunc(systemdFlagName, AutocompleteSystemdFlag) @@ -523,7 +515,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, timezoneFlagName := "tz" createFlags.StringVar( &cf.Timezone, - timezoneFlagName, containerConfig.TZ(), + timezoneFlagName, cf.Timezone, "Set timezone in container", ) _ = cmd.RegisterFlagCompletionFunc(timezoneFlagName, completion.AutocompleteNone) //TODO: add timezone completion @@ -531,7 +523,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, umaskFlagName := "umask" createFlags.StringVar( &cf.Umask, - umaskFlagName, containerConfig.Umask(), + umaskFlagName, cf.Umask, "Set umask in container", ) _ = cmd.RegisterFlagCompletionFunc(umaskFlagName, completion.AutocompleteNone) @@ -539,7 +531,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ulimitFlagName := "ulimit" createFlags.StringSliceVar( &cf.Ulimit, - ulimitFlagName, ulimits(), + ulimitFlagName, cf.Ulimit, "Ulimit options", ) _ = cmd.RegisterFlagCompletionFunc(ulimitFlagName, completion.AutocompleteNone) @@ -552,13 +544,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(userFlagName, AutocompleteUserFlag) - utsFlagName := "uts" - createFlags.String( - utsFlagName, "", - "UTS namespace to use", - ) - _ = cmd.RegisterFlagCompletionFunc(utsFlagName, AutocompleteNamespace) - mountFlagName := "mount" createFlags.StringArrayVar( &cf.Mount, @@ -578,7 +563,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, seccompPolicyFlagName := "seccomp-policy" createFlags.StringVar( &cf.SeccompPolicy, - seccompPolicyFlagName, "default", + seccompPolicyFlagName, cf.SeccompPolicy, "Policy for selecting a seccomp profile (experimental)", ) _ = cmd.RegisterFlagCompletionFunc(seccompPolicyFlagName, completion.AutocompleteDefault) @@ -629,6 +614,13 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) } if isInfra || (!clone && !isInfra) { // infra container flags, create should also pick these up + shmSizeFlagName := "shm-size" + createFlags.String( + shmSizeFlagName, shmSize(), + "Size of /dev/shm "+sizeWithUnitFormat, + ) + _ = cmd.RegisterFlagCompletionFunc(shmSizeFlagName, completion.AutocompleteNone) + sysctlFlagName := "sysctl" createFlags.StringSliceVar( &cf.Sysctl, @@ -685,6 +677,14 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(usernsFlagName, AutocompleteUserNamespace) + utsFlagName := "uts" + createFlags.StringVar( + &cf.UTS, + utsFlagName, "", + "UTS namespace to use", + ) + _ = cmd.RegisterFlagCompletionFunc(utsFlagName, AutocompleteNamespace) + cgroupParentFlagName := "cgroup-parent" createFlags.StringVar( &cf.CgroupParent, @@ -770,7 +770,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, volumeFlagName := "volume" createFlags.StringArrayVarP( &cf.Volume, - volumeFlagName, "v", volumes(), + volumeFlagName, "v", cf.Volume, volumeDesciption, ) _ = cmd.RegisterFlagCompletionFunc(volumeFlagName, AutocompleteVolumeFlag) @@ -864,14 +864,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(cpusetMemsFlagName, completion.AutocompleteNone) - memoryFlagName := "memory" - createFlags.StringVarP( - &cf.Memory, - memoryFlagName, "m", "", - "Memory limit "+sizeWithUnitFormat, - ) - _ = cmd.RegisterFlagCompletionFunc(memoryFlagName, completion.AutocompleteNone) - memoryReservationFlagName := "memory-reservation" createFlags.StringVar( &cf.MemoryReservation, @@ -891,12 +883,13 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, memorySwappinessFlagName := "memory-swappiness" createFlags.Int64Var( &cf.MemorySwappiness, - memorySwappinessFlagName, -1, + memorySwappinessFlagName, cf.MemorySwappiness, "Tune container memory swappiness (0 to 100, or -1 for system default)", ) _ = cmd.RegisterFlagCompletionFunc(memorySwappinessFlagName, completion.AutocompleteNone) } // anyone can use these + cpusFlagName := "cpus" createFlags.Float64Var( &cf.CPUS, @@ -912,4 +905,12 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, "CPUs in which to allow execution (0-3, 0,1)", ) _ = cmd.RegisterFlagCompletionFunc(cpusetCpusFlagName, completion.AutocompleteNone) + + memoryFlagName := "memory" + createFlags.StringVarP( + &cf.Memory, + memoryFlagName, "m", "", + "Memory limit "+sizeWithUnitFormat, + ) + _ = cmd.RegisterFlagCompletionFunc(memoryFlagName, completion.AutocompleteNone) } diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index c40d1ea51..fb5af8f59 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -1,472 +1,11 @@ package common import ( - "fmt" - "net" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/containers/common/libnetwork/types" - "github.com/containers/common/pkg/cgroups" - "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/libpod/define" - "github.com/containers/podman/v4/pkg/api/handlers" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/containers/podman/v4/pkg/rootless" - "github.com/containers/podman/v4/pkg/specgen" - "github.com/docker/docker/api/types/mount" - "github.com/pkg/errors" ) -func stringMaptoArray(m map[string]string) []string { - a := make([]string, 0, len(m)) - for k, v := range m { - a = append(a, fmt.Sprintf("%s=%s", k, v)) - } - return a -} - -// ContainerCreateToContainerCLIOpts converts a compat input struct to cliopts so it can be converted to -// a specgen spec. -func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.ContainerCreateOptions, []string, error) { - var ( - capAdd []string - cappDrop []string - entrypoint *string - init bool - specPorts []types.PortMapping - ) - - if cc.HostConfig.Init != nil { - init = *cc.HostConfig.Init - } - - // Iterate devices and convert back to string - devices := make([]string, 0, len(cc.HostConfig.Devices)) - for _, dev := range cc.HostConfig.Devices { - devices = append(devices, fmt.Sprintf("%s:%s:%s", dev.PathOnHost, dev.PathInContainer, dev.CgroupPermissions)) - } - - // iterate blkreaddevicebps - readBps := make([]string, 0, len(cc.HostConfig.BlkioDeviceReadBps)) - for _, dev := range cc.HostConfig.BlkioDeviceReadBps { - readBps = append(readBps, dev.String()) - } - - // iterate blkreaddeviceiops - readIops := make([]string, 0, len(cc.HostConfig.BlkioDeviceReadIOps)) - for _, dev := range cc.HostConfig.BlkioDeviceReadIOps { - readIops = append(readIops, dev.String()) - } - - // iterate blkwritedevicebps - writeBps := make([]string, 0, len(cc.HostConfig.BlkioDeviceWriteBps)) - for _, dev := range cc.HostConfig.BlkioDeviceWriteBps { - writeBps = append(writeBps, dev.String()) - } - - // iterate blkwritedeviceiops - writeIops := make([]string, 0, len(cc.HostConfig.BlkioDeviceWriteIOps)) - for _, dev := range cc.HostConfig.BlkioDeviceWriteIOps { - writeIops = append(writeIops, dev.String()) - } - - // entrypoint - // can be a string or slice. if it is a slice, we need to - // marshall it to json; otherwise it should just be the string - // value - if len(cc.Config.Entrypoint) > 0 { - entrypoint = &cc.Config.Entrypoint[0] - if len(cc.Config.Entrypoint) > 1 { - b, err := json.Marshal(cc.Config.Entrypoint) - if err != nil { - return nil, nil, err - } - var jsonString = string(b) - entrypoint = &jsonString - } - } - - // expose ports - expose := make([]string, 0, len(cc.Config.ExposedPorts)) - for p := range cc.Config.ExposedPorts { - expose = append(expose, fmt.Sprintf("%s/%s", p.Port(), p.Proto())) - } - - // mounts type=tmpfs/bind,source=...,target=...=,opt=val - volSources := make(map[string]bool) - volDestinations := make(map[string]bool) - mounts := make([]string, 0, len(cc.HostConfig.Mounts)) - var builder strings.Builder - for _, m := range cc.HostConfig.Mounts { - addField(&builder, "type", string(m.Type)) - addField(&builder, "source", m.Source) - addField(&builder, "target", m.Target) - - // Store source/dest so we don't add duplicates if a volume is - // also mentioned in cc.Volumes. - // Which Docker Compose v2.0 does, for unclear reasons... - volSources[m.Source] = true - volDestinations[m.Target] = true - - if m.ReadOnly { - addField(&builder, "ro", "true") - } - addField(&builder, "consistency", string(m.Consistency)) - // Map any specialized mount options that intersect between *Options and cli options - switch m.Type { - case mount.TypeBind: - if m.BindOptions != nil { - addField(&builder, "bind-propagation", string(m.BindOptions.Propagation)) - addField(&builder, "bind-nonrecursive", strconv.FormatBool(m.BindOptions.NonRecursive)) - } - case mount.TypeTmpfs: - if m.TmpfsOptions != nil { - addField(&builder, "tmpfs-size", strconv.FormatInt(m.TmpfsOptions.SizeBytes, 10)) - addField(&builder, "tmpfs-mode", strconv.FormatUint(uint64(m.TmpfsOptions.Mode), 8)) - } - case mount.TypeVolume: - // All current VolumeOpts are handled above - // See vendor/github.com/containers/common/pkg/parse/parse.go:ValidateVolumeOpts() - } - mounts = append(mounts, builder.String()) - builder.Reset() - } - - // dns - dns := make([]net.IP, 0, len(cc.HostConfig.DNS)) - for _, d := range cc.HostConfig.DNS { - dns = append(dns, net.ParseIP(d)) - } - - // publish - for port, pbs := range cc.HostConfig.PortBindings { - for _, pb := range pbs { - var hostport int - var err error - if pb.HostPort != "" { - hostport, err = strconv.Atoi(pb.HostPort) - } - if err != nil { - return nil, nil, err - } - tmpPort := types.PortMapping{ - HostIP: pb.HostIP, - ContainerPort: uint16(port.Int()), - HostPort: uint16(hostport), - Range: 0, - Protocol: port.Proto(), - } - specPorts = append(specPorts, tmpPort) - } - } - - // special case for NetworkMode, the podman default is slirp4netns for - // rootless but for better docker compat we want bridge. - netmode := string(cc.HostConfig.NetworkMode) - if netmode == "" || netmode == "default" { - netmode = "bridge" - } - nsmode, networks, netOpts, err := specgen.ParseNetworkFlag([]string{netmode}) - if err != nil { - return nil, nil, err - } - - // network - // Note: we cannot emulate compat exactly here. we only allow specifics of networks to be - // defined when there is only one network. - netInfo := entities.NetOptions{ - AddHosts: cc.HostConfig.ExtraHosts, - DNSOptions: cc.HostConfig.DNSOptions, - DNSSearch: cc.HostConfig.DNSSearch, - DNSServers: dns, - Network: nsmode, - PublishPorts: specPorts, - NetworkOptions: netOpts, - NoHosts: rtc.Containers.NoHosts, - } - - // network names - switch { - case len(cc.NetworkingConfig.EndpointsConfig) > 0: - endpointsConfig := cc.NetworkingConfig.EndpointsConfig - networks := make(map[string]types.PerNetworkOptions, len(endpointsConfig)) - for netName, endpoint := range endpointsConfig { - netOpts := types.PerNetworkOptions{} - if endpoint != nil { - netOpts.Aliases = endpoint.Aliases - - // if IP address is provided - if len(endpoint.IPAddress) > 0 { - staticIP := net.ParseIP(endpoint.IPAddress) - if staticIP == nil { - return nil, nil, errors.Errorf("failed to parse the ip address %q", endpoint.IPAddress) - } - netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) - } - - if endpoint.IPAMConfig != nil { - // if IPAMConfig.IPv4Address is provided - if len(endpoint.IPAMConfig.IPv4Address) > 0 { - staticIP := net.ParseIP(endpoint.IPAMConfig.IPv4Address) - if staticIP == nil { - return nil, nil, errors.Errorf("failed to parse the ipv4 address %q", endpoint.IPAMConfig.IPv4Address) - } - netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) - } - // if IPAMConfig.IPv6Address is provided - if len(endpoint.IPAMConfig.IPv6Address) > 0 { - staticIP := net.ParseIP(endpoint.IPAMConfig.IPv6Address) - if staticIP == nil { - return nil, nil, errors.Errorf("failed to parse the ipv6 address %q", endpoint.IPAMConfig.IPv6Address) - } - netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) - } - } - // If MAC address is provided - if len(endpoint.MacAddress) > 0 { - staticMac, err := net.ParseMAC(endpoint.MacAddress) - if err != nil { - return nil, nil, errors.Errorf("failed to parse the mac address %q", endpoint.MacAddress) - } - netOpts.StaticMAC = types.HardwareAddr(staticMac) - } - } - - networks[netName] = netOpts - } - - netInfo.Networks = networks - case len(cc.HostConfig.NetworkMode) > 0: - netInfo.Networks = networks - } - - parsedTmp := make([]string, 0, len(cc.HostConfig.Tmpfs)) - for path, options := range cc.HostConfig.Tmpfs { - finalString := path - if options != "" { - finalString += ":" + options - } - parsedTmp = append(parsedTmp, finalString) - } - - // Note: several options here are marked as "don't need". this is based - // on speculation by Matt and I. We think that these come into play later - // like with start. We believe this is just a difference in podman/compat - cliOpts := entities.ContainerCreateOptions{ - // Attach: nil, // don't need? - Authfile: "", - CapAdd: append(capAdd, cc.HostConfig.CapAdd...), - CapDrop: append(cappDrop, cc.HostConfig.CapDrop...), - CgroupParent: cc.HostConfig.CgroupParent, - CIDFile: cc.HostConfig.ContainerIDFile, - CPUPeriod: uint64(cc.HostConfig.CPUPeriod), - CPUQuota: cc.HostConfig.CPUQuota, - CPURTPeriod: uint64(cc.HostConfig.CPURealtimePeriod), - CPURTRuntime: cc.HostConfig.CPURealtimeRuntime, - CPUShares: uint64(cc.HostConfig.CPUShares), - // CPUS: 0, // don't need? - CPUSetCPUs: cc.HostConfig.CpusetCpus, - CPUSetMems: cc.HostConfig.CpusetMems, - // Detach: false, // don't need - // DetachKeys: "", // don't need - Devices: devices, - DeviceCgroupRule: nil, - DeviceReadBPs: readBps, - DeviceReadIOPs: readIops, - DeviceWriteBPs: writeBps, - DeviceWriteIOPs: writeIops, - Entrypoint: entrypoint, - Env: cc.Config.Env, - Expose: expose, - GroupAdd: cc.HostConfig.GroupAdd, - Hostname: cc.Config.Hostname, - ImageVolume: "bind", - Init: init, - Interactive: cc.Config.OpenStdin, - IPC: string(cc.HostConfig.IpcMode), - Label: stringMaptoArray(cc.Config.Labels), - LogDriver: cc.HostConfig.LogConfig.Type, - LogOptions: stringMaptoArray(cc.HostConfig.LogConfig.Config), - Name: cc.Name, - OOMScoreAdj: &cc.HostConfig.OomScoreAdj, - Arch: "", - OS: "", - Variant: "", - PID: string(cc.HostConfig.PidMode), - PIDsLimit: cc.HostConfig.PidsLimit, - Privileged: cc.HostConfig.Privileged, - PublishAll: cc.HostConfig.PublishAllPorts, - Quiet: false, - ReadOnly: cc.HostConfig.ReadonlyRootfs, - ReadOnlyTmpFS: true, // podman default - Rm: cc.HostConfig.AutoRemove, - SecurityOpt: cc.HostConfig.SecurityOpt, - StopSignal: cc.Config.StopSignal, - StorageOpts: stringMaptoArray(cc.HostConfig.StorageOpt), - Sysctl: stringMaptoArray(cc.HostConfig.Sysctls), - Systemd: "true", // podman default - TmpFS: parsedTmp, - TTY: cc.Config.Tty, - UnsetEnv: cc.UnsetEnv, - UnsetEnvAll: cc.UnsetEnvAll, - User: cc.Config.User, - UserNS: string(cc.HostConfig.UsernsMode), - UTS: string(cc.HostConfig.UTSMode), - Mount: mounts, - VolumesFrom: cc.HostConfig.VolumesFrom, - Workdir: cc.Config.WorkingDir, - Net: &netInfo, - HealthInterval: define.DefaultHealthCheckInterval, - HealthRetries: define.DefaultHealthCheckRetries, - HealthTimeout: define.DefaultHealthCheckTimeout, - HealthStartPeriod: define.DefaultHealthCheckStartPeriod, - } - if !rootless.IsRootless() { - var ulimits []string - if len(cc.HostConfig.Ulimits) > 0 { - for _, ul := range cc.HostConfig.Ulimits { - ulimits = append(ulimits, ul.String()) - } - cliOpts.Ulimit = ulimits - } - } - if cc.HostConfig.Resources.NanoCPUs > 0 { - if cliOpts.CPUPeriod != 0 || cliOpts.CPUQuota != 0 { - return nil, nil, errors.Errorf("NanoCpus conflicts with CpuPeriod and CpuQuota") - } - cliOpts.CPUPeriod = 100000 - cliOpts.CPUQuota = cc.HostConfig.Resources.NanoCPUs / 10000 - } - - // volumes - for _, vol := range cc.HostConfig.Binds { - cliOpts.Volume = append(cliOpts.Volume, vol) - // Extract the destination so we don't add duplicate mounts in - // the volumes phase. - splitVol := specgen.SplitVolumeString(vol) - switch len(splitVol) { - case 1: - volDestinations[vol] = true - default: - volSources[splitVol[0]] = true - volDestinations[splitVol[1]] = true - } - } - // Anonymous volumes are added differently from other volumes, in their - // own special field, for reasons known only to Docker. Still use the - // format of `-v` so we can just append them in there. - // Unfortunately, these may be duplicates of existing mounts in Binds. - // So... We need to catch that. - // This also handles volumes duplicated between cc.HostConfig.Mounts and - // cc.Volumes, as seen in compose v2.0. - for vol := range cc.Volumes { - if _, ok := volDestinations[filepath.Clean(vol)]; ok { - continue - } - cliOpts.Volume = append(cliOpts.Volume, vol) - } - // Make mount points for compat volumes - for vol := range volSources { - // This might be a named volume. - // Assume it is if it's not an absolute path. - if !filepath.IsAbs(vol) { - continue - } - // If volume already exists, there is nothing to do - if _, err := os.Stat(vol); err == nil { - continue - } - if err := os.MkdirAll(vol, 0755); err != nil { - if !os.IsExist(err) { - return nil, nil, errors.Wrapf(err, "error making volume mountpoint for volume %s", vol) - } - } - } - if len(cc.HostConfig.BlkioWeightDevice) > 0 { - devices := make([]string, 0, len(cc.HostConfig.BlkioWeightDevice)) - for _, d := range cc.HostConfig.BlkioWeightDevice { - devices = append(devices, d.String()) - } - cliOpts.BlkIOWeightDevice = devices - } - if cc.HostConfig.BlkioWeight > 0 { - cliOpts.BlkIOWeight = strconv.Itoa(int(cc.HostConfig.BlkioWeight)) - } - - if cc.HostConfig.Memory > 0 { - cliOpts.Memory = strconv.Itoa(int(cc.HostConfig.Memory)) - } - - if cc.HostConfig.MemoryReservation > 0 { - cliOpts.MemoryReservation = strconv.Itoa(int(cc.HostConfig.MemoryReservation)) - } - - cgroupsv2, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return nil, nil, err - } - if cc.HostConfig.MemorySwap > 0 && (!rootless.IsRootless() || (rootless.IsRootless() && cgroupsv2)) { - cliOpts.MemorySwap = strconv.Itoa(int(cc.HostConfig.MemorySwap)) - } - - if cc.Config.StopTimeout != nil { - cliOpts.StopTimeout = uint(*cc.Config.StopTimeout) - } - - if cc.HostConfig.ShmSize > 0 { - cliOpts.ShmSize = strconv.Itoa(int(cc.HostConfig.ShmSize)) - } - - if len(cc.HostConfig.RestartPolicy.Name) > 0 { - policy := cc.HostConfig.RestartPolicy.Name - // only add restart count on failure - if cc.HostConfig.RestartPolicy.IsOnFailure() { - policy += fmt.Sprintf(":%d", cc.HostConfig.RestartPolicy.MaximumRetryCount) - } - cliOpts.Restart = policy - } - - if cc.HostConfig.MemorySwappiness != nil && (!rootless.IsRootless() || rootless.IsRootless() && cgroupsv2 && rtc.Engine.CgroupManager == "systemd") { - cliOpts.MemorySwappiness = *cc.HostConfig.MemorySwappiness - } else { - cliOpts.MemorySwappiness = -1 - } - if cc.HostConfig.OomKillDisable != nil { - cliOpts.OOMKillDisable = *cc.HostConfig.OomKillDisable - } - if cc.Config.Healthcheck != nil { - finCmd := "" - for _, str := range cc.Config.Healthcheck.Test { - finCmd = finCmd + str + " " - } - if len(finCmd) > 1 { - finCmd = finCmd[:len(finCmd)-1] - } - cliOpts.HealthCmd = finCmd - if cc.Config.Healthcheck.Interval > 0 { - cliOpts.HealthInterval = cc.Config.Healthcheck.Interval.String() - } - if cc.Config.Healthcheck.Retries > 0 { - cliOpts.HealthRetries = uint(cc.Config.Healthcheck.Retries) - } - if cc.Config.Healthcheck.StartPeriod > 0 { - cliOpts.HealthStartPeriod = cc.Config.Healthcheck.StartPeriod.String() - } - if cc.Config.Healthcheck.Timeout > 0 { - cliOpts.HealthTimeout = cc.Config.Healthcheck.Timeout.String() - } - } - - // specgen assumes the image name is arg[0] - cmd := []string{cc.Config.Image} - cmd = append(cmd, cc.Config.Cmd...) - return &cliOpts, cmd, nil -} - func ulimits() []string { if !registry.IsRemote() { return containerConfig.Ulimits() @@ -488,7 +27,7 @@ func devices() []string { return nil } -func env() []string { +func Env() []string { if !registry.IsRemote() { return containerConfig.Env() } @@ -537,16 +76,20 @@ func LogDriver() string { return "" } -// addField is a helper function to populate mount options -func addField(b *strings.Builder, name string, value string) { - if value == "" { - return - } - - if b.Len() > 0 { - b.WriteRune(',') - } - b.WriteString(name) - b.WriteRune('=') - b.WriteString(value) +// DefineCreateDefault is used to initialize ctr create options before flag initialization +func DefineCreateDefaults(opts *entities.ContainerCreateOptions) { + opts.LogDriver = LogDriver() + opts.CgroupsMode = cgroupConfig() + opts.MemorySwappiness = -1 + opts.ImageVolume = containerConfig.Engine.ImageVolumeMode + opts.Pull = policy() + opts.ReadOnlyTmpFS = true + opts.SdNotifyMode = define.SdNotifyModeContainer + opts.StopTimeout = containerConfig.Engine.StopTimeout + opts.Systemd = "true" + opts.Timezone = containerConfig.TZ() + opts.Umask = containerConfig.Umask() + opts.Ulimit = ulimits() + opts.SeccompPolicy = "default" + opts.Volume = volumes() } diff --git a/cmd/podman/common/default.go b/cmd/podman/common/default.go index 7caec50ff..6f78d3d29 100644 --- a/cmd/podman/common/default.go +++ b/cmd/podman/common/default.go @@ -5,9 +5,6 @@ import ( ) var ( - - // DefaultImageVolume default value - DefaultImageVolume = "bind" // Pull in configured json library json = registry.JSONLibrary() ) diff --git a/cmd/podman/common/diffChanges.go b/cmd/podman/common/diffChanges.go index 99b5f1dcd..6d4c7154a 100644 --- a/cmd/podman/common/diffChanges.go +++ b/cmd/podman/common/diffChanges.go @@ -6,7 +6,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/storage/pkg/archive" - "github.com/pkg/errors" ) type ChangesReportJSON struct { @@ -26,7 +25,7 @@ func ChangesToJSON(diffs *entities.DiffReport) error { case archive.ChangeModify: body.Changed = append(body.Changed, row.Path) default: - return errors.Errorf("output kind %q not recognized", row.Kind) + return fmt.Errorf("output kind %q not recognized", row.Kind) } } diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go index 9dfe81d62..e7aa265d1 100644 --- a/cmd/podman/common/netflags.go +++ b/cmd/podman/common/netflags.go @@ -1,6 +1,8 @@ package common import ( + "errors" + "fmt" "net" "github.com/containers/common/libnetwork/types" @@ -10,7 +12,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/specgen" "github.com/containers/podman/v4/pkg/specgenutil" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -125,13 +126,13 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*enti if d == "none" { opts.UseImageResolvConf = true if len(servers) > 1 { - return nil, errors.Errorf("%s is not allowed to be specified with other DNS ip addresses", d) + return nil, fmt.Errorf("%s is not allowed to be specified with other DNS ip addresses", d) } break } dns := net.ParseIP(d) if dns == nil { - return nil, errors.Errorf("%s is not an ip address", d) + return nil, fmt.Errorf("%s is not an ip address", d) } opts.DNSServers = append(opts.DNSServers, dns) } @@ -154,7 +155,7 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*enti for _, dom := range dnsSearches { if dom == "." { if len(dnsSearches) > 1 { - return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'") + return nil, errors.New("cannot pass additional search domains when also specifying '.'") } continue } @@ -218,18 +219,18 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*enti if ip != "" { // if pod create --infra=false if infra, err := flags.GetBool("infra"); err == nil && !infra { - return nil, errors.Wrapf(define.ErrInvalidArg, "cannot set --%s without infra container", ipFlagName) + return nil, fmt.Errorf("cannot set --%s without infra container: %w", ipFlagName, define.ErrInvalidArg) } staticIP := net.ParseIP(ip) if staticIP == nil { - return nil, errors.Errorf("%q is not an ip address", ip) + return nil, fmt.Errorf("%q is not an ip address", ip) } if !opts.Network.IsBridge() && !opts.Network.IsDefault() { - return nil, errors.Wrapf(define.ErrInvalidArg, "--%s can only be set when the network mode is bridge", ipFlagName) + return nil, fmt.Errorf("--%s can only be set when the network mode is bridge: %w", ipFlagName, define.ErrInvalidArg) } if len(opts.Networks) != 1 { - return nil, errors.Wrapf(define.ErrInvalidArg, "--%s can only be set for a single network", ipFlagName) + return nil, fmt.Errorf("--%s can only be set for a single network: %w", ipFlagName, define.ErrInvalidArg) } for name, netOpts := range opts.Networks { netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) @@ -245,17 +246,17 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*enti if len(m) > 0 { // if pod create --infra=false if infra, err := flags.GetBool("infra"); err == nil && !infra { - return nil, errors.Wrap(define.ErrInvalidArg, "cannot set --mac without infra container") + return nil, fmt.Errorf("cannot set --mac without infra container: %w", define.ErrInvalidArg) } mac, err := net.ParseMAC(m) if err != nil { return nil, err } if !opts.Network.IsBridge() && !opts.Network.IsDefault() { - return nil, errors.Wrap(define.ErrInvalidArg, "--mac-address can only be set when the network mode is bridge") + return nil, fmt.Errorf("--mac-address can only be set when the network mode is bridge: %w", define.ErrInvalidArg) } if len(opts.Networks) != 1 { - return nil, errors.Wrap(define.ErrInvalidArg, "--mac-address can only be set for a single network") + return nil, fmt.Errorf("--mac-address can only be set for a single network: %w", define.ErrInvalidArg) } for name, netOpts := range opts.Networks { netOpts.StaticMAC = types.HardwareAddr(mac) @@ -270,10 +271,10 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*enti if len(aliases) > 0 { // if pod create --infra=false if infra, err := flags.GetBool("infra"); err == nil && !infra { - return nil, errors.Wrap(define.ErrInvalidArg, "cannot set --network-alias without infra container") + return nil, fmt.Errorf("cannot set --network-alias without infra container: %w", define.ErrInvalidArg) } if !opts.Network.IsBridge() && !opts.Network.IsDefault() { - return nil, errors.Wrap(define.ErrInvalidArg, "--network-alias can only be set when the network mode is bridge") + return nil, fmt.Errorf("--network-alias can only be set when the network mode is bridge: %w", define.ErrInvalidArg) } for name, netOpts := range opts.Networks { netOpts.Aliases = aliases diff --git a/cmd/podman/containers/attach.go b/cmd/podman/containers/attach.go index 1a05ea5c3..52adb1fcb 100644 --- a/cmd/podman/containers/attach.go +++ b/cmd/podman/containers/attach.go @@ -1,13 +1,13 @@ package containers import ( + "errors" "os" "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -70,7 +70,7 @@ func init() { func attach(cmd *cobra.Command, args []string) error { if len(args) > 1 || (len(args) == 0 && !attachOpts.Latest) { - return errors.Errorf("attach requires the name or id of one running container or the latest flag") + return errors.New("attach requires the name or id of one running container or the latest flag") } var name string diff --git a/cmd/podman/containers/checkpoint.go b/cmd/podman/containers/checkpoint.go index 40d689c4d..0eb0db394 100644 --- a/cmd/podman/containers/checkpoint.go +++ b/cmd/podman/containers/checkpoint.go @@ -2,6 +2,7 @@ package containers import ( "context" + "errors" "fmt" "strings" "time" @@ -15,7 +16,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/storage/pkg/archive" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -31,7 +31,7 @@ var ( Long: checkpointDescription, RunE: checkpoint, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompleteContainersRunning, Example: `podman container checkpoint --keep ctrID @@ -90,7 +90,7 @@ func checkpoint(cmd *cobra.Command, args []string) error { podmanStart := time.Now() if cmd.Flags().Changed("compress") { if checkpointOptions.Export == "" { - return errors.Errorf("--compress can only be used with --export") + return errors.New("--compress can only be used with --export") } compress, _ := cmd.Flags().GetString("compress") switch strings.ToLower(compress) { @@ -101,7 +101,7 @@ func checkpoint(cmd *cobra.Command, args []string) error { case "zstd": checkpointOptions.Compression = archive.Zstd default: - return errors.Errorf("Selected compression algorithm (%q) not supported. Please select one from: gzip, none, zstd", compress) + return fmt.Errorf("selected compression algorithm (%q) not supported. Please select one from: gzip, none, zstd", compress) } } else { checkpointOptions.Compression = archive.Zstd @@ -110,13 +110,13 @@ func checkpoint(cmd *cobra.Command, args []string) error { return errors.New("checkpointing a container requires root") } if checkpointOptions.Export == "" && checkpointOptions.IgnoreRootFS { - return errors.Errorf("--ignore-rootfs can only be used with --export") + return errors.New("--ignore-rootfs can only be used with --export") } if checkpointOptions.Export == "" && checkpointOptions.IgnoreVolumes { - return errors.Errorf("--ignore-volumes can only be used with --export") + return errors.New("--ignore-volumes can only be used with --export") } if checkpointOptions.WithPrevious && checkpointOptions.PreCheckPoint { - return errors.Errorf("--with-previous can not be used with --pre-checkpoint") + return errors.New("--with-previous can not be used with --pre-checkpoint") } if (checkpointOptions.WithPrevious || checkpointOptions.PreCheckPoint) && !criu.MemTrack() { return errors.New("system (architecture/kernel/CRIU) does not support memory tracking") diff --git a/cmd/podman/containers/cleanup.go b/cmd/podman/containers/cleanup.go index aa2734607..c9a5cb28b 100644 --- a/cmd/podman/containers/cleanup.go +++ b/cmd/podman/containers/cleanup.go @@ -1,6 +1,7 @@ package containers import ( + "errors" "fmt" "github.com/containers/common/pkg/completion" @@ -9,7 +10,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -23,11 +23,11 @@ var ( cleanupCommand = &cobra.Command{ Annotations: map[string]string{registry.EngineMode: registry.ABIMode}, Use: "cleanup [options] CONTAINER [CONTAINER...]", - Short: "Cleanup network and mountpoints of one or more containers", + Short: "Clean up network and mountpoints of one or more containers", Long: cleanupDescription, RunE: cleanup, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompleteContainersExited, Example: `podman container cleanup --latest @@ -65,11 +65,11 @@ func cleanup(cmd *cobra.Command, args []string) error { if cleanupOptions.Exec != "" { switch { case cleanupOptions.All: - return errors.Errorf("exec and all options conflict") + return errors.New("exec and all options conflict") case len(args) > 1: - return errors.Errorf("cannot use exec option when more than one container is given") + return errors.New("cannot use exec option when more than one container is given") case cleanupOptions.RemoveImage: - return errors.Errorf("exec and rmi options conflict") + return errors.New("exec and rmi options conflict") } } diff --git a/cmd/podman/containers/clone.go b/cmd/podman/containers/clone.go index 6912da1fc..9881a791c 100644 --- a/cmd/podman/containers/clone.go +++ b/cmd/podman/containers/clone.go @@ -7,7 +7,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -41,6 +40,7 @@ func cloneFlags(cmd *cobra.Command) { forceFlagName := "force" flags.BoolVarP(&ctrClone.Force, forceFlagName, "f", false, "force the existing container to be destroyed") + common.DefineCreateDefaults(&ctrClone.CreateOpts) common.DefineCreateFlags(cmd, &ctrClone.CreateOpts, false, true) } func init() { @@ -55,7 +55,7 @@ func init() { func clone(cmd *cobra.Command, args []string) error { switch len(args) { case 0: - return errors.Wrapf(define.ErrInvalidArg, "must specify at least 1 argument") + return fmt.Errorf("must specify at least 1 argument: %w", define.ErrInvalidArg) case 2: ctrClone.CreateOpts.Name = args[1] case 3: @@ -63,7 +63,7 @@ func clone(cmd *cobra.Command, args []string) error { ctrClone.Image = args[2] if !cliVals.RootFS { rawImageName := args[0] - name, err := PullImage(ctrClone.Image, ctrClone.CreateOpts) + name, err := PullImage(ctrClone.Image, &ctrClone.CreateOpts) if err != nil { return err } @@ -72,7 +72,7 @@ func clone(cmd *cobra.Command, args []string) error { } } if ctrClone.Force && !ctrClone.Destroy { - return errors.Wrapf(define.ErrInvalidArg, "cannot set --force without --destroy") + return fmt.Errorf("cannot set --force without --destroy: %w", define.ErrInvalidArg) } ctrClone.ID = args[0] diff --git a/cmd/podman/containers/commit.go b/cmd/podman/containers/commit.go index e0cadd5b0..fb6dccad4 100644 --- a/cmd/podman/containers/commit.go +++ b/cmd/podman/containers/commit.go @@ -11,7 +11,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -109,7 +108,7 @@ func commit(cmd *cobra.Command, args []string) error { } if len(iidFile) > 0 { if err = ioutil.WriteFile(iidFile, []byte(response.Id), 0644); err != nil { - return errors.Wrap(err, "failed to write image ID") + return fmt.Errorf("failed to write image ID: %w", err) } } fmt.Println(response.Id) diff --git a/cmd/podman/containers/cp.go b/cmd/podman/containers/cp.go index eb18dfce4..c38cba66e 100644 --- a/cmd/podman/containers/cp.go +++ b/cmd/podman/containers/cp.go @@ -1,6 +1,7 @@ package containers import ( + "fmt" "io" "io/ioutil" "os" @@ -9,6 +10,8 @@ import ( "strconv" "strings" + "errors" + buildahCopiah "github.com/containers/buildah/copier" "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" @@ -17,7 +20,6 @@ import ( "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/idtools" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -55,10 +57,13 @@ var ( func cpFlags(cmd *cobra.Command) { flags := cmd.Flags() - flags.BoolVar(&cpOpts.Extract, "extract", false, "Deprecated...") - flags.BoolVar(&cpOpts.Pause, "pause", true, "Deprecated") + flags.BoolVar(&cpOpts.OverwriteDirNonDir, "overwrite", false, "Allow to overwrite directories with non-directories and vice versa") flags.BoolVarP(&chown, "archive", "a", true, `Chown copied files to the primary uid/gid of the destination container.`) + + // Deprecated flags (both are NOPs): exist for backwards compat + flags.BoolVar(&cpOpts.Extract, "extract", false, "Deprecated...") _ = flags.MarkHidden("extract") + flags.BoolVar(&cpOpts.Pause, "pause", true, "Deprecated") _ = flags.MarkHidden("pause") } @@ -99,7 +104,7 @@ func containerMustExist(container string) error { return err } if !exists.Value { - return errors.Errorf("container %q does not exist", container) + return fmt.Errorf("container %q does not exist", container) } return nil } @@ -128,7 +133,7 @@ func copyContainerToContainer(sourceContainer string, sourcePath string, destCon sourceContainerInfo, err := registry.ContainerEngine().ContainerStat(registry.GetContext(), sourceContainer, sourcePath) if err != nil { - return errors.Wrapf(err, "%q could not be found on container %s", sourcePath, sourceContainer) + return fmt.Errorf("%q could not be found on container %s: %w", sourcePath, sourceContainer, err) } destContainerBaseName, destContainerInfo, destResolvedToParentDir, err := resolvePathOnDestinationContainer(destContainer, destPath, false) @@ -167,7 +172,7 @@ func copyContainerToContainer(sourceContainer string, sourcePath string, destCon return err } if err := copyFunc(); err != nil { - return errors.Wrap(err, "error copying from container") + return fmt.Errorf("error copying from container: %w", err) } return nil } @@ -175,7 +180,7 @@ func copyContainerToContainer(sourceContainer string, sourcePath string, destCon destContainerCopy := func() error { defer reader.Close() - copyOptions := entities.CopyOptions{Chown: chown} + copyOptions := entities.CopyOptions{Chown: chown, NoOverwriteDirNonDir: !cpOpts.OverwriteDirNonDir} if (!sourceContainerInfo.IsDir && !destContainerInfo.IsDir) || destResolvedToParentDir { // If we're having a file-to-file copy, make sure to // rename accordingly. @@ -187,7 +192,7 @@ func copyContainerToContainer(sourceContainer string, sourcePath string, destCon return err } if err := copyFunc(); err != nil { - return errors.Wrap(err, "error copying to container") + return fmt.Errorf("error copying to container: %w", err) } return nil } @@ -209,7 +214,7 @@ func copyFromContainer(container string, containerPath string, hostPath string) containerInfo, err := registry.ContainerEngine().ContainerStat(registry.GetContext(), container, containerPath) if err != nil { - return errors.Wrapf(err, "%q could not be found on container %s", containerPath, container) + return fmt.Errorf("%q could not be found on container %s: %w", containerPath, container, err) } var hostBaseName string @@ -217,13 +222,13 @@ func copyFromContainer(container string, containerPath string, hostPath string) hostInfo, hostInfoErr := copy.ResolveHostPath(hostPath) if hostInfoErr != nil { if strings.HasSuffix(hostPath, "/") { - return errors.Wrapf(hostInfoErr, "%q could not be found on the host", hostPath) + return fmt.Errorf("%q could not be found on the host: %w", hostPath, hostInfoErr) } // If it doesn't exist, then let's have a look at the parent dir. parentDir := filepath.Dir(hostPath) hostInfo, err = copy.ResolveHostPath(parentDir) if err != nil { - return errors.Wrapf(hostInfoErr, "%q could not be found on the host", hostPath) + return fmt.Errorf("%q could not be found on the host: %w", hostPath, hostInfoErr) } // If the specified path does not exist, we need to assume that // it'll be created while copying. Hence, we use it as the @@ -238,7 +243,7 @@ func copyFromContainer(container string, containerPath string, hostPath string) if !isStdout { if err := validateFileInfo(hostInfo); err != nil { - return errors.Wrap(err, "invalid destination") + return fmt.Errorf("invalid destination: %w", err) } } @@ -294,9 +299,11 @@ func copyFromContainer(container string, containerPath string, hostPath string) } putOptions := buildahCopiah.PutOptions{ - ChownDirs: &idPair, - ChownFiles: &idPair, - IgnoreDevices: true, + ChownDirs: &idPair, + ChownFiles: &idPair, + IgnoreDevices: true, + NoOverwriteDirNonDir: !cpOpts.OverwriteDirNonDir, + NoOverwriteNonDirDir: !cpOpts.OverwriteDirNonDir, } if (!containerInfo.IsDir && !hostInfo.IsDir) || resolvedToHostParentDir { // If we're having a file-to-file copy, make sure to @@ -308,7 +315,7 @@ func copyFromContainer(container string, containerPath string, hostPath string) dir = filepath.Dir(dir) } if err := buildahCopiah.Put(dir, "", putOptions, reader); err != nil { - return errors.Wrap(err, "error copying to host") + return fmt.Errorf("error copying to host: %w", err) } return nil } @@ -320,7 +327,7 @@ func copyFromContainer(container string, containerPath string, hostPath string) return err } if err := copyFunc(); err != nil { - return errors.Wrap(err, "error copying from container") + return fmt.Errorf("error copying from container: %w", err) } return nil } @@ -342,7 +349,7 @@ func copyToContainer(container string, containerPath string, hostPath string) er // Make sure that host path exists. hostInfo, err := copy.ResolveHostPath(hostPath) if err != nil { - return errors.Wrapf(err, "%q could not be found on the host", hostPath) + return fmt.Errorf("%q could not be found on the host: %w", hostPath, err) } containerBaseName, containerInfo, containerResolvedToParentDir, err := resolvePathOnDestinationContainer(container, containerPath, isStdin) @@ -417,7 +424,7 @@ func copyToContainer(container string, containerPath string, hostPath string) er getOptions.Rename = map[string]string{filepath.Base(hostTarget): containerBaseName} } if err := buildahCopiah.Get("/", "", getOptions, []string{hostTarget}, writer); err != nil { - return errors.Wrap(err, "error copying from host") + return fmt.Errorf("error copying from host: %w", err) } return nil } @@ -429,12 +436,12 @@ func copyToContainer(container string, containerPath string, hostPath string) er target = filepath.Dir(target) } - copyFunc, err := registry.ContainerEngine().ContainerCopyFromArchive(registry.GetContext(), container, target, reader, entities.CopyOptions{Chown: chown}) + copyFunc, err := registry.ContainerEngine().ContainerCopyFromArchive(registry.GetContext(), container, target, reader, entities.CopyOptions{Chown: chown, NoOverwriteDirNonDir: !cpOpts.OverwriteDirNonDir}) if err != nil { return err } if err := copyFunc(); err != nil { - return errors.Wrap(err, "error copying to container") + return fmt.Errorf("error copying to container: %w", err) } return nil } @@ -449,11 +456,11 @@ func resolvePathOnDestinationContainer(container string, containerPath string, i containerInfo, err = registry.ContainerEngine().ContainerStat(registry.GetContext(), container, containerPath) if err == nil { baseName = filepath.Base(containerInfo.LinkTarget) - return // nolint: nilerr + return //nolint: nilerr } if strings.HasSuffix(containerPath, "/") { - err = errors.Wrapf(err, "%q could not be found on container %s", containerPath, container) + err = fmt.Errorf("%q could not be found on container %s: %w", containerPath, container, err) return } if isStdin { @@ -474,13 +481,13 @@ func resolvePathOnDestinationContainer(container string, containerPath string, i parentDir, err := containerParentDir(container, path) if err != nil { - err = errors.Wrapf(err, "could not determine parent dir of %q on container %s", path, container) + err = fmt.Errorf("could not determine parent dir of %q on container %s: %w", path, container, err) return } containerInfo, err = registry.ContainerEngine().ContainerStat(registry.GetContext(), container, parentDir) if err != nil { - err = errors.Wrapf(err, "%q could not be found on container %s", containerPath, container) + err = fmt.Errorf("%q could not be found on container %s: %w", containerPath, container, err) return } @@ -500,7 +507,7 @@ func containerParentDir(container string, containerPath string) (string, error) return "", err } if len(inspectData) != 1 { - return "", errors.Errorf("inspecting container %q: expected 1 data item but got %d", container, len(inspectData)) + return "", fmt.Errorf("inspecting container %q: expected 1 data item but got %d", container, len(inspectData)) } workDir := filepath.Join("/", inspectData[0].Config.WorkingDir) workDir = filepath.Join(workDir, containerPath) @@ -513,5 +520,5 @@ func validateFileInfo(info *copy.FileInfo) error { if info.Mode.IsDir() || info.Mode.IsRegular() { return nil } - return errors.Errorf("%q must be a directory or a regular file", info.LinkTarget) + return fmt.Errorf("%q must be a directory or a regular file", info.LinkTarget) } diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 29e138e30..7d0f4d9ae 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -2,6 +2,7 @@ package containers import ( "context" + "errors" "fmt" "os" "strconv" @@ -9,6 +10,7 @@ import ( "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" + cutil "github.com/containers/common/pkg/util" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/podman/v4/cmd/podman/common" @@ -20,7 +22,6 @@ import ( "github.com/containers/podman/v4/pkg/specgenutil" "github.com/containers/podman/v4/pkg/util" "github.com/mattn/go-isatty" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -70,6 +71,7 @@ func createFlags(cmd *cobra.Command) { ) flags.SetInterspersed(false) + common.DefineCreateDefaults(&cliVals) common.DefineCreateFlags(cmd, &cliVals, false, false) common.DefineNetFlags(cmd) @@ -101,28 +103,37 @@ func init() { createFlags(containerCreateCommand) } -func create(cmd *cobra.Command, args []string) error { - var ( - err error - ) +func commonFlags(cmd *cobra.Command) error { + var err error flags := cmd.Flags() cliVals.Net, err = common.NetFlagsToNetOptions(nil, *flags) if err != nil { return err } + if cmd.Flags().Changed("image-volume") { + cliVals.ImageVolume = cmd.Flag("image-volume").Value.String() + } + return nil +} + +func create(cmd *cobra.Command, args []string) error { + if err := commonFlags(cmd); err != nil { + return err + } + // Check if initctr is used with --pod and the value is correct if initctr := InitContainerType; cmd.Flags().Changed("init-ctr") { if !cmd.Flags().Changed("pod") { return errors.New("must specify pod value with init-ctr") } - if !util.StringInSlice(initctr, []string{define.AlwaysInitContainer, define.OneShotInitContainer}) { - return errors.Errorf("init-ctr value must be '%s' or '%s'", define.AlwaysInitContainer, define.OneShotInitContainer) + if !cutil.StringInSlice(initctr, []string{define.AlwaysInitContainer, define.OneShotInitContainer}) { + return fmt.Errorf("init-ctr value must be '%s' or '%s'", define.AlwaysInitContainer, define.OneShotInitContainer) } cliVals.InitContainerType = initctr } - cliVals, err = CreateInit(cmd, cliVals, false) + cliVals, err := CreateInit(cmd, cliVals, false) if err != nil { return err } @@ -130,7 +141,7 @@ func create(cmd *cobra.Command, args []string) error { rawImageName := "" if !cliVals.RootFS { rawImageName = args[0] - name, err := PullImage(args[0], cliVals) + name, err := PullImage(args[0], &cliVals) if err != nil { return err } @@ -206,17 +217,13 @@ func CreateInit(c *cobra.Command, vals entities.ContainerCreateOptions, isInfra } if !isInfra { - if c.Flag("shm-size").Changed { - vals.ShmSize = c.Flag("shm-size").Value.String() - } if c.Flag("cpu-period").Changed && c.Flag("cpus").Changed { - return vals, errors.Errorf("--cpu-period and --cpus cannot be set together") + return vals, errors.New("--cpu-period and --cpus cannot be set together") } if c.Flag("cpu-quota").Changed && c.Flag("cpus").Changed { - return vals, errors.Errorf("--cpu-quota and --cpus cannot be set together") + return vals, errors.New("--cpu-quota and --cpus cannot be set together") } vals.IPC = c.Flag("ipc").Value.String() - vals.UTS = c.Flag("uts").Value.String() vals.PID = c.Flag("pid").Value.String() vals.CgroupNS = c.Flag("cgroupns").Value.String() @@ -260,27 +267,30 @@ func CreateInit(c *cobra.Command, vals entities.ContainerCreateOptions, isInfra if c.Flags().Changed("env") { env, err := c.Flags().GetStringArray("env") if err != nil { - return vals, errors.Wrapf(err, "retrieve env flag") + return vals, fmt.Errorf("retrieve env flag: %w", err) } vals.Env = env } if c.Flag("cgroups").Changed && vals.CgroupsMode == "split" && registry.IsRemote() { - return vals, errors.Errorf("the option --cgroups=%q is not supported in remote mode", vals.CgroupsMode) + return vals, fmt.Errorf("the option --cgroups=%q is not supported in remote mode", vals.CgroupsMode) } if c.Flag("pod").Changed && !strings.HasPrefix(c.Flag("pod").Value.String(), "new:") && c.Flag("userns").Changed { - return vals, errors.Errorf("--userns and --pod cannot be set together") + return vals, errors.New("--userns and --pod cannot be set together") } } + if c.Flag("shm-size").Changed { + vals.ShmSize = c.Flag("shm-size").Value.String() + } if (c.Flag("dns").Changed || c.Flag("dns-opt").Changed || c.Flag("dns-search").Changed) && vals.Net != nil && (vals.Net.Network.NSMode == specgen.NoNetwork || vals.Net.Network.IsContainer()) { - return vals, errors.Errorf("conflicting options: dns and the network mode: " + string(vals.Net.Network.NSMode)) + return vals, fmt.Errorf("conflicting options: dns and the network mode: " + string(vals.Net.Network.NSMode)) } noHosts, err := c.Flags().GetBool("no-hosts") if err != nil { return vals, err } if noHosts && c.Flag("add-host").Changed { - return vals, errors.Errorf("--no-hosts and --add-host cannot be set together") + return vals, errors.New("--no-hosts and --add-host cannot be set together") } if !isInfra && c.Flag("entrypoint").Changed { @@ -294,7 +304,8 @@ func CreateInit(c *cobra.Command, vals entities.ContainerCreateOptions, isInfra return vals, nil } -func PullImage(imageName string, cliVals entities.ContainerCreateOptions) (string, error) { +// Pulls image if any also parses and populates OS, Arch and Variant in specified container create options +func PullImage(imageName string, cliVals *entities.ContainerCreateOptions) (string, error) { pullPolicy, err := config.ParsePullPolicy(cliVals.Pull) if err != nil { return "", err @@ -303,7 +314,7 @@ func PullImage(imageName string, cliVals entities.ContainerCreateOptions) (strin if cliVals.Platform != "" || cliVals.Arch != "" || cliVals.OS != "" { if cliVals.Platform != "" { if cliVals.Arch != "" || cliVals.OS != "" { - return "", errors.Errorf("--platform option can not be specified with --arch or --os") + return "", errors.New("--platform option can not be specified with --arch or --os") } split := strings.SplitN(cliVals.Platform, "/", 2) cliVals.OS = split[0] @@ -351,7 +362,7 @@ func createPodIfNecessary(cmd *cobra.Command, s *specgen.SpecGenerator, netOpts } podName := strings.Replace(s.Pod, "new:", "", 1) if len(podName) < 1 { - return errors.Errorf("new pod name must be at least one character") + return errors.New("new pod name must be at least one character") } var err error diff --git a/cmd/podman/containers/diff.go b/cmd/podman/containers/diff.go index e1a8ea729..ce501f890 100644 --- a/cmd/podman/containers/diff.go +++ b/cmd/podman/containers/diff.go @@ -1,13 +1,14 @@ package containers import ( + "errors" + "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/diff" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -32,13 +33,9 @@ func init() { Parent: containerCmd, }) - diffOpts = &entities.DiffOptions{} + diffOpts = new(entities.DiffOptions) flags := diffCmd.Flags() - // FIXME: Why does this exists? It is not used anywhere. - flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") - _ = flags.MarkHidden("archive") - formatFlagName := "format" flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format (json)") _ = diffCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(nil)) diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go index f5b93a96a..303795489 100644 --- a/cmd/podman/containers/exec.go +++ b/cmd/podman/containers/exec.go @@ -2,6 +2,7 @@ package containers import ( "bufio" + "errors" "fmt" "os" @@ -13,7 +14,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" envLib "github.com/containers/podman/v4/pkg/env" "github.com/containers/podman/v4/pkg/rootless" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -126,14 +126,14 @@ func exec(_ *cobra.Command, args []string) error { cliEnv, err := envLib.ParseSlice(envInput) if err != nil { - return errors.Wrap(err, "error parsing environment variables") + return fmt.Errorf("error parsing environment variables: %w", err) } execOpts.Envs = envLib.Join(execOpts.Envs, cliEnv) for fd := 3; fd < int(3+execOpts.PreserveFDs); fd++ { if !rootless.IsFdInherited(fd) { - return errors.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd) + return fmt.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd) } } diff --git a/cmd/podman/containers/export.go b/cmd/podman/containers/export.go index 681df93e0..d78bfe960 100644 --- a/cmd/podman/containers/export.go +++ b/cmd/podman/containers/export.go @@ -2,6 +2,7 @@ package containers import ( "context" + "errors" "os" "github.com/containers/common/pkg/completion" @@ -9,7 +10,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/parse" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/term" ) @@ -70,7 +70,7 @@ func export(cmd *cobra.Command, args []string) error { if len(exportOpts.Output) == 0 { file := os.Stdout if term.IsTerminal(int(file.Fd())) { - return errors.Errorf("refusing to export to terminal. Use -o flag or redirect") + return errors.New("refusing to export to terminal. Use -o flag or redirect") } exportOpts.Output = "/dev/stdout" } else if err := parse.ValidateFileName(exportOpts.Output); err != nil { diff --git a/cmd/podman/containers/init.go b/cmd/podman/containers/init.go index 7336a2332..649cdf1c9 100644 --- a/cmd/podman/containers/init.go +++ b/cmd/podman/containers/init.go @@ -21,7 +21,7 @@ var ( Long: initDescription, RunE: initContainer, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompleteContainersCreated, Example: `podman init --latest diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go index e994fbf2c..5a5379389 100644 --- a/cmd/podman/containers/kill.go +++ b/cmd/podman/containers/kill.go @@ -2,6 +2,7 @@ package containers import ( "context" + "errors" "fmt" "io/ioutil" "strings" @@ -13,7 +14,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/signal" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -25,7 +25,7 @@ var ( Long: killDescription, RunE: kill, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "cidfile") }, ValidArgsFunction: common.AutocompleteContainersRunning, Example: `podman kill mywebserver @@ -35,7 +35,7 @@ var ( containerKillCommand = &cobra.Command{ Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "cidfile") }, Use: killCommand.Use, Short: killCommand.Short, @@ -97,7 +97,7 @@ func kill(_ *cobra.Command, args []string) error { for _, cidFile := range cidFiles { content, err := ioutil.ReadFile(cidFile) if err != nil { - return errors.Wrap(err, "error reading CIDFile") + return fmt.Errorf("error reading CIDFile: %w", err) } id := strings.Split(string(content), "\n")[0] args = append(args, id) diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go index 374bf6b1c..9a9749210 100644 --- a/cmd/podman/containers/logs.go +++ b/cmd/podman/containers/logs.go @@ -1,6 +1,8 @@ package containers import ( + "errors" + "fmt" "os" "github.com/containers/common/pkg/completion" @@ -9,7 +11,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -125,7 +126,7 @@ func logs(_ *cobra.Command, args []string) error { // parse time, error out if something is wrong since, err := util.ParseInputTime(logsOptions.SinceRaw, true) if err != nil { - return errors.Wrapf(err, "error parsing --since %q", logsOptions.SinceRaw) + return fmt.Errorf("error parsing --since %q: %w", logsOptions.SinceRaw, err) } logsOptions.Since = since } @@ -133,7 +134,7 @@ func logs(_ *cobra.Command, args []string) error { // parse time, error out if something is wrong until, err := util.ParseInputTime(logsOptions.UntilRaw, false) if err != nil { - return errors.Wrapf(err, "error parsing --until %q", logsOptions.UntilRaw) + return fmt.Errorf("error parsing --until %q: %w", logsOptions.UntilRaw, err) } logsOptions.Until = until } diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go index 18177e3ce..a1f0b0cf3 100644 --- a/cmd/podman/containers/mount.go +++ b/cmd/podman/containers/mount.go @@ -1,6 +1,7 @@ package containers import ( + "errors" "fmt" "os" @@ -10,7 +11,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -33,7 +33,7 @@ var ( Long: mountDescription, RunE: mount, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) + return validate.CheckAllLatestAndIDFile(cmd, args, true, "") }, ValidArgsFunction: common.AutocompleteContainers, } @@ -83,7 +83,7 @@ func init() { func mount(cmd *cobra.Command, args []string) error { if len(args) > 0 && mountOpts.Latest { - return errors.Errorf("--latest and containers cannot be used together") + return errors.New("--latest and containers cannot be used together") } reports, err := registry.ContainerEngine().ContainerMount(registry.GetContext(), args, mountOpts) if err != nil { @@ -108,7 +108,7 @@ func mount(cmd *cobra.Command, args []string) error { case mountOpts.Format == "": break // print defaults default: - return errors.Errorf("unknown --format argument: %q", mountOpts.Format) + return fmt.Errorf("unknown --format argument: %q", mountOpts.Format) } mrs := make([]mountReporter, 0, len(reports)) diff --git a/cmd/podman/containers/pause.go b/cmd/podman/containers/pause.go index 9dbe97d11..3c26fd5c8 100644 --- a/cmd/podman/containers/pause.go +++ b/cmd/podman/containers/pause.go @@ -2,6 +2,7 @@ package containers import ( "context" + "errors" "fmt" "github.com/containers/common/pkg/cgroups" @@ -10,7 +11,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/rootless" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -73,7 +73,7 @@ func pause(cmd *cobra.Command, args []string) error { } if len(args) < 1 && !pauseOpts.All { - return errors.Errorf("you must provide at least one container name or id") + return errors.New("you must provide at least one container name or id") } responses, err := registry.ContainerEngine().ContainerPause(context.Background(), args, pauseOpts) if err != nil { diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go index 22d1d16d3..74bfdf5c6 100644 --- a/cmd/podman/containers/port.go +++ b/cmd/podman/containers/port.go @@ -1,6 +1,7 @@ package containers import ( + "errors" "fmt" "strconv" "strings" @@ -9,13 +10,12 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) var ( - portDescription = `List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT + portDescription = `List port mappings for the CONTAINER, or look up the public-facing port that is NAT-ed to the PRIVATE_PORT ` portCommand = &cobra.Command{ Use: "port [options] CONTAINER [PORT]", @@ -23,7 +23,7 @@ var ( Long: portDescription, RunE: port, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) + return validate.CheckAllLatestAndIDFile(cmd, args, true, "") }, ValidArgsFunction: common.AutocompleteContainerOneArg, Example: `podman port --all @@ -37,7 +37,7 @@ var ( Long: portDescription, RunE: portCommand.RunE, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) + return validate.CheckAllLatestAndIDFile(cmd, args, true, "") }, ValidArgsFunction: portCommand.ValidArgsFunction, Example: `podman container port --all @@ -77,14 +77,14 @@ func port(_ *cobra.Command, args []string) error { ) if len(args) == 0 && !portOpts.Latest && !portOpts.All { - return errors.Errorf("you must supply a running container name or id") + return errors.New("you must supply a running container name or id") } if !portOpts.Latest && len(args) >= 1 { container = args[0] } port := "" if len(args) > 2 { - return errors.Errorf("`port` accepts at most 2 arguments") + return errors.New("`port` accepts at most 2 arguments") } if len(args) > 1 && !portOpts.Latest { port = args[1] @@ -95,7 +95,7 @@ func port(_ *cobra.Command, args []string) error { if len(port) > 0 { fields := strings.Split(port, "/") if len(fields) > 2 || len(fields) < 1 { - return errors.Errorf("port formats are port/protocol. '%s' is invalid", port) + return fmt.Errorf("port formats are port/protocol. '%s' is invalid", port) } if len(fields) == 1 { fields = append(fields, "tcp") @@ -149,7 +149,7 @@ func port(_ *cobra.Command, args []string) error { } } if !found && port != "" { - return errors.Errorf("failed to find published port %q", port) + return fmt.Errorf("failed to find published port %q", port) } } return nil diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index a011a8ae6..12b7f5dae 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -1,6 +1,7 @@ package containers import ( + "errors" "fmt" "os" "strings" @@ -16,7 +17,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/docker/go-units" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -108,18 +108,18 @@ func listFlagSet(cmd *cobra.Command) { func checkFlags(c *cobra.Command) error { // latest, and last are mutually exclusive. if listOpts.Last >= 0 && listOpts.Latest { - return errors.Errorf("last and latest are mutually exclusive") + return errors.New("last and latest are mutually exclusive") } // Quiet conflicts with size and namespace and is overridden by a Go // template. if listOpts.Quiet { if listOpts.Size || listOpts.Namespace { - return errors.Errorf("quiet conflicts with size and namespace") + return errors.New("quiet conflicts with size and namespace") } } // Size and namespace conflict with each other if listOpts.Size && listOpts.Namespace { - return errors.Errorf("size and namespace options conflict") + return errors.New("size and namespace options conflict") } if listOpts.Watch > 0 && listOpts.Latest { @@ -191,7 +191,7 @@ func ps(cmd *cobra.Command, _ []string) error { for _, f := range filters { split := strings.SplitN(f, "=", 2) if len(split) == 1 { - return errors.Errorf("invalid filter %q", f) + return fmt.Errorf("invalid filter %q", f) } listOpts.Filters[split[0]] = append(listOpts.Filters[split[0]], split[1]) } diff --git a/cmd/podman/containers/rename.go b/cmd/podman/containers/rename.go index 1c0b28ce3..d78492ae4 100644 --- a/cmd/podman/containers/rename.go +++ b/cmd/podman/containers/rename.go @@ -1,10 +1,11 @@ package containers import ( + "errors" + "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -44,7 +45,7 @@ func init() { func rename(cmd *cobra.Command, args []string) error { if len(args) > 2 { - return errors.Errorf("must provide at least two arguments to rename") + return errors.New("must provide at least two arguments to rename") } renameOpts := entities.ContainerRenameOptions{ NewName: args[1], diff --git a/cmd/podman/containers/restart.go b/cmd/podman/containers/restart.go index 69d8d71ea..9d704d671 100644 --- a/cmd/podman/containers/restart.go +++ b/cmd/podman/containers/restart.go @@ -11,7 +11,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -26,7 +25,7 @@ var ( Long: restartDescription, RunE: restart, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompleteContainers, Example: `podman restart ctrID @@ -85,10 +84,10 @@ func restart(cmd *cobra.Command, args []string) error { errs utils.OutputErrors ) if len(args) < 1 && !restartOptions.Latest && !restartOptions.All { - return errors.Wrapf(define.ErrInvalidArg, "you must provide at least one container name or ID") + return fmt.Errorf("you must provide at least one container name or ID: %w", define.ErrInvalidArg) } if len(args) > 0 && restartOptions.Latest { - return errors.Wrapf(define.ErrInvalidArg, "--latest and containers cannot be used together") + return fmt.Errorf("--latest and containers cannot be used together: %w", define.ErrInvalidArg) } if cmd.Flag("time").Changed { diff --git a/cmd/podman/containers/restore.go b/cmd/podman/containers/restore.go index eeda5a05f..1e4745354 100644 --- a/cmd/podman/containers/restore.go +++ b/cmd/podman/containers/restore.go @@ -28,7 +28,7 @@ var ( Long: restoreDescription, RunE: restore, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) + return validate.CheckAllLatestAndIDFile(cmd, args, true, "") }, ValidArgsFunction: common.AutocompleteContainersAndImages, Example: `podman container restore ctrID diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index 420e3c38d..9fa688d23 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -2,6 +2,7 @@ package containers import ( "context" + "errors" "fmt" "io/ioutil" "strings" @@ -13,7 +14,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -28,7 +28,7 @@ var ( Long: rmDescription, RunE: rm, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "cidfile") }, ValidArgsFunction: common.AutocompleteContainers, Example: `podman rm imageID @@ -104,7 +104,7 @@ func rm(cmd *cobra.Command, args []string) error { for _, cidFile := range cidFiles { content, err := ioutil.ReadFile(cidFile) if err != nil { - return errors.Wrap(err, "error reading CIDFile") + return fmt.Errorf("error reading CIDFile: %w", err) } id := strings.Split(string(content), "\n")[0] args = append(args, id) @@ -123,9 +123,7 @@ func rm(cmd *cobra.Command, args []string) error { // removeContainers will set the exit code according to the `podman-rm` man // page. func removeContainers(namesOrIDs []string, rmOptions entities.RmOptions, setExit bool) error { - var ( - errs utils.OutputErrors - ) + var errs utils.OutputErrors responses, err := registry.ContainerEngine().ContainerRm(context.Background(), namesOrIDs, rmOptions) if err != nil { if setExit { @@ -135,8 +133,7 @@ func removeContainers(namesOrIDs []string, rmOptions entities.RmOptions, setExit } for _, r := range responses { if r.Err != nil { - // TODO this will not work with the remote client - if errors.Cause(err) == define.ErrWillDeadlock { + if errors.Is(r.Err, define.ErrWillDeadlock) { logrus.Errorf("Potential deadlock detected - please run 'podman system renumber' to resolve") } if setExit { @@ -155,15 +152,9 @@ func setExitCode(err error) { if registry.GetExitCode() == 1 { return } - cause := errors.Cause(err) - switch { - case cause == define.ErrNoSuchCtr: + if errors.Is(err, define.ErrNoSuchCtr) || strings.Contains(err.Error(), define.ErrNoSuchCtr.Error()) { registry.SetExitCode(1) - case strings.Contains(cause.Error(), define.ErrNoSuchCtr.Error()): - registry.SetExitCode(1) - case cause == define.ErrCtrStateInvalid: - registry.SetExitCode(2) - case strings.Contains(cause.Error(), define.ErrCtrStateInvalid.Error()): + } else if errors.Is(err, define.ErrCtrStateInvalid) || strings.Contains(err.Error(), define.ErrCtrStateInvalid.Error()) { registry.SetExitCode(2) } } diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index 951981293..ef13ea95e 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -15,7 +15,6 @@ import ( "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/pkg/specgen" "github.com/containers/podman/v4/pkg/specgenutil" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/term" @@ -61,6 +60,7 @@ func runFlags(cmd *cobra.Command) { flags := cmd.Flags() flags.SetInterspersed(false) + common.DefineCreateDefaults(&cliVals) common.DefineCreateFlags(cmd, &cliVals, false, false) common.DefineNetFlags(cmd) @@ -109,7 +109,9 @@ func init() { } func run(cmd *cobra.Command, args []string) error { - var err error + if err := commonFlags(cmd); err != nil { + return err + } // TODO: Breaking change should be made fatal in next major Release if cliVals.TTY && cliVals.Interactive && !term.IsTerminal(int(os.Stdin.Fd())) { @@ -122,20 +124,16 @@ func run(cmd *cobra.Command, args []string) error { } } - flags := cmd.Flags() - cliVals.Net, err = common.NetFlagsToNetOptions(nil, *flags) - if err != nil { - return err - } runOpts.CIDFile = cliVals.CIDFile runOpts.Rm = cliVals.Rm - if cliVals, err = CreateInit(cmd, cliVals, false); err != nil { + cliVals, err := CreateInit(cmd, cliVals, false) + if err != nil { return err } for fd := 3; fd < int(3+runOpts.PreserveFDs); fd++ { if !rootless.IsFdInherited(fd) { - return errors.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd) + return fmt.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd) } } @@ -143,7 +141,7 @@ func run(cmd *cobra.Command, args []string) error { rawImageName := "" if !cliVals.RootFS { rawImageName = args[0] - name, err := PullImage(args[0], cliVals) + name, err := PullImage(args[0], &cliVals) if err != nil { return err } @@ -166,7 +164,7 @@ func run(cmd *cobra.Command, args []string) error { // If attach is set, clear stdin/stdout/stderr and only attach requested if cmd.Flag("attach").Changed { if passthrough { - return errors.Wrapf(define.ErrInvalidArg, "cannot specify --attach with --log-driver=passthrough") + return fmt.Errorf("cannot specify --attach with --log-driver=passthrough: %w", define.ErrInvalidArg) } runOpts.OutputStream = nil runOpts.ErrorStream = nil @@ -183,7 +181,7 @@ func run(cmd *cobra.Command, args []string) error { case "stdin": runOpts.InputStream = os.Stdin default: - return errors.Wrapf(define.ErrInvalidArg, "invalid stream %q for --attach - must be one of stdin, stdout, or stderr", stream) + return fmt.Errorf("invalid stream %q for --attach - must be one of stdin, stdout, or stderr: %w", stream, define.ErrInvalidArg) } } } @@ -194,6 +192,9 @@ func run(cmd *cobra.Command, args []string) error { return err } s.RawImageName = rawImageName + s.ImageOS = cliVals.OS + s.ImageArch = cliVals.Arch + s.ImageVariant = cliVals.Variant s.Passwd = &runOpts.Passwd runOpts.Spec = s diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go index b70e975b7..cd4fa17b8 100644 --- a/cmd/podman/containers/start.go +++ b/cmd/podman/containers/start.go @@ -1,6 +1,7 @@ package containers import ( + "errors" "fmt" "os" "strings" @@ -11,7 +12,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -88,19 +88,19 @@ func validateStart(cmd *cobra.Command, args []string) error { return errors.New("start requires at least one argument") } if startOptions.All && startOptions.Latest { - return errors.Errorf("--all and --latest cannot be used together") + return errors.New("--all and --latest cannot be used together") } if len(args) > 0 && startOptions.Latest { - return errors.Errorf("--latest and containers cannot be used together") + return errors.New("--latest and containers cannot be used together") } if len(args) > 1 && startOptions.Attach { - return errors.Errorf("you cannot start and attach multiple containers at once") + return errors.New("you cannot start and attach multiple containers at once") } if (len(args) > 0 || startOptions.Latest) && startOptions.All { - return errors.Errorf("either start all containers or the container(s) provided in the arguments") + return errors.New("either start all containers or the container(s) provided in the arguments") } if startOptions.Attach && startOptions.All { - return errors.Errorf("you cannot start and attach all containers at once") + return errors.New("you cannot start and attach all containers at once") } return nil } @@ -114,7 +114,7 @@ func start(cmd *cobra.Command, args []string) error { startOptions.SigProxy = sigProxy if sigProxy && !startOptions.Attach { - return errors.Wrapf(define.ErrInvalidArg, "you cannot use sig-proxy without --attach") + return fmt.Errorf("you cannot use sig-proxy without --attach: %w", define.ErrInvalidArg) } if startOptions.Attach { startOptions.Stdin = os.Stdin @@ -127,7 +127,7 @@ func start(cmd *cobra.Command, args []string) error { for _, f := range filters { split := strings.SplitN(f, "=", 2) if len(split) == 1 { - return errors.Errorf("invalid filter %q", f) + return fmt.Errorf("invalid filter %q", f) } startOptions.Filters[split[0]] = append(startOptions.Filters[split[0]], split[1]) } diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index 500671d31..0dd8ce80a 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -1,6 +1,7 @@ package containers import ( + "errors" "fmt" "os" @@ -14,7 +15,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/utils" "github.com/docker/go-units" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -105,7 +105,7 @@ func checkStatOptions(cmd *cobra.Command, args []string) error { opts++ } if opts > 1 { - return errors.Errorf("--all, --latest and containers cannot be used together") + return errors.New("--all, --latest and containers cannot be used together") } return nil } @@ -214,10 +214,6 @@ func (s *containerStats) BlockIO() string { } func (s *containerStats) PIDS() string { - if s.PIDs == 0 { - // If things go bazinga, return a safe value - return "--" - } return fmt.Sprintf("%d", s.PIDs) } @@ -231,7 +227,7 @@ func (s *containerStats) MemUsageBytes() string { func floatToPercentString(f float64) string { strippedFloat, err := utils.RemoveScientificNotationFromFloat(f) - if err != nil || strippedFloat == 0 { + if err != nil { // If things go bazinga, return a safe value return "--" } @@ -239,25 +235,19 @@ func floatToPercentString(f float64) string { } func combineHumanValues(a, b uint64) string { - if a == 0 && b == 0 { - return "-- / --" - } return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b))) } func combineBytesValues(a, b uint64) string { - if a == 0 && b == 0 { - return "-- / --" - } return fmt.Sprintf("%s / %s", units.BytesSize(float64(a)), units.BytesSize(float64(b))) } func outputJSON(stats []containerStats) error { type jstat struct { - Id string `json:"id"` // nolint + Id string `json:"id"` //nolint:revive,stylecheck Name string `json:"name"` CPUTime string `json:"cpu_time"` - CpuPercent string `json:"cpu_percent"` // nolint + CpuPercent string `json:"cpu_percent"` //nolint:revive,stylecheck AverageCPU string `json:"avg_cpu"` MemUsage string `json:"mem_usage"` MemPerc string `json:"mem_percent"` diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go index af2250abb..2ddd169a1 100644 --- a/cmd/podman/containers/stop.go +++ b/cmd/podman/containers/stop.go @@ -12,7 +12,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -26,7 +25,7 @@ var ( Long: stopDescription, RunE: stop, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "cidfile") }, ValidArgsFunction: common.AutocompleteContainersRunning, Example: `podman stop ctrID @@ -40,7 +39,7 @@ var ( Long: stopCommand.Long, RunE: stopCommand.RunE, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "cidfile") }, ValidArgsFunction: stopCommand.ValidArgsFunction, Example: `podman container stop ctrID @@ -102,7 +101,7 @@ func stop(cmd *cobra.Command, args []string) error { for _, cidFile := range cidFiles { content, err := ioutil.ReadFile(cidFile) if err != nil { - return errors.Wrap(err, "error reading CIDFile") + return fmt.Errorf("error reading CIDFile: %w", err) } id := strings.Split(string(content), "\n")[0] args = append(args, id) diff --git a/cmd/podman/containers/top.go b/cmd/podman/containers/top.go index 389034e37..9340acd9a 100644 --- a/cmd/podman/containers/top.go +++ b/cmd/podman/containers/top.go @@ -2,6 +2,7 @@ package containers import ( "context" + "errors" "fmt" "os" "strings" @@ -12,7 +13,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -88,7 +88,7 @@ func top(cmd *cobra.Command, args []string) error { } if len(args) < 1 && !topOptions.Latest { - return errors.Errorf("you must provide the name or id of a running container") + return errors.New("you must provide the name or id of a running container") } if topOptions.Latest { diff --git a/cmd/podman/containers/unmount.go b/cmd/podman/containers/unmount.go index 26b8cfcc5..6869de2e2 100644 --- a/cmd/podman/containers/unmount.go +++ b/cmd/podman/containers/unmount.go @@ -27,7 +27,7 @@ var ( Long: description, RunE: unmount, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompleteContainers, Example: `podman unmount ctrID @@ -43,7 +43,7 @@ var ( Long: unmountCommand.Long, RunE: unmountCommand.RunE, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompleteContainers, Example: `podman container unmount ctrID diff --git a/cmd/podman/containers/unpause.go b/cmd/podman/containers/unpause.go index eaf50f2c7..a5375e737 100644 --- a/cmd/podman/containers/unpause.go +++ b/cmd/podman/containers/unpause.go @@ -2,6 +2,7 @@ package containers import ( "context" + "errors" "fmt" "github.com/containers/common/pkg/cgroups" @@ -10,7 +11,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/rootless" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -70,7 +70,7 @@ func unpause(cmd *cobra.Command, args []string) error { } } if len(args) < 1 && !unPauseOptions.All { - return errors.Errorf("you must provide at least one container name or id") + return errors.New("you must provide at least one container name or id") } responses, err := registry.ContainerEngine().ContainerUnpause(context.Background(), args, unPauseOptions) if err != nil { diff --git a/cmd/podman/containers/wait.go b/cmd/podman/containers/wait.go index 720a696ce..5b8480781 100644 --- a/cmd/podman/containers/wait.go +++ b/cmd/podman/containers/wait.go @@ -2,6 +2,7 @@ package containers import ( "context" + "errors" "fmt" "time" @@ -12,7 +13,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -86,7 +86,7 @@ func wait(cmd *cobra.Command, args []string) error { } if !waitOptions.Latest && len(args) == 0 { - return errors.Errorf("%q requires a name, id, or the \"--latest\" flag", cmd.CommandPath()) + return fmt.Errorf("%q requires a name, id, or the \"--latest\" flag", cmd.CommandPath()) } if waitOptions.Latest && len(args) > 0 { return errors.New("--latest and containers cannot be used together") diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go index 7b78c8312..ec98fb5b5 100644 --- a/cmd/podman/diff.go +++ b/cmd/podman/diff.go @@ -35,9 +35,6 @@ func init() { Command: diffCmd, }) flags := diffCmd.Flags() - // FIXME: Why does this exists? It is not used anywhere. - flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") - _ = flags.MarkHidden("archive") formatFlagName := "format" flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format (json)") diff --git a/cmd/podman/diff/diff.go b/cmd/podman/diff/diff.go index a26502de9..06df767d0 100644 --- a/cmd/podman/diff/diff.go +++ b/cmd/podman/diff/diff.go @@ -2,6 +2,7 @@ package diff import ( "encoding/json" + "errors" "fmt" "os" @@ -9,11 +10,10 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/storage/pkg/archive" - "github.com/pkg/errors" "github.com/spf13/cobra" ) -func Diff(cmd *cobra.Command, args []string, options entities.DiffOptions) error { +func Diff(_ *cobra.Command, args []string, options entities.DiffOptions) error { results, err := registry.ContainerEngine().Diff(registry.GetContext(), args, options) if err != nil { return err @@ -46,7 +46,7 @@ func changesToJSON(diffs *entities.DiffReport) error { case archive.ChangeModify: body.Changed = append(body.Changed, row.Path) default: - return errors.Errorf("output kind %q not recognized", row.Kind) + return fmt.Errorf("output kind %q not recognized", row.Kind) } } @@ -63,7 +63,7 @@ func changesToTable(diffs *entities.DiffReport) error { return nil } -// IDOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag +// ValidateContainerDiffArgs used to validate a nameOrId was provided or the "--latest" flag func ValidateContainerDiffArgs(cmd *cobra.Command, args []string) error { given, _ := cmd.Flags().GetBool("latest") if len(args) > 0 && !given { @@ -73,7 +73,7 @@ func ValidateContainerDiffArgs(cmd *cobra.Command, args []string) error { return errors.New("--latest and containers cannot be used together") } if len(args) == 0 && !given { - return errors.Errorf("%q requires a name, id, or the \"--latest\" flag", cmd.CommandPath()) + return fmt.Errorf("%q requires a name, id, or the \"--latest\" flag", cmd.CommandPath()) } return nil } diff --git a/cmd/podman/early_init_linux.go b/cmd/podman/early_init_linux.go index dfd9d1e30..e26c4264e 100644 --- a/cmd/podman/early_init_linux.go +++ b/cmd/podman/early_init_linux.go @@ -6,7 +6,6 @@ import ( "syscall" "github.com/containers/podman/v4/libpod/define" - "github.com/pkg/errors" ) func setRLimits() error { @@ -15,11 +14,11 @@ func setRLimits() error { rlimits.Max = define.RLimitDefaultValue if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - return errors.Wrapf(err, "error getting rlimits") + return fmt.Errorf("error getting rlimits: %w", err) } rlimits.Cur = rlimits.Max if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - return errors.Wrapf(err, "error setting new rlimits") + return fmt.Errorf("error setting new rlimits: %w", err) } } return nil diff --git a/cmd/podman/generate/kube.go b/cmd/podman/generate/kube.go index c4c92799c..7bfc3dcf7 100644 --- a/cmd/podman/generate/kube.go +++ b/cmd/podman/generate/kube.go @@ -11,7 +11,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -68,10 +67,10 @@ func kube(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("filename") { if _, err := os.Stat(kubeFile); err == nil { - return errors.Errorf("cannot write to %q; file exists", kubeFile) + return fmt.Errorf("cannot write to %q; file exists", kubeFile) } if err := ioutil.WriteFile(kubeFile, content, 0644); err != nil { - return errors.Wrapf(err, "cannot write to %q", kubeFile) + return fmt.Errorf("cannot write to %q: %w", kubeFile, err) } return nil } diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go index 0dab6299d..1ece64a30 100644 --- a/cmd/podman/generate/systemd.go +++ b/cmd/podman/generate/systemd.go @@ -2,6 +2,7 @@ package pods import ( "encoding/json" + "errors" "fmt" "os" "path/filepath" @@ -13,7 +14,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/domain/entities" systemDefine "github.com/containers/podman/v4/pkg/systemd/define" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -156,7 +156,7 @@ func systemd(cmd *cobra.Command, args []string) error { if files { cwd, err := os.Getwd() if err != nil { - return errors.Wrap(err, "error getting current working directory") + return fmt.Errorf("error getting current working directory: %w", err) } for name, content := range reports.Units { path := filepath.Join(cwd, fmt.Sprintf("%s.service", name)) @@ -189,7 +189,7 @@ func systemd(cmd *cobra.Command, args []string) error { case format == "": return printDefault(reports.Units) default: - return errors.Errorf("unknown --format argument: %s", format) + return fmt.Errorf("unknown --format argument: %s", format) } } diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go index 3ea60e18a..9f1b86eb4 100644 --- a/cmd/podman/images/build.go +++ b/cmd/podman/images/build.go @@ -1,6 +1,7 @@ package images import ( + "errors" "fmt" "io" "io/ioutil" @@ -23,7 +24,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -191,15 +191,15 @@ func buildFlags(cmd *cobra.Command) { _ = flags.MarkHidden("compress") _ = flags.MarkHidden("volume") _ = flags.MarkHidden("output") + _ = flags.MarkHidden("logsplit") } } // build executes the build command. func build(cmd *cobra.Command, args []string) error { if (cmd.Flags().Changed("squash") && cmd.Flags().Changed("layers")) || - (cmd.Flags().Changed("squash-all") && cmd.Flags().Changed("layers")) || (cmd.Flags().Changed("squash-all") && cmd.Flags().Changed("squash")) { - return errors.New("cannot specify --squash, --squash-all and --layers options together") + return errors.New("cannot specify --squash with --layers and --squash-all with --squash") } if cmd.Flag("output").Changed && registry.IsRemote() { @@ -222,7 +222,7 @@ func build(cmd *cobra.Command, args []string) error { // The context directory could be a URL. Try to handle that. tempDir, subDir, err := buildahDefine.TempDirForURL("", "buildah", args[0]) if err != nil { - return errors.Wrapf(err, "error prepping temporary context directory") + return fmt.Errorf("error prepping temporary context directory: %w", err) } if tempDir != "" { // We had to download it to a temporary directory. @@ -237,7 +237,7 @@ func build(cmd *cobra.Command, args []string) error { // Nope, it was local. Use it as is. absDir, err := filepath.Abs(args[0]) if err != nil { - return errors.Wrapf(err, "error determining path to directory %q", args[0]) + return fmt.Errorf("error determining path to directory %q: %w", args[0], err) } contextDir = absDir } @@ -253,7 +253,7 @@ func build(cmd *cobra.Command, args []string) error { } absFile, err := filepath.Abs(containerFiles[i]) if err != nil { - return errors.Wrapf(err, "error determining path to file %q", containerFiles[i]) + return fmt.Errorf("error determining path to file %q: %w", containerFiles[i], err) } contextDir = filepath.Dir(absFile) containerFiles[i] = absFile @@ -262,10 +262,10 @@ func build(cmd *cobra.Command, args []string) error { } if contextDir == "" { - return errors.Errorf("no context directory and no Containerfile specified") + return errors.New("no context directory and no Containerfile specified") } if !utils.IsDir(contextDir) { - return errors.Errorf("context must be a directory: %q", contextDir) + return fmt.Errorf("context must be a directory: %q", contextDir) } if len(containerFiles) == 0 { if utils.FileExists(filepath.Join(contextDir, "Containerfile")) { @@ -296,14 +296,15 @@ func build(cmd *cobra.Command, args []string) error { if registry.IsRemote() { // errors from server does not contain ExitCode // so parse exit code from error message - remoteExitCode, parseErr := utils.ExitCodeFromBuildError(fmt.Sprint(errors.Cause(err))) + remoteExitCode, parseErr := utils.ExitCodeFromBuildError(err.Error()) if parseErr == nil { exitCode = remoteExitCode } } - if ee, ok := (errors.Cause(err)).(*exec.ExitError); ok { - exitCode = ee.ExitCode() + exitError := &exec.ExitError{} + if errors.As(err, &exitError) { + exitCode = exitError.ExitCode() } registry.SetExitCode(exitCode) @@ -356,7 +357,7 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil } if pullFlagsCount > 1 { - return nil, errors.Errorf("can only set one of 'pull' or 'pull-always' or 'pull-never'") + return nil, errors.New("can only set one of 'pull' or 'pull-always' or 'pull-never'") } // Allow for --pull, --pull=true, --pull=false, --pull=never, --pull=always @@ -418,7 +419,13 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil // Squash-all invoked, squash both new and old layers into one. if c.Flags().Changed("squash-all") { flags.Squash = true - flags.Layers = false + if !c.Flags().Changed("layers") { + // Buildah supports using layers and --squash together + // after https://github.com/containers/buildah/pull/3674 + // so podman must honor if user wants to still use layers + // with --squash-all. + flags.Layers = false + } } var stdin io.Reader @@ -442,22 +449,6 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil return nil, err } - // `buildah bud --layers=false` acts like `docker build --squash` does. - // That is all of the new layers created during the build process are - // condensed into one, any layers present prior to this build are retained - // without condensing. `buildah bud --squash` squashes both new and old - // layers down into one. Translate Podman commands into Buildah. - // Squash invoked, retain old layers, squash new layers into one. - if c.Flags().Changed("squash") && flags.Squash { - flags.Squash = false - flags.Layers = false - } - // Squash-all invoked, squash both new and old layers into one. - if c.Flags().Changed("squash-all") { - flags.Squash = true - flags.Layers = false - } - compression := buildahDefine.Gzip if flags.DisableCompression { compression = buildahDefine.Uncompressed @@ -487,7 +478,7 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil case strings.HasPrefix(flags.Format, buildahDefine.DOCKER): format = buildahDefine.Dockerv2ImageManifest default: - return nil, errors.Errorf("unrecognized image type %q", flags.Format) + return nil, fmt.Errorf("unrecognized image type %q", flags.Format) } runtimeFlags := []string{} @@ -510,12 +501,29 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil decConfig, err := getDecryptConfig(flags.DecryptionKeys) if err != nil { - return nil, errors.Wrapf(err, "unable to obtain decrypt config") + return nil, fmt.Errorf("unable to obtain decrypt config: %w", err) + } + + additionalBuildContext := make(map[string]*buildahDefine.AdditionalBuildContext) + if c.Flag("build-context").Changed { + for _, contextString := range flags.BuildContext { + av := strings.SplitN(contextString, "=", 2) + if len(av) > 1 { + parseAdditionalBuildContext, err := parse.GetAdditionalBuildContext(av[1]) + if err != nil { + return nil, fmt.Errorf("while parsing additional build context: %w", err) + } + additionalBuildContext[av[0]] = &parseAdditionalBuildContext + } else { + return nil, fmt.Errorf("while parsing additional build context: %q, accepts value in the form of key=value", av) + } + } } opts := buildahDefine.BuildOptions{ AddCapabilities: flags.CapAdd, AdditionalTags: tags, + AdditionalBuildContexts: additionalBuildContext, AllPlatforms: flags.AllPlatforms, Annotations: flags.Annotation, Args: args, @@ -525,6 +533,7 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil Compression: compression, ConfigureNetwork: networkPolicy, ContextDirectory: contextDir, + CPPFlags: flags.CPPFlags, DefaultMountsFilePath: containerConfig.Containers.DefaultMountsFile, Devices: flags.Devices, DropCapabilities: flags.CapDrop, @@ -539,6 +548,8 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil Labels: flags.Label, Layers: flags.Layers, LogRusage: flags.LogRusage, + LogFile: flags.Logfile, + LogSplitByPlatform: flags.LogSplitByPlatform, Manifest: flags.Manifest, MaxPullPushRetries: 3, NamespaceOptions: nsValues, @@ -570,7 +581,7 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil if flags.IgnoreFile != "" { excludes, err := parseDockerignore(flags.IgnoreFile) if err != nil { - return nil, errors.Wrapf(err, "unable to obtain decrypt config") + return nil, fmt.Errorf("unable to obtain decrypt config: %w", err) } opts.Excludes = excludes } @@ -589,7 +600,7 @@ func getDecryptConfig(decryptionKeys []string) (*encconfig.DecryptConfig, error) // decryption dcc, err := enchelpers.CreateCryptoConfig([]string{}, decryptionKeys) if err != nil { - return nil, errors.Wrapf(err, "invalid decryption keys") + return nil, fmt.Errorf("invalid decryption keys: %w", err) } cc := encconfig.CombineCryptoConfigs([]encconfig.CryptoConfig{dcc}) decConfig = cc.DecryptConfig diff --git a/cmd/podman/images/diff.go b/cmd/podman/images/diff.go index 13a8f1d9d..a017d569d 100644 --- a/cmd/podman/images/diff.go +++ b/cmd/podman/images/diff.go @@ -34,9 +34,7 @@ func init() { } func diffFlags(flags *pflag.FlagSet) { - diffOpts = &entities.DiffOptions{} - flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") - _ = flags.MarkDeprecated("archive", "Provided for backwards compatibility, has no impact on output.") + diffOpts = new(entities.DiffOptions) formatFlagName := "format" flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format (json)") diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go index e190941e7..8f910f82d 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -13,7 +13,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/docker/go-units" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -132,7 +131,7 @@ func history(cmd *cobra.Command, args []string) error { }) if err := rpt.Execute(hdrs); err != nil { - return errors.Wrapf(err, "failed to write report column headers") + return fmt.Errorf("failed to write report column headers: %w", err) } } return rpt.Execute(hr) diff --git a/cmd/podman/images/import.go b/cmd/podman/images/import.go index 1910fef6d..8343a0bda 100644 --- a/cmd/podman/images/import.go +++ b/cmd/podman/images/import.go @@ -2,6 +2,7 @@ package images import ( "context" + "errors" "fmt" "io" "io/ioutil" @@ -14,7 +15,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/hashicorp/go-multierror" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -102,7 +102,7 @@ func importCon(cmd *cobra.Command, args []string) error { ) switch len(args) { case 0: - return errors.Errorf("need to give the path to the tarball, or must specify a tarball of '-' for stdin") + return errors.New("need to give the path to the tarball, or must specify a tarball of '-' for stdin") case 1: source = args[0] case 2: @@ -112,20 +112,20 @@ func importCon(cmd *cobra.Command, args []string) error { // instead of the localhost ones reference = args[1] default: - return errors.Errorf("too many arguments. Usage TARBALL [REFERENCE]") + return errors.New("too many arguments. Usage TARBALL [REFERENCE]") } if source == "-" { outFile, err := ioutil.TempFile("", "podman") if err != nil { - return errors.Errorf("creating file %v", err) + return fmt.Errorf("creating file %v", err) } defer os.Remove(outFile.Name()) defer outFile.Close() _, err = io.Copy(outFile, os.Stdin) if err != nil { - return errors.Errorf("copying file %v", err) + return fmt.Errorf("copying file %v", err) } source = outFile.Name() } diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index 81011f9b1..94d8412e5 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -1,6 +1,7 @@ package images import ( + "errors" "fmt" "os" "sort" @@ -15,7 +16,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/docker/go-units" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -225,7 +225,7 @@ func sortImages(imageS []*entities.ImageSummary) ([]imageReporter, error) { h.ImageSummary = *e h.Repository, h.Tag, err = tokenRepoTag(tag) if err != nil { - return nil, errors.Wrapf(err, "error parsing repository tag: %q", tag) + return nil, fmt.Errorf("error parsing repository tag: %q: %w", tag, err) } if h.Tag == "<none>" { untagged = append(untagged, h) diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go index dbb7c32fa..367b628c7 100644 --- a/cmd/podman/images/load.go +++ b/cmd/podman/images/load.go @@ -2,6 +2,7 @@ package images import ( "context" + "errors" "fmt" "io" "io/ioutil" @@ -14,7 +15,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/term" ) @@ -91,18 +91,18 @@ func load(cmd *cobra.Command, args []string) error { } } else { if term.IsTerminal(int(os.Stdin.Fd())) { - return errors.Errorf("cannot read from terminal, use command-line redirection or the --input flag") + return errors.New("cannot read from terminal, use command-line redirection or the --input flag") } outFile, err := ioutil.TempFile(util.Tmpdir(), "podman") if err != nil { - return errors.Errorf("creating file %v", err) + return fmt.Errorf("creating file %v", err) } defer os.Remove(outFile.Name()) defer outFile.Close() _, err = io.Copy(outFile, os.Stdin) if err != nil { - return errors.Errorf("copying file %v", err) + return fmt.Errorf("copying file %v", err) } loadOpts.Input = outFile.Name() } @@ -110,6 +110,6 @@ func load(cmd *cobra.Command, args []string) error { if err != nil { return err } - fmt.Println("Loaded image(s): " + strings.Join(response.Names, ",")) + fmt.Println("Loaded image: " + strings.Join(response.Names, "\nLoaded image: ")) return nil } diff --git a/cmd/podman/images/mount.go b/cmd/podman/images/mount.go index d5ab3d274..cd54e24ae 100644 --- a/cmd/podman/images/mount.go +++ b/cmd/podman/images/mount.go @@ -1,15 +1,14 @@ package images import ( + "errors" "fmt" "os" "github.com/containers/common/pkg/report" "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" - "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -71,16 +70,12 @@ func mount(cmd *cobra.Command, args []string) error { return err } - if len(args) > 0 || mountOpts.All { - var errs utils.OutputErrors - for _, r := range reports { - if r.Err == nil { - fmt.Println(r.Path) - continue - } - errs = append(errs, r.Err) + if len(args) == 1 && mountOpts.Format == "" && !mountOpts.All { + if len(reports) != 1 { + return fmt.Errorf("internal error: expected 1 report but got %d", len(reports)) } - return errs.PrintErrors() + fmt.Println(reports[0].Path) + return nil } switch { @@ -89,7 +84,7 @@ func mount(cmd *cobra.Command, args []string) error { case mountOpts.Format == "": break // see default format below default: - return errors.Errorf("unknown --format argument: %q", mountOpts.Format) + return fmt.Errorf("unknown --format argument: %q", mountOpts.Format) } mrs := make([]mountReporter, 0, len(reports)) diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index a7da5518a..6e3ec1517 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -1,6 +1,7 @@ package images import ( + "errors" "fmt" "os" "strings" @@ -13,7 +14,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -138,7 +138,7 @@ func imagePull(cmd *cobra.Command, args []string) error { } if platform != "" { if pullOptions.Arch != "" || pullOptions.OS != "" { - return errors.Errorf("--platform option can not be specified with --arch or --os") + return errors.New("--platform option can not be specified with --arch or --os") } split := strings.SplitN(platform, "/", 2) pullOptions.OS = split[0] diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go index a59bdd93c..1b3419014 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -117,7 +117,6 @@ func pushFlags(cmd *cobra.Command) { _ = flags.MarkHidden("compress") _ = flags.MarkHidden("digestfile") _ = flags.MarkHidden("quiet") - _ = flags.MarkHidden("remove-signatures") _ = flags.MarkHidden("sign-by") } if !registry.IsRemote() { diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go index 13dab62d4..18b22e51d 100644 --- a/cmd/podman/images/rm.go +++ b/cmd/podman/images/rm.go @@ -1,13 +1,13 @@ package images import ( + "errors" "fmt" "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/errorhandling" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -62,10 +62,10 @@ func imageRemoveFlagSet(flags *pflag.FlagSet) { func rm(cmd *cobra.Command, args []string) error { if len(args) < 1 && !imageOpts.All { - return errors.Errorf("image name or ID must be specified") + return errors.New("image name or ID must be specified") } if len(args) > 0 && imageOpts.All { - return errors.Errorf("when using the --all switch, you may not pass any images names or IDs") + return errors.New("when using the --all switch, you may not pass any images names or IDs") } // Note: certain image-removal errors are non-fatal. Hence, the report diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go index 3394c2e99..43366e1b3 100644 --- a/cmd/podman/images/save.go +++ b/cmd/podman/images/save.go @@ -2,17 +2,18 @@ package images import ( "context" + "errors" + "fmt" "os" "strings" "github.com/containers/common/pkg/completion" + "github.com/containers/common/pkg/util" "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/parse" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/containers/podman/v4/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/term" ) @@ -31,14 +32,14 @@ var ( RunE: save, Args: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - return errors.Errorf("need at least 1 argument") + return errors.New("need at least 1 argument") } format, err := cmd.Flags().GetString("format") if err != nil { return err } if !util.StringInSlice(format, common.ValidSaveFormats) { - return errors.Errorf("format value must be one of %s", strings.Join(common.ValidSaveFormats, " ")) + return fmt.Errorf("format value must be one of %s", strings.Join(common.ValidSaveFormats, " ")) } return nil }, @@ -103,13 +104,13 @@ func save(cmd *cobra.Command, args []string) (finalErr error) { succeeded = false ) if cmd.Flag("compress").Changed && (saveOpts.Format != define.OCIManifestDir && saveOpts.Format != define.V2s2ManifestDir) { - return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'") + return errors.New("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'") } if len(saveOpts.Output) == 0 { saveOpts.Quiet = true fi := os.Stdout if term.IsTerminal(int(fi.Fd())) { - return errors.Errorf("refusing to save to terminal. Use -o flag or redirect") + return errors.New("refusing to save to terminal. Use -o flag or redirect") } pipePath, cleanup, err := setupPipe() if err != nil { diff --git a/cmd/podman/images/scp.go b/cmd/podman/images/scp.go index 3dbc9c331..a7aa43e61 100644 --- a/cmd/podman/images/scp.go +++ b/cmd/podman/images/scp.go @@ -1,28 +1,12 @@ package images import ( - "context" - "fmt" - "io/ioutil" - urlP "net/url" "os" - "os/exec" - "os/user" - "strconv" "strings" - "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" - "github.com/containers/podman/v4/cmd/podman/system/connection" - "github.com/containers/podman/v4/libpod/define" - "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/containers/podman/v4/utils" - scpD "github.com/dtylman/scp" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "golang.org/x/crypto/ssh" ) var ( @@ -32,7 +16,6 @@ var ( Annotations: map[string]string{ registry.UnshareNSRequired: "", registry.ParentNSRequired: "", - registry.EngineMode: registry.ABIMode, }, Long: saveScpDescription, Short: "securely copy images", @@ -46,9 +29,6 @@ var ( var ( parentFlags []string quiet bool - source entities.ImageScpOptions - dest entities.ImageScpOptions - sshInfo entities.ImageScpConnections ) func init() { @@ -66,7 +46,6 @@ func scpFlags(cmd *cobra.Command) { func scp(cmd *cobra.Command, args []string) (finalErr error) { var ( - // TODO add tag support for images err error ) for i, val := range os.Args { @@ -81,288 +60,17 @@ func scp(cmd *cobra.Command, args []string) (finalErr error) { } parentFlags = append(parentFlags, val) } - podman, err := os.Executable() - if err != nil { - return err - } - f, err := ioutil.TempFile("", "podman") // open temp file for load/save output - if err != nil { - return err - } - confR, err := config.NewConfig("") // create a hand made config for the remote engine since we might use remote and native at once - if err != nil { - return errors.Wrapf(err, "could not make config") - } - - abiEng, err := registry.NewImageEngine(cmd, args) // abi native engine - if err != nil { - return err - } - - cfg, err := config.ReadCustomConfig() // get ready to set ssh destination if necessary - if err != nil { - return err - } - locations := []*entities.ImageScpOptions{} - cliConnections := []string{} - var flipConnections bool - for _, arg := range args { - loc, connect, err := parseImageSCPArg(arg) - if err != nil { - return err - } - locations = append(locations, loc) - cliConnections = append(cliConnections, connect...) - } - source = *locations[0] - switch { - case len(locations) > 1: - if flipConnections, err = validateSCPArgs(locations); err != nil { - return err - } - if flipConnections { // the order of cliConnections matters, we need to flip both arrays since the args are parsed separately sometimes. - cliConnections[0], cliConnections[1] = cliConnections[1], cliConnections[0] - locations[0], locations[1] = locations[1], locations[0] - } - dest = *locations[1] - case len(locations) == 1: - switch { - case len(locations[0].Image) == 0: - return errors.Wrapf(define.ErrInvalidArg, "no source image specified") - case len(locations[0].Image) > 0 && !locations[0].Remote && len(locations[0].User) == 0: // if we have podman image scp $IMAGE - return errors.Wrapf(define.ErrInvalidArg, "must specify a destination") - } - } - - source.Quiet = quiet - source.File = f.Name() // after parsing the arguments, set the file for the save/load - dest.File = source.File - if err = os.Remove(source.File); err != nil { // remove the file and simply use its name so podman creates the file upon save. avoids umask errors - return err - } - - allLocal := true // if we are all localhost, do not validate connections but if we are using one localhost and one non we need to use sshd - for _, val := range cliConnections { - if !strings.Contains(val, "@localhost::") { - allLocal = false - break - } - } - if allLocal { - cliConnections = []string{} - } - - var serv map[string]config.Destination - serv, err = GetServiceInformation(cliConnections, cfg) - if err != nil { - return err - } - - // TODO: Add podman remote support - confR.Engine = config.EngineConfig{Remote: true, CgroupManager: "cgroupfs", ServiceDestinations: serv} // pass the service dest (either remote or something else) to engine - saveCmd, loadCmd := createCommands(podman) - switch { - case source.Remote: // if we want to load FROM the remote, dest can either be local or remote in this case - err = saveToRemote(source.Image, source.File, "", sshInfo.URI[0], sshInfo.Identities[0]) - if err != nil { - return err - } - if dest.Remote { // we want to load remote -> remote, both source and dest are remote - rep, err := loadToRemote(dest.File, "", sshInfo.URI[1], sshInfo.Identities[1]) - if err != nil { - return err - } - fmt.Println(rep) - break - } - err = execPodman(podman, loadCmd) - if err != nil { - return err - } - case dest.Remote: // remote host load, implies source is local - err = execPodman(podman, saveCmd) - if err != nil { - return err - } - rep, err := loadToRemote(source.File, "", sshInfo.URI[0], sshInfo.Identities[0]) - if err != nil { - return err - } - fmt.Println(rep) - if err = os.Remove(source.File); err != nil { - return err - } - // TODO: Add podman remote support - default: // else native load, both source and dest are local and transferring between users - if source.User == "" { // source user has to be set, destination does not - source.User = os.Getenv("USER") - if source.User == "" { - u, err := user.Current() - if err != nil { - return errors.Wrapf(err, "could not obtain user, make sure the environmental variable $USER is set") - } - source.User = u.Username - } - } - err := abiEng.Transfer(context.Background(), source, dest, parentFlags) - if err != nil { - return err - } - } - return nil -} - -// loadToRemote takes image and remote connection information. it connects to the specified client -// and copies the saved image dir over to the remote host and then loads it onto the machine -// returns a string containing output or an error -func loadToRemote(localFile string, tag string, url *urlP.URL, iden string) (string, error) { - dial, remoteFile, err := createConnection(url, iden) - if err != nil { - return "", err - } - defer dial.Close() - - n, err := scpD.CopyTo(dial, localFile, remoteFile) - if err != nil { - errOut := strconv.Itoa(int(n)) + " Bytes copied before error" - return " ", errors.Wrapf(err, errOut) - } - var run string - if tag != "" { - return "", errors.Wrapf(define.ErrInvalidArg, "Renaming of an image is currently not supported") - } - podman := os.Args[0] - run = podman + " image load --input=" + remoteFile + ";rm " + remoteFile // run ssh image load of the file copied via scp - out, err := connection.ExecRemoteCommand(dial, run) - if err != nil { - return "", err + src := args[0] + dst := "" + if len(args) > 1 { + dst = args[1] } - return strings.TrimSuffix(string(out), "\n"), nil -} - -// saveToRemote takes image information and remote connection information. it connects to the specified client -// and saves the specified image on the remote machine and then copies it to the specified local location -// returns an error if one occurs. -func saveToRemote(image, localFile string, tag string, uri *urlP.URL, iden string) error { - dial, remoteFile, err := createConnection(uri, iden) + err = registry.ImageEngine().Scp(registry.Context(), src, dst, parentFlags, quiet) if err != nil { return err } - defer dial.Close() - if tag != "" { - return errors.Wrapf(define.ErrInvalidArg, "Renaming of an image is currently not supported") - } - podman := os.Args[0] - run := podman + " image save " + image + " --format=oci-archive --output=" + remoteFile // run ssh image load of the file copied via scp. Files are reverse in this case... - _, err = connection.ExecRemoteCommand(dial, run) - if err != nil { - return err - } - n, err := scpD.CopyFrom(dial, remoteFile, localFile) - if _, conErr := connection.ExecRemoteCommand(dial, "rm "+remoteFile); conErr != nil { - logrus.Errorf("Removing file on endpoint: %v", conErr) - } - if err != nil { - errOut := strconv.Itoa(int(n)) + " Bytes copied before error" - return errors.Wrapf(err, errOut) - } return nil } - -// makeRemoteFile creates the necessary remote file on the host to -// save or load the image to. returns a string with the file name or an error -func makeRemoteFile(dial *ssh.Client) (string, error) { - run := "mktemp" - remoteFile, err := connection.ExecRemoteCommand(dial, run) - if err != nil { - return "", err - } - return strings.TrimSuffix(string(remoteFile), "\n"), nil -} - -// createConnections takes a boolean determining which ssh client to dial -// and returns the dials client, its newly opened remote file, and an error if applicable. -func createConnection(url *urlP.URL, iden string) (*ssh.Client, string, error) { - cfg, err := connection.ValidateAndConfigure(url, iden) - if err != nil { - return nil, "", err - } - dialAdd, err := ssh.Dial("tcp", url.Host, cfg) // dial the client - if err != nil { - return nil, "", errors.Wrapf(err, "failed to connect") - } - file, err := makeRemoteFile(dialAdd) - if err != nil { - return nil, "", err - } - - return dialAdd, file, nil -} - -// GetSerivceInformation takes the parsed list of hosts to connect to and validates the information -func GetServiceInformation(cliConnections []string, cfg *config.Config) (map[string]config.Destination, error) { - var serv map[string]config.Destination - var url string - var iden string - for i, val := range cliConnections { - splitEnv := strings.SplitN(val, "::", 2) - sshInfo.Connections = append(sshInfo.Connections, splitEnv[0]) - if len(splitEnv[1]) != 0 { - err := validateImageName(splitEnv[1]) - if err != nil { - return nil, err - } - source.Image = splitEnv[1] - //TODO: actually use the new name given by the user - } - conn, found := cfg.Engine.ServiceDestinations[sshInfo.Connections[i]] - if found { - url = conn.URI - iden = conn.Identity - } else { // no match, warn user and do a manual connection. - url = "ssh://" + sshInfo.Connections[i] - iden = "" - logrus.Warnf("Unknown connection name given. Please use system connection add to specify the default remote socket location") - } - urlT, err := urlP.Parse(url) // create an actual url to pass to exec command - if err != nil { - return nil, err - } - if urlT.User.Username() == "" { - if urlT.User, err = connection.GetUserInfo(urlT); err != nil { - return nil, err - } - } - sshInfo.URI = append(sshInfo.URI, urlT) - sshInfo.Identities = append(sshInfo.Identities, iden) - } - return serv, nil -} - -// execPodman executes the podman save/load command given the podman binary -func execPodman(podman string, command []string) error { - cmd := exec.Command(podman) - utils.CreateSCPCommand(cmd, command[1:]) - logrus.Debugf("Executing podman command: %q", cmd) - return cmd.Run() -} - -// createCommands forms the podman save and load commands used by SCP -func createCommands(podman string) ([]string, []string) { - var parentString string - quiet := "" - if source.Quiet { - quiet = "-q " - } - if len(parentFlags) > 0 { - parentString = strings.Join(parentFlags, " ") + " " // if there are parent args, an extra space needs to be added - } else { - parentString = strings.Join(parentFlags, " ") - } - loadCmd := strings.Split(fmt.Sprintf("%s %sload %s--input %s", podman, parentString, quiet, dest.File), " ") - saveCmd := strings.Split(fmt.Sprintf("%s %vsave %s--output %s %s", podman, parentString, quiet, source.File, source.Image), " ") - return saveCmd, loadCmd -} diff --git a/cmd/podman/images/scp_test.go b/cmd/podman/images/scp_test.go deleted file mode 100644 index 315fda2ab..000000000 --- a/cmd/podman/images/scp_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package images - -import ( - "testing" - - "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/stretchr/testify/assert" -) - -func TestParseSCPArgs(t *testing.T) { - args := []string{"alpine", "root@localhost::"} - var source *entities.ImageScpOptions - var dest *entities.ImageScpOptions - var err error - source, _, err = parseImageSCPArg(args[0]) - assert.Nil(t, err) - assert.Equal(t, source.Image, "alpine") - - dest, _, err = parseImageSCPArg(args[1]) - assert.Nil(t, err) - assert.Equal(t, dest.Image, "") - assert.Equal(t, dest.User, "root") - - args = []string{"root@localhost::alpine"} - source, _, err = parseImageSCPArg(args[0]) - assert.Nil(t, err) - assert.Equal(t, source.User, "root") - assert.Equal(t, source.Image, "alpine") - - args = []string{"charliedoern@192.168.68.126::alpine", "foobar@192.168.68.126::"} - source, _, err = parseImageSCPArg(args[0]) - assert.Nil(t, err) - assert.True(t, source.Remote) - assert.Equal(t, source.Image, "alpine") - - dest, _, err = parseImageSCPArg(args[1]) - assert.Nil(t, err) - assert.True(t, dest.Remote) - assert.Equal(t, dest.Image, "") - - args = []string{"charliedoern@192.168.68.126::alpine"} - source, _, err = parseImageSCPArg(args[0]) - assert.Nil(t, err) - assert.True(t, source.Remote) - assert.Equal(t, source.Image, "alpine") -} diff --git a/cmd/podman/images/scp_utils.go b/cmd/podman/images/scp_utils.go deleted file mode 100644 index a85687a42..000000000 --- a/cmd/podman/images/scp_utils.go +++ /dev/null @@ -1,88 +0,0 @@ -package images - -import ( - "strings" - - "github.com/containers/image/v5/docker/reference" - "github.com/containers/podman/v4/libpod/define" - "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" -) - -// parseImageSCPArg returns the valid connection, and source/destination data based off of the information provided by the user -// arg is a string containing one of the cli arguments returned is a filled out source/destination options structs as well as a connections array and an error if applicable -func parseImageSCPArg(arg string) (*entities.ImageScpOptions, []string, error) { - location := entities.ImageScpOptions{} - var err error - cliConnections := []string{} - - switch { - case strings.Contains(arg, "@localhost::"): // image transfer between users - location.User = strings.Split(arg, "@")[0] - location, err = validateImagePortion(location, arg) - if err != nil { - return nil, nil, err - } - cliConnections = append(cliConnections, arg) - case strings.Contains(arg, "::"): - location, err = validateImagePortion(location, arg) - if err != nil { - return nil, nil, err - } - location.Remote = true - cliConnections = append(cliConnections, arg) - default: - location.Image = arg - } - return &location, cliConnections, nil -} - -// validateImagePortion is a helper function to validate the image name in an SCP argument -func validateImagePortion(location entities.ImageScpOptions, arg string) (entities.ImageScpOptions, error) { - if remoteArgLength(arg, 1) > 0 { - err := validateImageName(strings.Split(arg, "::")[1]) - if err != nil { - return location, err - } - location.Image = strings.Split(arg, "::")[1] // this will get checked/set again once we validate connections - } - return location, nil -} - -// validateSCPArgs takes the array of source and destination options and checks for common errors -func validateSCPArgs(locations []*entities.ImageScpOptions) (bool, error) { - if len(locations) > 2 { - return false, errors.Wrapf(define.ErrInvalidArg, "cannot specify more than two arguments") - } - switch { - case len(locations[0].Image) > 0 && len(locations[1].Image) > 0: - return false, errors.Wrapf(define.ErrInvalidArg, "cannot specify an image rename") - case len(locations[0].Image) == 0 && len(locations[1].Image) == 0: - return false, errors.Wrapf(define.ErrInvalidArg, "a source image must be specified") - case len(locations[0].Image) == 0 && len(locations[1].Image) != 0: - if locations[0].Remote && locations[1].Remote { - return true, nil // we need to flip the cliConnections array so the save/load connections are in the right place - } - } - return false, nil -} - -// validateImageName makes sure that the image given is valid and no injections are occurring -// we simply use this for error checking, bot setting the image -func validateImageName(input string) error { - // ParseNormalizedNamed transforms a shortname image into its - // full name reference so busybox => docker.io/library/busybox - // we want to keep our shortnames, so only return an error if - // we cannot parse what the user has given us - _, err := reference.ParseNormalizedNamed(input) - return err -} - -// remoteArgLength is a helper function to simplify the extracting of host argument data -// returns an int which contains the length of a specified index in a host::image string -func remoteArgLength(input string, side int) int { - if strings.Contains(input, "::") { - return len((strings.Split(input, "::"))[side]) - } - return -1 -} diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index 335ea2b5a..eb876d3d4 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -1,6 +1,7 @@ package images import ( + "errors" "fmt" "os" "strings" @@ -12,7 +13,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -83,8 +83,7 @@ func searchFlags(cmd *cobra.Command) { filterFlagName := "filter" flags.StringSliceVarP(&searchOptions.Filters, filterFlagName, "f", []string{}, "Filter output based on conditions provided (default [])") - // TODO add custom filter function - _ = cmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + _ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteImageSearchFilters) formatFlagName := "format" flags.StringVar(&searchOptions.Format, formatFlagName, "", "Change the output format to JSON or a Go template") @@ -112,11 +111,11 @@ func imageSearch(cmd *cobra.Command, args []string) error { case 1: searchTerm = args[0] default: - return errors.Errorf("search requires exactly one argument") + return errors.New("search requires exactly one argument") } if searchOptions.ListTags && len(searchOptions.Filters) != 0 { - return errors.Errorf("filters are not applicable to list tags result") + return errors.New("filters are not applicable to list tags result") } // TLS verification in c/image is controlled via a `types.OptionalBool` @@ -156,7 +155,7 @@ func imageSearch(cmd *cobra.Command, args []string) error { switch { case searchOptions.ListTags: if len(searchOptions.Filters) != 0 { - return errors.Errorf("filters are not applicable to list tags result") + return errors.New("filters are not applicable to list tags result") } if isJSON { listTagsEntries := buildListTagsJSON(searchReport) @@ -182,7 +181,7 @@ func imageSearch(cmd *cobra.Command, args []string) error { if rpt.RenderHeaders { hdrs := report.Headers(entities.ImageSearchReport{}, nil) if err := rpt.Execute(hdrs); err != nil { - return errors.Wrapf(err, "failed to write report column headers") + return fmt.Errorf("failed to write report column headers: %w", err) } } return rpt.Execute(searchReport) diff --git a/cmd/podman/images/sign.go b/cmd/podman/images/sign.go index e4e201894..beea6d2b8 100644 --- a/cmd/podman/images/sign.go +++ b/cmd/podman/images/sign.go @@ -1,6 +1,7 @@ package images import ( + "errors" "os" "github.com/containers/common/pkg/auth" @@ -8,7 +9,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -57,7 +57,7 @@ func init() { func sign(cmd *cobra.Command, args []string) error { if signOptions.SignBy == "" { - return errors.Errorf("please provide an identity") + return errors.New("please provide an identity") } var sigStoreDir string diff --git a/cmd/podman/images/trust_set.go b/cmd/podman/images/trust_set.go index fff035d12..832e9f724 100644 --- a/cmd/podman/images/trust_set.go +++ b/cmd/podman/images/trust_set.go @@ -1,15 +1,15 @@ package images import ( + "fmt" "net/url" "regexp" "github.com/containers/common/pkg/completion" + "github.com/containers/common/pkg/util" "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/containers/podman/v4/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -61,7 +61,7 @@ func setTrust(cmd *cobra.Command, args []string) error { } if !util.StringInSlice(setOptions.Type, validTrustTypes) { - return errors.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", setOptions.Type) + return fmt.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", setOptions.Type) } return registry.ImageEngine().SetTrust(registry.Context(), args, setOptions) } @@ -71,17 +71,17 @@ func isValidImageURI(imguri string) (bool, error) { uri := "http://" + imguri u, err := url.Parse(uri) if err != nil { - return false, errors.Wrapf(err, "invalid image uri: %s", imguri) + return false, fmt.Errorf("invalid image uri: %s: %w", imguri, err) } reg := regexp.MustCompile(`^[a-zA-Z0-9-_\.]+\/?:?[0-9]*[a-z0-9-\/:]*$`) ret := reg.FindAllString(u.Host, -1) if len(ret) == 0 { - return false, errors.Wrapf(err, "invalid image uri: %s", imguri) + return false, fmt.Errorf("invalid image uri: %s: %w", imguri, err) } reg = regexp.MustCompile(`^[a-z0-9-:\./]*$`) ret = reg.FindAllString(u.Fragment, -1) if len(ret) == 0 { - return false, errors.Wrapf(err, "invalid image uri: %s", imguri) + return false, fmt.Errorf("invalid image uri: %s: %w", imguri, err) } return true, nil } diff --git a/cmd/podman/images/unmount.go b/cmd/podman/images/unmount.go index 3ada09937..2a3df7cbd 100644 --- a/cmd/podman/images/unmount.go +++ b/cmd/podman/images/unmount.go @@ -1,13 +1,13 @@ package images import ( + "errors" "fmt" "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) diff --git a/cmd/podman/images/utils_linux.go b/cmd/podman/images/utils_linux.go index f7c159415..5923716ec 100644 --- a/cmd/podman/images/utils_linux.go +++ b/cmd/podman/images/utils_linux.go @@ -1,12 +1,12 @@ package images import ( + "fmt" "io" "io/ioutil" "os" "path/filepath" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) @@ -26,7 +26,7 @@ func setupPipe() (string, func() <-chan error, error) { if e := os.RemoveAll(pipeDir); e != nil { logrus.Errorf("Removing named pipe: %q", e) } - return "", nil, errors.Wrapf(err, "error creating named pipe") + return "", nil, fmt.Errorf("error creating named pipe: %w", err) } go func() { fpipe, err := os.Open(pipePath) diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go index f6e3fca06..edddf026e 100644 --- a/cmd/podman/inspect/inspect.go +++ b/cmd/podman/inspect/inspect.go @@ -3,6 +3,7 @@ package inspect import ( "context" "encoding/json" // due to a bug in json-iterator it cannot be used here + "errors" "fmt" "os" "regexp" @@ -16,7 +17,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -41,7 +41,7 @@ func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions { return &opts } -// Inspect inspects the specified container/image names or IDs. +// Inspect inspects the specified container/image/pod/volume names or IDs. func Inspect(namesOrIDs []string, options entities.InspectOptions) error { inspector, err := newInspector(options) if err != nil { @@ -64,19 +64,19 @@ func newInspector(options entities.InspectOptions) (*inspector, error) { case common.ImageType, common.ContainerType, common.AllType, common.PodType, common.NetworkType, common.VolumeType: // Valid types. default: - return nil, errors.Errorf("invalid type %q: must be %q, %q, %q, %q, %q, or %q", options.Type, + return nil, fmt.Errorf("invalid type %q: must be %q, %q, %q, %q, %q, or %q", options.Type, common.ImageType, common.ContainerType, common.PodType, common.NetworkType, common.VolumeType, common.AllType) } if options.Type == common.ImageType { if options.Latest { - return nil, errors.Errorf("latest is not supported for type %q", common.ImageType) + return nil, fmt.Errorf("latest is not supported for type %q", common.ImageType) } if options.Size { - return nil, errors.Errorf("size is not supported for type %q", common.ImageType) + return nil, fmt.Errorf("size is not supported for type %q", common.ImageType) } } if options.Type == common.PodType && options.Size { - return nil, errors.Errorf("size is not supported for type %q", common.PodType) + return nil, fmt.Errorf("size is not supported for type %q", common.PodType) } podOpts := entities.PodInspectOptions{ Latest: options.Latest, @@ -93,7 +93,7 @@ func newInspector(options entities.InspectOptions) (*inspector, error) { // inspect inspects the specified container/image names or IDs. func (i *inspector) inspect(namesOrIDs []string) error { // data - dumping place for inspection results. - var data []interface{} // nolint + var data []interface{} var errs []error ctx := context.Background() @@ -145,8 +145,7 @@ func (i *inspector) inspect(namesOrIDs []string) error { i.podOptions.NameOrID = pod podData, err := i.containerEngine.PodInspect(ctx, i.podOptions) if err != nil { - cause := errors.Cause(err) - if !strings.Contains(cause.Error(), define.ErrNoSuchPod.Error()) { + if !strings.Contains(err.Error(), define.ErrNoSuchPod.Error()) { errs = []error{err} } else { return err @@ -159,8 +158,7 @@ func (i *inspector) inspect(namesOrIDs []string) error { if i.podOptions.Latest { // latest means there are no names in the namesOrID array podData, err := i.containerEngine.PodInspect(ctx, i.podOptions) if err != nil { - cause := errors.Cause(err) - if !strings.Contains(cause.Error(), define.ErrNoSuchPod.Error()) { + if !strings.Contains(err.Error(), define.ErrNoSuchPod.Error()) { errs = []error{err} } else { return err @@ -189,7 +187,7 @@ func (i *inspector) inspect(namesOrIDs []string) error { data = append(data, volumeData[i]) } default: - return errors.Errorf("invalid type %q: must be %q, %q, %q, %q, %q, or %q", i.options.Type, + return fmt.Errorf("invalid type %q: must be %q, %q, %q, %q, %q, or %q", i.options.Type, common.ImageType, common.ContainerType, common.PodType, common.NetworkType, common.VolumeType, common.AllType) } // Always print an empty array @@ -218,7 +216,7 @@ func (i *inspector) inspect(namesOrIDs []string) error { fmt.Fprintf(os.Stderr, "error inspecting object: %v\n", err) } } - return errors.Errorf("inspecting object: %v", errs[0]) + return fmt.Errorf("inspecting object: %w", errs[0]) } return nil } @@ -249,7 +247,7 @@ func printTmpl(typ, row string, data []interface{}) error { } func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]interface{}, []error, error) { - var data []interface{} // nolint + var data []interface{} allErrs := []error{} for _, name := range namesOrIDs { ctrData, errs, err := i.containerEngine.ContainerInspect(ctx, []string{name}, i.options) @@ -287,8 +285,7 @@ func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]inte i.podOptions.NameOrID = name podData, err := i.containerEngine.PodInspect(ctx, i.podOptions) if err != nil { - cause := errors.Cause(err) - if !strings.Contains(cause.Error(), define.ErrNoSuchPod.Error()) { + if !strings.Contains(err.Error(), define.ErrNoSuchPod.Error()) { return nil, nil, err } } else { @@ -296,7 +293,7 @@ func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]inte continue } if len(errs) > 0 { - allErrs = append(allErrs, errors.Errorf("no such object: %q", name)) + allErrs = append(allErrs, fmt.Errorf("no such object: %q", name)) continue } } diff --git a/cmd/podman/machine/info.go b/cmd/podman/machine/info.go new file mode 100644 index 000000000..9932027d8 --- /dev/null +++ b/cmd/podman/machine/info.go @@ -0,0 +1,182 @@ +//go:build amd64 || arm64 +// +build amd64 arm64 + +package machine + +import ( + "fmt" + "html/template" + "os" + "runtime" + + "github.com/containers/common/pkg/completion" + "github.com/containers/common/pkg/config" + "github.com/containers/common/pkg/report" + "github.com/containers/podman/v4/cmd/podman/common" + "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/cmd/podman/validate" + "github.com/containers/podman/v4/libpod/define" + "github.com/containers/podman/v4/pkg/machine" + "github.com/ghodss/yaml" + "github.com/spf13/cobra" +) + +var infoDescription = `Display information pertaining to the machine host.` + +var ( + infoCmd = &cobra.Command{ + Use: "info [options]", + Short: "Display machine host info", + Long: infoDescription, + PersistentPreRunE: rootlessOnly, + RunE: info, + Args: validate.NoArgs, + ValidArgsFunction: completion.AutocompleteNone, + Example: `podman machine info`, + } +) + +var ( + inFormat string +) + +// Info contains info on the machine host and version info +type Info struct { + Host *HostInfo `json:"Host"` + Version define.Version `json:"Version"` +} + +// HostInfo contains info on the machine host +type HostInfo struct { + Arch string `json:"Arch"` + CurrentMachine string `json:"CurrentMachine"` + DefaultMachine string `json:"DefaultMachine"` + EventsDir string `json:"EventsDir"` + MachineConfigDir string `json:"MachineConfigDir"` + MachineImageDir string `json:"MachineImageDir"` + MachineState string `json:"MachineState"` + NumberOfMachines int `json:"NumberOfMachines"` + OS string `json:"OS"` + VMType string `json:"VMType"` +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: infoCmd, + Parent: machineCmd, + }) + + flags := infoCmd.Flags() + formatFlagName := "format" + flags.StringVarP(&inFormat, formatFlagName, "f", "", "Change the output format to JSON or a Go template") + _ = infoCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&define.Info{})) +} + +func info(cmd *cobra.Command, args []string) error { + info := Info{} + version, err := define.GetVersion() + if err != nil { + return fmt.Errorf("error getting version info %w", err) + } + info.Version = version + + host, err := hostInfo() + if err != nil { + return err + } + info.Host = host + + switch { + case report.IsJSON(inFormat): + b, err := json.MarshalIndent(info, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + case cmd.Flags().Changed("format"): + tmpl := template.New(cmd.Name()).Funcs(template.FuncMap(report.DefaultFuncs)) + inFormat = report.NormalizeFormat(inFormat) + tmpl, err := tmpl.Parse(inFormat) + if err != nil { + return err + } + return tmpl.Execute(os.Stdout, info) + default: + b, err := yaml.Marshal(info) + if err != nil { + return err + } + fmt.Println(string(b)) + } + + return nil +} + +func hostInfo() (*HostInfo, error) { + host := HostInfo{} + + host.Arch = runtime.GOARCH + host.OS = runtime.GOOS + + provider := GetSystemDefaultProvider() + var listOpts machine.ListOptions + listResponse, err := provider.List(listOpts) + if err != nil { + return nil, fmt.Errorf("failed to get machines %w", err) + } + + host.NumberOfMachines = len(listResponse) + + cfg, err := config.ReadCustomConfig() + if err != nil { + return nil, err + } + + // Default state of machine is stopped + host.MachineState = "Stopped" + for _, vm := range listResponse { + // Set default machine if found + if vm.Name == cfg.Engine.ActiveService { + host.DefaultMachine = vm.Name + } + // If machine is running or starting, it is automatically the current machine + if vm.Running { + host.CurrentMachine = vm.Name + host.MachineState = "Running" + } else if vm.Starting { + host.CurrentMachine = vm.Name + host.MachineState = "Starting" + } + } + // If no machines are starting or running, set current machine to default machine + // If no default machines are found, do not report a default machine or a state + if host.CurrentMachine == "" { + if host.DefaultMachine == "" { + host.MachineState = "" + } else { + host.CurrentMachine = host.DefaultMachine + } + } + + host.VMType = provider.VMType() + + dataDir, err := machine.GetDataDir(host.VMType) + if err != nil { + return nil, fmt.Errorf("failed to get machine image dir") + } + host.MachineImageDir = dataDir + + confDir, err := machine.GetConfDir(host.VMType) + if err != nil { + return nil, fmt.Errorf("failed to get machine config dir %w", err) + } + host.MachineConfigDir = confDir + + eventsDir, err := eventSockDir() + if err != nil { + return nil, fmt.Errorf("failed to get events dir: %w", err) + } + host.EventsDir = eventsDir + + return &host, nil +} diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go index 6c31f3531..def3334e8 100644 --- a/cmd/podman/machine/init.go +++ b/cmd/podman/machine/init.go @@ -11,7 +11,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/machine" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -20,21 +19,20 @@ var ( Use: "init [options] [NAME]", Short: "Initialize a virtual machine", Long: "initialize a virtual machine ", + PersistentPreRunE: rootlessOnly, RunE: initMachine, Args: cobra.MaximumNArgs(1), Example: `podman machine init myvm`, ValidArgsFunction: completion.AutocompleteNone, } -) -var ( initOpts = machine.InitOptions{} defaultMachineName = machine.DefaultMachineName now bool ) // maxMachineNameSize is set to thirty to limit huge machine names primarily -// because macos has a much smaller file size limit. +// because macOS has a much smaller file size limit. const maxMachineNameSize = 30 func init() { @@ -111,7 +109,6 @@ func init() { flags.BoolVar(&initOpts.Rootful, rootfulFlagName, false, "Whether this machine should prefer rootful container execution") } -// TODO should we allow for a users to append to the qemu cmdline? func initMachine(cmd *cobra.Command, args []string) error { var ( err error @@ -122,12 +119,12 @@ func initMachine(cmd *cobra.Command, args []string) error { initOpts.Name = defaultMachineName if len(args) > 0 { if len(args[0]) > maxMachineNameSize { - return errors.New("machine name must be 30 characters or less") + return fmt.Errorf("machine name %q must be %d characters or less", args[0], maxMachineNameSize) } initOpts.Name = args[0] } if _, err := provider.LoadVMByName(initOpts.Name); err == nil { - return errors.Wrap(machine.ErrVMAlreadyExists, initOpts.Name) + return fmt.Errorf("%s: %w", initOpts.Name, machine.ErrVMAlreadyExists) } for idx, vol := range initOpts.Volumes { initOpts.Volumes[idx] = os.ExpandEnv(vol) @@ -150,17 +147,12 @@ func initMachine(cmd *cobra.Command, args []string) error { fmt.Println("Machine init complete") if now { - err = vm.Start(initOpts.Name, machine.StartOptions{}) - if err == nil { - fmt.Printf("Machine %q started successfully\n", initOpts.Name) - newMachineEvent(events.Start, events.Event{Name: initOpts.Name}) - } - } else { - extra := "" - if initOpts.Name != defaultMachineName { - extra = " " + initOpts.Name - } - fmt.Printf("To start your machine run:\n\n\tpodman machine start%s\n\n", extra) + return start(cmd, args) + } + extra := "" + if initOpts.Name != defaultMachineName { + extra = " " + initOpts.Name } + fmt.Printf("To start your machine run:\n\n\tpodman machine start%s\n\n", extra) return err } diff --git a/cmd/podman/machine/inspect.go b/cmd/podman/machine/inspect.go index 4600a2b6d..d69c382f2 100644 --- a/cmd/podman/machine/inspect.go +++ b/cmd/podman/machine/inspect.go @@ -20,6 +20,7 @@ var ( Use: "inspect [options] [MACHINE...]", Short: "Inspect an existing machine", Long: "Provide details on a managed virtual machine", + PersistentPreRunE: rootlessOnly, RunE: inspect, Example: `podman machine inspect myvm`, ValidArgsFunction: autocompleteMachine, diff --git a/cmd/podman/machine/list.go b/cmd/podman/machine/list.go index 5254d50cf..dd4a86697 100644 --- a/cmd/podman/machine/list.go +++ b/cmd/podman/machine/list.go @@ -4,6 +4,7 @@ package machine import ( + "fmt" "os" "sort" "strconv" @@ -15,9 +16,9 @@ import ( "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/validate" + "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/machine" "github.com/docker/go-units" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -27,6 +28,7 @@ var ( Aliases: []string{"ls"}, Short: "List machines", Long: "List managed virtual machines.", + PersistentPreRunE: rootlessOnly, RunE: list, Args: validate.NoArgs, ValidArgsFunction: completion.AutocompleteNone, @@ -43,22 +45,6 @@ type listFlagType struct { quiet bool } -type ListReporter struct { - Name string - Default bool - Created string - Running bool - LastUp string - Stream string - VMType string - CPUs uint64 - Memory string - DiskSize string - Port int - RemoteUsername string - IdentityPath string -} - func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Command: lsCmd, @@ -68,7 +54,7 @@ func init() { flags := lsCmd.Flags() formatFlagName := "format" flags.StringVar(&listFlag.format, formatFlagName, "{{.Name}}\t{{.VMType}}\t{{.Created}}\t{{.LastUp}}\t{{.CPUs}}\t{{.Memory}}\t{{.DiskSize}}\n", "Format volume output using JSON or a Go template") - _ = lsCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&ListReporter{})) + _ = lsCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&entities.ListReporter{})) flags.BoolVar(&listFlag.noHeading, "noheading", false, "Do not print headers") flags.BoolVarP(&listFlag.quiet, "quiet", "q", false, "Show only machine names") } @@ -87,7 +73,7 @@ func list(cmd *cobra.Command, args []string) error { provider := GetSystemDefaultProvider() listResponse, err = provider.List(opts) if err != nil { - return errors.Wrap(err, "error listing vms") + return fmt.Errorf("listing vms: %w", err) } // Sort by last run @@ -121,8 +107,8 @@ func list(cmd *cobra.Command, args []string) error { return outputTemplate(cmd, machineReporter) } -func outputTemplate(cmd *cobra.Command, responses []*ListReporter) error { - headers := report.Headers(ListReporter{}, map[string]string{ +func outputTemplate(cmd *cobra.Command, responses []*entities.ListReporter) error { + headers := report.Headers(entities.ListReporter{}, map[string]string{ "LastUp": "LAST UP", "VmType": "VM TYPE", "CPUs": "CPUS", @@ -137,7 +123,7 @@ func outputTemplate(cmd *cobra.Command, responses []*ListReporter) error { switch { case cmd.Flags().Changed("format"): row = cmd.Flag("format").Value.String() - listFlag.noHeading = !report.HasTable(row) + printHeader = report.HasTable(row) row = report.NormalizeFormat(row) default: row = cmd.Flag("format").Value.String() @@ -156,7 +142,7 @@ func outputTemplate(cmd *cobra.Command, responses []*ListReporter) error { defer w.Flush() if printHeader { if err := tmpl.Execute(w, headers); err != nil { - return errors.Wrapf(err, "failed to write report column headers") + return fmt.Errorf("failed to write report column headers: %w", err) } } return tmpl.Execute(w, responses) @@ -181,15 +167,15 @@ func streamName(imageStream string) string { return imageStream } -func toMachineFormat(vms []*machine.ListResponse) ([]*ListReporter, error) { +func toMachineFormat(vms []*machine.ListResponse) ([]*entities.ListReporter, error) { cfg, err := config.ReadCustomConfig() if err != nil { return nil, err } - machineResponses := make([]*ListReporter, 0, len(vms)) + machineResponses := make([]*entities.ListReporter, 0, len(vms)) for _, vm := range vms { - response := new(ListReporter) + response := new(entities.ListReporter) response.Default = vm.Name == cfg.Engine.ActiveService response.Name = vm.Name response.Running = vm.Running @@ -209,25 +195,29 @@ func toMachineFormat(vms []*machine.ListResponse) ([]*ListReporter, error) { return machineResponses, nil } -func toHumanFormat(vms []*machine.ListResponse) ([]*ListReporter, error) { +func toHumanFormat(vms []*machine.ListResponse) ([]*entities.ListReporter, error) { cfg, err := config.ReadCustomConfig() if err != nil { return nil, err } - humanResponses := make([]*ListReporter, 0, len(vms)) + humanResponses := make([]*entities.ListReporter, 0, len(vms)) for _, vm := range vms { - response := new(ListReporter) + response := new(entities.ListReporter) if vm.Name == cfg.Engine.ActiveService { response.Name = vm.Name + "*" response.Default = true } else { response.Name = vm.Name } - if vm.Running { + switch { + case vm.Running: response.LastUp = "Currently running" response.Running = true - } else { + case vm.Starting: + response.LastUp = "Currently starting" + response.Starting = true + default: response.LastUp = units.HumanDuration(time.Since(vm.LastUp)) + " ago" } response.Created = units.HumanDuration(time.Since(vm.CreatedAt)) + " ago" diff --git a/cmd/podman/machine/machine.go b/cmd/podman/machine/machine.go index 5a8a06b9d..0618337cc 100644 --- a/cmd/podman/machine/machine.go +++ b/cmd/podman/machine/machine.go @@ -101,12 +101,6 @@ func resolveEventSock() ([]string, error) { return []string{sock}, nil } - xdg, err := util.GetRuntimeDir() - if err != nil { - logrus.Warnf("Failed to get runtime dir, machine events will not be published: %s", err) - return nil, nil - } - re := regexp.MustCompile(`machine_events.*\.sock`) sockPaths := make([]string, 0) fn := func(path string, info os.DirEntry, err error) error { @@ -125,8 +119,12 @@ func resolveEventSock() ([]string, error) { sockPaths = append(sockPaths, path) return nil } + sockDir, err := eventSockDir() + if err != nil { + logrus.Warnf("Failed to get runtime dir, machine events will not be published: %s", err) + } - if err := filepath.WalkDir(filepath.Join(xdg, "podman"), fn); err != nil { + if err := filepath.WalkDir(sockDir, fn); err != nil { if errors.Is(err, os.ErrNotExist) { return nil, nil } @@ -135,6 +133,14 @@ func resolveEventSock() ([]string, error) { return sockPaths, nil } +func eventSockDir() (string, error) { + xdg, err := util.GetRuntimeDir() + if err != nil { + return "", err + } + return filepath.Join(xdg, "podman"), nil +} + func newMachineEvent(status events.Status, event events.Event) { openEventSock.Do(initMachineEvents) diff --git a/cmd/podman/machine/machine_unix.go b/cmd/podman/machine/machine_unix.go index b56d081ec..a2d9b9d8e 100644 --- a/cmd/podman/machine/machine_unix.go +++ b/cmd/podman/machine/machine_unix.go @@ -4,9 +4,20 @@ package machine import ( + "fmt" "os" + + "github.com/containers/podman/v4/pkg/rootless" + "github.com/spf13/cobra" ) func isUnixSocket(file os.DirEntry) bool { return file.Type()&os.ModeSocket != 0 } + +func rootlessOnly(cmd *cobra.Command, args []string) error { + if !rootless.IsRootless() { + return fmt.Errorf("cannot run command %q as root", cmd.CommandPath()) + } + return nil +} diff --git a/cmd/podman/machine/machine_windows.go b/cmd/podman/machine/machine_windows.go index ffd5d8827..bf1240868 100644 --- a/cmd/podman/machine/machine_windows.go +++ b/cmd/podman/machine/machine_windows.go @@ -3,9 +3,18 @@ package machine import ( "os" "strings" + + "github.com/spf13/cobra" ) func isUnixSocket(file os.DirEntry) bool { // Assume a socket on Windows, since sock mode is not supported yet https://github.com/golang/go/issues/33357 return !file.Type().IsDir() && strings.HasSuffix(file.Name(), ".sock") } + +func rootlessOnly(cmd *cobra.Command, args []string) error { + // Rootless is not relevant on Windows. In the future rootless.IsRootless + // could be switched to return true on Windows, and other codepaths migrated + + return nil +} diff --git a/cmd/podman/machine/rm.go b/cmd/podman/machine/rm.go index a6e66265c..362c9a7d3 100644 --- a/cmd/podman/machine/rm.go +++ b/cmd/podman/machine/rm.go @@ -20,6 +20,7 @@ var ( Use: "rm [options] [MACHINE]", Short: "Remove an existing machine", Long: "Remove a managed virtual machine ", + PersistentPreRunE: rootlessOnly, RunE: rm, Args: cobra.MaximumNArgs(1), Example: `podman machine rm myvm`, diff --git a/cmd/podman/machine/set.go b/cmd/podman/machine/set.go index 5777882da..1b9e1b2bd 100644 --- a/cmd/podman/machine/set.go +++ b/cmd/podman/machine/set.go @@ -18,6 +18,7 @@ var ( Use: "set [options] [NAME]", Short: "Sets a virtual machine setting", Long: "Sets an updatable virtual machine setting", + PersistentPreRunE: rootlessOnly, RunE: setMachine, Args: cobra.MaximumNArgs(1), Example: `podman machine set --rootful=false`, diff --git a/cmd/podman/machine/ssh.go b/cmd/podman/machine/ssh.go index 4a86da67a..cb2f62f51 100644 --- a/cmd/podman/machine/ssh.go +++ b/cmd/podman/machine/ssh.go @@ -4,22 +4,24 @@ package machine import ( + "fmt" "net/url" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/machine" - "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( sshCmd = &cobra.Command{ - Use: "ssh [options] [NAME] [COMMAND [ARG ...]]", - Short: "SSH into an existing machine", - Long: "SSH into a managed virtual machine ", - RunE: ssh, + Use: "ssh [options] [NAME] [COMMAND [ARG ...]]", + Short: "SSH into an existing machine", + Long: "SSH into a managed virtual machine ", + PersistentPreRunE: rootlessOnly, + RunE: ssh, Example: `podman machine ssh myvm podman machine ssh myvm echo hello`, ValidArgsFunction: autocompleteMachineSSH, @@ -87,9 +89,10 @@ func ssh(cmd *cobra.Command, args []string) error { vm, err = provider.LoadVMByName(vmName) if err != nil { - return errors.Wrapf(err, "vm %s not found", vmName) + return fmt.Errorf("vm %s not found: %w", vmName, err) } - return vm.SSH(vmName, sshOpts) + err = vm.SSH(vmName, sshOpts) + return utils.HandleOSExecError(err) } func remoteConnectionUsername() (string, error) { diff --git a/cmd/podman/machine/start.go b/cmd/podman/machine/start.go index c9b99e63b..15a75522a 100644 --- a/cmd/podman/machine/start.go +++ b/cmd/podman/machine/start.go @@ -9,7 +9,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/machine" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -18,6 +17,7 @@ var ( Use: "start [MACHINE]", Short: "Start an existing machine", Long: "Start a managed virtual machine ", + PersistentPreRunE: rootlessOnly, RunE: start, Args: cobra.MaximumNArgs(1), Example: `podman machine start myvm`, @@ -54,9 +54,9 @@ func start(_ *cobra.Command, args []string) error { } if active { if vmName == activeName { - return errors.Wrapf(machine.ErrVMAlreadyRunning, "cannot start VM %s", vmName) + return fmt.Errorf("cannot start VM %s: %w", vmName, machine.ErrVMAlreadyRunning) } - return errors.Wrapf(machine.ErrMultipleActiveVM, "cannot start VM %s. VM %s is currently running", vmName, activeName) + return fmt.Errorf("cannot start VM %s. VM %s is currently running or starting: %w", vmName, activeName, machine.ErrMultipleActiveVM) } fmt.Printf("Starting machine %q\n", vmName) if err := vm.Start(vmName, machine.StartOptions{}); err != nil { diff --git a/cmd/podman/machine/stop.go b/cmd/podman/machine/stop.go index 993662792..ce87a44c4 100644 --- a/cmd/podman/machine/stop.go +++ b/cmd/podman/machine/stop.go @@ -17,6 +17,7 @@ var ( Use: "stop [MACHINE]", Short: "Stop an existing machine", Long: "Stop a managed virtual machine ", + PersistentPreRunE: rootlessOnly, RunE: stop, Args: cobra.MaximumNArgs(1), Example: `podman machine stop myvm`, diff --git a/cmd/podman/manifest/push.go b/cmd/podman/manifest/push.go index b96a65c4a..9479e79a3 100644 --- a/cmd/podman/manifest/push.go +++ b/cmd/podman/manifest/push.go @@ -1,6 +1,7 @@ package manifest import ( + "fmt" "io/ioutil" "github.com/containers/common/pkg/auth" @@ -11,7 +12,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -88,10 +88,10 @@ func push(cmd *cobra.Command, args []string) error { listImageSpec := args[0] destSpec := args[1] if listImageSpec == "" { - return errors.Errorf(`invalid image name "%s"`, listImageSpec) + return fmt.Errorf(`invalid image name "%s"`, listImageSpec) } if destSpec == "" { - return errors.Errorf(`invalid destination "%s"`, destSpec) + return fmt.Errorf(`invalid destination "%s"`, destSpec) } if manifestPushOpts.CredentialsCLI != "" { diff --git a/cmd/podman/manifest/remove.go b/cmd/podman/manifest/remove.go index c32ffad78..4aa3b66b7 100644 --- a/cmd/podman/manifest/remove.go +++ b/cmd/podman/manifest/remove.go @@ -5,7 +5,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -31,7 +30,7 @@ func init() { func remove(cmd *cobra.Command, args []string) error { updatedListID, err := registry.ImageEngine().ManifestRemoveDigest(registry.Context(), args[0], args[1]) if err != nil { - return errors.Wrapf(err, "error removing from manifest list %s", args[0]) + return fmt.Errorf("removing from manifest list %s: %w", args[0], err) } fmt.Println(updatedListID) return nil diff --git a/cmd/podman/networks/create.go b/cmd/podman/networks/create.go index 84c58d4dc..2cf7023f3 100644 --- a/cmd/podman/networks/create.go +++ b/cmd/podman/networks/create.go @@ -1,6 +1,7 @@ package network import ( + "errors" "fmt" "net" @@ -11,7 +12,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/parse" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -97,11 +97,11 @@ func networkCreate(cmd *cobra.Command, args []string) error { var err error networkCreateOptions.Labels, err = parse.GetAllLabels([]string{}, labels) if err != nil { - return errors.Wrap(err, "failed to parse labels") + return fmt.Errorf("failed to parse labels: %w", err) } networkCreateOptions.Options, err = parse.GetAllLabels([]string{}, opts) if err != nil { - return errors.Wrapf(err, "unable to parse options") + return fmt.Errorf("unable to parse options: %w", err) } network := types.Network{ @@ -181,11 +181,11 @@ func parseRange(iprange string) (*types.LeaseRange, error) { startIP, err := util.FirstIPInSubnet(subnet) if err != nil { - return nil, errors.Wrap(err, "failed to get first ip in range") + return nil, fmt.Errorf("failed to get first ip in range: %w", err) } lastIP, err := util.LastIPInSubnet(subnet) if err != nil { - return nil, errors.Wrap(err, "failed to get last ip in range") + return nil, fmt.Errorf("failed to get last ip in range: %w", err) } return &types.LeaseRange{ StartIP: startIP, diff --git a/cmd/podman/networks/reload.go b/cmd/podman/networks/reload.go index 7b6323187..66248e9fb 100644 --- a/cmd/podman/networks/reload.go +++ b/cmd/podman/networks/reload.go @@ -21,7 +21,7 @@ var ( Long: networkReloadDescription, RunE: networkReload, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompleteContainers, Example: `podman network reload --latest diff --git a/cmd/podman/networks/rm.go b/cmd/podman/networks/rm.go index f71f59eea..c2d3f655f 100644 --- a/cmd/podman/networks/rm.go +++ b/cmd/podman/networks/rm.go @@ -1,6 +1,7 @@ package network import ( + "errors" "fmt" "strings" @@ -10,7 +11,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -78,15 +78,9 @@ func networkRm(cmd *cobra.Command, args []string) error { } func setExitCode(err error) { - cause := errors.Cause(err) - switch { - case cause == define.ErrNoSuchNetwork: + if errors.Is(err, define.ErrNoSuchNetwork) || strings.Contains(err.Error(), define.ErrNoSuchNetwork.Error()) { registry.SetExitCode(1) - case strings.Contains(cause.Error(), define.ErrNoSuchNetwork.Error()): - registry.SetExitCode(1) - case cause == define.ErrNetworkInUse: - registry.SetExitCode(2) - case strings.Contains(cause.Error(), define.ErrNetworkInUse.Error()): + } else if errors.Is(err, define.ErrNetworkInUse) || strings.Contains(err.Error(), define.ErrNetworkInUse.Error()) { registry.SetExitCode(2) } } diff --git a/cmd/podman/parse/filters.go b/cmd/podman/parse/filters.go index 8a10f2a97..e4ab942af 100644 --- a/cmd/podman/parse/filters.go +++ b/cmd/podman/parse/filters.go @@ -1,10 +1,9 @@ package parse import ( + "fmt" "net/url" "strings" - - "github.com/pkg/errors" ) func FilterArgumentsIntoFilters(filters []string) (url.Values, error) { @@ -12,7 +11,7 @@ func FilterArgumentsIntoFilters(filters []string) (url.Values, error) { for _, f := range filters { t := strings.SplitN(f, "=", 2) if len(t) < 2 { - return parsedFilters, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + return parsedFilters, fmt.Errorf("filter input must be in the form of filter=value: %s is invalid", f) } parsedFilters.Add(t[0], t[1]) } diff --git a/cmd/podman/parse/net.go b/cmd/podman/parse/net.go index 870690db3..9228c7127 100644 --- a/cmd/podman/parse/net.go +++ b/cmd/podman/parse/net.go @@ -1,4 +1,3 @@ -// nolint // most of these validate and parse functions have been taken from projectatomic/docker // and modified for cri-o package parse @@ -11,29 +10,13 @@ import ( "os" "regexp" "strings" - - "github.com/pkg/errors" ) const ( - Protocol_TCP Protocol = 0 - Protocol_UDP Protocol = 1 + LabelType string = "label" + ENVType string = "env" ) -type Protocol int32 - -// PortMapping specifies the port mapping configurations of a sandbox. -type PortMapping struct { - // Protocol of the port mapping. - Protocol Protocol `protobuf:"varint,1,opt,name=protocol,proto3,enum=runtime.Protocol" json:"protocol,omitempty"` - // Port number within the container. Default: 0 (not specified). - ContainerPort int32 `protobuf:"varint,2,opt,name=container_port,json=containerPort,proto3" json:"container_port,omitempty"` - // Port number on the host. Default: 0 (not specified). - HostPort int32 `protobuf:"varint,3,opt,name=host_port,json=hostPort,proto3" json:"host_port,omitempty"` - // Host IP. - HostIp string `protobuf:"bytes,4,opt,name=host_ip,json=hostIp,proto3" json:"host_ip,omitempty"` -} - // Note: for flags that are in the form <number><unit>, use the RAMInBytes function // from the units package in docker/go-units/size.go @@ -46,7 +29,7 @@ var ( // validateExtraHost validates that the specified string is a valid extrahost and returns it. // ExtraHost is in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6). // for add-host flag -func ValidateExtraHost(val string) (string, error) { // nolint +func ValidateExtraHost(val string) (string, error) { // allow for IPv6 addresses in extra hosts by only splitting on first ":" arr := strings.SplitN(val, ":", 2) if len(arr) != 2 || len(arr[0]) == 0 { @@ -89,16 +72,14 @@ func GetAllLabels(labelFile, inputLabels []string) (map[string]string, error) { // There's an argument that we SHOULD be doing that parsing for // all environment variables, even those sourced from files, but // that would require a substantial rework. - if err := parseEnvFile(labels, file); err != nil { - // FIXME: parseEnvFile is using parseEnv, so we need to add extra - // logic for labels. + if err := parseEnvOrLabelFile(labels, file, LabelType); err != nil { return nil, err } } for _, label := range inputLabels { split := strings.SplitN(label, "=", 2) if split[0] == "" { - return nil, errors.Errorf("invalid label format: %q", label) + return nil, fmt.Errorf("invalid label format: %q", label) } value := "" if len(split) > 1 { @@ -109,18 +90,18 @@ func GetAllLabels(labelFile, inputLabels []string) (map[string]string, error) { return labels, nil } -func parseEnv(env map[string]string, line string) error { +func parseEnvOrLabel(env map[string]string, line, configType string) error { data := strings.SplitN(line, "=", 2) // catch invalid variables such as "=" or "=A" if data[0] == "" { - return errors.Errorf("invalid environment variable: %q", line) + return fmt.Errorf("invalid environment variable: %q", line) } // trim the front of a variable, but nothing else name := strings.TrimLeft(data[0], whiteSpaces) if strings.ContainsAny(name, whiteSpaces) { - return errors.Errorf("name %q has white spaces, poorly formatted name", name) + return fmt.Errorf("name %q has white spaces, poorly formatted name", name) } if len(data) > 1 { @@ -137,7 +118,7 @@ func parseEnv(env map[string]string, line string) error { env[part[0]] = part[1] } } - } else { + } else if configType == ENVType { // if only a pass-through variable is given, clean it up. if val, ok := os.LookupEnv(name); ok { env[name] = val @@ -147,8 +128,9 @@ func parseEnv(env map[string]string, line string) error { return nil } -// parseEnvFile reads a file with environment variables enumerated by lines -func parseEnvFile(env map[string]string, filename string) error { +// parseEnvOrLabelFile reads a file with environment variables enumerated by lines +// configType should be set to either "label" or "env" based on what type is being parsed +func parseEnvOrLabelFile(envOrLabel map[string]string, filename, configType string) error { fh, err := os.Open(filename) if err != nil { return err @@ -161,7 +143,7 @@ func parseEnvFile(env map[string]string, filename string) error { line := strings.TrimLeft(scanner.Text(), whiteSpaces) // line is not empty, and not starting with '#' if len(line) > 0 && !strings.HasPrefix(line, "#") { - if err := parseEnv(env, line); err != nil { + if err := parseEnvOrLabel(envOrLabel, line, configType); err != nil { return err } } @@ -173,7 +155,7 @@ func parseEnvFile(env map[string]string, filename string) error { // as it is currently not supported func ValidateFileName(filename string) error { if strings.Contains(filename, ":") { - return errors.Errorf("invalid filename (should not contain ':') %q", filename) + return fmt.Errorf("invalid filename (should not contain ':') %q", filename) } return nil } @@ -182,10 +164,10 @@ func ValidateFileName(filename string) error { func ValidURL(urlStr string) error { url, err := url.ParseRequestURI(urlStr) if err != nil { - return errors.Wrapf(err, "invalid url %q", urlStr) + return fmt.Errorf("invalid url %q: %w", urlStr, err) } if url.Scheme == "" { - return errors.Errorf("invalid url %q: missing scheme", urlStr) + return fmt.Errorf("invalid url %q: missing scheme", urlStr) } return nil } diff --git a/cmd/podman/parse/net_test.go b/cmd/podman/parse/net_test.go index 51c8509df..a11edc2ca 100644 --- a/cmd/podman/parse/net_test.go +++ b/cmd/podman/parse/net_test.go @@ -1,4 +1,3 @@ -// nolint // most of these validate and parse functions have been taken from projectatomic/docker // and modified for cri-o package parse @@ -23,7 +22,6 @@ func createTmpFile(content []byte) (string, error) { if _, err := tmpfile.Write(content); err != nil { return "", err - } if err := tmpfile.Close(); err != nil { return "", err diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index f5b121009..8fd12baaf 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -1,6 +1,7 @@ package pods import ( + "errors" "fmt" "net" "os" @@ -16,7 +17,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -188,14 +188,14 @@ func kube(cmd *cobra.Command, args []string) error { for _, annotation := range annotations { splitN := strings.SplitN(annotation, "=", 2) if len(splitN) > 2 { - return errors.Errorf("annotation %q must include an '=' sign", annotation) + return fmt.Errorf("annotation %q must include an '=' sign", annotation) } if kubeOptions.Annotations == nil { kubeOptions.Annotations = make(map[string]string) } annotation := splitN[1] if len(annotation) > define.MaxKubeAnnotation { - return errors.Errorf("annotation exceeds maximum size, %d, of kubernetes annotation: %s", define.MaxKubeAnnotation, annotation) + return fmt.Errorf("annotation exceeds maximum size, %d, of kubernetes annotation: %s", define.MaxKubeAnnotation, annotation) } kubeOptions.Annotations[splitN[0]] = annotation } @@ -235,7 +235,7 @@ func teardown(yamlfile string) error { defer f.Close() reports, err := registry.ContainerEngine().PlayKubeDown(registry.GetContext(), f, *options) if err != nil { - return errors.Wrap(err, yamlfile) + return fmt.Errorf("%v: %w", yamlfile, err) } // Output stopped pods @@ -273,7 +273,7 @@ func playkube(yamlfile string) error { defer f.Close() report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), f, kubeOptions.PlayKubeOptions) if err != nil { - return errors.Wrap(err, yamlfile) + return fmt.Errorf("%s: %w", yamlfile, err) } // Print volumes report for i, volume := range report.Volumes { @@ -320,7 +320,7 @@ func playkube(yamlfile string) error { } if ctrsFailed > 0 { - return errors.Errorf("failed to start %d containers", ctrsFailed) + return fmt.Errorf("failed to start %d containers", ctrsFailed) } return nil diff --git a/cmd/podman/pods/clone.go b/cmd/podman/pods/clone.go new file mode 100644 index 000000000..9558c6aed --- /dev/null +++ b/cmd/podman/pods/clone.go @@ -0,0 +1,92 @@ +package pods + +import ( + "context" + "fmt" + + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v4/cmd/podman/common" + "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/libpod/define" + "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + podCloneDescription = `Create an exact copy of a pod and the containers within it` + + podCloneCommand = &cobra.Command{ + Use: "clone [options] POD NAME", + Short: "Clone an existing pod", + Long: podCloneDescription, + RunE: clone, + Args: cobra.RangeArgs(1, 2), + ValidArgsFunction: common.AutocompleteClone, + Example: `podman pod clone pod_name new_name`, + } +) + +var ( + podClone entities.PodCloneOptions +) + +func cloneFlags(cmd *cobra.Command) { + flags := cmd.Flags() + + destroyFlagName := "destroy" + flags.BoolVar(&podClone.Destroy, destroyFlagName, false, "destroy the original pod") + + startFlagName := "start" + flags.BoolVar(&podClone.Start, startFlagName, false, "start the new pod") + + nameFlagName := "name" + flags.StringVarP(&podClone.CreateOpts.Name, nameFlagName, "n", "", "name the new pod") + _ = podCloneCommand.RegisterFlagCompletionFunc(nameFlagName, completion.AutocompleteNone) + + common.DefineCreateDefaults(&podClone.InfraOptions) + common.DefineCreateFlags(cmd, &podClone.InfraOptions, true, false) + + podClone.InfraOptions.MemorySwappiness = -1 // this is not implemented for pods yet, need to set -1 default manually + + // need to fill an empty ctr create option for each container for sane defaults + // for now, these cannot be used. The flag names conflict too much + // this makes sense since this is a pod command not a container command + // TODO: add support for container specific arguments/flags + common.DefineCreateDefaults(&podClone.PerContainerOptions) +} +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: podCloneCommand, + Parent: podCmd, + }) + + cloneFlags(podCloneCommand) +} + +func clone(cmd *cobra.Command, args []string) error { + switch len(args) { + case 0: + return fmt.Errorf("must specify at least 1 argument: %w", define.ErrInvalidArg) + case 2: + podClone.CreateOpts.Name = args[1] + } + + podClone.ID = args[0] + + if cmd.Flag("shm-size").Changed { + podClone.InfraOptions.ShmSize = cmd.Flag("shm-size").Value.String() + } + + podClone.PerContainerOptions.IsClone = true + rep, err := registry.ContainerEngine().PodClone(context.Background(), podClone) + if err != nil { + if rep != nil { + fmt.Printf("pod %s created but error after creation\n", rep.Id) + } + return err + } + + fmt.Println(rep.Id) + + return nil +} diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index 62f820790..aea8a7229 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -2,6 +2,7 @@ package pods import ( "context" + "errors" "fmt" "io/ioutil" "os" @@ -16,7 +17,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/containers" "github.com/containers/podman/v4/cmd/podman/parse" "github.com/containers/podman/v4/cmd/podman/registry" - "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/errorhandling" @@ -24,7 +24,6 @@ import ( "github.com/containers/podman/v4/pkg/specgenutil" "github.com/containers/podman/v4/pkg/util" "github.com/docker/docker/pkg/parsers" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -36,12 +35,14 @@ var ( You can then start it at any time with the podman pod start <pod_id> command. The pod will be created with the initial state 'created'.` createCommand = &cobra.Command{ - Use: "create [options]", - Args: validate.NoArgs, + Use: "create [options] [NAME]", + Args: cobra.MaximumNArgs(1), Short: "Create a new empty pod", Long: podCreateDescription, RunE: create, ValidArgsFunction: completion.AutocompleteNone, + Example: `podman pod create + podman pod create --label foo=bar mypod`, } ) @@ -63,6 +64,7 @@ func init() { }) flags := createCommand.Flags() flags.SetInterspersed(false) + common.DefineCreateDefaults(&infraOptions) common.DefineCreateFlags(createCommand, &infraOptions, true, false) common.DefineNetFlags(createCommand) @@ -115,11 +117,17 @@ func create(cmd *cobra.Command, args []string) error { rawImageName string podName string ) + if len(args) > 0 { + if len(createOptions.Name) > 0 { + return fmt.Errorf("cannot specify --name and NAME at the same time") + } + createOptions.Name = args[0] + } labelFile = infraOptions.LabelFile labels = infraOptions.Label createOptions.Labels, err = parse.GetAllLabels(labelFile, labels) if err != nil { - return errors.Wrapf(err, "unable to process labels") + return fmt.Errorf("unable to process labels: %w", err) } if cmd.Flag("infra-image").Changed { @@ -128,7 +136,7 @@ func create(cmd *cobra.Command, args []string) error { img := imageName if !createOptions.Infra { if cmd.Flag("no-hosts").Changed { - return fmt.Errorf("cannot specify no-hosts without an infra container") + return fmt.Errorf("cannot specify --no-hosts without an infra container") } flags := cmd.Flags() createOptions.Net, err = common.NetFlagsToNetOptions(nil, *flags) @@ -157,9 +165,14 @@ func create(cmd *cobra.Command, args []string) error { return err } if strings.Contains(share, "cgroup") && shareParent { - return errors.Wrapf(define.ErrInvalidArg, "cannot define the pod as the cgroup parent at the same time as joining the infra container's cgroupNS") + return fmt.Errorf("cannot define the pod as the cgroup parent at the same time as joining the infra container's cgroupNS: %w", define.ErrInvalidArg) } - createOptions.Share = strings.Split(share, ",") + + if strings.HasPrefix(share, "+") { + createOptions.Share = append(createOptions.Share, strings.Split(specgen.DefaultKernelNamespaces, ",")...) + share = share[1:] + } + createOptions.Share = append(createOptions.Share, strings.Split(share, ",")...) createOptions.ShareParent = &shareParent if cmd.Flag("infra-command").Changed { // Only send content to server side if user changed defaults @@ -180,10 +193,10 @@ func create(cmd *cobra.Command, args []string) error { if cmd.Flag("pod-id-file").Changed { podIDFD, err = util.OpenExclusiveFile(podIDFile) if err != nil && os.IsExist(err) { - return errors.Errorf("pod id file exists. Ensure another pod is not using it or delete %s", podIDFile) + return fmt.Errorf("pod id file exists. Ensure another pod is not using it or delete %s", podIDFile) } if err != nil { - return errors.Errorf("opening pod-id-file %s", podIDFile) + return fmt.Errorf("opening pod-id-file %s", podIDFile) } defer errorhandling.CloseQuiet(podIDFD) defer errorhandling.SyncQuiet(podIDFD) @@ -191,7 +204,7 @@ func create(cmd *cobra.Command, args []string) error { if len(createOptions.Net.PublishPorts) > 0 { if !createOptions.Infra { - return errors.Errorf("you must have an infra container to publish port bindings to the host") + return fmt.Errorf("you must have an infra container to publish port bindings to the host") } } @@ -218,7 +231,7 @@ func create(cmd *cobra.Command, args []string) error { ret, err := parsers.ParseUintList(copy) copy = "" if err != nil { - return errors.Wrapf(err, "could not parse list") + return fmt.Errorf("could not parse list: %w", err) } var vals []int for ind, val := range ret { @@ -264,6 +277,7 @@ func create(cmd *cobra.Command, args []string) error { if err != nil { return err } + podSpec.Volumes = podSpec.InfraContainerSpec.Volumes podSpec.ImageVolumes = podSpec.InfraContainerSpec.ImageVolumes podSpec.OverlayVolumes = podSpec.InfraContainerSpec.OverlayVolumes @@ -288,7 +302,7 @@ func create(cmd *cobra.Command, args []string) error { if len(podIDFile) > 0 { if err = ioutil.WriteFile(podIDFile, []byte(response.Id), 0644); err != nil { - return errors.Wrapf(err, "failed to write pod ID to file") + return fmt.Errorf("failed to write pod ID to file: %w", err) } } fmt.Println(response.Id) diff --git a/cmd/podman/pods/inspect.go b/cmd/podman/pods/inspect.go index bb30fe6e6..082e8d9a1 100644 --- a/cmd/podman/pods/inspect.go +++ b/cmd/podman/pods/inspect.go @@ -2,6 +2,7 @@ package pods import ( "context" + "errors" "os" "text/template" @@ -10,7 +11,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -49,10 +49,10 @@ func init() { func inspect(cmd *cobra.Command, args []string) error { if len(args) < 1 && !inspectOptions.Latest { - return errors.Errorf("you must provide the name or id of a running pod") + return errors.New("you must provide the name or id of a running pod") } if len(args) > 0 && inspectOptions.Latest { - return errors.Errorf("--latest and containers cannot be used together") + return errors.New("--latest and containers cannot be used together") } if !inspectOptions.Latest { diff --git a/cmd/podman/pods/kill.go b/cmd/podman/pods/kill.go index 7216e08bb..5d3b15dc3 100644 --- a/cmd/podman/pods/kill.go +++ b/cmd/podman/pods/kill.go @@ -22,7 +22,7 @@ var ( Long: podKillDescription, RunE: kill, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompletePodsRunning, Example: `podman pod kill podID diff --git a/cmd/podman/pods/logs.go b/cmd/podman/pods/logs.go index 28e7b7a43..0102d4b71 100644 --- a/cmd/podman/pods/logs.go +++ b/cmd/podman/pods/logs.go @@ -1,6 +1,8 @@ package pods import ( + "errors" + "fmt" "os" "github.com/containers/common/pkg/completion" @@ -10,7 +12,6 @@ import ( "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -100,7 +101,7 @@ func logs(_ *cobra.Command, args []string) error { // parse time, error out if something is wrong since, err := util.ParseInputTime(logsPodOptions.SinceRaw, true) if err != nil { - return errors.Wrapf(err, "error parsing --since %q", logsPodOptions.SinceRaw) + return fmt.Errorf("error parsing --since %q: %w", logsPodOptions.SinceRaw, err) } logsPodOptions.Since = since } @@ -108,14 +109,14 @@ func logs(_ *cobra.Command, args []string) error { // parse time, error out if something is wrong until, err := util.ParseInputTime(logsPodOptions.UntilRaw, false) if err != nil { - return errors.Wrapf(err, "error parsing --until %q", logsPodOptions.UntilRaw) + return fmt.Errorf("error parsing --until %q: %w", logsPodOptions.UntilRaw, err) } logsPodOptions.Until = until } // Remote can only process one container at a time if registry.IsRemote() && logsPodOptions.ContainerName == "" { - return errors.Wrapf(define.ErrInvalidArg, "-c or --container cannot be empty") + return fmt.Errorf("-c or --container cannot be empty: %w", define.ErrInvalidArg) } logsPodOptions.StdoutWriter = os.Stdout diff --git a/cmd/podman/pods/pause.go b/cmd/podman/pods/pause.go index adc54d171..389fb8415 100644 --- a/cmd/podman/pods/pause.go +++ b/cmd/podman/pods/pause.go @@ -22,7 +22,7 @@ var ( Long: podPauseDescription, RunE: pause, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompletePodsRunning, Example: `podman pod pause podID1 podID2 diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index 1275e65dc..681c9c42e 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -2,6 +2,7 @@ package pods import ( "context" + "errors" "fmt" "os" "sort" @@ -15,7 +16,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/docker/go-units" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -24,7 +24,7 @@ var ( // Command: podman pod _ps_ psCmd = &cobra.Command{ - Use: "ps [options]", + Use: "ps [options]", Aliases: []string{"ls", "list"}, Short: "List pods", Long: psDescription, @@ -49,7 +49,6 @@ func init() { flags.BoolVar(&psInput.CtrNames, "ctr-names", false, "Display the container names") flags.BoolVar(&psInput.CtrIds, "ctr-ids", false, "Display the container UUIDs. If no-trunc is not set they will be truncated") flags.BoolVar(&psInput.CtrStatus, "ctr-status", false, "Display the container status") - // TODO should we make this a [] ? filterFlagName := "filter" flags.StringSliceVarP(&inputFilters, filterFlagName, "f", []string{}, "Filter output based on conditions given") @@ -81,7 +80,7 @@ func pods(cmd *cobra.Command, _ []string) error { for _, f := range inputFilters { split := strings.SplitN(f, "=", 2) if len(split) < 2 { - return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + return fmt.Errorf("filter input must be in the form of filter=value: %s is invalid", f) } psInput.Filters[split[0]] = append(psInput.Filters[split[0]], split[1]) } @@ -212,7 +211,7 @@ func (l ListPodReporter) ID() string { } // Id returns the Pod id -func (l ListPodReporter) Id() string { // nolint +func (l ListPodReporter) Id() string { //nolint:revive,stylecheck if noTrunc { return l.ListPodsReport.Id } @@ -226,7 +225,7 @@ func (l ListPodReporter) InfraID() string { // InfraId returns the infra container id for the pod // depending on trunc -func (l ListPodReporter) InfraId() string { // nolint +func (l ListPodReporter) InfraId() string { //nolint:revive,stylecheck if len(l.ListPodsReport.InfraId) == 0 { return "" } @@ -277,7 +276,7 @@ func sortPodPsOutput(sortBy string, lprs []*entities.ListPodsReport) error { case "status": sort.Sort(podPsSortedStatus{lprs}) default: - return errors.Errorf("invalid option for --sort, options are: id, names, or number") + return errors.New("invalid option for --sort, options are: id, names, or number") } return nil } diff --git a/cmd/podman/pods/restart.go b/cmd/podman/pods/restart.go index 6d624806a..a8e31ce07 100644 --- a/cmd/podman/pods/restart.go +++ b/cmd/podman/pods/restart.go @@ -22,7 +22,7 @@ var ( Long: podRestartDescription, RunE: restart, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, ValidArgsFunction: common.AutocompletePods, Example: `podman pod restart podID1 podID2 diff --git a/cmd/podman/pods/rm.go b/cmd/podman/pods/rm.go index 52a815534..2ffd968f9 100644 --- a/cmd/podman/pods/rm.go +++ b/cmd/podman/pods/rm.go @@ -2,6 +2,7 @@ package pods import ( "context" + "errors" "fmt" "strings" @@ -13,7 +14,6 @@ import ( "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/specgenutil" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -35,7 +35,7 @@ var ( Long: podRmDescription, RunE: rm, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndPodIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "pod-id-file") }, ValidArgsFunction: common.AutocompletePods, Example: `podman pod rm mywebserverpod @@ -112,11 +112,7 @@ func removePods(namesOrIDs []string, rmOptions entities.PodRmOptions, printIDs b } func setExitCode(err error) { - cause := errors.Cause(err) - switch { - case cause == define.ErrNoSuchPod: - registry.SetExitCode(1) - case strings.Contains(cause.Error(), define.ErrNoSuchPod.Error()): + if errors.Is(err, define.ErrNoSuchPod) || strings.Contains(err.Error(), define.ErrNoSuchPod.Error()) { registry.SetExitCode(1) } } diff --git a/cmd/podman/pods/start.go b/cmd/podman/pods/start.go index b668cdd61..9436d34a5 100644 --- a/cmd/podman/pods/start.go +++ b/cmd/podman/pods/start.go @@ -31,7 +31,7 @@ var ( Long: podStartDescription, RunE: start, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndPodIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "pod-id-file") }, ValidArgsFunction: common.AutocompletePods, Example: `podman pod start podID diff --git a/cmd/podman/pods/stop.go b/cmd/podman/pods/stop.go index c8c3d2732..e8f82bee9 100644 --- a/cmd/podman/pods/stop.go +++ b/cmd/podman/pods/stop.go @@ -36,7 +36,7 @@ var ( Long: podStopDescription, RunE: stop, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndPodIDFile(cmd, args, false, true) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "pod-id-file") }, ValidArgsFunction: common.AutocompletePodsRunning, Example: `podman pod stop mywebserverpod diff --git a/cmd/podman/pods/top.go b/cmd/podman/pods/top.go index 4e9c7a3ee..34f3d1c33 100644 --- a/cmd/podman/pods/top.go +++ b/cmd/podman/pods/top.go @@ -2,6 +2,7 @@ package pods import ( "context" + "errors" "fmt" "os" "strings" @@ -12,7 +13,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/util" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -67,7 +67,7 @@ func top(_ *cobra.Command, args []string) error { } if len(args) < 1 && !topOptions.Latest { - return errors.Errorf("you must provide the name or id of a running pod") + return errors.New("you must provide the name or id of a running pod") } if topOptions.Latest { diff --git a/cmd/podman/pods/unpause.go b/cmd/podman/pods/unpause.go index a308a82c3..47b29458b 100644 --- a/cmd/podman/pods/unpause.go +++ b/cmd/podman/pods/unpause.go @@ -22,11 +22,9 @@ var ( Long: podUnpauseDescription, RunE: unpause, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndIDFile(cmd, args, false, "") }, - // TODO have a function which shows only pods which could be unpaused - // for now show all - ValidArgsFunction: common.AutocompletePods, + ValidArgsFunction: common.AutoCompletePodsPause, Example: `podman pod unpause podID1 podID2 podman pod unpause --all podman pod unpause --latest`, @@ -43,7 +41,7 @@ func init() { Parent: podCmd, }) flags := unpauseCommand.Flags() - flags.BoolVarP(&unpauseOptions.All, "all", "a", false, "Pause all running pods") + flags.BoolVarP(&unpauseOptions.All, "all", "a", false, "Unpause all running pods") validate.AddLatestFlag(unpauseCommand, &unpauseOptions.Latest) } diff --git a/cmd/podman/registry/config.go b/cmd/podman/registry/config.go index b5c9b359c..cae618b44 100644 --- a/cmd/podman/registry/config.go +++ b/cmd/podman/registry/config.go @@ -11,7 +11,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/pkg/util" - "github.com/pkg/errors" ) const ( @@ -92,14 +91,14 @@ func setXdgDirs() error { return nil } - // Setup XDG_RUNTIME_DIR + // Set up XDG_RUNTIME_DIR if _, found := os.LookupEnv("XDG_RUNTIME_DIR"); !found { dir, err := util.GetRuntimeDir() if err != nil { return err } if err := os.Setenv("XDG_RUNTIME_DIR", dir); err != nil { - return errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR="+dir) + return fmt.Errorf("cannot set XDG_RUNTIME_DIR=%s: %w", dir, err) } } @@ -110,14 +109,14 @@ func setXdgDirs() error { } } - // Setup XDG_CONFIG_HOME + // Set up XDG_CONFIG_HOME if _, found := os.LookupEnv("XDG_CONFIG_HOME"); !found { cfgHomeDir, err := util.GetRootlessConfigHomeDir() if err != nil { return err } if err := os.Setenv("XDG_CONFIG_HOME", cfgHomeDir); err != nil { - return errors.Wrapf(err, "cannot set XDG_CONFIG_HOME="+cfgHomeDir) + return fmt.Errorf("cannot set XDG_CONFIG_HOME=%s: %w", cfgHomeDir, err) } } return nil diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 2bd4fa723..f28d92e2f 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "os" "path/filepath" @@ -20,7 +21,6 @@ import ( "github.com/containers/podman/v4/pkg/parallel" "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/version" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -137,22 +137,20 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { if cmd.Flag("import").Changed { runtime, err := crutils.CRGetRuntimeFromArchive(cmd.Flag("import").Value.String()) if err != nil { - return errors.Wrapf( - err, - "failed extracting runtime information from %s", - cmd.Flag("import").Value.String(), + return fmt.Errorf( + "failed extracting runtime information from %s: %w", + cmd.Flag("import").Value.String(), err, ) } - if cfg.RuntimePath == "" { + + runtimeFlag := cmd.Root().Flag("runtime") + if runtimeFlag == nil { + return errors.New("failed to load --runtime flag") + } + + if !runtimeFlag.Changed { // If the user did not select a runtime, this takes the one from // the checkpoint archives and tells Podman to use it for the restore. - runtimeFlag := cmd.Root().Flags().Lookup("runtime") - if runtimeFlag == nil { - return errors.Errorf( - "setting runtime to '%s' for restore", - *runtime, - ) - } if err := runtimeFlag.Value.Set(*runtime); err != nil { return err } @@ -161,7 +159,7 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { } else if cfg.RuntimePath != *runtime { // If the user selected a runtime on the command-line this checks if // it is the same then during checkpointing and errors out if not. - return errors.Errorf( + return fmt.Errorf( "checkpoint archive %s was created with runtime '%s' and cannot be restored with runtime '%s'", cmd.Flag("import").Value.String(), *runtime, @@ -179,15 +177,15 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { var err error cfg.URI, cfg.Identity, err = cfg.ActiveDestination() if err != nil { - return errors.Wrap(err, "failed to resolve active destination") + return fmt.Errorf("failed to resolve active destination: %w", err) } if err := cmd.Root().LocalFlags().Set("url", cfg.URI); err != nil { - return errors.Wrap(err, "failed to override --url flag") + return fmt.Errorf("failed to override --url flag: %w", err) } if err := cmd.Root().LocalFlags().Set("identity", cfg.Identity); err != nil { - return errors.Wrap(err, "failed to override --identity flag") + return fmt.Errorf("failed to override --identity flag: %w", err) } } @@ -256,7 +254,7 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { } if cfg.MaxWorks <= 0 { - return errors.Errorf("maximum workers must be set to a positive number (got %d)", cfg.MaxWorks) + return fmt.Errorf("maximum workers must be set to a positive number (got %d)", cfg.MaxWorks) } if err := parallel.SetMaxThreads(uint(cfg.MaxWorks)); err != nil { return err @@ -298,12 +296,12 @@ func persistentPostRunE(cmd *cobra.Command, args []string) error { if cmd.Flag("memory-profile").Changed { f, err := os.Create(registry.PodmanConfig().MemoryProfile) if err != nil { - return errors.Wrap(err, "creating memory profile") + return fmt.Errorf("creating memory profile: %w", err) } defer f.Close() runtime.GC() // get up-to-date GC statistics if err := pprof.WriteHeapProfile(f); err != nil { - return errors.Wrap(err, "writing memory profile") + return fmt.Errorf("writing memory profile: %w", err) } } @@ -423,7 +421,7 @@ func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { // -s is deprecated due to conflict with -s on subcommands storageDriverFlagName := "storage-driver" pFlags.StringVar(&opts.StorageDriver, storageDriverFlagName, "", "Select which storage driver is used to manage storage of images and containers") - _ = cmd.RegisterFlagCompletionFunc(storageDriverFlagName, completion.AutocompleteNone) //TODO: what can we recommend here? + _ = cmd.RegisterFlagCompletionFunc(storageDriverFlagName, completion.AutocompleteNone) tmpdirFlagName := "tmpdir" pFlags.StringVar(&opts.Engine.TmpDir, tmpdirFlagName, "", "Path to the tmp directory for libpod state content.\n\nNote: use the environment variable 'TMPDIR' to change the temporary storage location for container images, '/var/tmp'.\n") @@ -482,7 +480,7 @@ func resolveDestination() (string, string, string) { cfg, err := config.ReadCustomConfig() if err != nil { - logrus.Warning(errors.Wrap(err, "unable to read local containers.conf")) + logrus.Warning(fmt.Errorf("unable to read local containers.conf: %w", err)) return "", registry.DefaultAPIAddress(), "" } @@ -495,7 +493,7 @@ func resolveDestination() (string, string, string) { func formatError(err error) string { var message string - if errors.Cause(err) == define.ErrOCIRuntime { + if errors.Is(err, define.ErrOCIRuntime) { // OCIRuntimeErrors include the reason for the failure in the // second to last message in the error chain. message = fmt.Sprintf( diff --git a/cmd/podman/root_test.go b/cmd/podman/root_test.go index 0a73afdc4..98a3de79d 100644 --- a/cmd/podman/root_test.go +++ b/cmd/podman/root_test.go @@ -1,12 +1,12 @@ package main import ( + "errors" "fmt" "strings" "testing" "github.com/containers/podman/v4/libpod/define" - "github.com/pkg/errors" ) func TestFormatError(t *testing.T) { @@ -22,7 +22,7 @@ func TestFormatError(t *testing.T) { func TestFormatOCIError(t *testing.T) { expectedPrefix := "Error: " expectedSuffix := "OCI runtime output" - err := errors.Wrap(define.ErrOCIRuntime, expectedSuffix) + err := fmt.Errorf("%s: %w", expectedSuffix, define.ErrOCIRuntime) output := formatError(err) if !strings.HasPrefix(output, expectedPrefix) { diff --git a/cmd/podman/secrets/create.go b/cmd/podman/secrets/create.go index 01ee3d256..8ecfecf69 100644 --- a/cmd/podman/secrets/create.go +++ b/cmd/podman/secrets/create.go @@ -2,6 +2,7 @@ package secrets import ( "context" + "errors" "fmt" "io" "os" @@ -11,7 +12,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -66,7 +66,7 @@ func create(cmd *cobra.Command, args []string) error { case env: envValue := os.Getenv(path) if envValue == "" { - return errors.Errorf("cannot create store secret data: environment variable %s is not set", path) + return fmt.Errorf("cannot create store secret data: environment variable %s is not set", path) } reader = strings.NewReader(envValue) case path == "-" || path == "/dev/stdin": diff --git a/cmd/podman/secrets/inspect.go b/cmd/podman/secrets/inspect.go index 473d5620c..1fcc676b4 100644 --- a/cmd/podman/secrets/inspect.go +++ b/cmd/podman/secrets/inspect.go @@ -10,7 +10,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -78,7 +77,7 @@ func inspect(cmd *cobra.Command, args []string) error { fmt.Fprintf(os.Stderr, "error inspecting secret: %v\n", err) } } - return errors.Errorf("inspecting secret: %v", errs[0]) + return fmt.Errorf("inspecting secret: %w", errs[0]) } return nil } diff --git a/cmd/podman/secrets/list.go b/cmd/podman/secrets/list.go index 558a16ccf..8b1956eab 100644 --- a/cmd/podman/secrets/list.go +++ b/cmd/podman/secrets/list.go @@ -2,6 +2,7 @@ package secrets import ( "context" + "fmt" "os" "time" @@ -13,7 +14,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/docker/go-units" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -108,7 +108,7 @@ func outputTemplate(cmd *cobra.Command, responses []*entities.SecretListReport) if !listFlag.noHeading { if err := tmpl.Execute(w, headers); err != nil { - return errors.Wrapf(err, "failed to write report column headers") + return fmt.Errorf("failed to write report column headers: %w", err) } } return tmpl.Execute(w, responses) diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go index 387de3c58..191603718 100644 --- a/cmd/podman/system/connection/add.go +++ b/cmd/podman/system/connection/add.go @@ -2,25 +2,22 @@ package connection import ( "encoding/json" + "errors" "fmt" "net" "net/url" "os" - "os/user" "regexp" - "time" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/system" "github.com/containers/podman/v4/libpod/define" - "github.com/containers/podman/v4/pkg/terminal" - "github.com/pkg/errors" + "github.com/containers/podman/v4/pkg/domain/utils" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/agent" ) var ( @@ -79,7 +76,7 @@ func add(cmd *cobra.Command, args []string) error { // Default to ssh schema if none given dest := args[1] if match, err := regexp.Match("^[A-Za-z][A-Za-z0-9+.-]*://", []byte(dest)); err != nil { - return errors.Wrapf(err, "invalid destination") + return fmt.Errorf("invalid destination: %w", err) } else if !match { dest = "ssh://" + dest } @@ -95,7 +92,7 @@ func add(cmd *cobra.Command, args []string) error { switch uri.Scheme { case "ssh": if uri.User.Username() == "" { - if uri.User, err = GetUserInfo(uri); err != nil { + if uri.User, err = utils.GetUserInfo(uri); err != nil { return err } } @@ -180,44 +177,20 @@ func add(cmd *cobra.Command, args []string) error { return cfg.Write() } -func GetUserInfo(uri *url.URL) (*url.Userinfo, error) { - var ( - usr *user.User - err error - ) - if u, found := os.LookupEnv("_CONTAINERS_ROOTLESS_UID"); found { - usr, err = user.LookupId(u) - if err != nil { - return nil, errors.Wrapf(err, "failed to lookup rootless user") - } - } else { - usr, err = user.Current() - if err != nil { - return nil, errors.Wrapf(err, "failed to obtain current user") - } - } - - pw, set := uri.User.Password() - if set { - return url.UserPassword(usr.Username, pw), nil - } - return url.User(usr.Username), nil -} - func getUDS(uri *url.URL, iden string) (string, error) { - cfg, err := ValidateAndConfigure(uri, iden) + cfg, err := utils.ValidateAndConfigure(uri, iden) if err != nil { - return "", errors.Wrapf(err, "failed to validate") + return "", fmt.Errorf("failed to validate: %w", err) } dial, err := ssh.Dial("tcp", uri.Host, cfg) if err != nil { - return "", errors.Wrapf(err, "failed to connect") + return "", fmt.Errorf("failed to connect: %w", err) } defer dial.Close() session, err := dial.NewSession() if err != nil { - return "", errors.Wrapf(err, "failed to create new ssh session on %q", uri.Host) + return "", fmt.Errorf("failed to create new ssh session on %q: %w", uri.Host, err) } defer session.Close() @@ -226,94 +199,18 @@ func getUDS(uri *url.URL, iden string) (string, error) { if v, found := os.LookupEnv("PODMAN_BINARY"); found { podman = v } - infoJSON, err := ExecRemoteCommand(dial, podman+" info --format=json") + infoJSON, err := utils.ExecRemoteCommand(dial, podman+" info --format=json") if err != nil { return "", err } var info define.Info if err := json.Unmarshal(infoJSON, &info); err != nil { - return "", errors.Wrapf(err, "failed to parse 'podman info' results") + return "", fmt.Errorf("failed to parse 'podman info' results: %w", err) } if info.Host.RemoteSocket == nil || len(info.Host.RemoteSocket.Path) == 0 { - return "", errors.Errorf("remote podman %q failed to report its UDS socket", uri.Host) + return "", fmt.Errorf("remote podman %q failed to report its UDS socket", uri.Host) } return info.Host.RemoteSocket.Path, nil } - -// ValidateAndConfigure will take a ssh url and an identity key (rsa and the like) and ensure the information given is valid -// iden iden can be blank to mean no identity key -// once the function validates the information it creates and returns an ssh.ClientConfig. -func ValidateAndConfigure(uri *url.URL, iden string) (*ssh.ClientConfig, error) { - var signers []ssh.Signer - passwd, passwdSet := uri.User.Password() - if iden != "" { // iden might be blank if coming from image scp or if no validation is needed - value := iden - s, err := terminal.PublicKey(value, []byte(passwd)) - if err != nil { - return nil, errors.Wrapf(err, "failed to read identity %q", value) - } - signers = append(signers, s) - logrus.Debugf("SSH Ident Key %q %s %s", value, ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type()) - } - if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found { // validate ssh information, specifically the unix file socket used by the ssh agent. - logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock) - - c, err := net.Dial("unix", sock) - if err != nil { - return nil, err - } - agentSigners, err := agent.NewClient(c).Signers() - if err != nil { - return nil, err - } - - signers = append(signers, agentSigners...) - - if logrus.IsLevelEnabled(logrus.DebugLevel) { - for _, s := range agentSigners { - logrus.Debugf("SSH Agent Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type()) - } - } - } - var authMethods []ssh.AuthMethod // now we validate and check for the authorization methods, most notaibly public key authorization - if len(signers) > 0 { - var dedup = make(map[string]ssh.Signer) - for _, s := range signers { - fp := ssh.FingerprintSHA256(s.PublicKey()) - if _, found := dedup[fp]; found { - logrus.Debugf("Dedup SSH Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type()) - } - dedup[fp] = s - } - - var uniq []ssh.Signer - for _, s := range dedup { - uniq = append(uniq, s) - } - authMethods = append(authMethods, ssh.PublicKeysCallback(func() ([]ssh.Signer, error) { - return uniq, nil - })) - } - if passwdSet { // if password authentication is given and valid, add to the list - authMethods = append(authMethods, ssh.Password(passwd)) - } - if len(authMethods) == 0 { - authMethods = append(authMethods, ssh.PasswordCallback(func() (string, error) { - pass, err := terminal.ReadPassword(fmt.Sprintf("%s's login password:", uri.User.Username())) - return string(pass), err - })) - } - tick, err := time.ParseDuration("40s") - if err != nil { - return nil, err - } - cfg := &ssh.ClientConfig{ - User: uri.User.Username(), - Auth: authMethods, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), - Timeout: tick, - } - return cfg, nil -} diff --git a/cmd/podman/system/connection/remove.go b/cmd/podman/system/connection/remove.go index 463eae9fa..29bf98c43 100644 --- a/cmd/podman/system/connection/remove.go +++ b/cmd/podman/system/connection/remove.go @@ -1,11 +1,12 @@ package connection import ( + "errors" + "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/system" - "github.com/pkg/errors" "github.com/spf13/cobra" ) diff --git a/cmd/podman/system/connection/shared.go b/cmd/podman/system/connection/shared.go deleted file mode 100644 index 714ae827d..000000000 --- a/cmd/podman/system/connection/shared.go +++ /dev/null @@ -1,27 +0,0 @@ -package connection - -import ( - "bytes" - - "github.com/pkg/errors" - "golang.org/x/crypto/ssh" -) - -// ExecRemoteCommand takes a ssh client connection and a command to run and executes the -// command on the specified client. The function returns the Stdout from the client or the Stderr -func ExecRemoteCommand(dial *ssh.Client, run string) ([]byte, error) { - sess, err := dial.NewSession() // new ssh client session - if err != nil { - return nil, err - } - defer sess.Close() - - var buffer bytes.Buffer - var bufferErr bytes.Buffer - sess.Stdout = &buffer // output from client funneled into buffer - sess.Stderr = &bufferErr // err form client funneled into buffer - if err := sess.Run(run); err != nil { // run the command on the ssh client - return nil, errors.Wrapf(err, bufferErr.String()) - } - return buffer.Bytes(), nil -} diff --git a/cmd/podman/system/df.go b/cmd/podman/system/df.go index dad14df6b..5b8126be6 100644 --- a/cmd/podman/system/df.go +++ b/cmd/podman/system/df.go @@ -78,11 +78,11 @@ func printSummary(cmd *cobra.Command, reports *entities.SystemDfReport) error { } } imageSummary := dfSummary{ - Type: "Images", - Total: len(reports.Images), - Active: active, - size: size, - reclaimable: reclaimable, + Type: "Images", + Total: len(reports.Images), + Active: active, + RawSize: size, + RawReclaimable: reclaimable, } dfSummaries = append(dfSummaries, &imageSummary) @@ -100,11 +100,11 @@ func printSummary(cmd *cobra.Command, reports *entities.SystemDfReport) error { conSize += c.RWSize } containerSummary := dfSummary{ - Type: "Containers", - Total: len(reports.Containers), - Active: conActive, - size: conSize, - reclaimable: conReclaimable, + Type: "Containers", + Total: len(reports.Containers), + Active: conActive, + RawSize: conSize, + RawReclaimable: conReclaimable, } dfSummaries = append(dfSummaries, &containerSummary) @@ -120,11 +120,11 @@ func printSummary(cmd *cobra.Command, reports *entities.SystemDfReport) error { volumesReclaimable += v.ReclaimableSize } volumeSummary := dfSummary{ - Type: "Local Volumes", - Total: len(reports.Volumes), - Active: activeVolumes, - size: volumesSize, - reclaimable: volumesReclaimable, + Type: "Local Volumes", + Total: len(reports.Volumes), + Active: activeVolumes, + RawSize: volumesSize, + RawReclaimable: volumesReclaimable, } dfSummaries = append(dfSummaries, &volumeSummary) @@ -150,7 +150,7 @@ func printSummary(cmd *cobra.Command, reports *entities.SystemDfReport) error { return writeTemplate(rpt, hdrs, dfSummaries) } -func printVerbose(cmd *cobra.Command, reports *entities.SystemDfReport) error { // nolint:interfacer +func printVerbose(cmd *cobra.Command, reports *entities.SystemDfReport) error { //nolint:interfacer rpt := report.New(os.Stdout, cmd.Name()) defer rpt.Flush() @@ -277,22 +277,22 @@ func (d *dfVolume) Size() string { } type dfSummary struct { - Type string - Total int - Active int - size int64 - reclaimable int64 + Type string + Total int + Active int + RawSize int64 `json:"Size"` + RawReclaimable int64 `json:"Reclaimable"` } func (d *dfSummary) Size() string { - return units.HumanSize(float64(d.size)) + return units.HumanSize(float64(d.RawSize)) } func (d *dfSummary) Reclaimable() string { percent := 0 // make sure to check this to prevent div by zero problems - if d.size > 0 { - percent = int(math.Round(float64(d.reclaimable) / float64(d.size) * float64(100))) + if d.RawSize > 0 { + percent = int(math.Round(float64(d.RawReclaimable) / float64(d.RawSize) * float64(100))) } - return fmt.Sprintf("%s (%d%%)", units.HumanSize(float64(d.reclaimable)), percent) + return fmt.Sprintf("%s (%d%%)", units.HumanSize(float64(d.RawReclaimable)), percent) } diff --git a/cmd/podman/system/dial_stdio.go b/cmd/podman/system/dial_stdio.go index 8b665bedc..42ce65746 100644 --- a/cmd/podman/system/dial_stdio.go +++ b/cmd/podman/system/dial_stdio.go @@ -2,13 +2,15 @@ package system import ( "context" + "fmt" "io" "os" + "errors" + "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/bindings" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -40,15 +42,15 @@ func runDialStdio() error { defer cancel() bindCtx, err := bindings.NewConnection(ctx, cfg.URI) if err != nil { - return errors.Wrap(err, "failed to open connection to podman") + return fmt.Errorf("failed to open connection to podman: %w", err) } conn, err := bindings.GetClient(bindCtx) if err != nil { - return errors.Wrap(err, "failed to get connection after initialization") + return fmt.Errorf("failed to get connection after initialization: %w", err) } netConn, err := conn.GetDialer(bindCtx) if err != nil { - return errors.Wrap(err, "failed to open the raw stream connection") + return fmt.Errorf("failed to open the raw stream connection: %w", err) } defer netConn.Close() @@ -95,7 +97,7 @@ func copier(to halfWriteCloser, from halfReadCloser, debugDescription string) er } }() if _, err := io.Copy(to, from); err != nil { - return errors.Wrapf(err, "error while Copy (%s)", debugDescription) + return fmt.Errorf("error while Copy (%s): %w", debugDescription, err) } return nil } diff --git a/cmd/podman/system/prune.go b/cmd/podman/system/prune.go index ff78f93bb..1d6ba8155 100644 --- a/cmd/podman/system/prune.go +++ b/cmd/podman/system/prune.go @@ -75,6 +75,7 @@ func prune(cmd *cobra.Command, args []string) error { } } + // Remove all unused pods, containers, images, networks, and volume data. pruneOptions.Filters, err = parse.FilterArgumentsIntoFilters(filters) if err != nil { return err @@ -106,6 +107,11 @@ func prune(cmd *cobra.Command, args []string) error { if err != nil { return err } + // Print Network prune results + err = utils.PrintNetworkPruneResults(response.NetworkPruneReports, true) + if err != nil { + return err + } fmt.Printf("Total reclaimed space: %s\n", units.HumanSize((float64)(response.ReclaimedSpace))) return nil diff --git a/cmd/podman/system/reset.go b/cmd/podman/system/reset.go index 176573bf6..20f15a34f 100644 --- a/cmd/podman/system/reset.go +++ b/cmd/podman/system/reset.go @@ -91,18 +91,10 @@ func reset(cmd *cobra.Command, args []string) { registry.ContainerEngine().Shutdown(registry.Context()) registry.ImageEngine().Shutdown(registry.Context()) - engine, err := infra.NewSystemEngine(entities.ResetMode, registry.PodmanConfig()) - if err != nil { - logrus.Error(err) - os.Exit(define.ExecErrorCodeGeneric) - } - defer engine.Shutdown(registry.Context()) - - if err := engine.Reset(registry.Context()); err != nil { + // Do not try to shut the engine down, as a Reset engine is not valid + // after its creation. + if _, err := infra.NewSystemEngine(entities.ResetMode, registry.PodmanConfig()); err != nil { logrus.Error(err) - // FIXME change this to return the error like other commands - // defer will never run on os.Exit() - //nolint:gocritic os.Exit(define.ExecErrorCodeGeneric) } diff --git a/cmd/podman/system/service_abi.go b/cmd/podman/system/service_abi.go index 9dc9de1c8..6823d77ba 100644 --- a/cmd/podman/system/service_abi.go +++ b/cmd/podman/system/service_abi.go @@ -4,24 +4,46 @@ package system import ( + "errors" "fmt" "net" "net/url" "os" "path/filepath" + "github.com/containers/common/pkg/cgroups" "github.com/containers/podman/v4/cmd/podman/registry" api "github.com/containers/podman/v4/pkg/api/server" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/domain/infra" "github.com/containers/podman/v4/pkg/servicereaper" + "github.com/containers/podman/v4/utils" "github.com/coreos/go-systemd/v22/activation" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/pflag" "golang.org/x/sys/unix" ) +// maybeMoveToSubCgroup moves the current process in a sub cgroup when +// it is running in the root cgroup on a system that uses cgroupv2. +func maybeMoveToSubCgroup() error { + unifiedMode, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return err + } + if !unifiedMode { + return nil + } + cgroup, err := utils.GetOwnCgroup() + if err != nil { + return err + } + if cgroup == "/" { + return utils.MoveUnderCgroupSubtree("init") + } + return nil +} + func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities.ServiceOptions) error { var ( listener net.Listener @@ -46,11 +68,15 @@ func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities return fmt.Errorf("wrong number of file descriptors for socket activation protocol (%d != 1)", len(listeners)) } listener = listeners[0] + // note that activation.Listeners() returns nil when it cannot listen on the fd (i.e. udp connection) + if listener == nil { + return errors.New("unexpected fd received from systemd: cannot listen on it") + } libpodRuntime.SetRemoteURI(listeners[0].Addr().String()) } else { uri, err := url.Parse(opts.URI) if err != nil { - return errors.Errorf("%s is an invalid socket destination", opts.URI) + return fmt.Errorf("%s is an invalid socket destination", opts.URI) } switch uri.Scheme { @@ -70,7 +96,7 @@ func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities } else { listener, err = net.Listen(uri.Scheme, path) if err != nil { - return errors.Wrapf(err, "unable to create socket") + return fmt.Errorf("unable to create socket: %w", err) } } case "tcp": @@ -81,7 +107,7 @@ func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities } listener, err = net.Listen(uri.Scheme, host) if err != nil { - return errors.Wrapf(err, "unable to create socket %v", host) + return fmt.Errorf("unable to create socket %v: %w", host, err) } default: logrus.Debugf("Attempting API Service endpoint scheme %q", uri.Scheme) @@ -99,6 +125,10 @@ func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities return err } + if err := maybeMoveToSubCgroup(); err != nil { + return err + } + servicereaper.Start() infra.StartWatcher(libpodRuntime) server, err := api.NewServerWithSettings(libpodRuntime, listener, opts) diff --git a/cmd/podman/system/unshare.go b/cmd/podman/system/unshare.go index 0ae5b81ad..6d9c33b64 100644 --- a/cmd/podman/system/unshare.go +++ b/cmd/podman/system/unshare.go @@ -1,14 +1,14 @@ package system import ( + "errors" "os" - "os/exec" "github.com/containers/common/pkg/completion" "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/rootless" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -47,35 +47,18 @@ func init() { func unshare(cmd *cobra.Command, args []string) error { if isRootless := rootless.IsRootless(); !isRootless { - return errors.Errorf("please use unshare with rootless") + return errors.New("please use unshare with rootless") } // exec the specified command, if there is one if len(args) < 1 { // try to exec the shell, if one's set shell, shellSet := os.LookupEnv("SHELL") if !shellSet { - return errors.Errorf("no command specified and no $SHELL specified") + return errors.New("no command specified and no $SHELL specified") } args = []string{shell} } err := registry.ContainerEngine().Unshare(registry.Context(), args, unshareOptions) - if err != nil { - if exitError, ok := err.(*exec.ExitError); ok { - // the user command inside the unshare env has failed - // we set the exit code, do not return the error to the user - // otherwise "exit status X" will be printed - registry.SetExitCode(exitError.ExitCode()) - return nil - } - // cmd.Run() can return fs.ErrNotExist, fs.ErrPermission or exec.ErrNotFound - // follow podman run/exec standard with the exit codes - if errors.Is(err, os.ErrNotExist) || errors.Is(err, exec.ErrNotFound) { - registry.SetExitCode(127) - } else if errors.Is(err, os.ErrPermission) { - registry.SetExitCode(126) - } - return err - } - return nil + return utils.HandleOSExecError(err) } diff --git a/cmd/podman/utils/error.go b/cmd/podman/utils/error.go index 2aaa71373..3efff0301 100644 --- a/cmd/podman/utils/error.go +++ b/cmd/podman/utils/error.go @@ -4,10 +4,12 @@ import ( "errors" "fmt" "os" + "os/exec" "strconv" "strings" buildahCLI "github.com/containers/buildah/pkg/cli" + "github.com/containers/podman/v4/cmd/podman/registry" ) type OutputErrors []error @@ -43,3 +45,33 @@ func ExitCodeFromBuildError(errorMsg string) (int, error) { } return buildahCLI.ExecErrorCodeGeneric, errors.New("message does not contains a valid exit code") } + +// HandleOSExecError checks the given error for an exec.ExitError error and +// sets the same podman exit code as the error. +// No error will be returned in this case to make sure things like podman +// unshare false work correctly without extra output. +// When the exec file does not exists we set the exit code to 127, for +// permission errors 126 is used as exit code. In this case we still return +// the error so the user gets an error message. +// If the error is nil it returns nil. +func HandleOSExecError(err error) error { + if err == nil { + return nil + } + var exitError *exec.ExitError + if errors.As(err, &exitError) { + // the user command inside the unshare/ssh env has failed + // we set the exit code, do not return the error to the user + // otherwise "exit status X" will be printed + registry.SetExitCode(exitError.ExitCode()) + return nil + } + // cmd.Run() can return fs.ErrNotExist, fs.ErrPermission or exec.ErrNotFound + // follow podman run/exec standard with the exit codes + if errors.Is(err, os.ErrNotExist) || errors.Is(err, exec.ErrNotFound) { + registry.SetExitCode(127) + } else if errors.Is(err, os.ErrPermission) { + registry.SetExitCode(126) + } + return err +} diff --git a/cmd/podman/utils/signals_linux.go b/cmd/podman/utils/signals_linux.go deleted file mode 100644 index dd0507c0e..000000000 --- a/cmd/podman/utils/signals_linux.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !windows -// +build !windows - -package utils - -import ( - "os" - - "golang.org/x/sys/unix" -) - -// Platform specific signal synonyms -var ( - SIGHUP os.Signal = unix.SIGHUP -) diff --git a/cmd/podman/utils/signals_windows.go b/cmd/podman/utils/signals_windows.go deleted file mode 100644 index e6fcc1b32..000000000 --- a/cmd/podman/utils/signals_windows.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build windows -// +build windows - -package utils - -import ( - "os" - - "golang.org/x/sys/windows" -) - -// Platform specific signal synonyms -var ( - SIGHUP os.Signal = windows.SIGHUP -) diff --git a/cmd/podman/utils/utils.go b/cmd/podman/utils/utils.go index 6fd6647d0..2ae123388 100644 --- a/cmd/podman/utils/utils.go +++ b/cmd/podman/utils/utils.go @@ -44,7 +44,7 @@ func PrintPodPruneResults(podPruneReports []*entities.PodPruneReport, heading bo func PrintContainerPruneResults(containerPruneReports []*reports.PruneReport, heading bool) error { var errs OutputErrors - if heading && (len(containerPruneReports) > 0) { + if heading && len(containerPruneReports) > 0 { fmt.Println("Deleted Containers") } for _, v := range containerPruneReports { @@ -72,7 +72,7 @@ func PrintVolumePruneResults(volumePruneReport []*reports.PruneReport, heading b } func PrintImagePruneResults(imagePruneReports []*reports.PruneReport, heading bool) error { - if heading { + if heading && len(imagePruneReports) > 0 { fmt.Println("Deleted Images") } for _, r := range imagePruneReports { @@ -84,3 +84,18 @@ func PrintImagePruneResults(imagePruneReports []*reports.PruneReport, heading bo return nil } + +func PrintNetworkPruneResults(networkPruneReport []*reports.PruneReport, heading bool) error { + var errs OutputErrors + if heading && len(networkPruneReport) > 0 { + fmt.Println("Deleted Networks") + } + for _, r := range networkPruneReport { + if r.Err == nil { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Err) + } + } + return errs.PrintErrors() +} diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go index 669456bd3..39eedca64 100644 --- a/cmd/podman/validate/args.go +++ b/cmd/podman/validate/args.go @@ -1,12 +1,12 @@ package validate import ( + "errors" "fmt" "strconv" "strings" "github.com/containers/podman/v4/cmd/podman/registry" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -23,12 +23,12 @@ func SubCommandExists(cmd *cobra.Command, args []string) error { if len(args) > 0 { suggestions := cmd.SuggestionsFor(args[0]) if len(suggestions) == 0 { - return errors.Errorf("unrecognized command `%[1]s %[2]s`\nTry '%[1]s --help' for more information", cmd.CommandPath(), args[0]) + return fmt.Errorf("unrecognized command `%[1]s %[2]s`\nTry '%[1]s --help' for more information", cmd.CommandPath(), args[0]) } - return errors.Errorf("unrecognized command `%[1]s %[2]s`\n\nDid you mean this?\n\t%[3]s\n\nTry '%[1]s --help' for more information", cmd.CommandPath(), args[0], strings.Join(suggestions, "\n\t")) + return fmt.Errorf("unrecognized command `%[1]s %[2]s`\n\nDid you mean this?\n\t%[3]s\n\nTry '%[1]s --help' for more information", cmd.CommandPath(), args[0], strings.Join(suggestions, "\n\t")) } - cmd.Help() // nolint: errcheck - return errors.Errorf("missing command '%[1]s COMMAND'", cmd.CommandPath()) + cmd.Help() //nolint: errcheck + return fmt.Errorf("missing command '%[1]s COMMAND'", cmd.CommandPath()) } // IDOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag @@ -44,101 +44,56 @@ func IDOrLatestArgs(cmd *cobra.Command, args []string) error { return fmt.Errorf("%q requires a name, id, or the \"--latest\" flag", cmd.CommandPath()) } if len(args) > 0 && given { - return fmt.Errorf("--latest and containers cannot be used together") + return errors.New("--latest and containers cannot be used together") } } return nil } -// TODO: the two functions CheckAllLatestAndCIDFile and CheckAllLatestAndPodIDFile are almost identical. -// It may be worth looking into generalizing the two a bit more and share code but time is scarce and -// we only live once. - -// CheckAllLatestAndCIDFile checks that --all and --latest are used correctly. -// If cidfile is set, also check for the --cidfile flag. +// CheckAllLatestAndCIDFile checks that --all and --latest are used correctly for containers and pods +// If idFileFlag is set is set, also checks for the --cidfile or --pod-id-file flag. +// Note: this has been deprecated, use CheckAllLatestAndIDFile instead func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error { - var specifiedLatest bool - argLen := len(args) - if !registry.IsRemote() { - specifiedLatest, _ = c.Flags().GetBool("latest") - if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { - if !cidfile { - return errors.New("unable to lookup values for 'latest' or 'all'") - } else if c.Flags().Lookup("cidfile") == nil { - return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'") - } - } - } - - specifiedAll, _ := c.Flags().GetBool("all") - specifiedCIDFile := false - if cid, _ := c.Flags().GetStringArray("cidfile"); len(cid) > 0 { - specifiedCIDFile = true - } - - if specifiedCIDFile && (specifiedAll || specifiedLatest) { - return errors.Errorf("--all, --latest and --cidfile cannot be used together") - } else if specifiedAll && specifiedLatest { - return errors.Errorf("--all and --latest cannot be used together") - } - - if (argLen > 0) && specifiedAll { - return errors.Errorf("no arguments are needed with --all") - } - - if ignoreArgLen { - return nil - } - - if argLen > 0 { - if specifiedLatest { - return errors.Errorf("--latest and containers cannot be used together") - } else if cidfile && (specifiedLatest || specifiedCIDFile) { - return errors.Errorf("no arguments are needed with --latest or --cidfile") - } - } - - if specifiedCIDFile { - return nil - } - - if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedCIDFile { - return errors.Errorf("you must provide at least one name or id") - } - return nil + return CheckAllLatestAndIDFile(c, args, ignoreArgLen, "cidfile") } // CheckAllLatestAndPodIDFile checks that --all and --latest are used correctly. // If withIDFile is set, also check for the --pod-id-file flag. +// Note: this has been deprecated, use CheckAllLatestAndIDFile instead func CheckAllLatestAndPodIDFile(c *cobra.Command, args []string, ignoreArgLen bool, withIDFile bool) error { + return CheckAllLatestAndIDFile(c, args, ignoreArgLen, "pod-id-file") +} + +// CheckAllLatestAndIDFile checks that --all and --latest are used correctly for containers and pods +// If idFileFlag is set is set, also checks for the --cidfile or --pod-id-file flag. +func CheckAllLatestAndIDFile(c *cobra.Command, args []string, ignoreArgLen bool, idFileFlag string) error { var specifiedLatest bool argLen := len(args) if !registry.IsRemote() { - // remote clients have no latest flag specifiedLatest, _ = c.Flags().GetBool("latest") if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { - if !withIDFile { - return errors.New("unable to lookup values for 'latest' or 'all'") - } else if c.Flags().Lookup("pod-id-file") == nil { - return errors.New("unable to lookup values for 'latest', 'all' or 'pod-id-file'") + if idFileFlag == "" { + return errors.New("unable to look up values for 'latest' or 'all'") + } else if c.Flags().Lookup(idFileFlag) == nil { + return fmt.Errorf("unable to look up values for 'latest', 'all', or '%s'", idFileFlag) } } } specifiedAll, _ := c.Flags().GetBool("all") - specifiedPodIDFile := false - if pid, _ := c.Flags().GetStringArray("pod-id-file"); len(pid) > 0 { - specifiedPodIDFile = true + specifiedIDFile := false + if cid, _ := c.Flags().GetStringArray(idFileFlag); len(cid) > 0 { + specifiedIDFile = true } - if specifiedPodIDFile && (specifiedAll || specifiedLatest) { - return errors.Errorf("--all, --latest and --pod-id-file cannot be used together") + if specifiedIDFile && (specifiedAll || specifiedLatest) { + return fmt.Errorf("--all, --latest, and --%s cannot be used together", idFileFlag) } else if specifiedAll && specifiedLatest { - return errors.Errorf("--all and --latest cannot be used together") + return errors.New("--all and --latest cannot be used together") } if (argLen > 0) && specifiedAll { - return errors.Errorf("no arguments are needed with --all") + return errors.New("no arguments are needed with --all") } if ignoreArgLen { @@ -147,18 +102,18 @@ func CheckAllLatestAndPodIDFile(c *cobra.Command, args []string, ignoreArgLen bo if argLen > 0 { if specifiedLatest { - return errors.Errorf("--latest and pods cannot be used together") - } else if withIDFile && (specifiedLatest || specifiedPodIDFile) { - return errors.Errorf("no arguments are needed with --latest or --pod-id-file") + return errors.New("--latest and containers cannot be used together") + } else if idFileFlag != "" && (specifiedLatest || specifiedIDFile) { + return fmt.Errorf("no arguments are needed with --latest or --%s", idFileFlag) } } - if specifiedPodIDFile { + if specifiedIDFile { return nil } - if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedPodIDFile { - return errors.Errorf("you must provide at least one name or id") + if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedIDFile { + return errors.New("you must provide at least one name or id") } return nil } diff --git a/cmd/podman/volumes/create.go b/cmd/podman/volumes/create.go index 1668c72de..0d19fab47 100644 --- a/cmd/podman/volumes/create.go +++ b/cmd/podman/volumes/create.go @@ -8,7 +8,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/parse" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -17,6 +16,7 @@ var ( createCommand = &cobra.Command{ Use: "create [options] [NAME]", + Args: cobra.MaximumNArgs(1), Short: "Create a new volume", Long: createDescription, RunE: create, @@ -59,19 +59,16 @@ func create(cmd *cobra.Command, args []string) error { var ( err error ) - if len(args) > 1 { - return errors.Errorf("too many arguments, create takes at most 1 argument") - } if len(args) > 0 { createOpts.Name = args[0] } createOpts.Label, err = parse.GetAllLabels([]string{}, opts.Label) if err != nil { - return errors.Wrapf(err, "unable to process labels") + return fmt.Errorf("unable to process labels: %w", err) } createOpts.Options, err = parse.GetAllLabels([]string{}, opts.Opts) if err != nil { - return errors.Wrapf(err, "unable to process options") + return fmt.Errorf("unable to process options: %w", err) } response, err := registry.ContainerEngine().VolumeCreate(context.Background(), createOpts) if err != nil { diff --git a/cmd/podman/volumes/export.go b/cmd/podman/volumes/export.go index 5086323f9..f9e08be87 100644 --- a/cmd/podman/volumes/export.go +++ b/cmd/podman/volumes/export.go @@ -2,14 +2,15 @@ package volumes import ( "context" + "errors" "fmt" "github.com/containers/common/pkg/completion" "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/utils" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -58,10 +59,13 @@ func export(cmd *cobra.Command, args []string) error { return errors.New("expects output path, use --output=[path]") } inspectOpts.Type = common.VolumeType - volumeData, _, err := containerEngine.VolumeInspect(ctx, args, inspectOpts) + volumeData, errs, err := containerEngine.VolumeInspect(ctx, args, inspectOpts) if err != nil { return err } + if len(errs) > 0 { + return errorhandling.JoinErrors(errs) + } if len(volumeData) < 1 { return errors.New("no volume data found") } diff --git a/cmd/podman/volumes/import.go b/cmd/podman/volumes/import.go index 988c5536d..8f3c7f27e 100644 --- a/cmd/podman/volumes/import.go +++ b/cmd/podman/volumes/import.go @@ -1,6 +1,7 @@ package volumes import ( + "errors" "fmt" "os" @@ -8,8 +9,8 @@ import ( "github.com/containers/podman/v4/cmd/podman/parse" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/utils" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -60,10 +61,14 @@ func importVol(cmd *cobra.Command, args []string) error { } inspectOpts.Type = common.VolumeType - volumeData, _, err := containerEngine.VolumeInspect(ctx, volumes, inspectOpts) + inspectOpts.Type = common.VolumeType + volumeData, errs, err := containerEngine.VolumeInspect(ctx, volumes, inspectOpts) if err != nil { return err } + if len(errs) > 0 { + return errorhandling.JoinErrors(errs) + } if len(volumeData) < 1 { return errors.New("no volume data found") } diff --git a/cmd/podman/volumes/inspect.go b/cmd/podman/volumes/inspect.go index 7cf363f36..68ba2976a 100644 --- a/cmd/podman/volumes/inspect.go +++ b/cmd/podman/volumes/inspect.go @@ -1,12 +1,13 @@ package volumes import ( + "errors" + "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/inspect" "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go index c14cf08bd..06118513d 100644 --- a/cmd/podman/volumes/list.go +++ b/cmd/podman/volumes/list.go @@ -2,6 +2,7 @@ package volumes import ( "context" + "errors" "fmt" "os" @@ -12,7 +13,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/validate" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -119,7 +119,7 @@ func outputTemplate(cmd *cobra.Command, responses []*entities.VolumeListReport) if !(noHeading || cliOpts.Quiet || cmd.Flag("format").Changed) { if err := tmpl.Execute(w, headers); err != nil { - return errors.Wrapf(err, "failed to write report column headers") + return fmt.Errorf("failed to write report column headers: %w", err) } } return tmpl.Execute(w, responses) diff --git a/cmd/podman/volumes/reload.go b/cmd/podman/volumes/reload.go new file mode 100644 index 000000000..d0d76fb88 --- /dev/null +++ b/cmd/podman/volumes/reload.go @@ -0,0 +1,52 @@ +package volumes + +import ( + "fmt" + + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/cmd/podman/utils" + "github.com/containers/podman/v4/cmd/podman/validate" + "github.com/spf13/cobra" +) + +var ( + reloadDescription = `Check all configured volume plugins and update the libpod database with all available volumes. + + Existing volumes are also removed from the database when they are no longer present in the plugin.` + reloadCommand = &cobra.Command{ + Use: "reload", + Args: validate.NoArgs, + Short: "reload all volumes from volume plugins", + Long: reloadDescription, + RunE: reload, + ValidArgsFunction: completion.AutocompleteNone, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: reloadCommand, + Parent: volumeCmd, + }) +} + +func reload(cmd *cobra.Command, args []string) error { + report, err := registry.ContainerEngine().VolumeReload(registry.Context()) + if err != nil { + return err + } + printReload("Added", report.Added) + printReload("Removed", report.Removed) + errs := (utils.OutputErrors)(report.Errors) + return errs.PrintErrors() +} + +func printReload(typ string, values []string) { + if len(values) > 0 { + fmt.Println(typ + ":") + for _, name := range values { + fmt.Println(name) + } + } +} diff --git a/cmd/podman/volumes/rm.go b/cmd/podman/volumes/rm.go index 2012b7d3a..c160b8623 100644 --- a/cmd/podman/volumes/rm.go +++ b/cmd/podman/volumes/rm.go @@ -2,6 +2,7 @@ package volumes import ( "context" + "errors" "fmt" "strings" @@ -11,7 +12,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -80,15 +80,9 @@ func rm(cmd *cobra.Command, args []string) error { } func setExitCode(err error) { - cause := errors.Cause(err) - switch { - case cause == define.ErrNoSuchVolume: + if errors.Is(err, define.ErrNoSuchVolume) || strings.Contains(err.Error(), define.ErrNoSuchVolume.Error()) { registry.SetExitCode(1) - case strings.Contains(cause.Error(), define.ErrNoSuchVolume.Error()): - registry.SetExitCode(1) - case cause == define.ErrVolumeBeingUsed: - registry.SetExitCode(2) - case strings.Contains(cause.Error(), define.ErrVolumeBeingUsed.Error()): + } else if errors.Is(err, define.ErrVolumeBeingUsed) || strings.Contains(err.Error(), define.ErrVolumeBeingUsed.Error()) { registry.SetExitCode(2) } } diff --git a/cmd/rootlessport/main.go b/cmd/rootlessport/main.go index 5bd35a985..5410cd14a 100644 --- a/cmd/rootlessport/main.go +++ b/cmd/rootlessport/main.go @@ -6,6 +6,7 @@ package main import ( "context" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -18,7 +19,6 @@ import ( "github.com/containernetworking/plugins/pkg/ns" "github.com/containers/common/libnetwork/types" "github.com/containers/podman/v4/pkg/rootlessport" - "github.com/pkg/errors" rkport "github.com/rootless-containers/rootlesskit/pkg/port" rkbuiltin "github.com/rootless-containers/rootlesskit/pkg/port/builtin" rkportutil "github.com/rootless-containers/rootlesskit/pkg/port/portutil" @@ -226,8 +226,8 @@ outer: // https://github.com/containers/podman/issues/11248 // Copy /dev/null to stdout and stderr to prevent SIGPIPE errors if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil { - unix.Dup2(int(f.Fd()), 1) // nolint:errcheck - unix.Dup2(int(f.Fd()), 2) // nolint:errcheck + unix.Dup2(int(f.Fd()), 1) //nolint:errcheck + unix.Dup2(int(f.Fd()), 2) //nolint:errcheck f.Close() } // write and close ReadyFD (convention is same as slirp4netns --ready-fd) @@ -269,16 +269,16 @@ func handler(ctx context.Context, conn io.Reader, pm rkport.Manager) error { dec := json.NewDecoder(conn) err := dec.Decode(&childIP) if err != nil { - return errors.Wrap(err, "rootless port failed to decode ports") + return fmt.Errorf("rootless port failed to decode ports: %w", err) } portStatus, err := pm.ListPorts(ctx) if err != nil { - return errors.Wrap(err, "rootless port failed to list ports") + return fmt.Errorf("rootless port failed to list ports: %w", err) } for _, status := range portStatus { err = pm.RemovePort(ctx, status.ID) if err != nil { - return errors.Wrap(err, "rootless port failed to remove port") + return fmt.Errorf("rootless port failed to remove port: %w", err) } } // add the ports with the new child IP @@ -287,7 +287,7 @@ func handler(ctx context.Context, conn io.Reader, pm rkport.Manager) error { status.Spec.ChildIP = childIP _, err = pm.AddPort(ctx, status.Spec) if err != nil { - return errors.Wrap(err, "rootless port failed to add port") + return fmt.Errorf("rootless port failed to add port: %w", err) } } return nil diff --git a/cmd/rootlessport/wsl_test.go b/cmd/rootlessport/wsl_test.go index 83d7e3717..2c95251cd 100644 --- a/cmd/rootlessport/wsl_test.go +++ b/cmd/rootlessport/wsl_test.go @@ -20,7 +20,7 @@ type SpecData struct { } func TestDualStackSplit(t *testing.T) { - //nolint + //nolint:revive,stylecheck const ( IP4_ALL = "0.0.0.0" IP4__LO = "127.0.0.1" diff --git a/cmd/winpath/main.go b/cmd/winpath/main.go index 6fbe72837..bb57e39de 100644 --- a/cmd/winpath/main.go +++ b/cmd/winpath/main.go @@ -12,6 +12,7 @@ import ( "syscall" "unsafe" + "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" ) @@ -26,6 +27,7 @@ const ( Environment = "Environment" Add operation = iota Remove + Open NotSpecified ) @@ -37,6 +39,8 @@ func main() { op = Add case "remove": op = Remove + case "open": + op = Open } } @@ -46,6 +50,14 @@ func main() { os.Exit(ERR_BAD_ARGS) } + // Hidden operation as a workaround for the installer + if op == Open && len(os.Args) > 2 { + if err := winOpenFile(os.Args[2]); err != nil { + os.Exit(OPERATION_FAILED) + } + os.Exit(0) + } + if err := modify(op); err != nil { os.Exit(OPERATION_FAILED) } @@ -119,7 +131,7 @@ func removePathFromRegistry(path string) error { k, err := registry.OpenKey(registry.CURRENT_USER, Environment, registry.READ|registry.WRITE) if err != nil { if errors.Is(err, fs.ErrNotExist) { - // Nothing to cleanup, the Environment registry key does not exist. + // Nothing to clean up, the Environment registry key does not exist. return nil } return err @@ -182,3 +194,9 @@ func alert(caption string) int { return int(ret) } + +func winOpenFile(file string) error { + verb, _ := syscall.UTF16PtrFromString("open") + fileW, _ := syscall.UTF16PtrFromString(file) + return windows.ShellExecute(0, verb, fileW, nil, nil, windows.SW_NORMAL) +} |