diff options
36 files changed, 647 insertions, 156 deletions
@@ -41,6 +41,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func Diff(name: string) DiffInfo](#Diff) +[func EvictContainer(name: string, removeVolumes: bool) string](#EvictContainer) + [func ExecContainer(opts: ExecOpts) ](#ExecContainer) [func ExportContainer(name: string, path: string) string](#ExportContainer) @@ -480,6 +482,22 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.DeleteUnusedImages method Diff(name: [string](https://godoc.org/builtin#string)) [DiffInfo](#DiffInfo)</div> Diff returns a diff between libpod objects +### <a name="EvictContainer"></a>func EvictContainer +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method EvictContainer(name: [string](https://godoc.org/builtin#string), removeVolumes: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div> +EvictContainer requires the name or ID of a container as well as a boolean that +indicates to remove builtin volumes. Upon successful eviction of the container, +its ID is returned. If the container cannot be found by name or ID, +a [ContainerNotFound](#ContainerNotFound) error will be returned. +See also [RemoveContainer](RemoveContainer). +#### Example +~~~ +$ varlink call -m unix:/run/podman/io.podman/io.podman.EvictContainer '{"name": "62f4fd98cb57"}' +{ + "container": "62f4fd98cb57f529831e8f90610e54bba74bd6f02920ffb485e15376ed365c20" +} +~~~ ### <a name="ExecContainer"></a>func ExecContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -988,10 +1006,12 @@ ReceiveFile allows the host to send a remote client a file <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method RemoveContainer(name: [string](https://godoc.org/builtin#string), force: [bool](https://godoc.org/builtin#bool), removeVolumes: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div> -RemoveContainer requires the name or ID of container as well a boolean representing whether a running container can be stopped and removed, and a boolean +RemoveContainer requires the name or ID of a container as well as a boolean that +indicates whether a container should be forcefully removed (e.g., by stopping it), and a boolean indicating whether to remove builtin volumes. Upon successful removal of the container, its ID is returned. If the container cannot be found by name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned. +See also [EvictContainer](EvictContainer). #### Example ~~~ $ varlink call -m unix:/run/podman/io.podman/io.podman.RemoveContainer '{"name": "62f4fd98cb57"}' @@ -5,7 +5,7 @@ Libpod provides a library for applications looking to use the Container Pod concept, popularized by Kubernetes. Libpod also contains the Pod Manager tool `(Podman)`. Podman manages pods, containers, container images, and container volumes. -* [Latest Version: 1.5.1](https://github.com/containers/libpod/releases/latest) +* [Latest Version: 1.6.0](https://github.com/containers/libpod/releases/latest) * [Continuous Integration:](contrib/cirrus/README.md) [![Build Status](https://api.cirrus-ci.com/github/containers/libpod.svg)](https://cirrus-ci.com/github/containers/libpod/master) * [GoDoc: ![GoDoc](https://godoc.org/github.com/containers/libpod/libpod?status.svg)](https://godoc.org/github.com/containers/libpod/libpod) * Automated continuous release downloads (including remote-client): diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index b8796f9b3..5b5225f02 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -42,6 +42,8 @@ type MainFlags struct { ConnectionName string RemoteConfigFilePath string Port int + IdentityFile string + IgnoreHosts bool } type AttachValues struct { diff --git a/cmd/podman/main_remote.go b/cmd/podman/main_remote.go index f617422e6..623f4098e 100644 --- a/cmd/podman/main_remote.go +++ b/cmd/podman/main_remote.go @@ -3,9 +3,11 @@ package main import ( - "github.com/pkg/errors" + "os" "os/user" + "strconv" + "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -13,14 +15,32 @@ const remote = true func init() { var username string - if curruser, err := user.Current(); err == nil { - username = curruser.Username + if username = os.Getenv("PODMAN_USER"); username == "" { + if curruser, err := user.Current(); err == nil { + username = curruser.Username + } + } + host := os.Getenv("PODMAN_HOST") + port := 22 + if portstr := os.Getenv("PODMAN_PORT"); portstr != "" { + if p, err := strconv.Atoi(portstr); err == nil { + port = p + } + } + key := os.Getenv("PODMAN_IDENTITY_FILE") + ignore := false + if ignorestr := os.Getenv("PODMAN_IGNORE_HOSTS"); ignorestr != "" { + if b, err := strconv.ParseBool(ignorestr); err == nil { + ignore = b + } } rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.ConnectionName, "connection", "", "remote connection name") rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.RemoteConfigFilePath, "remote-config-path", "", "alternate path for configuration file") rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.RemoteUserName, "username", username, "username on the remote host") - rootCmd.PersistentFlags().IntVar(&MainGlobalOpts.Port, "port", 22, "port on remote host") - rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.RemoteHost, "remote-host", "", "remote host") + rootCmd.PersistentFlags().IntVar(&MainGlobalOpts.Port, "port", port, "port on remote host") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.RemoteHost, "remote-host", host, "remote host") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.IdentityFile, "identity-file", key, "identity-file") + rootCmd.PersistentFlags().BoolVar(&MainGlobalOpts.IgnoreHosts, "ignore-hosts", ignore, "ignore hosts") // TODO maybe we allow the altering of this for bridge connections? // rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.VarlinkAddress, "varlink-address", adapter.DefaultAddress, "address of the varlink socket") rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.LogLevel, "log-level", "error", "Log messages above specified level: debug, info, warn, error, fatal or panic. Logged to ~/.config/containers/podman.log") diff --git a/cmd/podman/network_create.go b/cmd/podman/network_create.go index 378a92568..11f13faad 100644 --- a/cmd/podman/network_create.go +++ b/cmd/podman/network_create.go @@ -4,11 +4,12 @@ package main import ( "fmt" - "github.com/containers/libpod/pkg/network" "net" "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/adapter" + "github.com/containers/libpod/pkg/network" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -58,6 +59,9 @@ func networkcreateCmd(c *cliconfig.NetworkCreateValues) error { if len(c.InputArgs) > 1 { return errors.Errorf("only one network can be created at a time") } + if len(c.InputArgs) > 0 && !libpod.NameRegex.MatchString(c.InputArgs[0]) { + return libpod.RegexError + } runtime, err := adapter.GetRuntimeNoStore(getContext(), &c.PodmanCommand) if err != nil { return err diff --git a/cmd/podman/remoteclientconfig/config.go b/cmd/podman/remoteclientconfig/config.go index 13880a868..3faa7954a 100644 --- a/cmd/podman/remoteclientconfig/config.go +++ b/cmd/podman/remoteclientconfig/config.go @@ -9,10 +9,12 @@ type RemoteConfig struct { // RemoteConnection describes the attributes of a podman-remote endpoint type RemoteConnection struct { - Destination string `toml:"destination"` - Username string `toml:"username"` - IsDefault bool `toml:"default"` - Port int `toml:"port"` + Destination string `toml:"destination"` + Username string `toml:"username"` + IsDefault bool `toml:"default"` + Port int `toml:"port"` + IdentityFile string `toml:"identity_file"` + IgnoreHosts bool `toml:"ignore_hosts"` } // GetConfigFilePath is a simple helper to export the configuration file's diff --git a/cmd/podman/remoteclientconfig/configfile_test.go b/cmd/podman/remoteclientconfig/configfile_test.go index ea2224ea7..0bcac29a8 100644 --- a/cmd/podman/remoteclientconfig/configfile_test.go +++ b/cmd/podman/remoteclientconfig/configfile_test.go @@ -143,7 +143,7 @@ func TestRemoteConfig_GetDefault(t *testing.T) { wantErr bool }{ // A good toml should return the connection that is marked isDefault - {"good", fields{Connections: makeGoodResult().Connections}, &RemoteConnection{"192.168.1.1", "myuser", true, 22}, false}, + {"good", fields{Connections: makeGoodResult().Connections}, &RemoteConnection{"192.168.1.1", "myuser", true, 22, "", false}, false}, // If nothing is marked as isDefault and there is more than one connection, error should occur {"nodefault", fields{Connections: noDefault}, nil, true}, // if nothing is marked as isDefault but there is only one connection, the one connection is considered the default @@ -183,9 +183,9 @@ func TestRemoteConfig_GetRemoteConnection(t *testing.T) { wantErr bool }{ // Good connection - {"goodhomer", fields{Connections: makeGoodResult().Connections}, args{name: "homer"}, &RemoteConnection{"192.168.1.1", "myuser", true, 22}, false}, + {"goodhomer", fields{Connections: makeGoodResult().Connections}, args{name: "homer"}, &RemoteConnection{"192.168.1.1", "myuser", true, 22, "", false}, false}, // Good connection - {"goodbart", fields{Connections: makeGoodResult().Connections}, args{name: "bart"}, &RemoteConnection{"foobar.com", "root", false, 22}, false}, + {"goodbart", fields{Connections: makeGoodResult().Connections}, args{name: "bart"}, &RemoteConnection{"foobar.com", "root", false, 22, "", false}, false}, // Getting an unknown connection should result in error {"noexist", fields{Connections: makeGoodResult().Connections}, args{name: "foobar"}, nil, true}, // Getting a connection when there are none should result in an error diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 9e3ce4d0b..89062f524 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -13,7 +13,7 @@ var ( rmCommand cliconfig.RmValues rmDescription = fmt.Sprintf(`Removes one or more containers from the host. The container name or ID can be used. - Command does not remove images. Running containers will not be removed without the -f option.`) + Command does not remove images. Running or unusable containers will not be removed without the -f option.`) _rmCommand = &cobra.Command{ Use: "rm [flags] CONTAINER [CONTAINER...]", Short: "Remove one or more containers", @@ -29,7 +29,8 @@ var ( }, Example: `podman rm imageID podman rm mywebserver myflaskserver 860a4b23 - podman rm --force --all`, + podman rm --force --all + podman rm -f c684f0d469f2`, } ) @@ -39,7 +40,7 @@ func init() { rmCommand.SetUsageTemplate(UsageTemplate()) flags := rmCommand.Flags() flags.BoolVarP(&rmCommand.All, "all", "a", false, "Remove all containers") - flags.BoolVarP(&rmCommand.Force, "force", "f", false, "Force removal of a running container. The default is false") + flags.BoolVarP(&rmCommand.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false") flags.BoolVarP(&rmCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.BoolVar(&rmCommand.Storage, "storage", false, "Remove container from storage library") flags.BoolVarP(&rmCommand.Volumes, "volumes", "v", false, "Remove the volumes associated with the container") diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 7239f5d2e..2408dc80c 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -727,10 +727,12 @@ method GetAttachSockets(name: string) -> (sockets: Sockets) # or name, a [ContainerNotFound](#ContainerNotFound) error is returned. method WaitContainer(name: string, interval: int) -> (exitcode: int) -# RemoveContainer requires the name or ID of container as well a boolean representing whether a running container can be stopped and removed, and a boolean +# RemoveContainer requires the name or ID of a container as well as a boolean that +# indicates whether a container should be forcefully removed (e.g., by stopping it), and a boolean # indicating whether to remove builtin volumes. Upon successful removal of the # container, its ID is returned. If the # container cannot be found by name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned. +# See also [EvictContainer](EvictContainer). # #### Example # ~~~ # $ varlink call -m unix:/run/podman/io.podman/io.podman.RemoveContainer '{"name": "62f4fd98cb57"}' @@ -740,6 +742,20 @@ method WaitContainer(name: string, interval: int) -> (exitcode: int) # ~~~ method RemoveContainer(name: string, force: bool, removeVolumes: bool) -> (container: string) +# EvictContainer requires the name or ID of a container as well as a boolean that +# indicates to remove builtin volumes. Upon successful eviction of the container, +# its ID is returned. If the container cannot be found by name or ID, +# a [ContainerNotFound](#ContainerNotFound) error will be returned. +# See also [RemoveContainer](RemoveContainer). +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.EvictContainer '{"name": "62f4fd98cb57"}' +# { +# "container": "62f4fd98cb57f529831e8f90610e54bba74bd6f02920ffb485e15376ed365c20" +# } +# ~~~ +method EvictContainer(name: string, removeVolumes: bool) -> (container: string) + # DeleteStoppedContainers will delete all containers that are not running. It will return a list the deleted # container IDs. See also [RemoveContainer](RemoveContainer). # #### Example diff --git a/docs/podman-remote.conf.5.md b/docs/podman-remote.conf.5.md index 3c8a1a801..e9cc05989 100644 --- a/docs/podman-remote.conf.5.md +++ b/docs/podman-remote.conf.5.md @@ -25,6 +25,12 @@ of the user's remote connections. **port** = int Use an alternative port for the ssh connections. The default port is 22. +**identity_file** = "" + Use an alternative location for the ssh private key + +**ignore_hosts** = bool + Don't match the remote ssh host key with known hosts + ## EXAMPLE diff --git a/docs/podman-rm.1.md b/docs/podman-rm.1.md index 88339af16..207d9d61d 100644 --- a/docs/podman-rm.1.md +++ b/docs/podman-rm.1.md @@ -9,7 +9,8 @@ podman\-rm - Remove one or more containers **podman container rm** [*options*] *container* ## DESCRIPTION -**podman rm** will remove one or more containers from the host. The container name or ID can be used. This does not remove images. Running containers will not be removed without the `-f` option +**podman rm** will remove one or more containers from the host. The container name or ID can be used. This does not remove images. +Running or unusable containers will not be removed without the `-f` option. ## OPTIONS @@ -19,9 +20,11 @@ Remove all containers. Can be used in conjunction with -f as well. **--force**, **-f** -Force the removal of running and paused containers. Forcing a containers removal also +Force the removal of running and paused containers. Forcing a container removal also removes containers from container storage even if the container is not known to podman. Containers could have been created by a different container engine. +In addition, forcing can be used to remove unusable containers, e.g. containers +whose OCI runtime has become unavailable. **--latest**, **-l** @@ -104,7 +104,7 @@ require ( google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601 // indirect google.golang.org/grpc v1.21.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.2.2 + gopkg.in/yaml.v2 v2.2.3 k8s.io/api v0.0.0-20190813020757-36bff7324fb7 k8s.io/apimachinery v0.0.0-20190809020650-423f5d784010 k8s.io/client-go v0.0.0-20190620085101-78d2af792bab @@ -758,6 +758,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v0.0.0-20190624233834-05ebafbffc79/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90= gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index a6fd9a7d8..e43d54eee 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -407,6 +407,60 @@ func (s *BoltState) Container(id string) (*Container, error) { return ctr, nil } +// LookupContainerID retrieves a container ID from the state by full or unique +// partial ID or name +func (s *BoltState) LookupContainerID(idOrName string) (string, error) { + if idOrName == "" { + return "", define.ErrEmptyID + } + + if !s.valid { + return "", define.ErrDBClosed + } + + db, err := s.getDBCon() + if err != nil { + return "", err + } + defer s.deferredCloseDBCon(db) + + var id []byte + err = db.View(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + namesBucket, err := getNamesBucket(tx) + if err != nil { + return err + } + + nsBucket, err := getNSBucket(tx) + if err != nil { + return err + } + + fullID, err := s.lookupContainerID(idOrName, ctrBucket, namesBucket, nsBucket) + // Check if it is in our namespace + if s.namespaceBytes != nil { + ns := nsBucket.Get(fullID) + if !bytes.Equal(ns, s.namespaceBytes) { + return errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName) + } + } + id = fullID + return err + }) + + if err != nil { + return "", err + } + + retID := string(id) + return retID, nil +} + // LookupContainer retrieves a container from the state by full or unique // partial ID or name func (s *BoltState) LookupContainer(idOrName string) (*Container, error) { @@ -444,67 +498,9 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) { return err } - // First, check if the ID given was the actual container ID - var id []byte - ctrExists := ctrBucket.Bucket([]byte(idOrName)) - if ctrExists != nil { - // A full container ID was given. - // It might not be in our namespace, but - // getContainerFromDB() will handle that case. - id = []byte(idOrName) - return s.getContainerFromDB(id, ctr, ctrBucket) - } - - // Next, check if the full name was given - isPod := false - fullID := namesBucket.Get([]byte(idOrName)) - if fullID != nil { - // The name exists and maps to an ID. - // However, we are not yet certain the ID is a - // container. - ctrExists = ctrBucket.Bucket(fullID) - if ctrExists != nil { - // A container bucket matching the full ID was - // found. - return s.getContainerFromDB(fullID, ctr, ctrBucket) - } - // Don't error if we have a name match but it's not a - // container - there's a chance we have a container with - // an ID starting with those characters. - // However, so we can return a good error, note whether - // this is a pod. - isPod = true - } - - // We were not given a full container ID or name. - // Search for partial ID matches. - exists := false - err = ctrBucket.ForEach(func(checkID, checkName []byte) error { - // If the container isn't in our namespace, we - // can't match it - if s.namespaceBytes != nil { - ns := nsBucket.Get(checkID) - if !bytes.Equal(ns, s.namespaceBytes) { - return nil - } - } - if strings.HasPrefix(string(checkID), idOrName) { - if exists { - return errors.Wrapf(define.ErrCtrExists, "more than one result for container ID %s", idOrName) - } - id = checkID - exists = true - } - - return nil - }) + id, err := s.lookupContainerID(idOrName, ctrBucket, namesBucket, nsBucket) if err != nil { return err - } else if !exists { - if isPod { - return errors.Wrapf(define.ErrNoSuchCtr, "%s is a pod, not a container", idOrName) - } - return errors.Wrapf(define.ErrNoSuchCtr, "no container with name or ID %s found", idOrName) } return s.getContainerFromDB(id, ctr, ctrBucket) @@ -860,6 +856,39 @@ func (s *BoltState) AllContainers() ([]*Container, error) { return ctrs, nil } +// GetContainerConfig returns a container config from the database by full ID +func (s *BoltState) GetContainerConfig(id string) (*ContainerConfig, error) { + if len(id) == 0 { + return nil, define.ErrEmptyID + } + + if !s.valid { + return nil, define.ErrDBClosed + } + + config := new(ContainerConfig) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + err = db.View(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + return s.getContainerConfigFromDB([]byte(id), config, ctrBucket) + }) + if err != nil { + return nil, err + } + + return config, nil +} + // RewriteContainerConfig rewrites a container's configuration. // WARNING: This function is DANGEROUS. Do not use without reading the full // comment on this function in state.go. diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index a50fce31e..ed87373e9 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -347,7 +347,7 @@ func getRuntimeConfigBucket(tx *bolt.Tx) (*bolt.Bucket, error) { return bkt, nil } -func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.Bucket) error { +func (s *BoltState) getContainerConfigFromDB(id []byte, config *ContainerConfig, ctrsBkt *bolt.Bucket) error { ctrBkt := ctrsBkt.Bucket(id) if ctrBkt == nil { return errors.Wrapf(define.ErrNoSuchCtr, "container %s not found in DB", string(id)) @@ -365,10 +365,18 @@ func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt. return errors.Wrapf(define.ErrInternal, "container %s missing config key in DB", string(id)) } - if err := json.Unmarshal(configBytes, ctr.config); err != nil { + if err := json.Unmarshal(configBytes, config); err != nil { return errors.Wrapf(err, "error unmarshalling container %s config", string(id)) } + return nil +} + +func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.Bucket) error { + if err := s.getContainerConfigFromDB(id, ctr.config, ctrsBkt); err != nil { + return err + } + // Get the lock lock, err := s.runtime.lockManager.RetrieveLock(ctr.config.LockID) if err != nil { @@ -388,7 +396,7 @@ func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt. ociRuntime, ok := s.runtime.ociRuntimes[runtimeName] if !ok { - return errors.Wrapf(define.ErrInternal, "container %s was created with OCI runtime %s, but that runtime is not available in the current configuration", ctr.ID(), ctr.config.OCIRuntime) + return errors.Wrapf(define.ErrOCIRuntimeUnavailable, "cannot find OCI runtime %q for container %s", ctr.config.OCIRuntime, ctr.ID()) } ctr.ociRuntime = ociRuntime } @@ -862,3 +870,72 @@ func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error return nil } + +// lookupContainerID retrieves a container ID from the state by full or unique +// partial ID or name. +// NOTE: the retrieved container ID namespace may not match the state namespace. +func (s *BoltState) lookupContainerID(idOrName string, ctrBucket, namesBucket, nsBucket *bolt.Bucket) ([]byte, error) { + // First, check if the ID given was the actual container ID + ctrExists := ctrBucket.Bucket([]byte(idOrName)) + if ctrExists != nil { + // A full container ID was given. + // It might not be in our namespace, but this will be handled + // the callers. + return []byte(idOrName), nil + } + + // Next, check if the full name was given + isPod := false + fullID := namesBucket.Get([]byte(idOrName)) + if fullID != nil { + // The name exists and maps to an ID. + // However, we are not yet certain the ID is a + // container. + ctrExists = ctrBucket.Bucket(fullID) + if ctrExists != nil { + // A container bucket matching the full ID was + // found. + return fullID, nil + } + // Don't error if we have a name match but it's not a + // container - there's a chance we have a container with + // an ID starting with those characters. + // However, so we can return a good error, note whether + // this is a pod. + isPod = true + } + + var id []byte + // We were not given a full container ID or name. + // Search for partial ID matches. + exists := false + err := ctrBucket.ForEach(func(checkID, checkName []byte) error { + // If the container isn't in our namespace, we + // can't match it + if s.namespaceBytes != nil { + ns := nsBucket.Get(checkID) + if !bytes.Equal(ns, s.namespaceBytes) { + return nil + } + } + if strings.HasPrefix(string(checkID), idOrName) { + if exists { + return errors.Wrapf(define.ErrCtrExists, "more than one result for container ID %s", idOrName) + } + id = checkID + exists = true + } + + return nil + }) + + if err != nil { + return nil, err + } else if !exists { + if isPod { + return nil, errors.Wrapf(define.ErrNoSuchCtr, "%s is a pod, not a container", idOrName) + } + return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container with name or ID %s found", idOrName) + } + return id, nil +} diff --git a/libpod/define/errors.go b/libpod/define/errors.go index 004acd58f..5392fbc62 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -112,6 +112,10 @@ var ( // that was not found ErrOCIRuntimeNotFound = errors.New("OCI runtime command not found error") + // ErrOCIRuntimeUnavailable indicates that the OCI runtime associated to a container + // could not be found in the configuration + ErrOCIRuntimeUnavailable = errors.New("OCI runtime not available in the current configuration") + // ErrConmonOutdated indicates the version of conmon found (whether via the configuration or $PATH) // is out of date for the current podman version ErrConmonOutdated = errors.New("outdated conmon version") diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index a008fcb39..5ab258772 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -115,15 +115,16 @@ func (s *InMemoryState) Container(id string) (*Container, error) { return ctr, nil } -// LookupContainer retrieves a container by full ID, unique partial ID, or name -func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) { +// lookupID retrieves a container or pod ID by full ID, unique partial ID, or +// name +func (s *InMemoryState) lookupID(idOrName string) (string, error) { var ( nameIndex *registrar.Registrar idIndex *truncindex.TruncIndex ) if idOrName == "" { - return nil, define.ErrEmptyID + return "", define.ErrEmptyID } if s.namespace != "" { @@ -131,7 +132,7 @@ func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) { if !ok { // We have no containers in the namespace // Return false - return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName) + return "", define.ErrNoSuchCtr } nameIndex = nsIndex.nameIndex idIndex = nsIndex.idIndex @@ -147,15 +148,55 @@ func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) { fullID, err = idIndex.Get(idOrName) if err != nil { if err == truncindex.ErrNotExist { - return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName) + return "", define.ErrNoSuchCtr } - return nil, errors.Wrapf(err, "error performing truncindex lookup for ID %s", idOrName) + return "", errors.Wrapf(err, "error performing truncindex lookup for ID %s", idOrName) } } else { - return nil, errors.Wrapf(err, "error performing registry lookup for ID %s", idOrName) + return "", errors.Wrapf(err, "error performing registry lookup for ID %s", idOrName) } } + return fullID, nil +} + +// LookupContainerID retrieves a container ID by full ID, unique partial ID, or +// name +func (s *InMemoryState) LookupContainerID(idOrName string) (string, error) { + fullID, err := s.lookupID(idOrName) + + switch err { + case nil: + _, ok := s.containers[fullID] + if !ok { + // It's a pod, not a container + return "", errors.Wrapf(define.ErrNoSuchCtr, "name or ID %s is a pod, not a container", idOrName) + } + + case define.ErrNoSuchCtr: + return "", errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName) + + default: + return "", err + } + + return fullID, nil +} + +// LookupContainer retrieves a container by full ID, unique partial ID, or name +func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) { + fullID, err := s.lookupID(idOrName) + + switch err { + case nil: + + case define.ErrNoSuchCtr: + return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName) + + default: + return nil, err + } + ctr, ok := s.containers[fullID] if !ok { // It's a pod, not a container @@ -385,6 +426,16 @@ func (s *InMemoryState) AllContainers() ([]*Container, error) { return ctrs, nil } +// GetContainerConfig returns a container config from the database by full ID +func (s *InMemoryState) GetContainerConfig(id string) (*ContainerConfig, error) { + ctr, err := s.LookupContainer(id) + if err != nil { + return nil, err + } + + return ctr.Config(), nil +} + // RewriteContainerConfig rewrites a container's configuration. // This function is DANGEROUS, even with an in-memory state. // Please read the full comment on it in state.go before using it. @@ -623,49 +674,22 @@ func (s *InMemoryState) Pod(id string) (*Pod, error) { // LookupPod retrieves a pod from the state from a full or unique partial ID or // a full name func (s *InMemoryState) LookupPod(idOrName string) (*Pod, error) { - var ( - nameIndex *registrar.Registrar - idIndex *truncindex.TruncIndex - ) + fullID, err := s.lookupID(idOrName) - if idOrName == "" { - return nil, define.ErrEmptyID - } + switch err { + case nil: - if s.namespace != "" { - nsIndex, ok := s.namespaceIndexes[s.namespace] - if !ok { - // We have no containers in the namespace - // Return false - return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName) - } - nameIndex = nsIndex.nameIndex - idIndex = nsIndex.idIndex - } else { - nameIndex = s.nameIndex - idIndex = s.idIndex - } + case define.ErrNoSuchCtr, define.ErrNoSuchPod: + return nil, errors.Wrapf(define.ErrNoSuchPod, "no pod found with name or ID %s", idOrName) - fullID, err := nameIndex.Get(idOrName) - if err != nil { - if err == registrar.ErrNameNotReserved { - // What was passed is not a name, assume it's an ID - fullID, err = idIndex.Get(idOrName) - if err != nil { - if err == truncindex.ErrNotExist { - return nil, errors.Wrapf(define.ErrNoSuchPod, "no pod found with name or ID %s", idOrName) - } - return nil, errors.Wrapf(err, "error performing truncindex lookup for ID %s", idOrName) - } - } else { - return nil, errors.Wrapf(err, "error performing registry lookup for ID %s", idOrName) - } + default: + return nil, err } pod, ok := s.pods[fullID] if !ok { // It's a container not a pod - return nil, errors.Wrapf(define.ErrNoSuchPod, "id or name %s is a container not a pod", idOrName) + return nil, errors.Wrapf(define.ErrNoSuchPod, "id or name %s is a container, not a pod", idOrName) } return pod, nil diff --git a/libpod/options.go b/libpod/options.go index d28cb3d8c..22ab22a95 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -20,8 +20,8 @@ import ( ) var ( - nameRegex = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9_.-]*$") - regexError = errors.Wrapf(define.ErrInvalidArg, "names must match [a-zA-Z0-9][a-zA-Z0-9_.-]*") + NameRegex = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9_.-]*$") + RegexError = errors.Wrapf(define.ErrInvalidArg, "names must match [a-zA-Z0-9][a-zA-Z0-9_.-]*") ) // Runtime Creation Options @@ -648,8 +648,8 @@ func WithName(name string) CtrCreateOption { } // Check the name against a regex - if !nameRegex.MatchString(name) { - return regexError + if !NameRegex.MatchString(name) { + return RegexError } ctr.config.Name = name @@ -1426,8 +1426,8 @@ func WithVolumeName(name string) VolumeCreateOption { } // Check the name against a regex - if !nameRegex.MatchString(name) { - return regexError + if !NameRegex.MatchString(name) { + return RegexError } volume.config.Name = name @@ -1532,8 +1532,8 @@ func WithPodName(name string) PodCreateOption { } // Check the name against a regex - if !nameRegex.MatchString(name) { - return regexError + if !NameRegex.MatchString(name) { + return RegexError } pod.config.Name = name @@ -1550,8 +1550,8 @@ func WithPodHostname(hostname string) PodCreateOption { } // Check the hostname against a regex - if !nameRegex.MatchString(hostname) { - return regexError + if !NameRegex.MatchString(hostname) { + return RegexError } pod.config.Hostname = hostname diff --git a/libpod/runtime_cstorage.go b/libpod/runtime_cstorage.go index 1e84aef4b..47a91c881 100644 --- a/libpod/runtime_cstorage.go +++ b/libpod/runtime_cstorage.go @@ -60,6 +60,12 @@ func (r *Runtime) RemoveStorageContainer(idOrName string, force bool) error { r.lock.Lock() defer r.lock.Unlock() + return r.removeStorageContainer(idOrName, force) +} + +// Internal function to remove the container storage without +// locking the runtime. +func (r *Runtime) removeStorageContainer(idOrName string, force bool) error { targetID, err := r.store.Lookup(idOrName) if err != nil { if err == storage.ErrLayerUnknown { diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index bffce7bca..1a2987244 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -550,6 +550,122 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, return cleanupErr } +// EvictContainer removes the given container partial or full ID or name, and +// returns the full ID of the evicted container and any error encountered. +// It should be used to remove a container when obtaining a Container struct +// pointer has failed. +// Running container will not be stopped. +// If removeVolume is specified, named volumes used by the container will +// be removed also if and only if the container is the sole user. +func (r *Runtime) EvictContainer(ctx context.Context, idOrName string, removeVolume bool) (string, error) { + r.lock.RLock() + defer r.lock.RUnlock() + return r.evictContainer(ctx, idOrName, removeVolume) +} + +// evictContainer is the internal function to handle container eviction based +// on its partial or full ID or name. +// It returns the full ID of the evicted container and any error encountered. +// This does not lock the runtime nor the container. +// removePod is used only when removing pods. It instructs Podman to ignore +// infra container protections, and *not* remove from the database (as pod +// remove will handle that). +func (r *Runtime) evictContainer(ctx context.Context, idOrName string, removeVolume bool) (string, error) { + var err error + + if !r.valid { + return "", define.ErrRuntimeStopped + } + id, err := r.state.LookupContainerID(idOrName) + if err != nil { + return "", errors.Wrapf(err, "Failed to find container %q in state", idOrName) + } + + // Error out if the container does not exist in libpod + exists, err := r.state.HasContainer(id) + if err != nil { + return id, err + } + if !exists { + return id, errors.Wrapf(err, "Failed to find container ID %q for eviction", id) + } + + // Re-create a container struct for removal purposes + c := new(Container) + c.config, err = r.state.GetContainerConfig(id) + if err != nil { + return id, errors.Wrapf(err, "failed to retrieve config for ctr ID %q", id) + } + c.state = new(ContainerState) + + // We need to lock the pod before we lock the container. + // To avoid races around removing a container and the pod it is in. + // Don't need to do this in pod removal case - we're evicting the entire + // pod. + var pod *Pod + if c.config.Pod != "" { + pod, err = r.state.Pod(c.config.Pod) + if err != nil { + return id, errors.Wrapf(err, "container %s is in pod %s, but pod cannot be retrieved", c.ID(), pod.ID()) + } + + // Lock the pod while we're removing container + pod.lock.Lock() + defer pod.lock.Unlock() + if err := pod.updatePod(); err != nil { + return id, err + } + + infraID := pod.state.InfraContainerID + if c.ID() == infraID { + return id, errors.Errorf("container %s is the infra container of pod %s and cannot be removed without removing the pod", c.ID(), pod.ID()) + } + } + + var cleanupErr error + // Remove the container from the state + if c.config.Pod != "" { + // If we're removing the pod, the container will be evicted + // from the state elsewhere + if err := r.state.RemoveContainerFromPod(pod, c); err != nil { + cleanupErr = err + } + } else { + if err := r.state.RemoveContainer(c); err != nil { + cleanupErr = err + } + } + + // Unmount container mount points + for _, mount := range c.config.Mounts { + Unmount(mount) + } + + // Remove container from c/storage + if err := r.removeStorageContainer(id, true); err != nil { + if cleanupErr == nil { + cleanupErr = err + } + } + + if !removeVolume { + return id, cleanupErr + } + + for _, v := range c.config.NamedVolumes { + if volume, err := r.state.Volume(v.Name); err == nil { + if !volume.IsCtrSpecific() { + continue + } + if err := r.removeVolume(ctx, volume, false); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed { + logrus.Errorf("cleanup volume (%s): %v", v, err) + } + } + } + + return id, cleanupErr +} + // GetContainer retrieves a container by its ID func (r *Runtime) GetContainer(id string) (*Container, error) { r.lock.RLock() diff --git a/libpod/state.go b/libpod/state.go index 40080d2cc..e38f820b5 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -58,6 +58,9 @@ type State interface { // If the container is not in the set namespace, an error will be // returned. Container(id string) (*Container, error) + // Return a container ID from the database by full or partial ID or full + // name. + LookupContainerID(idOrName string) (string, error) // Return a container from the database by full or partial ID or full // name. // Containers not in the set namespace will be ignored. @@ -98,6 +101,9 @@ type State interface { // returned. AllContainers() ([]*Container, error) + // Return a container config from the database by full ID + GetContainerConfig(id string) (*ContainerConfig, error) + // PLEASE READ FULL DESCRIPTION BEFORE USING. // Rewrite a container's configuration. // This function breaks libpod's normal prohibition on a read-only diff --git a/libpod/state_test.go b/libpod/state_test.go index 26a1dee7d..5db1f301c 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -452,6 +452,9 @@ func TestLookupContainerWithEmptyIDFails(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { _, err := state.LookupContainer("") assert.Error(t, err) + + _, err = state.LookupContainerID("") + assert.Error(t, err) }) } @@ -459,6 +462,9 @@ func TestLookupNonexistentContainerFails(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { _, err := state.LookupContainer("does not exist") assert.Error(t, err) + + _, err = state.LookupContainerID("does not exist") + assert.Error(t, err) }) } @@ -472,8 +478,11 @@ func TestLookupContainerByFullID(t *testing.T) { retrievedCtr, err := state.LookupContainer(testCtr.ID()) assert.NoError(t, err) - testContainersEqual(t, retrievedCtr, testCtr, true) + + retrievedID, err := state.LookupContainerID(testCtr.ID()) + assert.NoError(t, err) + assert.Equal(t, retrievedID, testCtr.ID()) }) } @@ -487,8 +496,11 @@ func TestLookupContainerByUniquePartialID(t *testing.T) { retrievedCtr, err := state.LookupContainer(testCtr.ID()[0:8]) assert.NoError(t, err) - testContainersEqual(t, retrievedCtr, testCtr, true) + + retrievedID, err := state.LookupContainerID(testCtr.ID()[0:8]) + assert.NoError(t, err) + assert.Equal(t, retrievedID, testCtr.ID()) }) } @@ -507,6 +519,9 @@ func TestLookupContainerByNonUniquePartialIDFails(t *testing.T) { _, err = state.LookupContainer(testCtr1.ID()[0:8]) assert.Error(t, err) + + _, err = state.LookupContainerID(testCtr1.ID()[0:8]) + assert.Error(t, err) }) } @@ -520,8 +535,11 @@ func TestLookupContainerByName(t *testing.T) { retrievedCtr, err := state.LookupContainer(testCtr.Name()) assert.NoError(t, err) - testContainersEqual(t, retrievedCtr, testCtr, true) + + retrievedID, err := state.LookupContainerID(testCtr.Name()) + assert.NoError(t, err) + assert.Equal(t, retrievedID, testCtr.ID()) }) } @@ -535,6 +553,9 @@ func TestLookupCtrByPodNameFails(t *testing.T) { _, err = state.LookupContainer(testPod.Name()) assert.Error(t, err) + + _, err = state.LookupContainerID(testPod.Name()) + assert.Error(t, err) }) } @@ -548,6 +569,9 @@ func TestLookupCtrByPodIDFails(t *testing.T) { _, err = state.LookupContainer(testPod.ID()) assert.Error(t, err) + + _, err = state.LookupContainerID(testPod.ID()) + assert.Error(t, err) }) } @@ -565,8 +589,11 @@ func TestLookupCtrInSameNamespaceSucceeds(t *testing.T) { ctr, err := state.LookupContainer(testCtr.ID()) assert.NoError(t, err) - testContainersEqual(t, ctr, testCtr, true) + + ctrID, err := state.LookupContainerID(testCtr.ID()) + assert.NoError(t, err) + assert.Equal(t, ctrID, testCtr.ID()) }) } @@ -584,6 +611,9 @@ func TestLookupCtrInDifferentNamespaceFails(t *testing.T) { _, err = state.LookupContainer(testCtr.ID()) assert.Error(t, err) + + _, err = state.LookupContainerID(testCtr.ID()) + assert.Error(t, err) }) } @@ -606,8 +636,11 @@ func TestLookupContainerMatchInDifferentNamespaceSucceeds(t *testing.T) { ctr, err := state.LookupContainer("000") assert.NoError(t, err) - testContainersEqual(t, ctr, testCtr2, true) + + ctrID, err := state.LookupContainerID("000") + assert.NoError(t, err) + assert.Equal(t, ctrID, testCtr2.ID()) }) } @@ -3599,3 +3632,30 @@ func TestSaveAndUpdatePodSameNamespace(t *testing.T) { testPodsEqual(t, testPod, statePod, false) }) } + +func TestGetContainerConfigSucceeds(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { + testCtr, err := getTestCtr1(manager) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + ctrCfg, err := state.GetContainerConfig(testCtr.ID()) + assert.NoError(t, err) + assert.Equal(t, ctrCfg, testCtr.Config()) + }) +} + +func TestGetContainerConfigEmptyIDFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { + _, err := state.GetContainerConfig("") + assert.Error(t, err) + }) +} +func TestGetContainerConfigNonExistentIDFails(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { + _, err := state.GetContainerConfig("does not exist") + assert.Error(t, err) + }) +} diff --git a/libpod/util_linux.go b/libpod/util_linux.go index d5c113daf..631f6836c 100644 --- a/libpod/util_linux.go +++ b/libpod/util_linux.go @@ -5,6 +5,7 @@ package libpod import ( "fmt" "strings" + "syscall" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/cgroups" @@ -12,6 +13,7 @@ import ( "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" ) // systemdSliceFromPath makes a new systemd slice under the given parent with @@ -107,3 +109,14 @@ func LabelVolumePath(path string, shared bool) error { } return nil } + +// Unmount umounts a target directory +func Unmount(mount string) { + if err := unix.Unmount(mount, unix.MNT_DETACH); err != nil { + if err != syscall.EINVAL { + logrus.Warnf("failed to unmount %s : %v", mount, err) + } else { + logrus.Debugf("failed to unmount %s : %v", mount, err) + } + } +} diff --git a/libpod/util_unsupported.go b/libpod/util_unsupported.go index 58b0dfbcd..9a9a6eeb6 100644 --- a/libpod/util_unsupported.go +++ b/libpod/util_unsupported.go @@ -28,3 +28,7 @@ func assembleSystemdCgroupName(baseSlice, newSlice string) (string, error) { func LabelVolumePath(path string, shared bool) error { return define.ErrNotImplemented } + +func Unmount(mount string) error { + return define.ErrNotImplemented +} diff --git a/pkg/adapter/client.go b/pkg/adapter/client.go index 1805c758d..da4670892 100644 --- a/pkg/adapter/client.go +++ b/pkg/adapter/client.go @@ -35,7 +35,7 @@ func (r RemoteRuntime) RemoteEndpoint() (remoteEndpoint *Endpoint, err error) { if len(r.cmd.RemoteUserName) < 1 { return nil, errors.New("you must provide a username when providing a remote host name") } - rc := remoteclientconfig.RemoteConnection{r.cmd.RemoteHost, r.cmd.RemoteUserName, false, r.cmd.Port} + rc := remoteclientconfig.RemoteConnection{r.cmd.RemoteHost, r.cmd.RemoteUserName, false, r.cmd.Port, r.cmd.IdentityFile, r.cmd.IgnoreHosts} remoteEndpoint, err = newBridgeConnection("", &rc, r.cmd.LogLevel) // if the user has a config file with connections in it } else if len(remoteConfigConnections.Connections) > 0 { diff --git a/pkg/adapter/client_unix.go b/pkg/adapter/client_unix.go index a7bc7c1c0..7af8b24c6 100644 --- a/pkg/adapter/client_unix.go +++ b/pkg/adapter/client_unix.go @@ -14,7 +14,14 @@ func formatDefaultBridge(remoteConn *remoteclientconfig.RemoteConnection, logLev if port == 0 { port = 22 } + options := "" + if remoteConn.IdentityFile != "" { + options += " -i " + remoteConn.IdentityFile + } + if remoteConn.IgnoreHosts { + options += " -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" + } return fmt.Sprintf( - `ssh -p %d -T %s@%s -- /usr/bin/varlink -A \'/usr/bin/podman --log-level=%s varlink \\\$VARLINK_ADDRESS\' bridge`, - port, remoteConn.Username, remoteConn.Destination, logLevel) + `ssh -p %d -T%s %s@%s -- varlink -A \'podman --log-level=%s varlink \\\$VARLINK_ADDRESS\' bridge`, + port, options, remoteConn.Username, remoteConn.Destination, logLevel) } diff --git a/pkg/adapter/client_windows.go b/pkg/adapter/client_windows.go index 31e5d9830..32302a600 100644 --- a/pkg/adapter/client_windows.go +++ b/pkg/adapter/client_windows.go @@ -9,7 +9,18 @@ import ( ) func formatDefaultBridge(remoteConn *remoteclientconfig.RemoteConnection, logLevel string) string { + port := remoteConn.Port + if port == 0 { + port = 22 + } + options := "" + if remoteConn.IdentityFile != "" { + options += " -i " + remoteConn.IdentityFile + } + if remoteConn.IgnoreHosts { + options += " -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" + } return fmt.Sprintf( - `ssh -T %s@%s -- /usr/bin/varlink -A '/usr/bin/podman --log-level=%s varlink $VARLINK_ADDRESS' bridge`, - remoteConn.Username, remoteConn.Destination, logLevel) + `ssh -p %d -T%s %s@%s -- varlink -A 'podman --log-level=%s varlink $VARLINK_ADDRESS' bridge`, + port, options, remoteConn.Username, remoteConn.Destination, logLevel) } diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 47db5c0dc..afca4c948 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -205,7 +205,22 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) if err != nil { - return ok, failures, err + // Failed to get containers. If force is specified, get the containers ID + // and evict them + if !cli.Force { + return ok, failures, err + } + + for _, ctr := range cli.InputArgs { + logrus.Debugf("Evicting container %q", ctr) + id, err := r.EvictContainer(ctx, ctr, cli.Volumes) + if err != nil { + failures[ctr] = errors.Wrapf(err, "Failed to evict container: %q", id) + continue + } + ok = append(ok, id) + } + return ok, failures, nil } pool := shared.NewPool("rm", maxWorkers, len(ctrs)) diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 6cecb92da..f7cb28b0c 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -321,16 +321,31 @@ func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillVa // RemoveContainer removes container(s) based on varlink inputs. func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmValues) ([]string, map[string]error, error) { - ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs) - if err != nil { - return nil, nil, TranslateError(err) - } - var ( ok = []string{} failures = map[string]error{} ) + ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs) + if err != nil { + // Failed to get containers. If force is specified, get the containers ID + // and evict them + if !cli.Force { + return nil, nil, TranslateError(err) + } + + for _, ctr := range cli.InputArgs { + logrus.Debugf("Evicting container %q", ctr) + id, err := iopodman.EvictContainer().Call(r.Conn, ctr, cli.Volumes) + if err != nil { + failures[ctr] = errors.Wrapf(err, "Failed to evict container: %q", id) + continue + } + ok = append(ok, string(id)) + } + return ok, failures, nil + } + for _, id := range ids { _, err := iopodman.RemoveContainer().Call(r.Conn, id, cli.Force, cli.Volumes) if err != nil { diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 93f9d4fe3..79fcef11a 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -508,7 +508,16 @@ func (i *LibpodAPI) RemoveContainer(call iopodman.VarlinkCall, name string, forc return call.ReplyErrorOccurred(err.Error()) } return call.ReplyRemoveContainer(ctr.ID()) +} +// EvictContainer ... +func (i *LibpodAPI) EvictContainer(call iopodman.VarlinkCall, name string, removeVolumes bool) error { + ctx := getContext() + id, err := i.Runtime.EvictContainer(ctx, name, removeVolumes) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyEvictContainer(id) } // DeleteStoppedContainers ... diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go index 410d0b97c..264219178 100644 --- a/test/e2e/network_create_test.go +++ b/test/e2e/network_create_test.go @@ -208,4 +208,10 @@ var _ = Describe("Podman network create", func() { Expect(ncFail.ExitCode()).ToNot(BeZero()) }) + It("podman network create with invalid network name", func() { + nc := podmanTest.Podman([]string{"network", "create", "foo "}) + nc.WaitWithDefaultTimeout() + Expect(nc.ExitCode()).ToNot(BeZero()) + }) + }) diff --git a/test/system/005-info.bats b/test/system/005-info.bats index 7fccc75af..5df6033fc 100644 --- a/test/system/005-info.bats +++ b/test/system/005-info.bats @@ -33,7 +33,7 @@ RunRoot: run_podman info --format=json expr_nvr="[a-z0-9-]\\\+-[a-z0-9.]\\\+-[a-z0-9]\\\+\." - expr_path="/[a-z0-9\\\-\\\/.]\\\+\\\$" + expr_path="/[a-z0-9\\\/.-]\\\+\\\$" tests=" host.BuildahVersion | [0-9.] diff --git a/test/system/400-unprivileged-access.bats b/test/system/400-unprivileged-access.bats index 738d8d87b..56c40e9c8 100644 --- a/test/system/400-unprivileged-access.bats +++ b/test/system/400-unprivileged-access.bats @@ -22,7 +22,7 @@ load helpers # as a user, the parent directory must be world-readable. test_script=$PODMAN_TMPDIR/fail-if-writable cat >$test_script <<"EOF" -#!/bin/sh +#!/bin/bash path="$1" diff --git a/vendor/gopkg.in/yaml.v2/decode.go b/vendor/gopkg.in/yaml.v2/decode.go index e4e56e28e..91679b5b4 100644 --- a/vendor/gopkg.in/yaml.v2/decode.go +++ b/vendor/gopkg.in/yaml.v2/decode.go @@ -229,6 +229,10 @@ type decoder struct { mapType reflect.Type terrors []string strict bool + + decodeCount int + aliasCount int + aliasDepth int } var ( @@ -315,6 +319,13 @@ func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unm } func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) { + d.decodeCount++ + if d.aliasDepth > 0 { + d.aliasCount++ + } + if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > 0.99 { + failf("document contains excessive aliasing") + } switch n.kind { case documentNode: return d.document(n, out) @@ -353,7 +364,9 @@ func (d *decoder) alias(n *node, out reflect.Value) (good bool) { failf("anchor '%s' value contains itself", n.value) } d.aliases[n] = true + d.aliasDepth++ good = d.unmarshal(n.alias, out) + d.aliasDepth-- delete(d.aliases, n) return good } diff --git a/vendor/gopkg.in/yaml.v2/resolve.go b/vendor/gopkg.in/yaml.v2/resolve.go index 6c151db6f..4120e0c91 100644 --- a/vendor/gopkg.in/yaml.v2/resolve.go +++ b/vendor/gopkg.in/yaml.v2/resolve.go @@ -81,7 +81,7 @@ func resolvableTag(tag string) bool { return false } -var yamlStyleFloat = regexp.MustCompile(`^[-+]?[0-9]*\.?[0-9]+([eE][-+][0-9]+)?$`) +var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`) func resolve(tag string, in string) (rtag string, out interface{}) { if !resolvableTag(tag) { diff --git a/vendor/modules.txt b/vendor/modules.txt index dc113b619..1d92a249d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -549,7 +549,7 @@ gopkg.in/fsnotify.v1 gopkg.in/inf.v0 # gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 gopkg.in/tomb.v1 -# gopkg.in/yaml.v2 v2.2.2 +# gopkg.in/yaml.v2 v2.2.3 gopkg.in/yaml.v2 # k8s.io/api v0.0.0-20190813020757-36bff7324fb7 k8s.io/api/core/v1 |