diff options
Diffstat (limited to 'cmd/podman/common')
-rw-r--r-- | cmd/podman/common/completion.go | 347 | ||||
-rw-r--r-- | cmd/podman/common/completion_test.go | 72 | ||||
-rw-r--r-- | cmd/podman/common/create.go | 83 | ||||
-rw-r--r-- | cmd/podman/common/create_opts.go | 491 | ||||
-rw-r--r-- | cmd/podman/common/default.go | 3 | ||||
-rw-r--r-- | cmd/podman/common/diffChanges.go | 3 | ||||
-rw-r--r-- | cmd/podman/common/netflags.go | 27 |
7 files changed, 435 insertions, 591 deletions
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 |