summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/podman/common/completion.go27
-rw-r--r--cmd/podman/common/create.go33
-rw-r--r--cmd/podman/common/create_opts.go86
-rw-r--r--cmd/podman/common/create_test.go2
-rw-r--r--cmd/podman/common/netflags.go144
-rw-r--r--cmd/podman/containers/checkpoint.go4
-rw-r--r--cmd/podman/containers/create.go7
-rw-r--r--cmd/podman/containers/kill.go9
-rw-r--r--cmd/podman/containers/run.go6
-rw-r--r--cmd/podman/images/build.go18
-rw-r--r--cmd/podman/images/scp.go309
-rw-r--r--cmd/podman/images/scp_test.go46
-rw-r--r--cmd/podman/images/scp_utils.go87
-rw-r--r--cmd/podman/machine/init.go54
-rw-r--r--cmd/podman/machine/list.go23
-rw-r--r--cmd/podman/machine/machine.go6
-rw-r--r--cmd/podman/machine/machine_unsupported.go2
-rw-r--r--cmd/podman/machine/platform.go12
-rw-r--r--cmd/podman/machine/platform_windows.go10
-rw-r--r--cmd/podman/machine/rm.go15
-rw-r--r--cmd/podman/machine/ssh.go29
-rw-r--r--cmd/podman/machine/start.go22
-rw-r--r--cmd/podman/machine/stop.go15
-rw-r--r--cmd/podman/main.go4
-rw-r--r--cmd/podman/networks/connect.go33
-rw-r--r--cmd/podman/networks/list.go6
-rw-r--r--cmd/podman/play/kube.go2
-rw-r--r--cmd/podman/pods/create.go4
-rw-r--r--cmd/podman/secrets/list.go16
-rw-r--r--cmd/podman/utils/error.go26
-rw-r--r--cmd/podman/volumes/list.go13
-rw-r--r--cmd/podman/volumes/prune.go3
-rw-r--r--cmd/winpath/main.go184
33 files changed, 893 insertions, 364 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..32d227e65 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")
@@ -535,14 +540,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
)
_ = cmd.RegisterFlagCompletionFunc(secretFlagName, AutocompleteSecrets)
- securityOptFlagName := "security-opt"
- createFlags.StringArrayVar(
- &cf.SecurityOpt,
- securityOptFlagName, []string{},
- "Security Options",
- )
- _ = cmd.RegisterFlagCompletionFunc(securityOptFlagName, AutocompleteSecurityOption)
-
shmSizeFlagName := "shm-size"
createFlags.String(
shmSizeFlagName, shmSize(),
@@ -715,6 +712,13 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
`If a container with the same name exists, replace it`,
)
}
+ securityOptFlagName := "security-opt"
+ createFlags.StringArrayVar(
+ &cf.SecurityOpt,
+ securityOptFlagName, []string{},
+ "Security Options",
+ )
+ _ = cmd.RegisterFlagCompletionFunc(securityOptFlagName, AutocompleteSecurityOption)
subgidnameFlagName := "subgidname"
createFlags.StringVar(
@@ -885,6 +889,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
"Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)",
)
_ = cmd.RegisterFlagCompletionFunc(deviceReadBpsFlagName, completion.AutocompleteDefault)
+
volumesFromFlagName := "volumes-from"
createFlags.StringArrayVar(
&cf.VolumesFrom,
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/images/scp.go b/cmd/podman/images/scp.go
index 5c9cadc7a..f02a3c15e 100644
--- a/cmd/podman/images/scp.go
+++ b/cmd/podman/images/scp.go
@@ -6,18 +6,19 @@ import (
"io/ioutil"
urlP "net/url"
"os"
+ "os/exec"
+ "os/user"
"strconv"
"strings"
"github.com/containers/common/pkg/config"
"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/system/connection"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/rootless"
- "github.com/docker/distribution/reference"
+ "github.com/containers/podman/v3/utils"
scpD "github.com/dtylman/scp"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -28,8 +29,12 @@ import (
var (
saveScpDescription = `Securely copy an image from one host to another.`
imageScpCommand = &cobra.Command{
- Use: "scp [options] IMAGE [HOST::]",
- Annotations: map[string]string{registry.EngineMode: registry.ABIMode},
+ Use: "scp [options] IMAGE [HOST::]",
+ Annotations: map[string]string{
+ registry.UnshareNSRequired: "",
+ registry.ParentNSRequired: "",
+ registry.EngineMode: registry.ABIMode,
+ },
Long: saveScpDescription,
Short: "securely copy images",
RunE: scp,
@@ -40,7 +45,10 @@ var (
)
var (
- scpOpts entities.ImageScpOptions
+ parentFlags []string
+ source entities.ImageScpOptions
+ dest entities.ImageScpOptions
+ sshInfo entities.ImageScpConnections
)
func init() {
@@ -53,7 +61,7 @@ func init() {
func scpFlags(cmd *cobra.Command) {
flags := cmd.Flags()
- flags.BoolVarP(&scpOpts.Save.Quiet, "quiet", "q", false, "Suppress the output")
+ flags.BoolVarP(&source.Quiet, "quiet", "q", false, "Suppress the output")
}
func scp(cmd *cobra.Command, args []string) (finalErr error) {
@@ -61,24 +69,31 @@ func scp(cmd *cobra.Command, args []string) (finalErr error) {
// TODO add tag support for images
err error
)
- if scpOpts.Save.Quiet { // set quiet for both load and save
- scpOpts.Load.Quiet = true
+ for i, val := range os.Args {
+ if val == "image" {
+ break
+ }
+ if i == 0 {
+ continue
+ }
+ if strings.Contains(val, "CIRRUS") { // need to skip CIRRUS flags for testing suite purposes
+ continue
+ }
+ parentFlags = append(parentFlags, val)
}
- f, err := ioutil.TempFile("", "podman") // open temp file for load/save output
+ podman, err := os.Executable()
if err != nil {
return err
}
- defer os.Remove(f.Name())
-
- scpOpts.Save.Output = f.Name()
- scpOpts.Load.Input = scpOpts.Save.Output
- if err := parse.ValidateFileName(saveOpts.Output); err != nil {
+ f, err := ioutil.TempFile("", "podman") // open temp file for load/save output
+ if err != nil {
return err
}
confR, err := config.NewConfig("") // create a hand made config for the remote engine since we might use remote and native at once
if err != nil {
return errors.Wrapf(err, "could not make config")
}
+
abiEng, err := registry.NewImageEngine(cmd, args) // abi native engine
if err != nil {
return err
@@ -88,77 +103,115 @@ func scp(cmd *cobra.Command, args []string) (finalErr error) {
if err != nil {
return err
}
- serv, err := parseArgs(args, cfg) // parses connection data and "which way" we are loading and saving
+ locations := []*entities.ImageScpOptions{}
+ cliConnections := []string{}
+ flipConnections := false
+ for _, arg := range args {
+ loc, connect, err := parseImageSCPArg(arg)
+ if err != nil {
+ return err
+ }
+ locations = append(locations, loc)
+ cliConnections = append(cliConnections, connect...)
+ }
+ source = *locations[0]
+ switch {
+ case len(locations) > 1:
+ if flipConnections, err = validateSCPArgs(locations); err != nil {
+ return err
+ }
+ if flipConnections { // the order of cliConnections matters, we need to flip both arrays since the args are parsed separately sometimes.
+ connect := cliConnections[0]
+ cliConnections[0] = cliConnections[1]
+ cliConnections[1] = connect
+
+ loc := locations[0]
+ locations[0] = locations[1]
+ locations[1] = loc
+ }
+ dest = *locations[1]
+ case len(locations) == 1:
+ switch {
+ case len(locations[0].Image) == 0:
+ return errors.Wrapf(define.ErrInvalidArg, "no source image specified")
+ case len(locations[0].Image) > 0 && !locations[0].Remote && len(locations[0].User) == 0: // if we have podman image scp $IMAGE
+ return errors.Wrapf(define.ErrInvalidArg, "must specify a destination")
+ }
+ }
+
+ source.File = f.Name() // after parsing the arguments, set the file for the save/load
+ dest.File = source.File
+ if err = os.Remove(source.File); err != nil { // remove the file and simply use its name so podman creates the file upon save. avoids umask errors
+ return err
+ }
+
+ var serv map[string]config.Destination
+ serv, err = GetServiceInformation(cliConnections, cfg)
if err != nil {
return err
}
+
// TODO: Add podman remote support
confR.Engine = config.EngineConfig{Remote: true, CgroupManager: "cgroupfs", ServiceDestinations: serv} // pass the service dest (either remote or something else) to engine
+ saveCmd, loadCmd := createCommands(podman)
switch {
- case scpOpts.FromRemote: // if we want to load FROM the remote
- err = saveToRemote(scpOpts.SourceImageName, scpOpts.Save.Output, "", scpOpts.URI[0], scpOpts.Iden[0])
+ case source.Remote: // if we want to load FROM the remote, dest can either be local or remote in this case
+ err = saveToRemote(source.Image, source.File, "", sshInfo.URI[0], sshInfo.Identities[0])
if err != nil {
return err
}
- if scpOpts.ToRemote { // we want to load remote -> remote
- rep, err := loadToRemote(scpOpts.Save.Output, "", scpOpts.URI[1], scpOpts.Iden[1])
+ if dest.Remote { // we want to load remote -> remote, both source and dest are remote
+ rep, err := loadToRemote(dest.File, "", sshInfo.URI[1], sshInfo.Identities[1])
if err != nil {
return err
}
fmt.Println(rep)
break
}
- report, err := abiEng.Load(context.Background(), scpOpts.Load)
+ err = execPodman(podman, loadCmd)
if err != nil {
return err
}
- fmt.Println("Loaded image(s): " + strings.Join(report.Names, ","))
- case scpOpts.ToRemote: // remote host load
- scpOpts.Save.Format = "oci-archive"
- abiErr := abiEng.Save(context.Background(), scpOpts.SourceImageName, []string{}, scpOpts.Save) // save the image locally before loading it on remote, local, or ssh
- if abiErr != nil {
- errors.Wrapf(abiErr, "could not save image as specified")
- }
- rep, err := loadToRemote(scpOpts.Save.Output, "", scpOpts.URI[0], scpOpts.Iden[0])
+ case dest.Remote: // remote host load, implies source is local
+ err = execPodman(podman, saveCmd)
if err != nil {
return err
}
- fmt.Println(rep)
- // TODO: Add podman remote support
- default: // else native load
- scpOpts.Save.Format = "oci-archive"
- _, err := os.Open(scpOpts.Save.Output)
+ rep, err := loadToRemote(source.File, "", sshInfo.URI[0], sshInfo.Identities[0])
if err != nil {
return err
}
- if scpOpts.Tag != "" {
- return errors.Wrapf(define.ErrInvalidArg, "Renaming of an image is currently not supported")
- }
- scpOpts.Save.Format = "oci-archive"
- abiErr := abiEng.Save(context.Background(), scpOpts.SourceImageName, []string{}, scpOpts.Save) // save the image locally before loading it on remote, local, or ssh
- if abiErr != nil {
- return errors.Wrapf(abiErr, "could not save image as specified")
+ fmt.Println(rep)
+ if err = os.Remove(source.File); err != nil {
+ return err
}
- if !rootless.IsRootless() && scpOpts.Rootless {
- if scpOpts.User == "" {
- scpOpts.User = os.Getenv("SUDO_USER")
- if scpOpts.User == "" {
- return errors.New("could not obtain root user, make sure the environmental variable SUDO_USER is set, and that this command is being run as root")
+ // TODO: Add podman remote support
+ default: // else native load, both source and dest are local and transferring between users
+ if source.User == "" { // source user has to be set, destination does not
+ source.User = os.Getenv("USER")
+ if source.User == "" {
+ u, err := user.Current()
+ if err != nil {
+ return errors.Wrapf(err, "could not obtain user, make sure the environmental variable $USER is set")
}
+ source.User = u.Username
}
- err := abiEng.Transfer(context.Background(), scpOpts)
- if err != nil {
- return err
- }
- } else {
- rep, err := abiEng.Load(context.Background(), scpOpts.Load)
- if err != nil {
- return err
- }
- fmt.Println("Loaded image(s): " + strings.Join(rep.Names, ","))
+ }
+ err := abiEng.Transfer(context.Background(), source, dest, parentFlags)
+ if err != nil {
+ return err
}
}
+ src, err := json.MarshalIndent(source, "", " ")
+ if err != nil {
+ return err
+ }
+ dst, err := json.MarshalIndent(dest, "", " ")
+ if err != nil {
+ return err
+ }
+ fmt.Printf("SOURCE: %s\nDEST: %s\n", string(src), string(dst))
return nil
}
@@ -249,119 +302,28 @@ func createConnection(url *urlP.URL, iden string) (*ssh.Client, string, error) {
return dialAdd, file, nil
}
-// validateImageName makes sure that the image given is valid and no injections are occurring
-// we simply use this for error checking, bot setting the image
-func validateImageName(input string) error {
- // ParseNormalizedNamed transforms a shortname image into its
- // full name reference so busybox => docker.io/library/busybox
- // we want to keep our shortnames, so only return an error if
- // we cannot parse what th euser has given us
- _, err := reference.ParseNormalizedNamed(input)
- return err
-}
-
-// remoteArgLength is a helper function to simplify the extracting of host argument data
-// returns an int which contains the length of a specified index in a host::image string
-func remoteArgLength(input string, side int) int {
- return len((strings.Split(input, "::"))[side])
-}
-
-// parseArgs returns the valid connection data based off of the information provided by the user
-// args is an array of the command arguments and cfg is tooling configuration used to get service destinations
-// returned is serv and an error if applicable. serv is a map of service destinations with the connection name as the index
-// this connection name is intended to be used as EngineConfig.ServiceDestinations
-// this function modifies the global scpOpt entities: FromRemote, ToRemote, Connections, and SourceImageName
-func parseArgs(args []string, cfg *config.Config) (map[string]config.Destination, error) {
- serv := map[string]config.Destination{}
- cliConnections := []string{}
- switch len(args) {
- case 1:
- if strings.Contains(args[0], "localhost") {
- if strings.Split(args[0], "@")[0] != "root" {
- return nil, errors.Wrapf(define.ErrInvalidArg, "cannot transfer images from any user besides root using sudo")
- }
- scpOpts.Rootless = true
- scpOpts.SourceImageName = strings.Split(args[0], "::")[1]
- } else if strings.Contains(args[0], "::") {
- scpOpts.FromRemote = true
- cliConnections = append(cliConnections, args[0])
- } else {
- err := validateImageName(args[0])
- if err != nil {
- return nil, err
- }
- scpOpts.SourceImageName = args[0]
- }
- case 2:
- if strings.Contains(args[0], "localhost") || strings.Contains(args[1], "localhost") { // only supporting root to local using sudo at the moment
- if strings.Split(args[0], "@")[0] != "root" {
- return nil, errors.Wrapf(define.ErrInvalidArg, "currently, transferring images to a user account is not supported")
- }
- if len(strings.Split(args[0], "::")) > 1 {
- scpOpts.Rootless = true
- scpOpts.User = strings.Split(args[1], "@")[0]
- scpOpts.SourceImageName = strings.Split(args[0], "::")[1]
- } else {
- return nil, errors.Wrapf(define.ErrInvalidArg, "currently, you cannot rename images during the transfer or transfer them to a user account")
- }
- } else if strings.Contains(args[0], "::") {
- if !(strings.Contains(args[1], "::")) && remoteArgLength(args[0], 1) == 0 { // if an image is specified, this mean we are loading to our client
- cliConnections = append(cliConnections, args[0])
- scpOpts.ToRemote = true
- scpOpts.SourceImageName = args[1]
- } else if strings.Contains(args[1], "::") { // both remote clients
- scpOpts.FromRemote = true
- scpOpts.ToRemote = true
- if remoteArgLength(args[0], 1) == 0 { // is save->load w/ one image name
- cliConnections = append(cliConnections, args[0])
- cliConnections = append(cliConnections, args[1])
- } else if remoteArgLength(args[0], 1) > 0 && remoteArgLength(args[1], 1) > 0 {
- //in the future, this function could, instead of rejecting renames, also set a DestImageName field
- return nil, errors.Wrapf(define.ErrInvalidArg, "cannot specify an image rename")
- } else { // else its a load save (order of args)
- cliConnections = append(cliConnections, args[1])
- cliConnections = append(cliConnections, args[0])
- }
- } else {
- //in the future, this function could, instead of rejecting renames, also set a DestImageName field
- return nil, errors.Wrapf(define.ErrInvalidArg, "cannot specify an image rename")
- }
- } else if strings.Contains(args[1], "::") { // if we are given image host::
- if remoteArgLength(args[1], 1) > 0 {
- //in the future, this function could, instead of rejecting renames, also set a DestImageName field
- return nil, errors.Wrapf(define.ErrInvalidArg, "cannot specify an image rename")
- }
- err := validateImageName(args[0])
- if err != nil {
- return nil, err
- }
- scpOpts.SourceImageName = args[0]
- scpOpts.ToRemote = true
- cliConnections = append(cliConnections, args[1])
- } else {
- //in the future, this function could, instead of rejecting renames, also set a DestImageName field
- return nil, errors.Wrapf(define.ErrInvalidArg, "cannot specify an image rename")
- }
- }
+// GetSerivceInformation takes the parsed list of hosts to connect to and validates the information
+func GetServiceInformation(cliConnections []string, cfg *config.Config) (map[string]config.Destination, error) {
+ var serv map[string]config.Destination
var url string
var iden string
for i, val := range cliConnections {
splitEnv := strings.SplitN(val, "::", 2)
- scpOpts.Connections = append(scpOpts.Connections, splitEnv[0])
+ sshInfo.Connections = append(sshInfo.Connections, splitEnv[0])
if len(splitEnv[1]) != 0 {
err := validateImageName(splitEnv[1])
if err != nil {
return nil, err
}
- scpOpts.SourceImageName = splitEnv[1]
+ source.Image = splitEnv[1]
//TODO: actually use the new name given by the user
}
- conn, found := cfg.Engine.ServiceDestinations[scpOpts.Connections[i]]
+ conn, found := cfg.Engine.ServiceDestinations[sshInfo.Connections[i]]
if found {
url = conn.URI
iden = conn.Identity
} else { // no match, warn user and do a manual connection.
- url = "ssh://" + scpOpts.Connections[i]
+ url = "ssh://" + sshInfo.Connections[i]
iden = ""
logrus.Warnf("Unknown connection name given. Please use system connection add to specify the default remote socket location")
}
@@ -374,8 +336,45 @@ func parseArgs(args []string, cfg *config.Config) (map[string]config.Destination
return nil, err
}
}
- scpOpts.URI = append(scpOpts.URI, urlT)
- scpOpts.Iden = append(scpOpts.Iden, iden)
+ sshInfo.URI = append(sshInfo.URI, urlT)
+ sshInfo.Identities = append(sshInfo.Identities, iden)
}
return serv, nil
}
+
+// execPodman executes the podman save/load command given the podman binary
+func execPodman(podman string, command []string) error {
+ if rootless.IsRootless() {
+ cmd := exec.Command(podman)
+ utils.CreateSCPCommand(cmd, command[1:])
+ logrus.Debug("Executing podman command")
+ return cmd.Run()
+ }
+ machinectl, err := exec.LookPath("machinectl")
+ if err != nil {
+ cmd := exec.Command("su", "-l", "root", "--command")
+ cmd = utils.CreateSCPCommand(cmd, []string{strings.Join(command, " ")})
+ return cmd.Run()
+ }
+ cmd := exec.Command(machinectl, "shell", "-q", "root@.host")
+ cmd = utils.CreateSCPCommand(cmd, command)
+ logrus.Debug("Executing load command machinectl")
+ return cmd.Run()
+}
+
+// createCommands forms the podman save and load commands used by SCP
+func createCommands(podman string) ([]string, []string) {
+ var parentString string
+ quiet := ""
+ if source.Quiet {
+ quiet = "-q "
+ }
+ if len(parentFlags) > 0 {
+ parentString = strings.Join(parentFlags, " ") + " " // if there are parent args, an extra space needs to be added
+ } else {
+ parentString = strings.Join(parentFlags, " ")
+ }
+ loadCmd := strings.Split(fmt.Sprintf("%s %sload %s--input %s", podman, parentString, quiet, dest.File), " ")
+ saveCmd := strings.Split(fmt.Sprintf("%s %vsave %s--output %s %s", podman, parentString, quiet, source.File, source.Image), " ")
+ return saveCmd, loadCmd
+}
diff --git a/cmd/podman/images/scp_test.go b/cmd/podman/images/scp_test.go
new file mode 100644
index 000000000..d4d8f8e58
--- /dev/null
+++ b/cmd/podman/images/scp_test.go
@@ -0,0 +1,46 @@
+package images
+
+import (
+ "testing"
+
+ "github.com/containers/podman/v3/pkg/domain/entities"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParseSCPArgs(t *testing.T) {
+ args := []string{"alpine", "root@localhost::"}
+ var source *entities.ImageScpOptions
+ var dest *entities.ImageScpOptions
+ var err error
+ source, _, err = parseImageSCPArg(args[0])
+ assert.Nil(t, err)
+ assert.Equal(t, source.Image, "alpine")
+
+ dest, _, err = parseImageSCPArg(args[1])
+ assert.Nil(t, err)
+ assert.Equal(t, dest.Image, "")
+ assert.Equal(t, dest.User, "root")
+
+ args = []string{"root@localhost::alpine"}
+ source, _, err = parseImageSCPArg(args[0])
+ assert.Nil(t, err)
+ assert.Equal(t, source.User, "root")
+ assert.Equal(t, source.Image, "alpine")
+
+ args = []string{"charliedoern@192.168.68.126::alpine", "foobar@192.168.68.126::"}
+ source, _, err = parseImageSCPArg(args[0])
+ assert.Nil(t, err)
+ assert.True(t, source.Remote)
+ assert.Equal(t, source.Image, "alpine")
+
+ dest, _, err = parseImageSCPArg(args[1])
+ assert.Nil(t, err)
+ assert.True(t, dest.Remote)
+ assert.Equal(t, dest.Image, "")
+
+ args = []string{"charliedoern@192.168.68.126::alpine"}
+ source, _, err = parseImageSCPArg(args[0])
+ assert.Nil(t, err)
+ assert.True(t, source.Remote)
+ assert.Equal(t, source.Image, "alpine")
+}
diff --git a/cmd/podman/images/scp_utils.go b/cmd/podman/images/scp_utils.go
new file mode 100644
index 000000000..ebb874c1c
--- /dev/null
+++ b/cmd/podman/images/scp_utils.go
@@ -0,0 +1,87 @@
+package images
+
+import (
+ "strings"
+
+ "github.com/containers/image/v5/docker/reference"
+ "github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/pkg/domain/entities"
+ "github.com/pkg/errors"
+)
+
+// parseImageSCPArg returns the valid connection, and source/destination data based off of the information provided by the user
+// arg is a string containing one of the cli arguments returned is a filled out source/destination options structs as well as a connections array and an error if applicable
+func parseImageSCPArg(arg string) (*entities.ImageScpOptions, []string, error) {
+ location := entities.ImageScpOptions{}
+ var err error
+ cliConnections := []string{}
+
+ switch {
+ case strings.Contains(arg, "@localhost"): // image transfer between users
+ location.User = strings.Split(arg, "@")[0]
+ location, err = validateImagePortion(location, arg)
+ if err != nil {
+ return nil, nil, err
+ }
+ case strings.Contains(arg, "::"):
+ location, err = validateImagePortion(location, arg)
+ if err != nil {
+ return nil, nil, err
+ }
+ location.Remote = true
+ cliConnections = append(cliConnections, arg)
+ default:
+ location.Image = arg
+ }
+ return &location, cliConnections, nil
+}
+
+// validateImagePortion is a helper function to validate the image name in an SCP argument
+func validateImagePortion(location entities.ImageScpOptions, arg string) (entities.ImageScpOptions, error) {
+ if remoteArgLength(arg, 1) > 0 {
+ err := validateImageName(strings.Split(arg, "::")[1])
+ if err != nil {
+ return location, err
+ }
+ location.Image = strings.Split(arg, "::")[1] // this will get checked/set again once we validate connections
+ }
+ return location, nil
+}
+
+// validateSCPArgs takes the array of source and destination options and checks for common errors
+func validateSCPArgs(locations []*entities.ImageScpOptions) (bool, error) {
+ if len(locations) > 2 {
+ return false, errors.Wrapf(define.ErrInvalidArg, "cannot specify more than two arguments")
+ }
+ switch {
+ case len(locations[0].Image) > 0 && len(locations[1].Image) > 0:
+ return false, errors.Wrapf(define.ErrInvalidArg, "cannot specify an image rename")
+ case len(locations[0].Image) == 0 && len(locations[1].Image) == 0:
+ return false, errors.Wrapf(define.ErrInvalidArg, "a source image must be specified")
+ case len(locations[0].Image) == 0 && len(locations[1].Image) != 0:
+ if locations[0].Remote && locations[1].Remote {
+ return true, nil // we need to flip the cliConnections array so the save/load connections are in the right place
+ }
+ }
+ return false, nil
+}
+
+// validateImageName makes sure that the image given is valid and no injections are occurring
+// we simply use this for error checking, bot setting the image
+func validateImageName(input string) error {
+ // ParseNormalizedNamed transforms a shortname image into its
+ // full name reference so busybox => docker.io/library/busybox
+ // we want to keep our shortnames, so only return an error if
+ // we cannot parse what the user has given us
+ _, err := reference.ParseNormalizedNamed(input)
+ return err
+}
+
+// remoteArgLength is a helper function to simplify the extracting of host argument data
+// returns an int which contains the length of a specified index in a host::image string
+func remoteArgLength(input string, side int) int {
+ if strings.Contains(input, "::") {
+ return len((strings.Split(input, "::"))[side])
+ }
+ return -1
+}
diff --git a/cmd/podman/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/main.go b/cmd/podman/main.go
index b7f5f1720..b38734617 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -52,14 +52,14 @@ func parseCommands() *cobra.Command {
// Command cannot be run rootless
_, found := c.Command.Annotations[registry.UnshareNSRequired]
if found {
- if rootless.IsRootless() && os.Getuid() != 0 {
+ if rootless.IsRootless() && os.Getuid() != 0 && c.Command.Name() != "scp" {
c.Command.RunE = func(cmd *cobra.Command, args []string) error {
return fmt.Errorf("cannot run command %q in rootless mode, must execute `podman unshare` first", cmd.CommandPath())
}
}
} else {
_, found = c.Command.Annotations[registry.ParentNSRequired]
- if rootless.IsRootless() && found {
+ if rootless.IsRootless() && found && c.Command.Name() != "scp" {
c.Command.RunE = func(cmd *cobra.Command, args []string) error {
return fmt.Errorf("cannot run command %q in rootless mode", cmd.CommandPath())
}
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:")
diff --git a/cmd/winpath/main.go b/cmd/winpath/main.go
new file mode 100644
index 000000000..494d1cf3c
--- /dev/null
+++ b/cmd/winpath/main.go
@@ -0,0 +1,184 @@
+//go:build windows
+// +build windows
+
+package main
+
+import (
+ "errors"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/sys/windows/registry"
+)
+
+type operation int
+
+const (
+ HWND_BROADCAST = 0xFFFF
+ WM_SETTINGCHANGE = 0x001A
+ SMTO_ABORTIFHUNG = 0x0002
+ ERR_BAD_ARGS = 0x000A
+ OPERATION_FAILED = 0x06AC
+ Environment = "Environment"
+ Add operation = iota
+ Remove
+ NotSpecified
+)
+
+func main() {
+ op := NotSpecified
+ if len(os.Args) >= 2 {
+ switch os.Args[1] {
+ case "add":
+ op = Add
+ case "remove":
+ op = Remove
+ }
+ }
+
+ // Stay silent since ran from an installer
+ if op == NotSpecified {
+ alert("Usage: " + filepath.Base(os.Args[0]) + " [add|remove]\n\nThis utility adds or removes the podman directory to the Windows Path.")
+ os.Exit(ERR_BAD_ARGS)
+ }
+
+ if err := modify(op); err != nil {
+ os.Exit(OPERATION_FAILED)
+ }
+}
+
+func modify(op operation) error {
+ exe, err := os.Executable()
+ if err != nil {
+ return err
+ }
+ exe, err = filepath.EvalSymlinks(exe)
+ if err != nil {
+ return err
+ }
+ target := filepath.Dir(exe)
+
+ if op == Remove {
+ return removePathFromRegistry(target)
+ }
+
+ return addPathToRegistry(target)
+}
+
+// Appends a directory to the Windows Path stored in the registry
+func addPathToRegistry(dir string) error {
+ k, _, err := registry.CreateKey(registry.CURRENT_USER, Environment, registry.WRITE|registry.READ)
+ if err != nil {
+ return err
+ }
+
+ defer k.Close()
+
+ existing, typ, err := k.GetStringValue("Path")
+ if err != nil {
+ return err
+ }
+
+ // Is this directory already on the windows path?
+ for _, element := range strings.Split(existing, ";") {
+ if strings.EqualFold(element, dir) {
+ // Path already added
+ return nil
+ }
+ }
+
+ // If the existing path is empty we don't want to start with a delimiter
+ if len(existing) > 0 {
+ existing += ";"
+ }
+
+ existing += dir
+
+ // It's important to preserve the registry key type so that it will be interpreted correctly
+ // EXPAND = evaluate variables in the expression, e.g. %PATH% should be expanded to the system path
+ // STRING = treat the contents as a string literal
+ if typ == registry.EXPAND_SZ {
+ err = k.SetExpandStringValue("Path", existing)
+ } else {
+ err = k.SetStringValue("Path", existing)
+ }
+
+ if err == nil {
+ broadcastEnvironmentChange()
+ }
+
+ return err
+}
+
+// Removes all occurences of a directory path from the Windows path stored in the registry
+func removePathFromRegistry(path string) error {
+ k, err := registry.OpenKey(registry.CURRENT_USER, Environment, registry.READ|registry.WRITE)
+ if err != nil {
+ if errors.Is(err, fs.ErrNotExist) {
+ // Nothing to cleanup, the Environment registry key does not exist.
+ return nil
+ }
+ return err
+ }
+
+ defer k.Close()
+
+ existing, typ, err := k.GetStringValue("Path")
+ if err != nil {
+ return err
+ }
+
+ var elements []string
+ for _, element := range strings.Split(existing, ";") {
+ if strings.EqualFold(element, path) {
+ continue
+ }
+ elements = append(elements, element)
+ }
+
+ newPath := strings.Join(elements, ";")
+ // Preserve value type (see corresponding comment above)
+ if typ == registry.EXPAND_SZ {
+ err = k.SetExpandStringValue("Path", newPath)
+ } else {
+ err = k.SetStringValue("Path", newPath)
+ }
+
+ if err == nil {
+ broadcastEnvironmentChange()
+ }
+
+ return err
+}
+
+// Sends a notification message to all top level windows informing them the environmental setings have changed.
+// Applications such as the Windows command prompt and powershell will know to stop caching stale values on
+// subsequent restarts. Since applications block the sender when receiving a message, we set a 3 second timeout
+func broadcastEnvironmentChange() {
+ env, _ := syscall.UTF16PtrFromString(Environment)
+ user32 := syscall.NewLazyDLL("user32")
+ proc := user32.NewProc("SendMessageTimeoutW")
+ millis := 3000
+ _, _, _ = proc.Call(HWND_BROADCAST, WM_SETTINGCHANGE, 0, uintptr(unsafe.Pointer(env)), SMTO_ABORTIFHUNG, uintptr(millis), 0)
+}
+
+// Creates an "error" style pop-up window
+func alert(caption string) int {
+ // Error box style
+ format := 0x10
+
+ user32 := syscall.NewLazyDLL("user32.dll")
+ captionPtr, _ := syscall.UTF16PtrFromString(caption)
+ titlePtr, _ := syscall.UTF16PtrFromString("winpath")
+ ret, _, _ := user32.NewProc("MessageBoxW").Call(
+ uintptr(0),
+ uintptr(unsafe.Pointer(captionPtr)),
+ uintptr(unsafe.Pointer(titlePtr)),
+ uintptr(format))
+
+ return int(ret)
+}