diff options
Diffstat (limited to 'cmd/podman')
28 files changed, 412 insertions, 199 deletions
diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index cb3efe592..f1dea4113 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -13,7 +13,6 @@ import ( "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/network/types" "github.com/containers/podman/v3/pkg/domain/entities" - "github.com/containers/podman/v3/pkg/network" "github.com/containers/podman/v3/pkg/rootless" systemdDefine "github.com/containers/podman/v3/pkg/systemd/define" "github.com/containers/podman/v3/pkg/util" @@ -209,7 +208,7 @@ func getImages(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellComp return suggestions, cobra.ShellCompDirectiveNoFileComp } -func getSecrets(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) { +func getSecrets(cmd *cobra.Command, toComplete string, cType completeType) ([]string, cobra.ShellCompDirective) { suggestions := []string{} engine, err := setupContainerEngine(cmd) @@ -224,7 +223,13 @@ func getSecrets(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCom } for _, s := range secrets { - if strings.HasPrefix(s.Spec.Name, toComplete) { + // works the same as in getNetworks + if ((len(toComplete) > 1 && cType == completeDefault) || + cType == completeIDs) && strings.HasPrefix(s.ID, toComplete) { + suggestions = append(suggestions, s.ID[0:12]) + } + // include name in suggestions + if cType != completeIDs && strings.HasPrefix(s.Spec.Name, toComplete) { suggestions = append(suggestions, s.Spec.Name) } } @@ -256,12 +261,11 @@ func getNetworks(cmd *cobra.Command, toComplete string, cType completeType) ([]s } for _, n := range networks { - id := network.GetNetworkID(n.Name) // include ids in suggestions if cType == completeIDs or // more then 2 chars are typed and cType == completeDefault if ((len(toComplete) > 1 && cType == completeDefault) || - cType == completeIDs) && strings.HasPrefix(id, toComplete) { - suggestions = append(suggestions, id[0:12]) + cType == completeIDs) && strings.HasPrefix(n.ID, toComplete) { + suggestions = append(suggestions, n.ID[0:12]) } // include name in suggestions if cType != completeIDs && strings.HasPrefix(n.Name, toComplete) { @@ -470,7 +474,7 @@ func AutocompleteSecrets(cmd *cobra.Command, args []string, toComplete string) ( if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - return getSecrets(cmd, toComplete) + return getSecrets(cmd, toComplete, completeDefault) } func AutocompleteSecretCreate(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -1283,6 +1287,15 @@ func AutocompleteVolumeFilters(cmd *cobra.Command, args []string, toComplete str return completeKeyValues(toComplete, kv) } +// AutocompleteSecretFilters - Autocomplete secret ls --filter options. +func AutocompleteSecretFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + kv := keyValueCompletion{ + "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getSecrets(cmd, s, completeNames) }, + "id=": func(s string) ([]string, cobra.ShellCompDirective) { return getSecrets(cmd, s, completeIDs) }, + } + return completeKeyValues(toComplete, kv) +} + // AutocompleteCheckpointCompressType - Autocomplete checkpoint compress type options. // -> "gzip", "none", "zstd" func AutocompleteCheckpointCompressType(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index dad79348d..f02c5713b 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -292,6 +292,14 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, "Set proxy environment variables in the container based on the host proxy vars", ) + hostUserFlagName := "hostuser" + createFlags.StringSliceVar( + &cf.HostUsers, + hostUserFlagName, []string{}, + "Host user account to add to /etc/passwd within container", + ) + _ = cmd.RegisterFlagCompletionFunc(hostUserFlagName, completion.AutocompleteNone) + imageVolumeFlagName := "image-volume" createFlags.StringVar( &cf.ImageVolume, @@ -327,13 +335,10 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(ipcFlagName, AutocompleteNamespace) - kernelMemoryFlagName := "kernel-memory" - createFlags.StringVar( - &cf.KernelMemory, - kernelMemoryFlagName, "", - "Kernel memory limit "+sizeWithUnitFormat, + createFlags.String( + "kernel-memory", "", + "DEPRECATED: Option is just hear for compatibility with Docker", ) - _ = cmd.RegisterFlagCompletionFunc(kernelMemoryFlagName, completion.AutocompleteNone) // kernel-memory is deprecated in the runtime spec. _ = createFlags.MarkHidden("kernel-memory") diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 7d6471fd4..f2335a2be 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -18,7 +18,6 @@ import ( "github.com/containers/podman/v3/pkg/specgen" "github.com/docker/docker/api/types/mount" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) func stringMaptoArray(m map[string]string) []string { @@ -156,18 +155,11 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c } // netMode - nsmode, networks, err := specgen.ParseNetworkNamespace(string(cc.HostConfig.NetworkMode), true) + nsmode, networks, netOpts, err := specgen.ParseNetworkFlag([]string{string(cc.HostConfig.NetworkMode)}) if err != nil { return nil, nil, err } - var netOpts map[string][]string - parts := strings.SplitN(string(cc.HostConfig.NetworkMode), ":", 2) - if len(parts) > 1 { - netOpts = make(map[string][]string) - netOpts[parts[0]] = strings.Split(parts[1], ",") - } - // network // Note: we cannot emulate compat exactly here. we only allow specifics of networks to be // defined when there is only one network. @@ -184,52 +176,56 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c // network names switch { case len(cc.NetworkingConfig.EndpointsConfig) > 0: - var aliases []string - endpointsConfig := cc.NetworkingConfig.EndpointsConfig - cniNetworks := make([]string, 0, len(endpointsConfig)) + networks := make(map[string]types.PerNetworkOptions, len(endpointsConfig)) for netName, endpoint := range endpointsConfig { - cniNetworks = append(cniNetworks, netName) - - if endpoint == nil { - continue - } - if len(endpoint.Aliases) > 0 { - aliases = append(aliases, endpoint.Aliases...) - } - } + netOpts := types.PerNetworkOptions{} + if endpoint != nil { + netOpts.Aliases = endpoint.Aliases - // static IP and MAC - if len(endpointsConfig) == 1 { - for _, ep := range endpointsConfig { - if ep == nil { - continue - } // if IP address is provided - if len(ep.IPAddress) > 0 { - staticIP := net.ParseIP(ep.IPAddress) - netInfo.StaticIP = &staticIP + 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 IPAMConfig.IPv4Address is provided - if ep.IPAMConfig != nil && ep.IPAMConfig.IPv4Address != "" { - staticIP := net.ParseIP(ep.IPAMConfig.IPv4Address) - netInfo.StaticIP = &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(ep.MacAddress) > 0 { - staticMac, err := net.ParseMAC(ep.MacAddress) + if len(endpoint.MacAddress) > 0 { + staticMac, err := net.ParseMAC(endpoint.MacAddress) if err != nil { - return nil, nil, err + return nil, nil, errors.Errorf("failed to parse the mac address %q", endpoint.MacAddress) } - netInfo.StaticMAC = &staticMac + netOpts.StaticMAC = types.HardwareAddr(staticMac) } - break } + + networks[netName] = netOpts } - netInfo.Aliases = aliases - netInfo.CNINetworks = cniNetworks + + netInfo.Networks = networks case len(cc.HostConfig.NetworkMode) > 0: - netInfo.CNINetworks = networks + netInfo.Networks = networks } parsedTmp := make([]string, 0, len(cc.HostConfig.Tmpfs)) @@ -388,9 +384,6 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c if cc.HostConfig.Memory > 0 { cliOpts.Memory = strconv.Itoa(int(cc.HostConfig.Memory)) } - if cc.HostConfig.KernelMemory > 0 { - logrus.Warnf("The --kernel-memory flag has been deprecated. May not work properly on your system.") - } if cc.HostConfig.MemoryReservation > 0 { cliOpts.MemoryReservation = strconv.Itoa(int(cc.HostConfig.MemoryReservation)) @@ -412,9 +405,6 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c cliOpts.ShmSize = strconv.Itoa(int(cc.HostConfig.ShmSize)) } - if cc.HostConfig.KernelMemory > 0 { - cliOpts.KernelMemory = strconv.Itoa(int(cc.HostConfig.KernelMemory)) - } if len(cc.HostConfig.RestartPolicy.Name) > 0 { policy := cc.HostConfig.RestartPolicy.Name // only add restart count on failure diff --git a/cmd/podman/common/create_test.go b/cmd/podman/common/create_test.go index 17b47dd16..601078b61 100644 --- a/cmd/podman/common/create_test.go +++ b/cmd/podman/common/create_test.go @@ -12,7 +12,7 @@ import ( func TestPodOptions(t *testing.T) { entry := "/test1" - exampleOptions := entities.ContainerCreateOptions{CPUS: 5.5, CPUSetCPUs: "0-4", Entrypoint: &entry, Hostname: "foo", Name: "testing123", Volume: []string{"/fakeVol1", "/fakeVol2"}, Net: &entities.NetOptions{CNINetworks: []string{"FakeNetwork"}}, PID: "ns:/proc/self/ns"} + exampleOptions := entities.ContainerCreateOptions{CPUS: 5.5, CPUSetCPUs: "0-4", Entrypoint: &entry, Hostname: "foo", Name: "testing123", Volume: []string{"/fakeVol1", "/fakeVol2"}, Net: &entities.NetOptions{DNSSearch: []string{"search"}}, PID: "ns:/proc/self/ns"} podOptions := entities.PodCreateOptions{} err := common.ContainerToPodOptions(&exampleOptions, &podOptions) diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go index d11f3c9d2..425d85c9d 100644 --- a/cmd/podman/common/netflags.go +++ b/cmd/podman/common/netflags.go @@ -6,6 +6,7 @@ import ( "github.com/containers/common/pkg/completion" "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/libpod/define" + "github.com/containers/podman/v3/libpod/network/types" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/specgenutil" @@ -52,6 +53,13 @@ func DefineNetFlags(cmd *cobra.Command) { ) _ = cmd.RegisterFlagCompletionFunc(ipFlagName, completion.AutocompleteNone) + ip6FlagName := "ip6" + netFlags.String( + ip6FlagName, "", + "Specify a static IPv6 address for the container", + ) + _ = cmd.RegisterFlagCompletionFunc(ip6FlagName, completion.AutocompleteNone) + macAddressFlagName := "mac-address" netFlags.String( macAddressFlagName, "", @@ -60,8 +68,8 @@ func DefineNetFlags(cmd *cobra.Command) { _ = cmd.RegisterFlagCompletionFunc(macAddressFlagName, completion.AutocompleteNone) networkFlagName := "network" - netFlags.String( - networkFlagName, containerConfig.NetNS(), + netFlags.StringArray( + networkFlagName, nil, "Connect a container to a network", ) _ = cmd.RegisterFlagCompletionFunc(networkFlagName, AutocompleteNetworkFlag) @@ -87,9 +95,7 @@ func DefineNetFlags(cmd *cobra.Command) { } // NetFlagsToNetOptions parses the network flags for the given cmd. -// The netnsFromConfig bool is used to indicate if the --network flag -// should always be parsed regardless if it was set on the cli. -func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet, netnsFromConfig bool) (*entities.NetOptions, error) { +func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet) (*entities.NetOptions, error) { var ( err error ) @@ -151,18 +157,6 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet, netnsF } opts.DNSSearch = dnsSearches - m, err := flags.GetString("mac-address") - if err != nil { - return nil, err - } - if len(m) > 0 { - mac, err := net.ParseMAC(m) - if err != nil { - return nil, err - } - opts.StaticMAC = &mac - } - inputPorts, err := flags.GetStringSlice("publish") if err != nil { return nil, err @@ -174,52 +168,108 @@ func NetFlagsToNetOptions(opts *entities.NetOptions, flags pflag.FlagSet, netnsF } } - ip, err := flags.GetString("ip") - if err != nil { - return nil, err - } - if ip != "" { - staticIP := net.ParseIP(ip) - if staticIP == nil { - return nil, errors.Errorf("%s is not an ip address", ip) - } - if staticIP.To4() == nil { - return nil, errors.Wrapf(define.ErrInvalidArg, "%s is not an IPv4 address", ip) - } - opts.StaticIP = &staticIP - } - opts.NoHosts, err = flags.GetBool("no-hosts") if err != nil { return nil, err } - // parse the --network value only when the flag is set or we need to use - // the netns config value, e.g. when --pod is not used - if netnsFromConfig || flags.Changed("network") { - network, err := flags.GetString("network") + // parse the network only when network was changed + // otherwise we send default to server so that the server + // can pick the correct default instead of the client + if flags.Changed("network") { + network, err := flags.GetStringArray("network") if err != nil { return nil, err } - ns, cniNets, options, err := specgen.ParseNetworkString(network) + ns, networks, options, err := specgen.ParseNetworkFlag(network) if err != nil { return nil, err } - if len(options) > 0 { - opts.NetworkOptions = options - } + opts.NetworkOptions = options opts.Network = ns - opts.CNINetworks = cniNets + opts.Networks = networks } - aliases, err := flags.GetStringSlice("network-alias") - if err != nil { - return nil, err - } - if len(aliases) > 0 { - opts.Aliases = aliases + if flags.Changed("ip") || flags.Changed("ip6") || flags.Changed("mac-address") || flags.Changed("network-alias") { + // if there is no network we add the default + if len(opts.Networks) == 0 { + opts.Networks = map[string]types.PerNetworkOptions{ + "default": {}, + } + } + + for _, ipFlagName := range []string{"ip", "ip6"} { + ip, err := flags.GetString(ipFlagName) + if err != nil { + return nil, err + } + 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) + } + + staticIP := net.ParseIP(ip) + if staticIP == nil { + return nil, errors.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) + } + if len(opts.Networks) != 1 { + return nil, errors.Wrapf(define.ErrInvalidArg, "--%s can only be set for a single network", ipFlagName) + } + for name, netOpts := range opts.Networks { + netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) + opts.Networks[name] = netOpts + } + } + } + + m, err := flags.GetString("mac-address") + if err != nil { + return nil, err + } + 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") + } + 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") + } + if len(opts.Networks) != 1 { + return nil, errors.Wrap(define.ErrInvalidArg, "--mac-address can only be set for a single network") + } + for name, netOpts := range opts.Networks { + netOpts.StaticMAC = types.HardwareAddr(mac) + opts.Networks[name] = netOpts + } + } + + aliases, err := flags.GetStringSlice("network-alias") + if err != nil { + return nil, err + } + 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") + } + 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") + } + for name, netOpts := range opts.Networks { + netOpts.Aliases = aliases + opts.Networks[name] = netOpts + } + } } return opts, err diff --git a/cmd/podman/containers/checkpoint.go b/cmd/podman/containers/checkpoint.go index e8dd25978..43a1b75e5 100644 --- a/cmd/podman/containers/checkpoint.go +++ b/cmd/podman/containers/checkpoint.go @@ -11,6 +11,7 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/utils" "github.com/containers/podman/v3/cmd/podman/validate" + "github.com/containers/podman/v3/pkg/criu" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/storage/pkg/archive" @@ -113,6 +114,9 @@ func checkpoint(cmd *cobra.Command, args []string) error { if checkpointOptions.WithPrevious && checkpointOptions.PreCheckPoint { return errors.Errorf("--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") + } responses, err := registry.ContainerEngine().ContainerCheckpoint(context.Background(), args, checkpointOptions) if err != nil { return err diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index a17fcaa1c..9610c29dc 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -21,6 +21,7 @@ import ( "github.com/containers/podman/v3/pkg/util" "github.com/mattn/go-isatty" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -105,7 +106,7 @@ func create(cmd *cobra.Command, args []string) error { err error ) flags := cmd.Flags() - cliVals.Net, err = common.NetFlagsToNetOptions(nil, *flags, cliVals.Pod == "" && cliVals.PodIDFile == "") + cliVals.Net, err = common.NetFlagsToNetOptions(nil, *flags) if err != nil { return err } @@ -191,6 +192,10 @@ func CreateInit(c *cobra.Command, vals entities.ContainerCreateOptions, isInfra vals.UserNS = "private" } } + if c.Flag("kernel-memory") != nil && c.Flag("kernel-memory").Changed { + logrus.Warnf("The --kernel-memory flag is no longer supported. This flag is a noop.") + } + if cliVals.LogDriver == define.PassthroughLogging { if isatty.IsTerminal(0) || isatty.IsTerminal(1) || isatty.IsTerminal(2) { return vals, errors.New("the '--log-driver passthrough' option cannot be used on a TTY") diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go index 449484449..fe4083df8 100644 --- a/cmd/podman/containers/kill.go +++ b/cmd/podman/containers/kill.go @@ -108,10 +108,13 @@ func kill(_ *cobra.Command, args []string) error { return err } for _, r := range responses { - if r.Err == nil { - fmt.Println(r.RawInput) - } else { + switch { + case r.Err != nil: errs = append(errs, r.Err) + case r.RawInput != "": + fmt.Println(r.RawInput) + default: + fmt.Println(r.Id) } } return errs.PrintErrors() diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index 9d1b040cc..b9a2c3bb5 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -83,6 +83,9 @@ func runFlags(cmd *cobra.Command) { _ = cmd.RegisterFlagCompletionFunc(gpuFlagName, completion.AutocompleteNone) _ = flags.MarkHidden("gpus") + passwdFlagName := "passwd" + flags.BoolVar(&runOpts.Passwd, passwdFlagName, true, "add entries to /etc/passwd and /etc/group") + if registry.IsRemote() { _ = flags.MarkHidden("preserve-fds") _ = flags.MarkHidden("conmon-pidfile") @@ -120,7 +123,7 @@ func run(cmd *cobra.Command, args []string) error { } flags := cmd.Flags() - cliVals.Net, err = common.NetFlagsToNetOptions(nil, *flags, cliVals.Pod == "" && cliVals.PodIDFile == "") + cliVals.Net, err = common.NetFlagsToNetOptions(nil, *flags) if err != nil { return err } @@ -191,6 +194,7 @@ func run(cmd *cobra.Command, args []string) error { return err } s.RawImageName = rawImageName + s.Passwd = &runOpts.Passwd runOpts.Spec = s if _, err := createPodIfNecessary(cmd, s, cliVals.Net); err != nil { diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go index 4c563ed27..751db099f 100644 --- a/cmd/podman/images/build.go +++ b/cmd/podman/images/build.go @@ -1,9 +1,11 @@ package images import ( + "fmt" "io" "io/ioutil" "os" + "os/exec" "path/filepath" "strings" "time" @@ -289,7 +291,23 @@ func build(cmd *cobra.Command, args []string) error { } report, err := registry.ImageEngine().Build(registry.GetContext(), containerFiles, *apiBuildOpts) + if err != nil { + exitCode := buildahCLI.ExecErrorCodeGeneric + 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))) + if parseErr == nil { + exitCode = remoteExitCode + } + } + + if ee, ok := (errors.Cause(err)).(*exec.ExitError); ok { + exitCode = ee.ExitCode() + } + + registry.SetExitCode(exitCode) return err } diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go index adde887f7..14e87c201 100644 --- a/cmd/podman/machine/init.go +++ b/cmd/podman/machine/init.go @@ -1,4 +1,4 @@ -// +build amd64,!windows arm64,!windows +// +build amd64 arm64 package machine @@ -8,7 +8,6 @@ import ( "github.com/containers/common/pkg/completion" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -38,6 +37,7 @@ func init() { }) flags := initCmd.Flags() cfg := registry.PodmanConfig() + initOpts.Username = cfg.Config.Machine.User cpusFlagName := "cpus" flags.Uint64Var( @@ -69,6 +69,20 @@ func init() { "now", false, "Start machine now", ) + timezoneFlagName := "timezone" + defaultTz := cfg.TZ() + if len(defaultTz) < 1 { + defaultTz = "local" + } + flags.StringVar(&initOpts.TimeZone, timezoneFlagName, defaultTz, "Set timezone") + _ = initCmd.RegisterFlagCompletionFunc(timezoneFlagName, completion.AutocompleteDefault) + + flags.BoolVar( + &initOpts.ReExec, + "reexec", false, + "process was rexeced", + ) + flags.MarkHidden("reexec") ImagePathFlagName := "image-path" flags.StringVar(&initOpts.ImagePath, ImagePathFlagName, cfg.Machine.Image, "Path to qcow image") @@ -82,33 +96,47 @@ func init() { // TODO should we allow for a users to append to the qemu cmdline? func initMachine(cmd *cobra.Command, args []string) error { var ( - vm machine.VM - vmType string - err error + vm machine.VM + err error ) + + provider := getSystemDefaultProvider() initOpts.Name = defaultMachineName if len(args) > 0 { initOpts.Name = args[0] } - switch vmType { - default: // qemu is the default - if _, err := qemu.LoadVMByName(initOpts.Name); err == nil { - return errors.Wrap(machine.ErrVMAlreadyExists, initOpts.Name) - } - vm, err = qemu.NewMachine(initOpts) + if _, err := provider.LoadVMByName(initOpts.Name); err == nil { + return errors.Wrap(machine.ErrVMAlreadyExists, initOpts.Name) } + + vm, err = provider.NewMachine(initOpts) if err != nil { return err } - err = vm.Init(initOpts) - if err != nil { + + if finished, err := vm.Init(initOpts); err != nil || !finished { + // Finished = true, err = nil - Success! Log a message with further instructions + // Finished = false, err = nil - The installation is partially complete and podman should + // exit gracefully with no error and no success message. + // Examples: + // - a user has chosen to perform their own reboot + // - reexec for limited admin operations, returning to parent + // Finished = *, err != nil - Exit with an error message + return err } + 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) } + } 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 err } diff --git a/cmd/podman/machine/list.go b/cmd/podman/machine/list.go index 774ab4fd0..858d87401 100644 --- a/cmd/podman/machine/list.go +++ b/cmd/podman/machine/list.go @@ -1,4 +1,5 @@ -// +build amd64,!windows arm64,!windows +//go:build amd64 || arm64 +// +build amd64 arm64 package machine @@ -16,7 +17,6 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -69,9 +69,14 @@ func init() { } func list(cmd *cobra.Command, args []string) error { - var opts machine.ListOptions - // We only have qemu VM's for now - listResponse, err := qemu.List(opts) + var ( + opts machine.ListOptions + listResponse []*machine.ListResponse + err error + ) + + provider := getSystemDefaultProvider() + listResponse, err = provider.List(opts) if err != nil { return errors.Wrap(err, "error listing vms") } @@ -182,8 +187,8 @@ func toMachineFormat(vms []*machine.ListResponse) ([]*machineReporter, error) { response.Stream = streamName(vm.Stream) response.VMType = vm.VMType response.CPUs = vm.CPUs - response.Memory = strUint(vm.Memory * units.MiB) - response.DiskSize = strUint(vm.DiskSize * units.GiB) + response.Memory = strUint(vm.Memory) + response.DiskSize = strUint(vm.DiskSize) machineResponses = append(machineResponses, response) } @@ -214,8 +219,8 @@ func toHumanFormat(vms []*machine.ListResponse) ([]*machineReporter, error) { response.Created = units.HumanDuration(time.Since(vm.CreatedAt)) + " ago" response.VMType = vm.VMType response.CPUs = vm.CPUs - response.Memory = units.HumanSize(float64(vm.Memory) * units.MiB) - response.DiskSize = units.HumanSize(float64(vm.DiskSize) * units.GiB) + response.Memory = units.HumanSize(float64(vm.Memory)) + response.DiskSize = units.HumanSize(float64(vm.DiskSize)) humanResponses = append(humanResponses, response) } diff --git a/cmd/podman/machine/machine.go b/cmd/podman/machine/machine.go index 8ff9055f0..22ffbbee7 100644 --- a/cmd/podman/machine/machine.go +++ b/cmd/podman/machine/machine.go @@ -1,4 +1,4 @@ -// +build amd64,!windows arm64,!windows +// +build amd64 arm64 package machine @@ -8,7 +8,6 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" "github.com/spf13/cobra" ) @@ -51,7 +50,8 @@ func autocompleteMachine(cmd *cobra.Command, args []string, toComplete string) ( func getMachines(toComplete string) ([]string, cobra.ShellCompDirective) { suggestions := []string{} - machines, err := qemu.List(machine.ListOptions{}) + provider := getSystemDefaultProvider() + machines, err := provider.List(machine.ListOptions{}) if err != nil { cobra.CompErrorln(err.Error()) return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/podman/machine/machine_unsupported.go b/cmd/podman/machine/machine_unsupported.go index f8392694a..2f4189446 100644 --- a/cmd/podman/machine/machine_unsupported.go +++ b/cmd/podman/machine/machine_unsupported.go @@ -1,4 +1,4 @@ -// +build !amd64 amd64,windows +// +build !amd64,!arm64 package machine diff --git a/cmd/podman/machine/platform.go b/cmd/podman/machine/platform.go new file mode 100644 index 000000000..fc3186205 --- /dev/null +++ b/cmd/podman/machine/platform.go @@ -0,0 +1,12 @@ +// +build amd64,!windows arm64,!windows + +package machine + +import ( + "github.com/containers/podman/v3/pkg/machine" + "github.com/containers/podman/v3/pkg/machine/qemu" +) + +func getSystemDefaultProvider() machine.Provider { + return qemu.GetQemuProvider() +} diff --git a/cmd/podman/machine/platform_windows.go b/cmd/podman/machine/platform_windows.go new file mode 100644 index 000000000..a4a35e712 --- /dev/null +++ b/cmd/podman/machine/platform_windows.go @@ -0,0 +1,10 @@ +package machine + +import ( + "github.com/containers/podman/v3/pkg/machine" + "github.com/containers/podman/v3/pkg/machine/wsl" +) + +func getSystemDefaultProvider() machine.Provider { + return wsl.GetWSLProvider() +} diff --git a/cmd/podman/machine/rm.go b/cmd/podman/machine/rm.go index c17399c78..c58e74a42 100644 --- a/cmd/podman/machine/rm.go +++ b/cmd/podman/machine/rm.go @@ -1,4 +1,4 @@ -// +build amd64,!windows arm64,!windows +// +build amd64 arm64 package machine @@ -10,7 +10,6 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" "github.com/spf13/cobra" ) @@ -52,18 +51,16 @@ func init() { func rm(cmd *cobra.Command, args []string) error { var ( - err error - vm machine.VM - vmType string + err error + vm machine.VM ) vmName := defaultMachineName if len(args) > 0 && len(args[0]) > 0 { vmName = args[0] } - switch vmType { - default: - vm, err = qemu.LoadVMByName(vmName) - } + + provider := getSystemDefaultProvider() + vm, err = provider.LoadVMByName(vmName) if err != nil { return err } diff --git a/cmd/podman/machine/ssh.go b/cmd/podman/machine/ssh.go index da0a09338..5ef34afc6 100644 --- a/cmd/podman/machine/ssh.go +++ b/cmd/podman/machine/ssh.go @@ -1,4 +1,4 @@ -// +build amd64,!windows arm64,!windows +// +build amd64 arm64 package machine @@ -9,7 +9,6 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -47,27 +46,24 @@ func ssh(cmd *cobra.Command, args []string) error { err error validVM bool vm machine.VM - vmType string ) // Set the VM to default vmName := defaultMachineName + provider := getSystemDefaultProvider() // If len is greater than 0, it means we may have been // provided the VM name. If so, we check. The VM name, // if provided, must be in args[0]. if len(args) > 0 { - switch vmType { - default: - validVM, err = qemu.IsValidVMName(args[0]) - if err != nil { - return err - } - if validVM { - vmName = args[0] - } else { - sshOpts.Args = append(sshOpts.Args, args[0]) - } + validVM, err = provider.IsValidVMName(args[0]) + if err != nil { + return err + } + if validVM { + vmName = args[0] + } else { + sshOpts.Args = append(sshOpts.Args, args[0]) } } @@ -88,10 +84,7 @@ func ssh(cmd *cobra.Command, args []string) error { } } - switch vmType { - default: - vm, err = qemu.LoadVMByName(vmName) - } + vm, err = provider.LoadVMByName(vmName) if err != nil { return errors.Wrapf(err, "vm %s not found", vmName) } diff --git a/cmd/podman/machine/start.go b/cmd/podman/machine/start.go index 4ae31e6de..9c9c24f64 100644 --- a/cmd/podman/machine/start.go +++ b/cmd/podman/machine/start.go @@ -1,4 +1,4 @@ -// +build amd64,!windows arm64,!windows +// +build amd64 arm64 package machine @@ -7,7 +7,6 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -33,30 +32,31 @@ func init() { func start(cmd *cobra.Command, args []string) error { var ( - err error - vm machine.VM - vmType string + err error + vm machine.VM ) vmName := defaultMachineName if len(args) > 0 && len(args[0]) > 0 { vmName = args[0] } - // We only have qemu VM's for now - active, activeName, err := qemu.CheckActiveVM() + provider := getSystemDefaultProvider() + vm, err = provider.LoadVMByName(vmName) if err != nil { return err } + + active, activeName, cerr := provider.CheckExclusiveActiveVM() + if cerr != nil { + return cerr + } if active { if vmName == activeName { return errors.Wrapf(machine.ErrVMAlreadyRunning, "cannot start VM %s", vmName) } return errors.Wrapf(machine.ErrMultipleActiveVM, "cannot start VM %s. VM %s is currently running", vmName, activeName) } - switch vmType { - default: - vm, err = qemu.LoadVMByName(vmName) - } + vm, err = provider.LoadVMByName(vmName) if err != nil { return err } diff --git a/cmd/podman/machine/stop.go b/cmd/podman/machine/stop.go index 75666f734..17969298b 100644 --- a/cmd/podman/machine/stop.go +++ b/cmd/podman/machine/stop.go @@ -1,4 +1,5 @@ -// +build amd64,!windows arm64,!windows +//go:build amd64 || arm64 +// +build amd64 arm64 package machine @@ -7,7 +8,6 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/machine" - "github.com/containers/podman/v3/pkg/machine/qemu" "github.com/spf13/cobra" ) @@ -33,18 +33,15 @@ func init() { // TODO Name shouldn't be required, need to create a default vm func stop(cmd *cobra.Command, args []string) error { var ( - err error - vm machine.VM - vmType string + err error + vm machine.VM ) vmName := defaultMachineName if len(args) > 0 && len(args[0]) > 0 { vmName = args[0] } - switch vmType { - default: - vm, err = qemu.LoadVMByName(vmName) - } + provider := getSystemDefaultProvider() + vm, err = provider.LoadVMByName(vmName) if err != nil { return err } diff --git a/cmd/podman/networks/connect.go b/cmd/podman/networks/connect.go index 0d62a45df..b0ffbfe6d 100644 --- a/cmd/podman/networks/connect.go +++ b/cmd/podman/networks/connect.go @@ -1,9 +1,12 @@ package network import ( + "net" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" + "github.com/containers/podman/v3/libpod/network/types" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -23,13 +26,28 @@ var ( var ( networkConnectOptions entities.NetworkConnectOptions + ipv4 net.IP + ipv6 net.IP + macAddress string ) func networkConnectFlags(cmd *cobra.Command) { flags := cmd.Flags() aliasFlagName := "alias" - flags.StringSliceVar(&networkConnectOptions.Aliases, aliasFlagName, []string{}, "network scoped alias for container") + flags.StringSliceVar(&networkConnectOptions.Aliases, aliasFlagName, nil, "network scoped alias for container") _ = cmd.RegisterFlagCompletionFunc(aliasFlagName, completion.AutocompleteNone) + + ipAddressFlagName := "ip" + flags.IPVar(&ipv4, ipAddressFlagName, nil, "set a static ipv4 address for this container network") + _ = cmd.RegisterFlagCompletionFunc(ipAddressFlagName, completion.AutocompleteNone) + + ipv6AddressFlagName := "ip6" + flags.IPVar(&ipv6, ipv6AddressFlagName, nil, "set a static ipv6 address for this container network") + _ = cmd.RegisterFlagCompletionFunc(ipv6AddressFlagName, completion.AutocompleteNone) + + macAddressFlagName := "mac-address" + flags.StringVar(&macAddress, macAddressFlagName, "", "set a static mac address for this container network") + _ = cmd.RegisterFlagCompletionFunc(macAddressFlagName, completion.AutocompleteNone) } func init() { @@ -42,5 +60,18 @@ func init() { func networkConnect(cmd *cobra.Command, args []string) error { networkConnectOptions.Container = args[1] + if macAddress != "" { + mac, err := net.ParseMAC(macAddress) + if err != nil { + return err + } + networkConnectOptions.StaticMAC = types.HardwareAddr(mac) + } + for _, ip := range []net.IP{ipv4, ipv6} { + if ip != nil { + networkConnectOptions.StaticIPs = append(networkConnectOptions.StaticIPs, ip) + } + } + return registry.ContainerEngine().NetworkConnect(registry.Context(), args[0], networkConnectOptions) } diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go index 6f1a7742a..7ce566225 100644 --- a/cmd/podman/networks/list.go +++ b/cmd/podman/networks/list.go @@ -3,6 +3,7 @@ package network import ( "fmt" "os" + "sort" "strings" "github.com/containers/common/pkg/completion" @@ -73,6 +74,11 @@ func networkList(cmd *cobra.Command, args []string) error { return err } + // sort the networks to make sure the order is deterministic + sort.Slice(responses, func(i, j int) bool { + return responses[i].Name < responses[j].Name + }) + switch { // quiet means we only print the network names case networkListOptions.Quiet: diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index 11b5d7d34..ae4066a6f 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -69,7 +69,7 @@ func init() { _ = kubeCmd.RegisterFlagCompletionFunc(staticMACFlagName, completion.AutocompleteNone) networkFlagName := "network" - flags.StringVar(&kubeOptions.Network, networkFlagName, "", "Connect pod to CNI network(s)") + flags.StringArrayVar(&kubeOptions.Networks, networkFlagName, nil, "Connect pod to network(s) or network mode") _ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworkFlag) staticIPFlagName := "ip" diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index 7399dd029..f844812c2 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -116,7 +116,7 @@ func create(cmd *cobra.Command, args []string) error { return fmt.Errorf("cannot specify no-hosts without an infra container") } flags := cmd.Flags() - createOptions.Net, err = common.NetFlagsToNetOptions(nil, *flags, createOptions.Infra) + createOptions.Net, err = common.NetFlagsToNetOptions(nil, *flags) if err != nil { return err } @@ -133,7 +133,7 @@ func create(cmd *cobra.Command, args []string) error { } else { // reassign certain options for lbpod api, these need to be populated in spec flags := cmd.Flags() - infraOptions.Net, err = common.NetFlagsToNetOptions(nil, *flags, createOptions.Infra) + infraOptions.Net, err = common.NetFlagsToNetOptions(nil, *flags) if err != nil { return err } diff --git a/cmd/podman/secrets/list.go b/cmd/podman/secrets/list.go index 255d9ae1a..2074ab973 100644 --- a/cmd/podman/secrets/list.go +++ b/cmd/podman/secrets/list.go @@ -8,6 +8,7 @@ import ( "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" + "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/pkg/domain/entities" @@ -32,6 +33,7 @@ var ( type listFlagType struct { format string noHeading bool + filter []string } func init() { @@ -44,14 +46,26 @@ func init() { formatFlagName := "format" flags.StringVar(&listFlag.format, formatFlagName, "{{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.CreatedAt}}\t{{.UpdatedAt}}\t\n", "Format volume output using Go template") _ = lsCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(entities.SecretInfoReport{})) + filterFlagName := "filter" + flags.StringSliceVarP(&listFlag.filter, filterFlagName, "f", []string{}, "Filter secret output") + _ = lsCmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteSecretFilters) flags.BoolVar(&listFlag.noHeading, "noheading", false, "Do not print headers") } func ls(cmd *cobra.Command, args []string) error { - responses, err := registry.ContainerEngine().SecretList(context.Background(), entities.SecretListRequest{}) + var err error + lsOpts := entities.SecretListRequest{} + + lsOpts.Filters, err = parse.FilterArgumentsIntoFilters(listFlag.filter) + if err != nil { + return err + } + + responses, err := registry.ContainerEngine().SecretList(context.Background(), lsOpts) if err != nil { return err } + listed := make([]*entities.SecretListReport, 0, len(responses)) for _, response := range responses { listed = append(listed, &entities.SecretListReport{ diff --git a/cmd/podman/utils/error.go b/cmd/podman/utils/error.go index 2d58bc70d..aab1da675 100644 --- a/cmd/podman/utils/error.go +++ b/cmd/podman/utils/error.go @@ -1,8 +1,13 @@ package utils import ( + "errors" "fmt" "os" + "strconv" + "strings" + + buildahCLI "github.com/containers/buildah/pkg/cli" ) type OutputErrors []error @@ -17,3 +22,24 @@ func (o OutputErrors) PrintErrors() (lastError error) { } return } + +/* For remote client, server does not returns error with exit code + instead returns a message and we cast it to a new error. + + Following function performs parsing on build error and returns + exit status which was exepected for this current build +*/ +func ExitCodeFromBuildError(errorMsg string) (int, error) { + if strings.Contains(errorMsg, "exit status") { + errorSplit := strings.Split(errorMsg, " ") + if errorSplit[len(errorSplit)-2] == "status" { + tmpSplit := strings.Split(errorSplit[len(errorSplit)-1], "\n") + exitCodeRemote, err := strconv.Atoi(tmpSplit[0]) + if err == nil { + return exitCodeRemote, nil + } + return buildahCLI.ExecErrorCodeGeneric, err + } + } + return buildahCLI.ExecErrorCodeGeneric, errors.New("error message does not contains a valid exit code") +} diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go index c372527de..97fa2c61f 100644 --- a/cmd/podman/volumes/list.go +++ b/cmd/podman/volumes/list.go @@ -4,11 +4,11 @@ import ( "context" "fmt" "os" - "strings" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/common" + "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/libpod/define" @@ -64,19 +64,18 @@ func init() { } func list(cmd *cobra.Command, args []string) error { + var err error if cliOpts.Quiet && cmd.Flag("format").Changed { return errors.New("quiet and format flags cannot be used together") } if len(cliOpts.Filter) > 0 { lsOpts.Filter = make(map[string][]string) } - for _, f := range cliOpts.Filter { - filterSplit := strings.SplitN(f, "=", 2) - if len(filterSplit) < 2 { - return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) - } - lsOpts.Filter[filterSplit[0]] = append(lsOpts.Filter[filterSplit[0]], filterSplit[1]) + lsOpts.Filter, err = parse.FilterArgumentsIntoFilters(cliOpts.Filter) + if err != nil { + return err } + responses, err := registry.ContainerEngine().VolumeList(context.Background(), lsOpts) if err != nil { return err diff --git a/cmd/podman/volumes/prune.go b/cmd/podman/volumes/prune.go index 1f3cc6913..43b529768 100644 --- a/cmd/podman/volumes/prune.go +++ b/cmd/podman/volumes/prune.go @@ -58,6 +58,9 @@ func prune(cmd *cobra.Command, args []string) error { return err } pruneOptions.Filters, err = parse.FilterArgumentsIntoFilters(filter) + if err != nil { + return err + } if !force { reader := bufio.NewReader(os.Stdin) fmt.Println("WARNING! This will remove all volumes not used by at least one container. The following volumes will be removed:") |