diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/boltdb_state.go | 293 | ||||
-rw-r--r-- | libpod/boltdb_state_internal.go | 62 | ||||
-rw-r--r-- | libpod/container.go | 70 | ||||
-rw-r--r-- | libpod/container_config.go | 39 | ||||
-rw-r--r-- | libpod/container_inspect.go | 75 | ||||
-rw-r--r-- | libpod/container_internal.go | 36 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 250 | ||||
-rw-r--r-- | libpod/container_validate.go | 13 | ||||
-rw-r--r-- | libpod/define/annotations.go | 12 | ||||
-rw-r--r-- | libpod/define/container_inspect.go | 40 | ||||
-rw-r--r-- | libpod/define/pod_inspect.go | 2 | ||||
-rw-r--r-- | libpod/healthcheck_linux.go | 32 | ||||
-rw-r--r-- | libpod/kube.go | 2 | ||||
-rw-r--r-- | libpod/network/internal/util/util.go | 2 | ||||
-rw-r--r-- | libpod/network/types/network.go | 3 | ||||
-rw-r--r-- | libpod/networking_linux.go | 221 | ||||
-rw-r--r-- | libpod/options.go | 94 | ||||
-rw-r--r-- | libpod/pod_api.go | 17 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 64 | ||||
-rw-r--r-- | libpod/runtime_img.go | 17 | ||||
-rw-r--r-- | libpod/runtime_volume_linux.go | 42 | ||||
-rw-r--r-- | libpod/state.go | 13 | ||||
-rw-r--r-- | libpod/state_test.go | 18 | ||||
-rw-r--r-- | libpod/volume_internal.go | 2 | ||||
-rw-r--r-- | libpod/volume_internal_linux.go | 35 |
25 files changed, 747 insertions, 707 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 1242a8d6b..9669cf921 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -2,11 +2,14 @@ package libpod import ( "bytes" + "fmt" + "net" "os" "strings" "sync" "github.com/containers/podman/v3/libpod/define" + "github.com/containers/podman/v3/libpod/network/types" jsoniter "github.com/json-iterator/go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -971,7 +974,7 @@ func (s *BoltState) AllContainers() ([]*Container, error) { } // GetNetworks returns the CNI networks this container is a part of. -func (s *BoltState) GetNetworks(ctr *Container) ([]string, error) { +func (s *BoltState) GetNetworks(ctr *Container) (map[string]types.PerNetworkOptions, error) { if !s.valid { return nil, define.ErrDBClosed } @@ -984,61 +987,9 @@ func (s *BoltState) GetNetworks(ctr *Container) ([]string, error) { return nil, errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) } - ctrID := []byte(ctr.ID()) - - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) - - networks := []string{} - - err = db.View(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } - - dbCtr := ctrBucket.Bucket(ctrID) - if dbCtr == nil { - ctr.valid = false - return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) - } - - ctrNetworkBkt := dbCtr.Bucket(networksBkt) - if ctrNetworkBkt == nil { - return errors.Wrapf(define.ErrNoSuchNetwork, "container %s is not joined to any CNI networks", ctr.ID()) - } - - return ctrNetworkBkt.ForEach(func(network, v []byte) error { - networks = append(networks, string(network)) - return nil - }) - }) - if err != nil { - return nil, err - } - return networks, nil -} - -// GetNetworkAliases retrieves the network aliases for the given container in -// the given CNI network. -func (s *BoltState) GetNetworkAliases(ctr *Container, network string) ([]string, error) { - if !s.valid { - return nil, define.ErrDBClosed - } - - if !ctr.valid { - return nil, define.ErrCtrRemoved - } - - if network == "" { - return nil, errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") - } - - if s.namespace != "" && s.namespace != ctr.config.Namespace { - return nil, errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + // if the network mode is not bridge return no networks + if !ctr.config.NetMode.IsBridge() { + return nil, nil } ctrID := []byte(ctr.ID()) @@ -1049,7 +1000,9 @@ func (s *BoltState) GetNetworkAliases(ctr *Container, network string) ([]string, } defer s.deferredCloseDBCon(db) - aliases := []string{} + networks := make(map[string]types.PerNetworkOptions) + + var convertDB bool err = db.View(func(tx *bolt.Tx) error { ctrBucket, err := getCtrBucket(tx) @@ -1065,115 +1018,137 @@ func (s *BoltState) GetNetworkAliases(ctr *Container, network string) ([]string, ctrNetworkBkt := dbCtr.Bucket(networksBkt) if ctrNetworkBkt == nil { - // No networks joined, so no aliases + // convert if needed + convertDB = true return nil } - inNetwork := ctrNetworkBkt.Get([]byte(network)) - if inNetwork == nil { - return errors.Wrapf(define.ErrNoAliases, "container %s is not part of network %s, no aliases found", ctr.ID(), network) - } - - ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) - if ctrAliasesBkt == nil { - // No aliases - return nil - } - - netAliasesBkt := ctrAliasesBkt.Bucket([]byte(network)) - if netAliasesBkt == nil { - // No aliases for this specific network. - return nil - } - - return netAliasesBkt.ForEach(func(alias, v []byte) error { - aliases = append(aliases, string(alias)) + return ctrNetworkBkt.ForEach(func(network, v []byte) error { + opts := types.PerNetworkOptions{} + if err := json.Unmarshal(v, &opts); err != nil { + // special case for backwards compat + // earlier version used the container id as value so we set a + // special error to indicate the we have to migrate the db + if !bytes.Equal(v, ctrID) { + return err + } + convertDB = true + } + networks[string(network)] = opts return nil }) }) if err != nil { return nil, err } + if convertDB { + err = db.Update(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } - return aliases, nil -} - -// GetAllNetworkAliases retrieves the network aliases for the given container in -// all CNI networks. -func (s *BoltState) GetAllNetworkAliases(ctr *Container) (map[string][]string, error) { - if !s.valid { - return nil, define.ErrDBClosed - } - - if !ctr.valid { - return nil, define.ErrCtrRemoved - } - - if s.namespace != "" && s.namespace != ctr.config.Namespace { - return nil, errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) - } - - ctrID := []byte(ctr.ID()) + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) + } - db, err := s.getDBCon() - if err != nil { - return nil, err - } - defer s.deferredCloseDBCon(db) + var networkList []string - aliases := make(map[string][]string) + ctrNetworkBkt := dbCtr.Bucket(networksBkt) + if ctrNetworkBkt == nil { + ctrNetworkBkt, err = dbCtr.CreateBucket(networksBkt) + if err != nil { + return errors.Wrapf(err, "error creating networks bucket for container %s", ctr.ID()) + } + // the container has no networks in the db lookup config and write to the db + networkList = ctr.config.NetworksDeprecated + // if there are no networks we have to add the default + if len(networkList) == 0 { + networkList = []string{ctr.runtime.config.Network.DefaultNetwork} + } + } else { + err = ctrNetworkBkt.ForEach(func(network, v []byte) error { + networkList = append(networkList, string(network)) + return nil + }) + if err != nil { + return err + } + } - err = db.View(func(tx *bolt.Tx) error { - ctrBucket, err := getCtrBucket(tx) - if err != nil { - return err - } + // the container has no networks in the db lookup config and write to the db + for i, network := range networkList { + var intName string + if ctr.state.NetInterfaceDescriptions != nil { + eth, exists := ctr.state.NetInterfaceDescriptions.getInterfaceByName(network) + if !exists { + return errors.Errorf("no network interface name for container %s on network %s", ctr.config.ID, network) + } + intName = eth + } else { + intName = fmt.Sprintf("eth%d", i) + } + getAliases := func(network string) []string { + var aliases []string + ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) + if ctrAliasesBkt == nil { + return nil + } + netAliasesBkt := ctrAliasesBkt.Bucket([]byte(network)) + if netAliasesBkt == nil { + // No aliases for this specific network. + return nil + } - dbCtr := ctrBucket.Bucket(ctrID) - if dbCtr == nil { - ctr.valid = false - return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) - } + // lets ignore the error here there is nothing we can do + _ = netAliasesBkt.ForEach(func(alias, v []byte) error { + aliases = append(aliases, string(alias)) + return nil + }) + // also add the short container id as alias + return aliases + } - ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) - if ctrAliasesBkt == nil { - // No aliases present - return nil - } + netOpts := &types.PerNetworkOptions{ + InterfaceName: intName, + // we have to add the short id as alias for docker compat + Aliases: append(getAliases(network), ctr.config.ID[:12]), + } + // only set the static ip/mac on the first network + if i == 0 { + if ctr.config.StaticIP != nil { + netOpts.StaticIPs = []net.IP{ctr.config.StaticIP} + } + netOpts.StaticMAC = ctr.config.StaticMAC + } - ctrNetworkBkt := dbCtr.Bucket(networksBkt) - if ctrNetworkBkt == nil { - // No networks joined, so no aliases - return nil - } + optsBytes, err := json.Marshal(netOpts) + if err != nil { + return err + } + // insert into network map because we need to return this + networks[network] = *netOpts - return ctrNetworkBkt.ForEach(func(network, v []byte) error { - netAliasesBkt := ctrAliasesBkt.Bucket(network) - if netAliasesBkt == nil { - return nil + err = ctrNetworkBkt.Put([]byte(network), optsBytes) + if err != nil { + return err + } } - - netAliases := []string{} - - _ = netAliasesBkt.ForEach(func(alias, v []byte) error { - netAliases = append(netAliases, string(alias)) - return nil - }) - - aliases[string(network)] = netAliases return nil }) - }) - if err != nil { - return nil, err + if err != nil { + return nil, err + } } - return aliases, nil + return networks, nil } // NetworkConnect adds the given container to the given network. If aliases are // specified, those will be added to the given network. -func (s *BoltState) NetworkConnect(ctr *Container, network string, aliases []string) error { +func (s *BoltState) NetworkConnect(ctr *Container, network string, opts types.PerNetworkOptions) error { if !s.valid { return define.ErrDBClosed } @@ -1190,6 +1165,11 @@ func (s *BoltState) NetworkConnect(ctr *Container, network string, aliases []str return errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) } + optBytes, err := json.Marshal(opts) + if err != nil { + return errors.Wrapf(err, "error marshalling network options JSON for container %s", ctr.ID()) + } + ctrID := []byte(ctr.ID()) db, err := s.getDBCon() @@ -1210,47 +1190,20 @@ func (s *BoltState) NetworkConnect(ctr *Container, network string, aliases []str return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) } - ctrAliasesBkt, err := dbCtr.CreateBucketIfNotExists(aliasesBkt) - if err != nil { - return errors.Wrapf(err, "error creating aliases bucket for container %s", ctr.ID()) - } - ctrNetworksBkt := dbCtr.Bucket(networksBkt) if ctrNetworksBkt == nil { - ctrNetworksBkt, err = dbCtr.CreateBucket(networksBkt) - if err != nil { - return errors.Wrapf(err, "error creating networks bucket for container %s", ctr.ID()) - } - ctrNetworks := ctr.config.Networks - if len(ctrNetworks) == 0 { - ctrNetworks = []string{ctr.runtime.config.Network.DefaultNetwork} - } - // Copy in all the container's CNI networks - for _, net := range ctrNetworks { - if err := ctrNetworksBkt.Put([]byte(net), ctrID); err != nil { - return errors.Wrapf(err, "error adding container %s network %s to DB", ctr.ID(), net) - } - } + return errors.Wrapf(define.ErrNoSuchNetwork, "container %s does not have a network bucket", ctr.ID()) } netConnected := ctrNetworksBkt.Get([]byte(network)) if netConnected != nil { - return errors.Wrapf(define.ErrNetworkExists, "container %s is already connected to CNI network %q", ctr.ID(), network) + return errors.Wrapf(define.ErrNetworkExists, "container %s is already connected to network %q", ctr.ID(), network) } // Add the network - if err := ctrNetworksBkt.Put([]byte(network), ctrID); err != nil { + if err := ctrNetworksBkt.Put([]byte(network), optBytes); err != nil { return errors.Wrapf(err, "error adding container %s to network %s in DB", ctr.ID(), network) } - ctrNetAliasesBkt, err := ctrAliasesBkt.CreateBucketIfNotExists([]byte(network)) - if err != nil { - return errors.Wrapf(err, "error adding container %s network aliases bucket for network %s", ctr.ID(), network) - } - for _, alias := range aliases { - if err := ctrNetAliasesBkt.Put([]byte(alias), ctrID); err != nil { - return errors.Wrapf(err, "error adding container %s network alias %s for network %s", ctr.ID(), alias, network) - } - } return nil }) } diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index e71d82736..5f0f6ba7d 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -563,6 +563,28 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { ctrNamespace = []byte(ctr.config.Namespace) } + // make sure to marshal the network options before we get the db lock + networks := make(map[string][]byte, len(ctr.config.Networks)) + for net, opts := range ctr.config.Networks { + // Check that we don't have any empty network names + if net == "" { + return errors.Wrapf(define.ErrInvalidArg, "network names cannot be an empty string") + } + if opts.InterfaceName == "" { + return errors.Wrapf(define.ErrInvalidArg, "network interface name cannot be an empty string") + } + // always add the short id as alias for docker compat + opts.Aliases = append(opts.Aliases, ctr.config.ID[:12]) + optBytes, err := json.Marshal(opts) + if err != nil { + return errors.Wrapf(err, "error marshalling network options JSON for container %s", ctr.ID()) + } + networks[net] = optBytes + } + // Set the original value to nil. We can safe some space by not storing it in the config + // since we store it in a different mutable bucket anyway. + ctr.config.Networks = nil + db, err := s.getDBCon() if err != nil { return err @@ -646,23 +668,6 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { return errors.Wrapf(err, "name \"%s\" is in use", ctr.Name()) } - allNets := make(map[string]bool) - - // Check that we don't have any empty network names - for _, net := range ctr.config.Networks { - if net == "" { - return errors.Wrapf(define.ErrInvalidArg, "network names cannot be an empty string") - } - allNets[net] = true - } - - // Each network we have aliases for, must exist in networks - for net := range ctr.config.NetworkAliases { - if !allNets[net] { - return errors.Wrapf(define.ErrNoSuchNetwork, "container %s has network aliases for network %q but is not part of that network", ctr.ID(), net) - } - } - // No overlapping containers // Add the new container to the DB if err := idsBucket.Put(ctrID, ctrName); err != nil { @@ -706,34 +711,17 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { return errors.Wrapf(err, "error adding container %s netns path to DB", ctr.ID()) } } - if ctr.config.Networks != nil { + if len(networks) > 0 { ctrNetworksBkt, err := newCtrBkt.CreateBucket(networksBkt) if err != nil { return errors.Wrapf(err, "error creating networks bucket for container %s", ctr.ID()) } - for _, network := range ctr.config.Networks { - if err := ctrNetworksBkt.Put([]byte(network), ctrID); err != nil { + for network, opts := range networks { + if err := ctrNetworksBkt.Put([]byte(network), opts); err != nil { return errors.Wrapf(err, "error adding network %q to networks bucket for container %s", network, ctr.ID()) } } } - if ctr.config.NetworkAliases != nil { - ctrAliasesBkt, err := newCtrBkt.CreateBucket(aliasesBkt) - if err != nil { - return errors.Wrapf(err, "error creating network aliases bucket for container %s", ctr.ID()) - } - for net, aliases := range ctr.config.NetworkAliases { - netAliasesBkt, err := ctrAliasesBkt.CreateBucket([]byte(net)) - if err != nil { - return errors.Wrapf(err, "error creating network aliases bucket for network %q in container %s", net, ctr.ID()) - } - for _, alias := range aliases { - if err := netAliasesBkt.Put([]byte(alias), ctrID); err != nil { - return errors.Wrapf(err, "error creating network alias %q in network %q for container %s", alias, net, ctr.ID()) - } - } - } - } if _, err := newCtrBkt.CreateBucket(dependenciesBkt); err != nil { return errors.Wrapf(err, "error creating dependencies bucket for container %s", ctr.ID()) diff --git a/libpod/container.go b/libpod/container.go index 2b74a1943..c746f97c7 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -213,6 +213,15 @@ type ContainerState struct { // containerPlatformState holds platform-specific container state. containerPlatformState + + // Following checkpoint/restore related information is displayed + // if the container has been checkpointed or restored. + CheckpointedTime time.Time `json:"checkpointedTime,omitempty"` + RestoredTime time.Time `json:"restoredTime,omitempty"` + CheckpointLog string `json:"checkpointLog,omitempty"` + CheckpointPath string `json:"checkpointPath,omitempty"` + RestoreLog string `json:"restoreLog,omitempty"` + Restored bool `json:"restored,omitempty"` } // ContainerNamedVolume is a named volume that will be mounted into the @@ -1166,17 +1175,28 @@ func (c *Container) Secrets() []*ContainerSecret { // is joining the default CNI network - the network name will be included in the // returned array of network names, but the container did not explicitly join // this network. -func (c *Container) Networks() ([]string, bool, error) { +func (c *Container) Networks() ([]string, error) { if !c.batched { c.lock.Lock() defer c.lock.Unlock() if err := c.syncContainer(); err != nil { - return nil, false, err + return nil, err } } - return c.networks() + networks, err := c.networks() + if err != nil { + return nil, err + } + + names := make([]string, 0, len(networks)) + + for name := range networks { + names = append(names, name) + } + + return names, nil } // NetworkMode gets the configured network mode for the container. @@ -1220,36 +1240,8 @@ func (c *Container) NetworkMode() string { } // Unlocked accessor for networks -func (c *Container) networks() ([]string, bool, error) { - networks, err := c.runtime.state.GetNetworks(c) - if err != nil && errors.Cause(err) == define.ErrNoSuchNetwork { - if len(c.config.Networks) == 0 && c.config.NetMode.IsBridge() { - return []string{c.runtime.config.Network.DefaultNetwork}, true, nil - } - return c.config.Networks, false, nil - } - - return networks, false, err -} - -// networksByNameIndex provides us with a map of container networks where key -// is network name and value is the index position -func (c *Container) networksByNameIndex() (map[string]int, error) { - networks, _, err := c.networks() - if err != nil { - return nil, err - } - networkNamesByIndex := make(map[string]int, len(networks)) - for index, name := range networks { - networkNamesByIndex[name] = index - } - return networkNamesByIndex, nil -} - -// add puts the new given CNI network name into the tracking map -// and assigns it a new integer based on the map length -func (d ContainerNetworkDescriptions) add(networkName string) { - d[networkName] = len(d) +func (c *Container) networks() (map[string]types.PerNetworkOptions, error) { + return c.runtime.state.GetNetworks(c) } // getInterfaceByName returns a formatted interface name for a given @@ -1270,9 +1262,7 @@ func (c *Container) getNetworkStatus() map[string]types.StatusBlock { return c.state.NetworkStatus } if c.state.NetworkStatusOld != nil { - // Note: NetworkStatusOld does not contain the network names so we get them extra - // Generally the order should be the same - networks, _, err := c.networks() + networks, err := c.networks() if err != nil { return nil } @@ -1280,12 +1270,16 @@ func (c *Container) getNetworkStatus() map[string]types.StatusBlock { return nil } result := make(map[string]types.StatusBlock, len(c.state.NetworkStatusOld)) - for i := range c.state.NetworkStatusOld { + i := 0 + // Note: NetworkStatusOld does not contain the network names so we get them extra + // We cannot guarantee the same order but after a state refresh it should work + for netName := range networks { status, err := cni.CNIResultToStatus(c.state.NetworkStatusOld[i]) if err != nil { return nil } - result[networks[i]] = status + result[netName] = status + i++ } c.state.NetworkStatus = result _ = c.save() diff --git a/libpod/container_config.go b/libpod/container_config.go index 57f5b92ac..288524dbd 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -163,6 +163,8 @@ type ContainerRootFSConfig struct { // Volatile specifies whether the container storage can be optimized // at the cost of not syncing all the dirty files in memory. Volatile bool `json:"volatile,omitempty"` + // Passwd allows to user to override podman's passwd/group file setup + Passwd *bool `json:"passwd,omitempty"` } // ContainerSecurityConfig is an embedded sub-config providing security configuration @@ -196,6 +198,8 @@ type ContainerSecurityConfig struct { // Groups are additional groups to add the container's user to. These // are resolved within the container using the container's /etc/passwd. Groups []string `json:"groups,omitempty"` + // HostUsers are a list of host user accounts to add to /etc/passwd + HostUsers []string `json:"HostUsers,omitempty"` // AddCurrentUserPasswdEntry indicates that Libpod should ensure that // the container's /etc/passwd contains an entry for the user running // Libpod - mostly used in rootless containers where the user running @@ -229,10 +233,12 @@ type ContainerNetworkConfig struct { // StaticIP is a static IP to request for the container. // This cannot be set unless CreateNetNS is set. // If not set, the container will be dynamically assigned an IP by CNI. + // Deprecated: Do no use this anymore, this is only for DB backwards compat. StaticIP net.IP `json:"staticIP"` // StaticMAC is a static MAC to request for the container. // This cannot be set unless CreateNetNS is set. // If not set, the container will be dynamically assigned a MAC by CNI. + // Deprecated: Do no use this anymore, this is only for DB backwards compat. StaticMAC types.HardwareAddr `json:"staticMAC"` // PortMappings are the ports forwarded to the container's network // namespace @@ -269,24 +275,24 @@ type ContainerNetworkConfig struct { // Hosts to add in container // Will be appended to host's host file HostAdd []string `json:"hostsAdd,omitempty"` - // Network names (CNI) to add container to. Empty to use default network. + // Network names with the network specific options. + // Please note that these can be altered at runtime. The actual list is + // stored in the DB and should be retrieved from there via c.networks() + // this value is only used for container create. + // Added in podman 4.0, previously NetworksDeprecated was used. Make + // sure to not change the json tags. + Networks map[string]types.PerNetworkOptions `json:"newNetworks,omitempty"` + // Network names to add container to. Empty to use default network. // Please note that these can be altered at runtime. The actual list is // stored in the DB and should be retrieved from there; this is only the // set of networks the container was *created* with. - Networks []string `json:"networks,omitempty"` + // Deprecated: Do no use this anymore, this is only for DB backwards compat. + // Also note that we need to keep the old json tag to decode from DB correctly + NetworksDeprecated []string `json:"networks,omitempty"` // Network mode specified for the default network. NetMode namespaces.NetworkMode `json:"networkMode,omitempty"` // NetworkOptions are additional options for each network NetworkOptions map[string][]string `json:"network_options,omitempty"` - // NetworkAliases are aliases that will be added to each network. - // These are additional names that this container can be accessed as via - // DNS when the CNI dnsname plugin is in use. - // Please note that these can be altered at runtime. As such, the actual - // list is stored in the database and should be retrieved from there; - // this is only the set of aliases the container was *created with*. - // Formatted as map of network name to aliases. All network names must - // be present in the Networks list above. - NetworkAliases map[string][]string `json:"network_alises,omitempty"` } // ContainerImageConfig is an embedded sub-config providing image configuration @@ -394,3 +400,14 @@ type ContainerMiscConfig struct { // and if so, what type: always or once are possible non-nil entries InitContainerType string `json:"init_container_type,omitempty"` } + +type InfraInherit struct { + InfraSecurity ContainerSecurityConfig + InfraLabels []string `json:"labelopts,omitempty"` + InfraVolumes []*ContainerNamedVolume `json:"namedVolumes,omitempty"` + InfraOverlay []*ContainerOverlayVolume `json:"overlayVolumes,omitempty"` + InfraImageVolumes []*ContainerImageVolume `json:"ctrImageVolumes,omitempty"` + InfraUserVolumes []string `json:"userVolumes,omitempty"` + InfraResources *spec.LinuxResources `json:"resources,omitempty"` + InfraDevices []spec.LinuxDevice `json:"device_host_src,omitempty"` +} diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 83b643266..792dfc58e 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -113,20 +113,26 @@ func (c *Container) getContainerInspectData(size bool, driverData *define.Driver Path: path, Args: args, State: &define.InspectContainerState{ - OciVersion: ctrSpec.Version, - Status: runtimeInfo.State.String(), - Running: runtimeInfo.State == define.ContainerStateRunning, - Paused: runtimeInfo.State == define.ContainerStatePaused, - OOMKilled: runtimeInfo.OOMKilled, - Dead: runtimeInfo.State.String() == "bad state", - Pid: runtimeInfo.PID, - ConmonPid: runtimeInfo.ConmonPID, - ExitCode: runtimeInfo.ExitCode, - Error: "", // can't get yet - StartedAt: runtimeInfo.StartedTime, - FinishedAt: runtimeInfo.FinishedTime, - Checkpointed: runtimeInfo.Checkpointed, - CgroupPath: cgroupPath, + OciVersion: ctrSpec.Version, + Status: runtimeInfo.State.String(), + Running: runtimeInfo.State == define.ContainerStateRunning, + Paused: runtimeInfo.State == define.ContainerStatePaused, + OOMKilled: runtimeInfo.OOMKilled, + Dead: runtimeInfo.State.String() == "bad state", + Pid: runtimeInfo.PID, + ConmonPid: runtimeInfo.ConmonPID, + ExitCode: runtimeInfo.ExitCode, + Error: "", // can't get yet + StartedAt: runtimeInfo.StartedTime, + FinishedAt: runtimeInfo.FinishedTime, + Checkpointed: runtimeInfo.Checkpointed, + CgroupPath: cgroupPath, + RestoredAt: runtimeInfo.RestoredTime, + CheckpointedAt: runtimeInfo.CheckpointedTime, + Restored: runtimeInfo.Restored, + CheckpointPath: runtimeInfo.CheckpointPath, + CheckpointLog: runtimeInfo.CheckpointLog, + RestoreLog: runtimeInfo.RestoreLog, }, Image: config.RootfsImageID, ImageName: config.RootfsImageName, @@ -267,6 +273,27 @@ func (c *Container) GetInspectMounts(namedVolumes []*ContainerNamedVolume, image return inspectMounts, nil } +// GetSecurityOptions retrives and returns the security related annotations and process information upon inspection +func (c *Container) GetSecurityOptions() []string { + ctrSpec := c.config.Spec + SecurityOpt := []string{} + if ctrSpec.Process != nil { + if ctrSpec.Process.NoNewPrivileges { + SecurityOpt = append(SecurityOpt, "no-new-privileges") + } + } + if label, ok := ctrSpec.Annotations[define.InspectAnnotationLabel]; ok { + SecurityOpt = append(SecurityOpt, fmt.Sprintf("label=%s", label)) + } + if seccomp, ok := ctrSpec.Annotations[define.InspectAnnotationSeccomp]; ok { + SecurityOpt = append(SecurityOpt, fmt.Sprintf("seccomp=%s", seccomp)) + } + if apparmor, ok := ctrSpec.Annotations[define.InspectAnnotationApparmor]; ok { + SecurityOpt = append(SecurityOpt, fmt.Sprintf("apparmor=%s", apparmor)) + } + return SecurityOpt +} + // Parse mount options so we can populate them in the mount structure. // The mount passed in will be modified. func parseMountOptionsForInspect(options []string, mount *define.InspectMount) { @@ -371,6 +398,8 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp ctrConfig.Umask = c.config.Umask } + ctrConfig.Passwd = c.config.Passwd + return ctrConfig } @@ -414,16 +443,14 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named hostConfig.GroupAdd = make([]string, 0, len(c.config.Groups)) hostConfig.GroupAdd = append(hostConfig.GroupAdd, c.config.Groups...) - hostConfig.SecurityOpt = []string{} if ctrSpec.Process != nil { if ctrSpec.Process.OOMScoreAdj != nil { hostConfig.OomScoreAdj = *ctrSpec.Process.OOMScoreAdj } - if ctrSpec.Process.NoNewPrivileges { - hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, "no-new-privileges") - } } + hostConfig.SecurityOpt = c.GetSecurityOptions() + hostConfig.ReadonlyRootfs = ctrSpec.Root.Readonly hostConfig.ShmSize = c.config.ShmSize hostConfig.Runtime = "oci" @@ -448,15 +475,6 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named if ctrSpec.Annotations[define.InspectAnnotationInit] == define.InspectResponseTrue { hostConfig.Init = true } - if label, ok := ctrSpec.Annotations[define.InspectAnnotationLabel]; ok { - hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("label=%s", label)) - } - if seccomp, ok := ctrSpec.Annotations[define.InspectAnnotationSeccomp]; ok { - hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("seccomp=%s", seccomp)) - } - if apparmor, ok := ctrSpec.Annotations[define.InspectAnnotationApparmor]; ok { - hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("apparmor=%s", apparmor)) - } } // Resource limits @@ -485,9 +503,6 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named if ctrSpec.Linux.Resources.Memory.Limit != nil { hostConfig.Memory = *ctrSpec.Linux.Resources.Memory.Limit } - if ctrSpec.Linux.Resources.Memory.Kernel != nil { - hostConfig.KernelMemory = *ctrSpec.Linux.Resources.Memory.Kernel - } if ctrSpec.Linux.Resources.Memory.Reservation != nil { hostConfig.MemoryReservation = *ctrSpec.Linux.Resources.Memory.Reservation } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index d8187c609..2d12a90d1 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -634,6 +634,12 @@ func resetState(state *ContainerState) { state.RestartPolicyMatch = false state.RestartCount = 0 state.Checkpointed = false + state.Restored = false + state.CheckpointedTime = time.Time{} + state.RestoredTime = time.Time{} + state.CheckpointPath = "" + state.CheckpointLog = "" + state.RestoreLog = "" } // Refresh refreshes the container's state after a restart. @@ -756,7 +762,7 @@ func (c *Container) export(path string) error { if !c.state.Mounted { containerMount, err := c.runtime.store.Mount(c.ID(), c.config.MountLabel) if err != nil { - return errors.Wrapf(err, "error mounting container %q", c.ID()) + return errors.Wrapf(err, "mounting container %q", c.ID()) } mountPoint = containerMount defer func() { @@ -1111,6 +1117,12 @@ func (c *Container) init(ctx context.Context, retainRetries bool) error { } c.state.Checkpointed = false + c.state.Restored = false + c.state.CheckpointedTime = time.Time{} + c.state.RestoredTime = time.Time{} + c.state.CheckpointPath = "" + c.state.CheckpointLog = "" + c.state.RestoreLog = "" c.state.ExitCode = 0 c.state.Exited = false c.state.State = define.ContainerStateCreated @@ -1877,7 +1889,7 @@ func (c *Container) cleanupStorage() error { return cleanupErr } -// Unmount the a container and free its resources +// Unmount the container and free its resources func (c *Container) cleanup(ctx context.Context) error { var lastError error @@ -1885,7 +1897,7 @@ func (c *Container) cleanup(ctx context.Context) error { // Remove healthcheck unit/timer file if it execs if c.config.HealthCheckConfig != nil { - if err := c.removeTimer(); err != nil { + if err := c.removeTransientFiles(ctx); err != nil { logrus.Errorf("Removing timer for container %s healthcheck: %v", c.ID(), err) } } @@ -2206,6 +2218,24 @@ func (c *Container) canWithPrevious() error { // prepareCheckpointExport writes the config and spec to // JSON files for later export func (c *Container) prepareCheckpointExport() error { + networks, err := c.networks() + if err != nil { + return err + } + // make sure to exclude the short ID alias since the container gets a new ID on restore + for net, opts := range networks { + newAliases := make([]string, 0, len(opts.Aliases)) + for _, alias := range opts.Aliases { + if alias != c.config.ID[:12] { + newAliases = append(newAliases, alias) + } + } + opts.Aliases = newAliases + networks[net] = opts + } + + // add the networks from the db to the config so that the exported checkpoint still stores all current networks + c.config.Networks = networks // save live config if _, err := metadata.WriteJSONFile(c.config, c.bundlePath(), metadata.ConfigDumpFile); err != nil { return err diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index ef9f13f44..7745646b6 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -305,13 +305,40 @@ func (c *Container) getUserOverrides() *lookup.Overrides { return &overrides } +func lookupHostUser(name string) (*runcuser.ExecUser, error) { + var execUser runcuser.ExecUser + // Lookup User on host + u, err := util.LookupUser(name) + if err != nil { + return &execUser, err + } + uid, err := strconv.ParseUint(u.Uid, 8, 32) + if err != nil { + return &execUser, err + } + + gid, err := strconv.ParseUint(u.Gid, 8, 32) + if err != nil { + return &execUser, err + } + execUser.Uid = int(uid) + execUser.Gid = int(gid) + execUser.Home = u.HomeDir + return &execUser, nil +} + // Generate spec for a container // Accepts a map of the container's dependencies func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { overrides := c.getUserOverrides() execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, overrides) if err != nil { - return nil, err + if util.StringInSlice(c.config.User, c.config.HostUsers) { + execUser, err = lookupHostUser(c.config.User) + } + if err != nil { + return nil, err + } } g := generate.NewFromSpec(c.config.Spec) @@ -990,6 +1017,7 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error { includeFiles := []string{ "artifacts", + metadata.DevShmCheckpointTar, metadata.ConfigDumpFile, metadata.SpecDumpFile, metadata.NetworkStatusFile, @@ -1134,11 +1162,38 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO return nil, 0, err } + // Setting CheckpointLog early in case there is a failure. + c.state.CheckpointLog = path.Join(c.bundlePath(), "dump.log") + c.state.CheckpointPath = c.CheckpointPath() + runtimeCheckpointDuration, err := c.ociRuntime.CheckpointContainer(c, options) if err != nil { return nil, 0, err } + // Keep the content of /dev/shm directory + if c.config.ShmDir != "" && c.state.BindMounts["/dev/shm"] == c.config.ShmDir { + shmDirTarFileFullPath := filepath.Join(c.bundlePath(), metadata.DevShmCheckpointTar) + + shmDirTarFile, err := os.Create(shmDirTarFileFullPath) + if err != nil { + return nil, 0, err + } + defer shmDirTarFile.Close() + + input, err := archive.TarWithOptions(c.config.ShmDir, &archive.TarOptions{ + Compression: archive.Uncompressed, + IncludeSourceDir: true, + }) + if err != nil { + return nil, 0, err + } + + if _, err = io.Copy(shmDirTarFile, input); err != nil { + return nil, 0, err + } + } + // Save network.status. This is needed to restore the container with // the same IP. Currently limited to one IP address in a container // with one interface. @@ -1169,6 +1224,9 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO if !options.KeepRunning && !options.PreCheckPoint { c.state.State = define.ContainerStateStopped c.state.Checkpointed = true + c.state.CheckpointedTime = time.Now() + c.state.Restored = false + c.state.RestoredTime = time.Time{} // Cleanup Storage and Network if err := c.cleanup(ctx); err != nil { @@ -1216,6 +1274,8 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO logrus.Debugf("Unable to remove file %s", file) } } + // The file has been deleted. Do not mention it. + c.state.CheckpointLog = "" } c.state.FinishedTime = time.Now() @@ -1293,22 +1353,9 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti return nil, 0, err } - // If a container is restored multiple times from an exported checkpoint with - // the help of '--import --name', the restore will fail if during 'podman run' - // a static container IP was set with '--ip'. The user can tell the restore - // process to ignore the static IP with '--ignore-static-ip' - if options.IgnoreStaticIP { - c.config.StaticIP = nil - } - - // If a container is restored multiple times from an exported checkpoint with - // the help of '--import --name', the restore will fail if during 'podman run' - // a static container MAC address was set with '--mac-address'. The user - // can tell the restore process to ignore the static MAC with - // '--ignore-static-mac' - if options.IgnoreStaticMAC { - c.config.StaticMAC = nil - } + // Setting RestoreLog early in case there is a failure. + c.state.RestoreLog = path.Join(c.bundlePath(), "restore.log") + c.state.CheckpointPath = c.CheckpointPath() // Read network configuration from checkpoint var netStatus map[string]types.StatusBlock @@ -1325,19 +1372,19 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti if err == nil && options.Name == "" && (!options.IgnoreStaticIP || !options.IgnoreStaticMAC) { // The file with the network.status does exist. Let's restore the // container with the same networks settings as during checkpointing. - aliases, err := c.GetAllNetworkAliases() + networkOpts, err := c.networks() if err != nil { return nil, 0, err } + netOpts := make(map[string]types.PerNetworkOptions, len(netStatus)) - for network, status := range netStatus { - perNetOpts := types.PerNetworkOptions{} - for name, netInt := range status.Interfaces { - perNetOpts = types.PerNetworkOptions{ - InterfaceName: name, - Aliases: aliases[network], - } - if !options.IgnoreStaticMAC { + for network, perNetOpts := range networkOpts { + // unset mac and ips before we start adding the ones from the status + perNetOpts.StaticMAC = nil + perNetOpts.StaticIPs = nil + for name, netInt := range netStatus[network].Interfaces { + perNetOpts.InterfaceName = name + if !options.IgnoreStaticIP { perNetOpts.StaticMAC = netInt.MacAddress } if !options.IgnoreStaticIP { @@ -1349,13 +1396,6 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti // For now just use the first interface to get the ips this should be good enough for most cases. break } - if perNetOpts.InterfaceName == "" { - eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(network) - if !exists { - return nil, 0, errors.Errorf("no network interface name for container %s on network %s", c.config.ID, network) - } - perNetOpts.InterfaceName = eth - } netOpts[network] = perNetOpts } c.perNetworkOpts = netOpts @@ -1497,6 +1537,24 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti } } + // Restore /dev/shm content + if c.config.ShmDir != "" && c.state.BindMounts["/dev/shm"] == c.config.ShmDir { + shmDirTarFileFullPath := filepath.Join(c.bundlePath(), metadata.DevShmCheckpointTar) + if _, err := os.Stat(shmDirTarFileFullPath); err != nil { + logrus.Debug("Container checkpoint doesn't contain dev/shm: ", err.Error()) + } else { + shmDirTarFile, err := os.Open(shmDirTarFileFullPath) + if err != nil { + return nil, 0, err + } + defer shmDirTarFile.Close() + + if err := archive.UntarUncompressed(shmDirTarFile, c.config.ShmDir, nil); err != nil { + return nil, 0, err + } + } + } + // Cleanup for a working restore. if err := c.removeConmonFiles(); err != nil { return nil, 0, err @@ -1583,6 +1641,9 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti c.state.State = define.ContainerStateRunning c.state.Checkpointed = false + c.state.Restored = true + c.state.CheckpointedTime = time.Time{} + c.state.RestoredTime = time.Now() if !options.Keep { // Delete all checkpoint related files. At this point, in theory, all files @@ -1593,6 +1654,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti if err != nil { logrus.Debugf("Non-fatal: removal of checkpoint directory (%s) failed: %v", c.CheckpointPath(), err) } + c.state.CheckpointPath = "" err = os.RemoveAll(c.PreCheckPointPath()) if err != nil { logrus.Debugf("Non-fatal: removal of pre-checkpoint directory (%s) failed: %v", c.PreCheckPointPath(), err) @@ -1613,6 +1675,8 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti logrus.Debugf("Non-fatal: removal of checkpoint file (%s) failed: %v", file, err) } } + c.state.CheckpointLog = "" + c.state.RestoreLog = "" } return criuStatistics, runtimeRestoreDuration, c.save() @@ -1740,11 +1804,9 @@ func (c *Container) makeBindMounts() error { } if !c.config.UseImageHosts { - newHosts, err := c.generateHosts("/etc/hosts") - if err != nil { + if err := c.updateHosts("/etc/hosts"); err != nil { return errors.Wrapf(err, "error creating hosts file for container %s", c.ID()) } - c.state.BindMounts["/etc/hosts"] = newHosts } } @@ -1761,32 +1823,32 @@ func (c *Container) makeBindMounts() error { } } else { if !c.config.UseImageHosts && c.state.BindMounts["/etc/hosts"] == "" { - newHosts, err := c.generateHosts("/etc/hosts") - if err != nil { + if err := c.updateHosts("/etc/hosts"); err != nil { return errors.Wrapf(err, "error creating hosts file for container %s", c.ID()) } - c.state.BindMounts["/etc/hosts"] = newHosts } } // SHM is always added when we mount the container c.state.BindMounts["/dev/shm"] = c.config.ShmDir - newPasswd, newGroup, err := c.generatePasswdAndGroup() - if err != nil { - return errors.Wrapf(err, "error creating temporary passwd file for container %s", c.ID()) - } - if newPasswd != "" { - // Make /etc/passwd - // If it already exists, delete so we can recreate - delete(c.state.BindMounts, "/etc/passwd") - c.state.BindMounts["/etc/passwd"] = newPasswd - } - if newGroup != "" { - // Make /etc/group - // If it already exists, delete so we can recreate - delete(c.state.BindMounts, "/etc/group") - c.state.BindMounts["/etc/group"] = newGroup + if c.config.Passwd == nil || *c.config.Passwd { + newPasswd, newGroup, err := c.generatePasswdAndGroup() + if err != nil { + return errors.Wrapf(err, "error creating temporary passwd file for container %s", c.ID()) + } + if newPasswd != "" { + // Make /etc/passwd + // If it already exists, delete so we can recreate + delete(c.state.BindMounts, "/etc/passwd") + c.state.BindMounts["/etc/passwd"] = newPasswd + } + if newGroup != "" { + // Make /etc/group + // If it already exists, delete so we can recreate + delete(c.state.BindMounts, "/etc/group") + c.state.BindMounts["/etc/group"] = newGroup + } } // Make /etc/hostname @@ -2053,18 +2115,29 @@ func (c *Container) generateResolvConf() (string, error) { return destPath, nil } -// generateHosts creates a containers hosts file -func (c *Container) generateHosts(path string) (string, error) { +// updateHosts updates the container's hosts file +func (c *Container) updateHosts(path string) error { + var hosts string + orig, err := ioutil.ReadFile(path) if err != nil { - return "", err + // Ignore if the path does not exist + if !os.IsNotExist(err) { + return err + } + } else { + hosts = string(orig) } - hosts := string(orig) - hosts += c.getHosts() + hosts += c.getHosts() hosts = c.appendLocalhost(hosts) - return c.writeStringToRundir("hosts", hosts) + newHosts, err := c.writeStringToRundir("hosts", hosts) + if err != nil { + return err + } + c.state.BindMounts["/etc/hosts"] = newHosts + return nil } // based on networking mode we may want to append the localhost @@ -2159,11 +2232,24 @@ func (c *Container) getHosts() string { } } } else if c.config.NetMode.IsSlirp4netns() { - gatewayIP, err := GetSlirp4netnsGateway(c.slirp4netnsSubnet) - if err != nil { - logrus.Warn("Failed to determine gatewayIP: ", err.Error()) - } else { - hosts += fmt.Sprintf("%s host.containers.internal\n", gatewayIP.String()) + // getLocalIP returns the non loopback local IP of the host + getLocalIP := func() string { + addrs, err := net.InterfaceAddrs() + if err != nil { + return "" + } + for _, address := range addrs { + // check the address type and if it is not a loopback the display it + if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + return ipnet.IP.String() + } + } + } + return "" + } + if ip := getLocalIP(); ip != "" { + hosts += fmt.Sprintf("%s\t%s\n", ip, "host.containers.internal") } } else { logrus.Debug("Network configuration does not support host.containers.internal address") @@ -2289,12 +2375,25 @@ func (c *Container) generateUserGroupEntry(addedGID int) (string, int, error) { // /etc/passwd via AddCurrentUserPasswdEntry (though this does not trigger if // the user in question already exists in /etc/passwd) or the UID to be added // is 0). +// 3. The user specified additional host user accounts to add the the /etc/passwd file // Returns password entry (as a string that can be appended to /etc/passwd) and // any error that occurred. func (c *Container) generatePasswdEntry() (string, error) { passwdString := "" addedUID := 0 + for _, userid := range c.config.HostUsers { + // Lookup User on host + u, err := util.LookupUser(userid) + if err != nil { + return "", err + } + entry, err := c.userPasswdEntry(u) + if err != nil { + return "", err + } + passwdString += entry + } if c.config.AddCurrentUserPasswdEntry { entry, uid, _, err := c.generateCurrentUserPasswdEntry() if err != nil { @@ -2327,17 +2426,25 @@ func (c *Container) generateCurrentUserPasswdEntry() (string, int, int, error) { if err != nil { return "", 0, 0, errors.Wrapf(err, "failed to get current user") } + pwd, err := c.userPasswdEntry(u) + if err != nil { + return "", 0, 0, err + } + return pwd, uid, rootless.GetRootlessGID(), nil +} + +func (c *Container) userPasswdEntry(u *user.User) (string, error) { // Lookup the user to see if it exists in the container image. - _, err = lookup.GetUser(c.state.Mountpoint, u.Username) + _, err := lookup.GetUser(c.state.Mountpoint, u.Username) if err != runcuser.ErrNoPasswdEntries { - return "", 0, 0, err + return "", err } // Lookup the UID to see if it exists in the container image. _, err = lookup.GetUser(c.state.Mountpoint, u.Uid) if err != runcuser.ErrNoPasswdEntries { - return "", 0, 0, err + return "", err } // If the user's actual home directory exists, or was mounted in - use @@ -2371,7 +2478,7 @@ func (c *Container) generateCurrentUserPasswdEntry() (string, int, int, error) { c.config.Spec.Process.Env = append(c.config.Spec.Process.Env, fmt.Sprintf("HOME=%s", homeDir)) } - return fmt.Sprintf("%s:*:%s:%s:%s:%s:/bin/sh\n", u.Username, u.Uid, u.Gid, u.Name, homeDir), uid, rootless.GetRootlessGID(), nil + return fmt.Sprintf("%s:*:%s:%s:%s:%s:/bin/sh\n", u.Username, u.Uid, u.Gid, u.Name, homeDir), nil } // generateUserPasswdEntry generates an /etc/passwd entry for the container user @@ -2426,7 +2533,7 @@ func (c *Container) generateUserPasswdEntry(addedUID int) (string, int, int, err // generatePasswdAndGroup generates container-specific passwd and group files // iff g.config.User is a number or we are configured to make a passwd entry for -// the current user. +// the current user or the user specified HostsUsers // Returns path to file to mount at /etc/passwd, path to file to mount at // /etc/group, and any error that occurred. If no passwd/group file were // required, the empty string will be returned for those path (this may occur @@ -2437,7 +2544,8 @@ func (c *Container) generateUserPasswdEntry(addedUID int) (string, int, int, err // with a bind mount). This is done in cases where the container is *not* // read-only. In this case, the function will return nothing ("", "", nil). func (c *Container) generatePasswdAndGroup() (string, string, error) { - if !c.config.AddCurrentUserPasswdEntry && c.config.User == "" { + if !c.config.AddCurrentUserPasswdEntry && c.config.User == "" && + len(c.config.HostUsers) == 0 { return "", "", nil } diff --git a/libpod/container_validate.go b/libpod/container_validate.go index 91ebe93fb..ca5ce8b2a 100644 --- a/libpod/container_validate.go +++ b/libpod/container_validate.go @@ -74,7 +74,7 @@ func (c *Container) validate() error { // Cannot set static IP or MAC if joining >1 CNI network. if len(c.config.Networks) > 1 && (c.config.StaticIP != nil || c.config.StaticMAC != nil) { - return errors.Wrapf(define.ErrInvalidArg, "cannot set static IP or MAC address if joining more than one CNI network") + return errors.Wrapf(define.ErrInvalidArg, "cannot set static IP or MAC address if joining more than one network") } // Using image resolv.conf conflicts with various DNS settings. @@ -115,17 +115,6 @@ func (c *Container) validate() error { destinations[vol.Dest] = true } - // Check that networks and network aliases match up. - ctrNets := make(map[string]bool) - for _, net := range c.config.Networks { - ctrNets[net] = true - } - for net := range c.config.NetworkAliases { - if _, ok := ctrNets[net]; !ok { - return errors.Wrapf(define.ErrNoSuchNetwork, "container tried to set network aliases for network %s but is not connected to the network", net) - } - } - // If User in the OCI spec is set, require that c.config.User is set for // security reasons (a lot of our code relies on c.config.User). if c.config.User == "" && (c.config.Spec.Process.User.UID != 0 || c.config.Spec.Process.User.GID != 0) { diff --git a/libpod/define/annotations.go b/libpod/define/annotations.go index f6b1c06ea..3964a1237 100644 --- a/libpod/define/annotations.go +++ b/libpod/define/annotations.go @@ -66,3 +66,15 @@ const ( // annotation. InspectResponseFalse = "FALSE" ) + +// IsReservedAnnotation returns true if the specified value corresponds to an +// already reserved annotation that Podman sets during container creation. +func IsReservedAnnotation(value string) bool { + switch value { + case InspectAnnotationCIDFile, InspectAnnotationAutoremove, InspectAnnotationVolumesFrom, InspectAnnotationPrivileged, InspectAnnotationPublishAll, InspectAnnotationInit, InspectAnnotationLabel, InspectAnnotationSeccomp, InspectAnnotationApparmor, InspectResponseTrue, InspectResponseFalse: + return true + + default: + return false + } +} diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go index 677b39218..ba73e4196 100644 --- a/libpod/define/container_inspect.go +++ b/libpod/define/container_inspect.go @@ -68,6 +68,8 @@ type InspectContainerConfig struct { Timeout uint `json:"Timeout"` // StopTimeout is time before container is stopped when calling stop StopTimeout uint `json:"StopTimeout"` + // Passwd determines whether or not podman can add entries to /etc/passwd and /etc/group + Passwd *bool `json:"Passwd,omitempty"` } // InspectRestartPolicy holds information about the container's restart policy. @@ -189,22 +191,28 @@ type InspectMount struct { // Docker, but here we see more fields that are unused (nonsensical in the // context of Libpod). type InspectContainerState struct { - OciVersion string `json:"OciVersion"` - Status string `json:"Status"` - Running bool `json:"Running"` - Paused bool `json:"Paused"` - Restarting bool `json:"Restarting"` // TODO - OOMKilled bool `json:"OOMKilled"` - Dead bool `json:"Dead"` - Pid int `json:"Pid"` - ConmonPid int `json:"ConmonPid,omitempty"` - ExitCode int32 `json:"ExitCode"` - Error string `json:"Error"` // TODO - StartedAt time.Time `json:"StartedAt"` - FinishedAt time.Time `json:"FinishedAt"` - Health HealthCheckResults `json:"Health,omitempty"` - Checkpointed bool `json:"Checkpointed,omitempty"` - CgroupPath string `json:"CgroupPath,omitempty"` + OciVersion string `json:"OciVersion"` + Status string `json:"Status"` + Running bool `json:"Running"` + Paused bool `json:"Paused"` + Restarting bool `json:"Restarting"` // TODO + OOMKilled bool `json:"OOMKilled"` + Dead bool `json:"Dead"` + Pid int `json:"Pid"` + ConmonPid int `json:"ConmonPid,omitempty"` + ExitCode int32 `json:"ExitCode"` + Error string `json:"Error"` // TODO + StartedAt time.Time `json:"StartedAt"` + FinishedAt time.Time `json:"FinishedAt"` + Health HealthCheckResults `json:"Health,omitempty"` + Checkpointed bool `json:"Checkpointed,omitempty"` + CgroupPath string `json:"CgroupPath,omitempty"` + CheckpointedAt time.Time `json:"CheckpointedAt,omitempty"` + RestoredAt time.Time `json:"RestoredAt,omitempty"` + CheckpointLog string `json:"CheckpointLog,omitempty"` + CheckpointPath string `json:"CheckpointPath,omitempty"` + RestoreLog string `json:"RestoreLog,omitempty"` + Restored bool `json:"Restored,omitempty"` } // Healthcheck returns the HealthCheckResults. This is used for old podman compat diff --git a/libpod/define/pod_inspect.go b/libpod/define/pod_inspect.go index 97e7ffdfb..e7adc8700 100644 --- a/libpod/define/pod_inspect.go +++ b/libpod/define/pod_inspect.go @@ -65,6 +65,8 @@ type InspectPodData struct { BlkioDeviceReadBps []InspectBlkioThrottleDevice `json:"device_read_bps,omitempty"` // VolumesFrom contains the containers that the pod inherits mounts from VolumesFrom []string `json:"volumes_from,omitempty"` + // SecurityOpt contains the specified security labels and related SELinux information + SecurityOpts []string `json:"security_opt,omitempty"` } // InspectPodInfraConfig contains the configuration of the pod's infra diff --git a/libpod/healthcheck_linux.go b/libpod/healthcheck_linux.go index e08214809..a1f3e8491 100644 --- a/libpod/healthcheck_linux.go +++ b/libpod/healthcheck_linux.go @@ -1,6 +1,7 @@ package libpod import ( + "context" "fmt" "os" "os/exec" @@ -59,9 +60,9 @@ func (c *Container) startTimer() error { return err } -// removeTimer removes the systemd timer and unit files +// removeTransientFiles removes the systemd timer and unit files // for the container -func (c *Container) removeTimer() error { +func (c *Container) removeTransientFiles(ctx context.Context) error { if c.disableHealthCheckSystemd() { return nil } @@ -71,12 +72,29 @@ func (c *Container) removeTimer() error { } defer conn.Close() timerFile := fmt.Sprintf("%s.timer", c.ID()) - _, err = conn.StopUnit(timerFile, "fail", nil) + serviceFile := fmt.Sprintf("%s.service", c.ID()) - // We want to ignore errors where the timer unit has already been removed. The error - // return is generic so we have to check against the string in the error - if err != nil && strings.HasSuffix(err.Error(), ".timer not loaded.") { - return nil + // If the service has failed (the healthcheck has failed), then + // the .service file is not removed on stopping the unit file. If + // we check the properties of the service, it will automatically + // reset the state. But checking the state takes msecs vs usecs to + // blindly call reset. + if err := conn.ResetFailedUnitContext(ctx, serviceFile); err != nil { + logrus.Debugf("failed to reset unit file: %q", err) + } + + // We want to ignore errors where the timer unit and/or service unit has already + // been removed. The error return is generic so we have to check against the + // string in the error + if _, err = conn.StopUnitContext(ctx, serviceFile, "fail", nil); err != nil { + if !strings.HasSuffix(err.Error(), ".service not loaded.") { + return errors.Wrapf(err, "unable to remove service file") + } + } + if _, err = conn.StopUnitContext(ctx, timerFile, "fail", nil); err != nil { + if strings.HasSuffix(err.Error(), ".timer not loaded.") { + return nil + } } return err } diff --git a/libpod/kube.go b/libpod/kube.go index 4e61b5377..d667616d0 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -747,7 +747,7 @@ func libpodEnvVarsToKubeEnvVars(envs []string, imageEnvs []string) ([]v1.EnvVar, defaultEnv := env.DefaultEnvVariables() envVars := make([]v1.EnvVar, 0, len(envs)) imageMap := make(map[string]string, len(imageEnvs)) - for _, ie := range envs { + for _, ie := range imageEnvs { split := strings.SplitN(ie, "=", 2) imageMap[split[0]] = split[1] } diff --git a/libpod/network/internal/util/util.go b/libpod/network/internal/util/util.go index bf9d70aba..d9b9a8dc0 100644 --- a/libpod/network/internal/util/util.go +++ b/libpod/network/internal/util/util.go @@ -78,7 +78,7 @@ func GetUsedSubnets(n NetUtil) ([]*net.IPNet, error) { return append(subnets, liveSubnets...), nil } -// GetFreeIPv6NetworkSubnet returns a unused ipv4 subnet +// GetFreeIPv4NetworkSubnet returns a unused ipv4 subnet func GetFreeIPv4NetworkSubnet(usedNetworks []*net.IPNet) (*types.Subnet, error) { // the default podman network is 10.88.0.0/16 // start locking for free /24 networks diff --git a/libpod/network/types/network.go b/libpod/network/types/network.go index 105641e70..37fa11461 100644 --- a/libpod/network/types/network.go +++ b/libpod/network/types/network.go @@ -204,7 +204,8 @@ type PerNetworkOptions struct { Aliases []string `json:"aliases,omitempty"` // StaticMac for this container. Optional. StaticMAC HardwareAddr `json:"static_mac,omitempty"` - // InterfaceName for this container. Required. + // InterfaceName for this container. Required in the backend. + // Optional in the frontend. Will be filled with ethX (where X is a integer) when empty. InterfaceName string `json:"interface_name"` } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 5fb97dd73..a931774f8 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -53,41 +53,6 @@ const ( persistentCNIDir = "/var/lib/cni" ) -// GetAllNetworkAliases returns all configured aliases for this container. -// It also adds the container short ID as alias to match docker. -func (c *Container) GetAllNetworkAliases() (map[string][]string, error) { - allAliases, err := c.runtime.state.GetAllNetworkAliases(c) - if err != nil { - return nil, err - } - - // get the all attached networks, we cannot use GetAllNetworkAliases() - // since it returns nil if there are no aliases - nets, _, err := c.networks() - if err != nil { - return nil, err - } - - // add container short ID as alias to match docker - for _, net := range nets { - allAliases[net] = append(allAliases[net], c.config.ID[:12]) - } - return allAliases, nil -} - -// GetNetworkAliases returns configured aliases for this network. -// It also adds the container short ID as alias to match docker. -func (c *Container) GetNetworkAliases(netName string) ([]string, error) { - aliases, err := c.runtime.state.GetNetworkAliases(c, netName) - if err != nil { - return nil, err - } - - // add container short ID as alias to match docker - aliases = append(aliases, c.config.ID[:12]) - return aliases, nil -} - // convertPortMappings will remove the HostIP part from the ports when running inside podman machine. // This is need because a HostIP of 127.0.0.1 would now allow the gvproxy forwarder to reach to open ports. // For machine the HostIP must only be used by gvproxy and never in the VM. @@ -104,53 +69,20 @@ func (c *Container) convertPortMappings() []types.PortMapping { return newPorts } -func (c *Container) getNetworkOptions() (types.NetworkOptions, error) { +func (c *Container) getNetworkOptions(networkOpts map[string]types.PerNetworkOptions) (types.NetworkOptions, error) { opts := types.NetworkOptions{ ContainerID: c.config.ID, ContainerName: getCNIPodName(c), } opts.PortMappings = c.convertPortMappings() - networks, _, err := c.networks() - if err != nil { - return opts, err - } - aliases, err := c.GetAllNetworkAliases() - if err != nil { - return opts, err - } // If the container requested special network options use this instead of the config. // This is the case for container restore or network reload. if c.perNetworkOpts != nil { opts.Networks = c.perNetworkOpts - return opts, nil - } - - // Update container map of interface descriptions - if err := c.setupNetworkDescriptions(networks); err != nil { - return opts, err - } - - nets := make(map[string]types.PerNetworkOptions, len(networks)) - for i, network := range networks { - eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(network) - if !exists { - return opts, errors.Errorf("no network interface name for container %s on network %s", c.config.ID, network) - } - netOpts := types.PerNetworkOptions{ - InterfaceName: eth, - Aliases: aliases[network], - } - // only set the static ip/mac on the first network - if i == 0 { - if c.config.StaticIP != nil { - netOpts.StaticIPs = []net.IP{c.config.StaticIP} - } - netOpts.StaticMAC = c.config.StaticMAC - } - nets[network] = netOpts + } else { + opts.Networks = networkOpts } - opts.Networks = nets return opts, nil } @@ -697,7 +629,7 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (status map[str if ctr.config.NetMode.IsSlirp4netns() { return nil, r.setupSlirp4netns(ctr, ctrNS) } - networks, _, err := ctr.networks() + networks, err := ctr.networks() if err != nil { return nil, err } @@ -707,7 +639,7 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (status map[str return nil, nil } - netOpts, err := ctr.getNetworkOptions() + netOpts, err := ctr.getNetworkOptions(networks) if err != nil { return nil, err } @@ -862,13 +794,13 @@ func (r *Runtime) teardownCNI(ctr *Container) error { logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID()) - networks, _, err := ctr.networks() + networks, err := ctr.networks() if err != nil { return err } if !ctr.config.NetMode.IsSlirp4netns() && len(networks) > 0 { - netOpts, err := ctr.getNetworkOptions() + netOpts, err := ctr.getNetworkOptions(networks) if err != nil { return err } @@ -960,22 +892,17 @@ func (r *Runtime) reloadContainerNetwork(ctr *Container) (map[string]types.Statu } } - aliases, err := ctr.GetAllNetworkAliases() + networkOpts, err := ctr.networks() if err != nil { return nil, err } // Set the same network settings as before.. netStatus := ctr.getNetworkStatus() - netOpts := make(map[string]types.PerNetworkOptions, len(netStatus)) - for network, status := range netStatus { - perNetOpts := types.PerNetworkOptions{} - for name, netInt := range status.Interfaces { - perNetOpts = types.PerNetworkOptions{ - InterfaceName: name, - Aliases: aliases[network], - StaticMAC: netInt.MacAddress, - } + for network, perNetOpts := range networkOpts { + for name, netInt := range netStatus[network].Interfaces { + perNetOpts.InterfaceName = name + perNetOpts.StaticMAC = netInt.MacAddress for _, netAddress := range netInt.Subnets { perNetOpts.StaticIPs = append(perNetOpts.StaticIPs, netAddress.IPNet.IP) } @@ -983,16 +910,9 @@ func (r *Runtime) reloadContainerNetwork(ctr *Container) (map[string]types.Statu // For now just use the first interface to get the ips this should be good enough for most cases. break } - if perNetOpts.InterfaceName == "" { - eth, exists := ctr.state.NetInterfaceDescriptions.getInterfaceByName(network) - if !exists { - return nil, errors.Errorf("no network interface name for container %s on network %s", ctr.config.ID, network) - } - perNetOpts.InterfaceName = eth - } - netOpts[network] = perNetOpts + networkOpts[network] = perNetOpts } - ctr.perNetworkOpts = netOpts + ctr.perNetworkOpts = networkOpts return r.configureNetNS(ctr, ctr.state.NetNS) } @@ -1049,7 +969,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e settings := new(define.InspectNetworkSettings) settings.Ports = makeInspectPortBindings(c.config.PortMappings, c.config.ExposedPorts) - networks, isDefault, err := c.networks() + networks, err := c.networks() if err != nil { return nil, err } @@ -1060,14 +980,10 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e // the container joined. if len(networks) > 0 { settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(networks)) - for _, net := range networks { + for net, opts := range networks { cniNet := new(define.InspectAdditionalNetwork) cniNet.NetworkID = net - aliases, err := c.GetNetworkAliases(net) - if err != nil { - return nil, err - } - cniNet.Aliases = aliases + cniNet.Aliases = opts.Aliases settings.Networks[net] = cniNet } } @@ -1092,7 +1008,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e settings.Networks = make(map[string]*define.InspectAdditionalNetwork) - for _, name := range networks { + for name, opts := range networks { result := netStatus[name] addedNet := new(define.InspectAdditionalNetwork) addedNet.NetworkID = name @@ -1101,19 +1017,17 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e if err != nil { return nil, err } - - aliases, err := c.GetNetworkAliases(name) - if err != nil { - return nil, err - } - addedNet.Aliases = aliases + addedNet.Aliases = opts.Aliases addedNet.InspectBasicNetworkConfig = basicConfig settings.Networks[name] = addedNet } - if !isDefault { + // if not only the default network is connected we can return here + // otherwise we have to populate the InspectBasicNetworkConfig settings + _, isDefaultNet := networks[c.runtime.config.Network.DefaultNetwork] + if !(len(networks) == 1 && isDefaultNet) { return settings, nil } } @@ -1135,29 +1049,6 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e return settings, nil } -// setupNetworkDescriptions adds networks and eth values to the container's -// network descriptions -func (c *Container) setupNetworkDescriptions(networks []string) error { - // if the map is nil and we have networks - if c.state.NetInterfaceDescriptions == nil && len(networks) > 0 { - c.state.NetInterfaceDescriptions = make(ContainerNetworkDescriptions) - } - origLen := len(c.state.NetInterfaceDescriptions) - for _, n := range networks { - // if the network is not in the map, add it - if _, exists := c.state.NetInterfaceDescriptions[n]; !exists { - c.state.NetInterfaceDescriptions.add(n) - } - } - // if the map changed, we need to save the container state - if origLen != len(c.state.NetInterfaceDescriptions) { - if err := c.save(); err != nil { - return err - } - } - return nil -} - // resultToBasicNetworkConfig produces an InspectBasicNetworkConfig from a CNI // result func resultToBasicNetworkConfig(result types.StatusBlock) (define.InspectBasicNetworkConfig, error) { @@ -1213,7 +1104,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro c.lock.Lock() defer c.lock.Unlock() - networks, err := c.networksByNameIndex() + networks, err := c.networks() if err != nil { return err } @@ -1254,14 +1145,8 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro ContainerName: getCNIPodName(c), } opts.PortMappings = c.convertPortMappings() - eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName) - if !exists { - return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName) - } opts.Networks = map[string]types.PerNetworkOptions{ - netName: { - InterfaceName: eth, - }, + netName: networks[netName], } if err := c.runtime.teardownNetwork(c.state.NetNS.Path(), opts); err != nil { @@ -1285,7 +1170,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro } // ConnectNetwork connects a container to a given network -func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) error { +func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNetworkOptions) error { // only the bridge mode supports cni networks if err := isBridgeNetMode(c.config.NetMode); err != nil { return err @@ -1294,7 +1179,7 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e c.lock.Lock() defer c.lock.Unlock() - networks, err := c.networksByNameIndex() + networks, err := c.networks() if err != nil { return err } @@ -1317,11 +1202,20 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e if err != nil { return err } - if !network.DNSEnabled && len(aliases) > 0 { + if !network.DNSEnabled && len(netOpts.Aliases) > 0 { return errors.Wrapf(define.ErrInvalidArg, "cannot set network aliases for network %q because dns is disabled", netName) } + // always add the short id as alias for docker compat + netOpts.Aliases = append(netOpts.Aliases, c.config.ID[:12]) - if err := c.runtime.state.NetworkConnect(c, netName, aliases); err != nil { + if netOpts.InterfaceName == "" { + netOpts.InterfaceName = getFreeInterfaceName(networks) + if netOpts.InterfaceName == "" { + return errors.New("could not find free network interface name") + } + } + + if err := c.runtime.state.NetworkConnect(c, netName, netOpts); err != nil { return err } c.newNetworkEvent(events.NetworkConnect, netName) @@ -1332,30 +1226,13 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e return errors.Wrapf(define.ErrNoNetwork, "unable to connect %s to %s", nameOrID, netName) } - ctrNetworks, _, err := c.networks() - if err != nil { - return err - } - // Update network descriptions - if err := c.setupNetworkDescriptions(ctrNetworks); err != nil { - return err - } - opts := types.NetworkOptions{ ContainerID: c.config.ID, ContainerName: getCNIPodName(c), } opts.PortMappings = c.convertPortMappings() - eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName) - if !exists { - return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName) - } - aliases = append(aliases, c.config.ID[:12]) opts.Networks = map[string]types.PerNetworkOptions{ - netName: { - Aliases: aliases, - InterfaceName: eth, - }, + netName: netOpts, } results, err := c.runtime.setUpNetwork(c.state.NetNS.Path(), opts) @@ -1385,6 +1262,22 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e return nil } +// get a free interface name for a new network +// return an empty string if no free name was found +func getFreeInterfaceName(networks map[string]types.PerNetworkOptions) string { + ifNames := make([]string, 0, len(networks)) + for _, opts := range networks { + ifNames = append(ifNames, opts.InterfaceName) + } + for i := 0; i < 100000; i++ { + ifName := fmt.Sprintf("eth%d", i) + if !util.StringInSlice(ifName, ifNames) { + return ifName + } + } + return "" +} + // DisconnectContainerFromNetwork removes a container from its CNI network func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error { ctr, err := r.LookupContainer(nameOrID) @@ -1395,12 +1288,12 @@ func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force } // ConnectContainerToNetwork connects a container to a CNI network -func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, aliases []string) error { +func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, netOpts types.PerNetworkOptions) error { ctr, err := r.LookupContainer(nameOrID) if err != nil { return err } - return ctr.NetworkConnect(nameOrID, netName, aliases) + return ctr.NetworkConnect(nameOrID, netName, netOpts) } // normalizeNetworkName takes a network name, a partial or a full network ID and returns the network name. diff --git a/libpod/options.go b/libpod/options.go index 8f2d5cb15..6edb9972b 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1058,7 +1058,7 @@ func WithDependencyCtrs(ctrs []*Container) CtrCreateOption { // namespace with a minimal configuration. // An optional array of port mappings can be provided. // Conflicts with WithNetNSFrom(). -func WithNetNS(portMappings []nettypes.PortMapping, exposedPorts map[uint16][]string, postConfigureNetNS bool, netmode string, networks []string) CtrCreateOption { +func WithNetNS(portMappings []nettypes.PortMapping, exposedPorts map[uint16][]string, postConfigureNetNS bool, netmode string, networks map[string]nettypes.PerNetworkOptions) CtrCreateOption { return func(ctr *Container) error { if ctr.valid { return define.ErrCtrFinalized @@ -1070,24 +1070,10 @@ func WithNetNS(portMappings []nettypes.PortMapping, exposedPorts map[uint16][]st ctr.config.PortMappings = portMappings ctr.config.ExposedPorts = exposedPorts - ctr.config.Networks = networks - - return nil - } -} - -// WithStaticIP indicates that the container should request a static IP from -// the CNI plugins. -// It cannot be set unless WithNetNS has already been passed. -// Further, it cannot be set if additional CNI networks to join have been -// specified. -func WithStaticIP(ip net.IP) CtrCreateOption { - return func(ctr *Container) error { - if ctr.valid { - return define.ErrCtrFinalized + if !ctr.config.NetMode.IsBridge() && len(networks) > 0 { + return errors.New("cannot use networks when network mode is not bridge") } - - ctr.config.StaticIP = ip + ctr.config.Networks = networks return nil } @@ -1106,23 +1092,6 @@ func WithNetworkOptions(options map[string][]string) CtrCreateOption { } } -// WithStaticMAC indicates that the container should request a static MAC from -// the CNI plugins. -// It cannot be set unless WithNetNS has already been passed. -// Further, it cannot be set if additional CNI networks to join have been -// specified. -func WithStaticMAC(mac nettypes.HardwareAddr) CtrCreateOption { - return func(ctr *Container) error { - if ctr.valid { - return define.ErrCtrFinalized - } - - ctr.config.StaticMAC = mac - - return nil - } -} - // WithLogDriver sets the log driver for the container func WithLogDriver(driver string) CtrCreateOption { return func(ctr *Container) error { @@ -1572,20 +1541,6 @@ func WithCreateWorkingDir() CtrCreateOption { } } -// WithNetworkAliases sets network aliases for the container. -// Accepts a map of network name to aliases. -func WithNetworkAliases(aliases map[string][]string) CtrCreateOption { - return func(ctr *Container) error { - if ctr.valid { - return define.ErrCtrFinalized - } - - ctr.config.NetworkAliases = aliases - - return nil - } -} - // Volume Creation Options // WithVolumeName sets the name of the volume. @@ -1813,6 +1768,17 @@ func WithPidFile(pidFile string) CtrCreateOption { } } +// WithHostUsers indicates host users to add to /etc/passwd +func WithHostUsers(hostUsers []string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + ctr.config.HostUsers = hostUsers + return nil + } +} + // WithInitCtrType indicates the container is a initcontainer func WithInitCtrType(containerType string) CtrCreateOption { return func(ctr *Container) error { @@ -1839,6 +1805,36 @@ func WithHostDevice(dev []specs.LinuxDevice) CtrCreateOption { } } +// WithSelectedPasswordManagement makes it so that the container either does or does not setup /etc/passwd or /etc/group +func WithSelectedPasswordManagement(passwd *bool) CtrCreateOption { + return func(c *Container) error { + if c.valid { + return define.ErrCtrFinalized + } + c.config.Passwd = passwd + return nil + } +} + +// WithInfraConfig allows for inheritance of compatible config entities from the infra container +func WithInfraConfig(compatibleOptions InfraInherit) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + compatMarshal, err := json.Marshal(compatibleOptions) + if err != nil { + return errors.New("Could not marshal compatible options") + } + + err = json.Unmarshal(compatMarshal, ctr.config) + if err != nil { + return errors.New("Could not unmarshal compatible options into contrainer config") + } + return nil + } +} + // Pod Creation Options // WithPodCreateCommand adds the full command plus arguments of the current diff --git a/libpod/pod_api.go b/libpod/pod_api.go index 80ecb690a..526e0c28b 100644 --- a/libpod/pod_api.go +++ b/libpod/pod_api.go @@ -586,6 +586,7 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) { var inspectMounts []define.InspectMount var devices []define.InspectDevice var deviceLimits []define.InspectBlkioThrottleDevice + var infraSecurity []string if p.state.InfraContainerID != "" { infra, err := p.runtime.GetContainer(p.state.InfraContainerID) if err != nil { @@ -603,6 +604,7 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) { infraConfig.UserNS = p.UserNSMode() namedVolumes, mounts := infra.sortUserVolumes(infra.config.Spec) inspectMounts, err = infra.GetInspectMounts(namedVolumes, infra.config.ImageVolumes, mounts) + infraSecurity = infra.GetSecurityOptions() if err != nil { return nil, err } @@ -637,9 +639,17 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) { infraConfig.HostAdd = make([]string, 0, len(infra.config.HostAdd)) infraConfig.HostAdd = append(infraConfig.HostAdd, infra.config.HostAdd...) } - if len(infra.config.ContainerNetworkConfig.Networks) > 0 { - infraConfig.Networks = make([]string, 0, len(infra.config.ContainerNetworkConfig.Networks)) - infraConfig.Networks = append(infraConfig.Networks, infra.config.ContainerNetworkConfig.Networks...) + + networks, err := infra.networks() + if err != nil { + return nil, err + } + netNames := make([]string, 0, len(networks)) + for name := range networks { + netNames = append(netNames, name) + } + if len(netNames) > 0 { + infraConfig.Networks = netNames } infraConfig.NetworkOptions = infra.config.ContainerNetworkConfig.NetworkOptions infraConfig.PortBindings = makeInspectPortBindings(infra.config.ContainerNetworkConfig.PortMappings, nil) @@ -670,6 +680,7 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) { Devices: devices, BlkioDeviceReadBps: deviceLimits, VolumesFrom: p.VolumesFrom(), + SecurityOpts: infraSecurity, } return &inspectData, nil diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 6c46eb747..59a1fd153 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -2,6 +2,7 @@ package libpod import ( "context" + "fmt" "os" "path" "path/filepath" @@ -13,10 +14,12 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/events" + "github.com/containers/podman/v3/libpod/network/types" "github.com/containers/podman/v3/libpod/shutdown" "github.com/containers/podman/v3/pkg/domain/entities/reports" "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/pkg/specgen" + "github.com/containers/podman/v3/pkg/util" "github.com/containers/storage" "github.com/containers/storage/pkg/stringid" "github.com/docker/go-units" @@ -230,39 +233,56 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Container, retErr error) { // normalize the networks to names - // ocicni only knows about cni names so we have to make + // the db backend only knows about network names so we have to make // sure we do not use ids internally if len(ctr.config.Networks) > 0 { - netNames := make([]string, 0, len(ctr.config.Networks)) - for _, nameOrID := range ctr.config.Networks { - netName, err := r.normalizeNetworkName(nameOrID) - if err != nil { - return nil, err + normalizeNetworks := make(map[string]types.PerNetworkOptions, len(ctr.config.Networks)) + // first get the already used interface names so we do not conflict + usedIfNames := make([]string, 0, len(ctr.config.Networks)) + for _, opts := range ctr.config.Networks { + if opts.InterfaceName != "" { + // check that no name is assigned to more than network + if util.StringInSlice(opts.InterfaceName, usedIfNames) { + return nil, errors.Errorf("network interface name %q is already assigned to another network", opts.InterfaceName) + } + usedIfNames = append(usedIfNames, opts.InterfaceName) } - netNames = append(netNames, netName) } - ctr.config.Networks = netNames - } - - // https://github.com/containers/podman/issues/11285 - // normalize the networks aliases to use network names and never ids - if len(ctr.config.NetworkAliases) > 0 { - netAliases := make(map[string][]string, len(ctr.config.NetworkAliases)) - for nameOrID, aliases := range ctr.config.NetworkAliases { + i := 0 + for nameOrID, opts := range ctr.config.Networks { netName, err := r.normalizeNetworkName(nameOrID) if err != nil { return nil, err } - network, err := r.network.NetworkInspect(netName) - if err != nil { - return nil, err + if len(opts.Aliases) > 0 { + network, err := r.network.NetworkInspect(netName) + if err != nil { + return nil, err + } + if !network.DNSEnabled { + return nil, errors.Wrapf(define.ErrInvalidArg, "cannot set network aliases for network %q because dns is disabled", netName) + } } - if !network.DNSEnabled { - return nil, errors.Wrapf(define.ErrInvalidArg, "cannot set network aliases for network %q because dns is disabled", netName) + // assign interface name if empty + if opts.InterfaceName == "" { + for i < 100000 { + ifName := fmt.Sprintf("eth%d", i) + if !util.StringInSlice(ifName, usedIfNames) { + opts.InterfaceName = ifName + usedIfNames = append(usedIfNames, ifName) + break + } + i++ + } + // if still empty we did not find a free name + if opts.InterfaceName == "" { + return nil, errors.New("failed to find free network interface name") + } } - netAliases[netName] = aliases + + normalizeNetworks[netName] = opts } - ctr.config.NetworkAliases = netAliases + ctr.config.Networks = normalizeNetworks } // Validate the container diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 52ac0d4d7..bf0fc4585 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -36,10 +36,21 @@ func (r *Runtime) RemoveContainersForImageCallback(ctx context.Context) libimage return err } for _, ctr := range ctrs { - if ctr.config.RootfsImageID == imageID { - var timeout *uint + if ctr.config.RootfsImageID != imageID { + continue + } + var timeout *uint + if ctr.config.IsInfra { + pod, err := r.state.Pod(ctr.config.Pod) + if err != nil { + return errors.Wrapf(err, "container %s is in pod %s, but pod cannot be retrieved", ctr.ID(), pod.ID()) + } + if err := r.removePod(ctx, pod, true, true, timeout); err != nil { + return errors.Wrapf(err, "removing image %s: container %s using image could not be removed", imageID, ctr.ID()) + } + } else { if err := r.removeContainer(ctx, ctr, true, false, false, timeout); err != nil { - return errors.Wrapf(err, "error removing image %s: container %s using image could not be removed", imageID, ctr.ID()) + return errors.Wrapf(err, "removing image %s: container %s using image could not be removed", imageID, ctr.ID()) } } } diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go index ed3cc971c..d4d9a4438 100644 --- a/libpod/runtime_volume_linux.go +++ b/libpod/runtime_volume_linux.go @@ -35,7 +35,7 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) volume := newVolume(r) for _, option := range options { if err := option(volume); err != nil { - return nil, errors.Wrapf(err, "error running volume create option") + return nil, errors.Wrapf(err, "running volume create option") } } @@ -50,7 +50,7 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) // Check if volume with given name exists. exists, err := r.state.HasVolume(volume.config.Name) if err != nil { - return nil, errors.Wrapf(err, "error checking if volume with name %s exists", volume.config.Name) + return nil, errors.Wrapf(err, "checking if volume with name %s exists", volume.config.Name) } if exists { return nil, errors.Wrapf(define.ErrVolumeExists, "volume with name %s already exists", volume.config.Name) @@ -67,9 +67,15 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) if volume.config.Driver == define.VolumeDriverLocal { logrus.Debugf("Validating options for local driver") // Validate options - for key := range volume.config.Options { - switch key { - case "device", "o", "type", "UID", "GID", "SIZE", "INODES": + for key, val := range volume.config.Options { + switch strings.ToLower(key) { + case "device": + if strings.ToLower(volume.config.Options["type"]) == "bind" { + if _, err := os.Stat(val); err != nil { + return nil, errors.Wrapf(err, "invalid volume option %s for driver 'local'", key) + } + } + case "o", "type", "uid", "gid", "size", "inodes": // Do nothing, valid keys default: return nil, errors.Wrapf(define.ErrInvalidArg, "invalid mount option %s for driver 'local'", key) @@ -92,17 +98,17 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) // Create the mountpoint of this volume volPathRoot := filepath.Join(r.config.Engine.VolumePath, volume.config.Name) if err := os.MkdirAll(volPathRoot, 0700); err != nil { - return nil, errors.Wrapf(err, "error creating volume directory %q", volPathRoot) + return nil, errors.Wrapf(err, "creating volume directory %q", volPathRoot) } if err := os.Chown(volPathRoot, volume.config.UID, volume.config.GID); err != nil { - return nil, errors.Wrapf(err, "error chowning volume directory %q to %d:%d", volPathRoot, volume.config.UID, volume.config.GID) + return nil, errors.Wrapf(err, "chowning volume directory %q to %d:%d", volPathRoot, volume.config.UID, volume.config.GID) } fullVolPath := filepath.Join(volPathRoot, "_data") if err := os.MkdirAll(fullVolPath, 0755); err != nil { - return nil, errors.Wrapf(err, "error creating volume directory %q", fullVolPath) + return nil, errors.Wrapf(err, "creating volume directory %q", fullVolPath) } if err := os.Chown(fullVolPath, volume.config.UID, volume.config.GID); err != nil { - return nil, errors.Wrapf(err, "error chowning volume directory %q to %d:%d", fullVolPath, volume.config.UID, volume.config.GID) + return nil, errors.Wrapf(err, "chowning volume directory %q to %d:%d", fullVolPath, volume.config.UID, volume.config.GID) } if err := LabelVolumePath(fullVolPath); err != nil { return nil, err @@ -132,7 +138,7 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) lock, err := r.lockManager.AllocateLock() if err != nil { - return nil, errors.Wrapf(err, "error allocating lock for new volume") + return nil, errors.Wrapf(err, "allocating lock for new volume") } volume.lock = lock volume.config.LockID = volume.lock.ID() @@ -149,7 +155,7 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) // Add the volume to state if err := r.state.AddVolume(volume); err != nil { - return nil, errors.Wrapf(err, "error adding volume to state") + return nil, errors.Wrapf(err, "adding volume to state") } defer volume.newVolumeEvent(events.Create) return volume, nil @@ -181,7 +187,7 @@ func makeVolumeInPluginIfNotExist(name string, options map[string]string, plugin createReq.Name = name createReq.Options = options if err := plugin.CreateVolume(createReq); err != nil { - return errors.Wrapf(err, "error creating volume %q in plugin %s", name, plugin.Name) + return errors.Wrapf(err, "creating volume %q in plugin %s", name, plugin.Name) } } @@ -225,13 +231,13 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeo continue } - return errors.Wrapf(err, "error removing container %s that depends on volume %s", dep, v.Name()) + return errors.Wrapf(err, "removing container %s that depends on volume %s", dep, v.Name()) } logrus.Debugf("Removing container %s (depends on volume %q)", ctr.ID(), v.Name()) if err := r.removeContainer(ctx, ctr, force, false, false, timeout); err != nil { - return errors.Wrapf(err, "error removing container %s that depends on volume %s", ctr.ID(), v.Name()) + return errors.Wrapf(err, "removing container %s that depends on volume %s", ctr.ID(), v.Name()) } } } @@ -244,7 +250,7 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeo // them. logrus.Errorf("Unmounting volume %s: %v", v.Name(), err) } else { - return errors.Wrapf(err, "error unmounting volume %s", v.Name()) + return errors.Wrapf(err, "unmounting volume %s", v.Name()) } } @@ -288,13 +294,13 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeo if removalErr != nil { logrus.Errorf("Removing volume %s from plugin %s: %v", v.Name(), v.Driver(), removalErr) } - return errors.Wrapf(err, "error removing volume %s", v.Name()) + return errors.Wrapf(err, "removing volume %s", v.Name()) } // Free the volume's lock if err := v.lock.Free(); err != nil { if removalErr == nil { - removalErr = errors.Wrapf(err, "error freeing lock for volume %s", v.Name()) + removalErr = errors.Wrapf(err, "freeing lock for volume %s", v.Name()) } else { logrus.Errorf("Freeing lock for volume %q: %v", v.Name(), err) } @@ -304,7 +310,7 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeo // from /var/lib/containers/storage/volumes if err := v.teardownStorage(); err != nil { if removalErr == nil { - removalErr = errors.Wrapf(err, "error cleaning up volume storage for %q", v.Name()) + removalErr = errors.Wrapf(err, "cleaning up volume storage for %q", v.Name()) } else { logrus.Errorf("Cleaning up volume storage for volume %q: %v", v.Name(), err) } diff --git a/libpod/state.go b/libpod/state.go index 4b711bae9..21525107f 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -1,5 +1,7 @@ package libpod +import "github.com/containers/podman/v3/libpod/network/types" + // State is a storage backend for libpod's current state. // A State is only initialized once per instance of libpod. // As such, initialization methods for State implementations may safely assume @@ -99,14 +101,9 @@ type State interface { AllContainers() ([]*Container, error) // Get networks the container is currently connected to. - GetNetworks(ctr *Container) ([]string, error) - // Get network aliases for the given container in the given network. - GetNetworkAliases(ctr *Container, network string) ([]string, error) - // Get all network aliases for the given container. - GetAllNetworkAliases(ctr *Container) (map[string][]string, error) - // Add the container to the given network, adding the given aliases - // (if present). - NetworkConnect(ctr *Container, network string, aliases []string) error + GetNetworks(ctr *Container) (map[string]types.PerNetworkOptions, error) + // Add the container to the given network with the given options + NetworkConnect(ctr *Container, network string, opts types.PerNetworkOptions) error // Remove the container from the given network, removing all aliases for // the container in that network in the process. NetworkDisconnect(ctr *Container, network string) error diff --git a/libpod/state_test.go b/libpod/state_test.go index 5c3b0d7f7..b0793127d 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -1299,21 +1299,9 @@ func TestAddContainerEmptyNetworkNameErrors(t *testing.T) { testCtr, err := getTestCtr1(manager) assert.NoError(t, err) - testCtr.config.Networks = []string{""} - - err = state.AddContainer(testCtr) - assert.Error(t, err) - }) -} - -func TestAddContainerNetworkAliasesButNoMatchingNetwork(t *testing.T) { - runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { - testCtr, err := getTestCtr1(manager) - assert.NoError(t, err) - - testCtr.config.Networks = []string{"test1"} - testCtr.config.NetworkAliases = make(map[string][]string) - testCtr.config.NetworkAliases["test2"] = []string{"alias1"} + testCtr.config.Networks = map[string]types.PerNetworkOptions{ + "": {}, + } err = state.AddContainer(testCtr) assert.Error(t, err) diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go index f69f1c044..f9e1ea87d 100644 --- a/libpod/volume_internal.go +++ b/libpod/volume_internal.go @@ -81,7 +81,7 @@ func (v *Volume) save() error { func (v *Volume) refresh() error { lock, err := v.runtime.lockManager.AllocateAndRetrieveLock(v.config.LockID) if err != nil { - return errors.Wrapf(err, "error acquiring lock %d for volume %s", v.config.LockID, v.Name()) + return errors.Wrapf(err, "acquiring lock %d for volume %s", v.config.LockID, v.Name()) } v.lock = lock diff --git a/libpod/volume_internal_linux.go b/libpod/volume_internal_linux.go index 45cd22385..abd31df0f 100644 --- a/libpod/volume_internal_linux.go +++ b/libpod/volume_internal_linux.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/pkg/rootless" pluginapi "github.com/docker/go-plugins-helpers/volume" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -32,13 +31,6 @@ func (v *Volume) mount() error { return nil } - // We cannot mount 'local' volumes as rootless. - if !v.UsesVolumeDriver() && rootless.IsRootless() { - // This check should only be applied to 'local' driver - // so Volume Drivers must be excluded - return errors.Wrapf(define.ErrRootless, "cannot mount volumes without root privileges") - } - // Update the volume from the DB to get an accurate mount counter. if err := v.update(); err != nil { return err @@ -90,22 +82,27 @@ func (v *Volume) mount() error { // TODO: might want to cache this path in the runtime? mountPath, err := exec.LookPath("mount") if err != nil { - return errors.Wrapf(err, "error locating 'mount' binary") + return errors.Wrapf(err, "locating 'mount' binary") } mountArgs := []string{} if volOptions != "" { mountArgs = append(mountArgs, "-o", volOptions) } - if volType != "" { + switch volType { + case "": + case "bind": + mountArgs = append(mountArgs, "-o", volType) + default: mountArgs = append(mountArgs, "-t", volType) } + mountArgs = append(mountArgs, volDevice, v.config.MountPoint) mountCmd := exec.Command(mountPath, mountArgs...) logrus.Debugf("Running mount command: %s %s", mountPath, strings.Join(mountArgs, " ")) if output, err := mountCmd.CombinedOutput(); err != nil { logrus.Debugf("Mount %v failed with %v", mountCmd, err) - return errors.Wrapf(errors.Errorf(string(output)), "error mounting volume %s", v.Name()) + return errors.Errorf(string(output)) } logrus.Debugf("Mounted volume %s", v.Name()) @@ -139,20 +136,6 @@ func (v *Volume) unmount(force bool) error { return nil } - // We cannot unmount 'local' volumes as rootless. - if !v.UsesVolumeDriver() && rootless.IsRootless() { - // If force is set, just clear the counter and bail without - // error, so we can remove volumes from the state if they are in - // an awkward configuration. - if force { - logrus.Errorf("Volume %s is mounted despite being rootless - state is not sane", v.Name()) - v.state.MountCount = 0 - return v.save() - } - - return errors.Wrapf(define.ErrRootless, "cannot mount or unmount volumes without root privileges") - } - if !force { v.state.MountCount-- } else { @@ -184,7 +167,7 @@ func (v *Volume) unmount(force bool) error { // Ignore EINVAL - the mount no longer exists. return nil } - return errors.Wrapf(err, "error unmounting volume %s", v.Name()) + return errors.Wrapf(err, "unmounting volume %s", v.Name()) } logrus.Debugf("Unmounted volume %s", v.Name()) } |