diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/container.go | 5 | ||||
-rw-r--r-- | libpod/container_inspect.go | 6 | ||||
-rw-r--r-- | libpod/container_internal.go | 2 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 26 | ||||
-rw-r--r-- | libpod/events/config.go | 2 | ||||
-rw-r--r-- | libpod/image/image.go | 25 | ||||
-rw-r--r-- | libpod/info.go | 1 | ||||
-rw-r--r-- | libpod/network/create.go | 52 | ||||
-rw-r--r-- | libpod/network/files.go | 48 | ||||
-rw-r--r-- | libpod/network/netconflist.go | 92 | ||||
-rw-r--r-- | libpod/network/network.go | 11 | ||||
-rw-r--r-- | libpod/networking_linux.go | 119 | ||||
-rw-r--r-- | libpod/oci_conmon_exec_linux.go | 2 | ||||
-rw-r--r-- | libpod/oci_conmon_linux.go | 8 | ||||
-rw-r--r-- | libpod/options.go | 13 | ||||
-rw-r--r-- | libpod/plugin/volume_api.go | 454 | ||||
-rw-r--r-- | libpod/reset.go | 2 | ||||
-rw-r--r-- | libpod/runtime.go | 15 | ||||
-rw-r--r-- | libpod/runtime_cstorage.go | 6 | ||||
-rw-r--r-- | libpod/runtime_img.go | 52 | ||||
-rw-r--r-- | libpod/runtime_migrate.go | 6 | ||||
-rw-r--r-- | libpod/runtime_migrate_unsupported.go | 2 | ||||
-rw-r--r-- | libpod/runtime_pod_infra_linux.go | 67 | ||||
-rw-r--r-- | libpod/runtime_pod_linux.go | 4 |
24 files changed, 859 insertions, 161 deletions
diff --git a/libpod/container.go b/libpod/container.go index e954d84eb..4e0687318 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -267,6 +267,11 @@ func (c *Container) Config() *ContainerConfig { return returnConfig } +// Runtime returns the container's Runtime. +func (c *Container) Runtime() *Runtime { + return c.runtime +} + // Spec returns the container's OCI runtime spec // The spec returned is the one used to create the container. The running // spec may differ slightly as mounts are added based on the image diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index f78d74ef7..2ce3e8e68 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -488,7 +488,7 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named // Weight? For now, ignore anything // without Weight set. if dev.Weight == nil { - logrus.Warnf("Ignoring weight device %s as it lacks a weight", key) + logrus.Infof("Ignoring weight device %s as it lacks a weight", key) continue } if deviceNodes == nil { @@ -500,7 +500,7 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named } path, ok := deviceNodes[key] if !ok { - logrus.Warnf("Could not locate weight device %s in system devices", key) + logrus.Infof("Could not locate weight device %s in system devices", key) continue } weightDev := define.InspectBlkioWeightDevice{} @@ -522,7 +522,7 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named } path, ok := deviceNodes[key] if !ok { - logrus.Warnf("Could not locate throttle device %s in system devices", key) + logrus.Infof("Could not locate throttle device %s in system devices", key) continue } throttleDev := define.InspectBlkioThrottleDevice{} diff --git a/libpod/container_internal.go b/libpod/container_internal.go index b6a3244ea..c751d775d 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -525,7 +525,7 @@ func (c *Container) teardownStorage() error { // Potentially another tool using containers/storage already // removed it? if errors.Cause(err) == storage.ErrNotAContainer || errors.Cause(err) == storage.ErrContainerUnknown { - logrus.Warnf("Storage for container %s already removed", c.ID()) + logrus.Infof("Storage for container %s already removed", c.ID()) return nil } diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 56575c195..72eaeac8e 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -35,6 +35,7 @@ import ( "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/podman/v2/pkg/util" "github.com/containers/podman/v2/utils" + "github.com/containers/podman/v2/version" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/idtools" securejoin "github.com/cyphar/filepath-securejoin" @@ -352,7 +353,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { if !MountExists(g.Mounts(), dstPath) { g.AddMount(newMount) } else { - logrus.Warnf("User mount overriding libpod mount at %q", dstPath) + logrus.Infof("User mount overriding libpod mount at %q", dstPath) } } @@ -1423,11 +1424,26 @@ func (c *Container) makeBindMounts() error { } } - // Make .containerenv - // Empty file, so no need to recreate if it exists + // Make .containerenv if it does not exist if _, ok := c.state.BindMounts["/run/.containerenv"]; !ok { - // Empty string for now, but we may consider populating this later - containerenvPath, err := c.writeStringToRundir(".containerenv", "") + var containerenv string + isRootless := 0 + if rootless.IsRootless() { + isRootless = 1 + } + imageID, imageName := c.Image() + + if c.Privileged() { + // Populate the .containerenv with container information + containerenv = fmt.Sprintf(`engine="podman-%s" +name=%q +id=%q +image=%q +imageid=%q +rootless=%d +`, version.Version.String(), c.Name(), c.ID(), imageName, imageID, isRootless) + } + containerenvPath, err := c.writeStringToRundir(".containerenv", containerenv) if err != nil { return errors.Wrapf(err, "error creating containerenv file for container %s", c.ID()) } diff --git a/libpod/events/config.go b/libpod/events/config.go index fc1457289..085fa9d52 100644 --- a/libpod/events/config.go +++ b/libpod/events/config.go @@ -121,6 +121,8 @@ const ( Cleanup Status = "cleanup" // Commit ... Commit Status = "commit" + // Copy ... + Copy Status = "copy" // Create ... Create Status = "create" // Exec ... diff --git a/libpod/image/image.go b/libpod/image/image.go index cecd64eb7..5c3f3b9e4 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -1222,6 +1222,11 @@ func (i *Image) inspect(ctx context.Context, calculateSize bool) (*inspect.Image } } + parent, err := i.ParentID(ctx) + if err != nil { + return nil, err + } + repoTags, err := i.RepoTags() if err != nil { return nil, err @@ -1248,6 +1253,7 @@ func (i *Image) inspect(ctx context.Context, calculateSize bool) (*inspect.Image data := &inspect.ImageData{ ID: i.ID(), + Parent: parent, RepoTags: repoTags, RepoDigests: repoDigests, Comment: comment, @@ -1258,10 +1264,12 @@ func (i *Image) inspect(ctx context.Context, calculateSize bool) (*inspect.Image Config: &ociv1Img.Config, Version: info.DockerVersion, Size: size, - VirtualSize: size, - Annotations: annotations, - Digest: i.Digest(), - Labels: info.Labels, + // This is good enough for now, but has to be + // replaced later with correct calculation logic + VirtualSize: size, + Annotations: annotations, + Digest: i.Digest(), + Labels: info.Labels, RootFS: &inspect.RootFS{ Type: ociv1Img.RootFS.Type, Layers: ociv1Img.RootFS.DiffIDs, @@ -1505,6 +1513,15 @@ func (i *Image) GetParent(ctx context.Context) (*Image, error) { return tree.parent(ctx, i) } +// ParentID returns the image ID of the parent. Return empty string if a parent is not found. +func (i *Image) ParentID(ctx context.Context) (string, error) { + parent, err := i.GetParent(ctx) + if err == nil && parent != nil { + return parent.ID(), nil + } + return "", err +} + // GetChildren returns a list of the imageIDs that depend on the image func (i *Image) GetChildren(ctx context.Context) ([]string, error) { children, err := i.getChildren(ctx, true) diff --git a/libpod/info.go b/libpod/info.go index dd7a521c1..2f64a107e 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -117,7 +117,6 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) { if rootless.IsRootless() { if path, err := exec.LookPath("slirp4netns"); err == nil { - logrus.Warnf("Failed to retrieve program version for %s: %v", path, err) version, err := programVersion(path) if err != nil { logrus.Warnf("Failed to retrieve program version for %s: %v", path, err) diff --git a/libpod/network/create.go b/libpod/network/create.go index 7e4fc574a..094fbe349 100644 --- a/libpod/network/create.go +++ b/libpod/network/create.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strconv" "github.com/containernetworking/cni/pkg/version" "github.com/containers/common/pkg/config" @@ -76,6 +77,29 @@ func validateBridgeOptions(options entities.NetworkCreateOptions) error { } +// parseMTU parses the mtu option +func parseMTU(mtu string) (int, error) { + if mtu == "" { + return 0, nil // default + } + m, err := strconv.Atoi(mtu) + if err != nil { + return 0, err + } + if m < 0 { + return 0, errors.Errorf("the value %d for mtu is less than zero", m) + } + return m, nil +} + +// parseVlan parses the vlan option +func parseVlan(vlan string) (int, error) { + if vlan == "" { + return 0, nil // default + } + return strconv.Atoi(vlan) +} + // createBridge creates a CNI network func createBridge(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (string, error) { var ( @@ -149,6 +173,28 @@ func createBridge(name string, options entities.NetworkCreateOptions, runtimeCon ipMasq = false } + var mtu int + var vlan int + for k, v := range options.Options { + var err error + switch k { + case "mtu": + mtu, err = parseMTU(v) + if err != nil { + return "", err + } + + case "vlan": + vlan, err = parseVlan(v) + if err != nil { + return "", err + } + + default: + return "", errors.Errorf("unsupported option %s", k) + } + } + // obtain host bridge name bridgeDeviceName, err := GetFreeDeviceName(runtimeConfig) if err != nil { @@ -169,10 +215,10 @@ func createBridge(name string, options entities.NetworkCreateOptions, runtimeCon } // create CNI plugin configuration - ncList := NewNcList(name, version.Current()) + ncList := NewNcList(name, version.Current(), options.Labels) var plugins []CNIPlugins // TODO need to iron out the role of isDefaultGW and IPMasq - bridge := NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig) + bridge := NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, mtu, vlan, ipamConfig) plugins = append(plugins, bridge) plugins = append(plugins, NewPortMapPlugin()) plugins = append(plugins, NewFirewallPlugin()) @@ -223,7 +269,7 @@ func createMacVLAN(name string, options entities.NetworkCreateOptions, runtimeCo return "", err } } - ncList := NewNcList(name, version.Current()) + ncList := NewNcList(name, version.Current(), options.Labels) macvlan := NewMacVLANPlugin(options.MacVLAN) plugins = append(plugins, macvlan) ncList["plugins"] = plugins diff --git a/libpod/network/files.go b/libpod/network/files.go index 7f1e3ee18..33cf01064 100644 --- a/libpod/network/files.go +++ b/libpod/network/files.go @@ -12,6 +12,7 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/podman/v2/libpod/define" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // ErrNoSuchNetworkInterface indicates that no network interface exists @@ -49,13 +50,15 @@ func LoadCNIConfsFromDir(dir string) ([]*libcni.NetworkConfigList, error) { return configs, nil } -// GetCNIConfigPathByName finds a CNI network by name and +// GetCNIConfigPathByNameOrID finds a CNI network by name and // returns its configuration file path -func GetCNIConfigPathByName(config *config.Config, name string) (string, error) { +func GetCNIConfigPathByNameOrID(config *config.Config, name string) (string, error) { files, err := libcni.ConfFiles(GetCNIConfDir(config), []string{".conflist"}) if err != nil { return "", err } + idMatch := 0 + file := "" for _, confFile := range files { conf, err := libcni.ConfListFromFile(confFile) if err != nil { @@ -64,6 +67,16 @@ func GetCNIConfigPathByName(config *config.Config, name string) (string, error) if conf.Name == name { return confFile, nil } + if strings.HasPrefix(GetNetworkID(conf.Name), name) { + idMatch++ + file = confFile + } + } + if idMatch == 1 { + return file, nil + } + if idMatch > 1 { + return "", errors.Errorf("more than one result for network ID %s", name) } return "", errors.Wrap(define.ErrNoSuchNetwork, fmt.Sprintf("unable to find network configuration for %s", name)) } @@ -71,7 +84,7 @@ func GetCNIConfigPathByName(config *config.Config, name string) (string, error) // ReadRawCNIConfByName reads the raw CNI configuration for a CNI // network by name func ReadRawCNIConfByName(config *config.Config, name string) ([]byte, error) { - confFile, err := GetCNIConfigPathByName(config, name) + confFile, err := GetCNIConfigPathByNameOrID(config, name) if err != nil { return nil, err } @@ -89,6 +102,35 @@ func GetCNIPlugins(list *libcni.NetworkConfigList) string { return strings.Join(plugins, ",") } +// GetNetworkLabels returns a list of labels as a string +func GetNetworkLabels(list *libcni.NetworkConfigList) NcLabels { + cniJSON := make(map[string]interface{}) + err := json.Unmarshal(list.Bytes, &cniJSON) + if err != nil { + logrus.Errorf("failed to unmarshal network config %v %v", cniJSON["name"], err) + return nil + } + if args, ok := cniJSON["args"]; ok { + if key, ok := args.(map[string]interface{}); ok { + if labels, ok := key[PodmanLabelKey]; ok { + if labels, ok := labels.(map[string]interface{}); ok { + result := make(NcLabels, len(labels)) + for k, v := range labels { + if v, ok := v.(string); ok { + result[k] = v + } else { + logrus.Errorf("network config %v invalid label value type %T should be string", cniJSON["name"], labels) + } + } + return result + } + logrus.Errorf("network config %v invalid label type %T should be map[string]string", cniJSON["name"], labels) + } + } + } + return nil +} + // GetNetworksFromFilesystem gets all the networks from the cni configuration // files func GetNetworksFromFilesystem(config *config.Config) ([]*allocator.Net, error) { diff --git a/libpod/network/netconflist.go b/libpod/network/netconflist.go index ee9adce14..d61b96ecb 100644 --- a/libpod/network/netconflist.go +++ b/libpod/network/netconflist.go @@ -4,6 +4,11 @@ import ( "net" "os" "path/filepath" + "strings" + + "github.com/containernetworking/cni/libcni" + "github.com/containers/podman/v2/pkg/util" + "github.com/pkg/errors" ) const ( @@ -14,22 +19,36 @@ const ( // NcList describes a generic map type NcList map[string]interface{} +// NcArgs describes the cni args field +type NcArgs map[string]NcLabels + +// NcLabels describes the label map +type NcLabels map[string]string + +// PodmanLabelKey key used to store the podman network label in a cni config +const PodmanLabelKey = "podman_labels" + // NewNcList creates a generic map of values with string // keys and adds in version and network name -func NewNcList(name, version string) NcList { +func NewNcList(name, version string, labels NcLabels) NcList { n := NcList{} n["cniVersion"] = version n["name"] = name + if len(labels) > 0 { + n["args"] = NcArgs{PodmanLabelKey: labels} + } return n } // NewHostLocalBridge creates a new LocalBridge for host-local -func NewHostLocalBridge(name string, isGateWay, isDefaultGW, ipMasq bool, ipamConf IPAMHostLocalConf) *HostLocalBridge { +func NewHostLocalBridge(name string, isGateWay, isDefaultGW, ipMasq bool, mtu int, vlan int, ipamConf IPAMHostLocalConf) *HostLocalBridge { hostLocalBridge := HostLocalBridge{ PluginType: "bridge", BrName: name, IPMasq: ipMasq, + MTU: mtu, HairpinMode: true, + Vlan: vlan, IPAM: ipamConf, } if isGateWay { @@ -159,3 +178,72 @@ func NewMacVLANPlugin(device string) MacVLANConfig { } return m } + +// IfPassesFilter filters NetworkListReport and returns true if the filter match the given config +func IfPassesFilter(netconf *libcni.NetworkConfigList, filters map[string][]string) (bool, error) { + result := true + for key, filterValues := range filters { + result = false + switch strings.ToLower(key) { + case "name": + // matches one name, regex allowed + result = util.StringMatchRegexSlice(netconf.Name, filterValues) + + case "plugin": + // match one plugin + plugins := GetCNIPlugins(netconf) + for _, val := range filterValues { + if strings.Contains(plugins, val) { + result = true + break + } + } + + case "label": + // matches all labels + labels := GetNetworkLabels(netconf) + outer: + for _, filterValue := range filterValues { + filterArray := strings.SplitN(filterValue, "=", 2) + filterKey := filterArray[0] + if len(filterArray) > 1 { + filterValue = filterArray[1] + } else { + filterValue = "" + } + for labelKey, labelValue := range labels { + if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { + result = true + continue outer + } + } + result = false + } + + case "driver": + // matches only for the DefaultNetworkDriver + for _, filterValue := range filterValues { + plugins := GetCNIPlugins(netconf) + if filterValue == DefaultNetworkDriver && + strings.Contains(plugins, DefaultNetworkDriver) { + result = true + } + } + + case "id": + // matches part of one id + for _, filterValue := range filterValues { + if strings.Contains(GetNetworkID(netconf.Name), filterValue) { + result = true + break + } + } + + // TODO: add dangling filter + + default: + return false, errors.Errorf("invalid filter %q", key) + } + } + return result, nil +} diff --git a/libpod/network/network.go b/libpod/network/network.go index 0febb52f6..89f0b67ac 100644 --- a/libpod/network/network.go +++ b/libpod/network/network.go @@ -1,6 +1,8 @@ package network import ( + "crypto/sha256" + "encoding/hex" "encoding/json" "net" "os" @@ -175,7 +177,7 @@ func RemoveNetwork(config *config.Config, name string) error { return err } defer l.releaseCNILock() - cniPath, err := GetCNIConfigPathByName(config, name) + cniPath, err := GetCNIConfigPathByNameOrID(config, name) if err != nil { return err } @@ -229,3 +231,10 @@ func Exists(config *config.Config, name string) (bool, error) { } return true, nil } + +// GetNetworkID return the network ID for a given name. +// It is just the sha256 hash but this should be good enough. +func GetNetworkID(name string) string { + hash := sha256.Sum256([]byte(name)) + return hex.EncodeToString(hash[:]) +} diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 15e470c80..463378af7 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -245,7 +245,7 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error { // setupSlirp4netns can be called in rootful as well as in rootless func (r *Runtime) setupSlirp4netns(ctr *Container) error { path := r.config.Engine.NetworkCmdPath - + slirpOptions := r.config.Engine.NetworkCmdOptions if path == "" { var err error path, err = exec.LookPath("slirp4netns") @@ -273,68 +273,69 @@ func (r *Runtime) setupSlirp4netns(ctr *Container) error { outboundAddr6 := "" if ctr.config.NetworkOptions != nil { - slirpOptions := ctr.config.NetworkOptions["slirp4netns"] - for _, o := range slirpOptions { - parts := strings.SplitN(o, "=", 2) - if len(parts) < 2 { - return errors.Errorf("unknown option for slirp4netns: %q", o) + slirpOptions = append(slirpOptions, ctr.config.NetworkOptions["slirp4netns"]...) + } + + for _, o := range slirpOptions { + parts := strings.SplitN(o, "=", 2) + if len(parts) < 2 { + return errors.Errorf("unknown option for slirp4netns: %q", o) + } + option, value := parts[0], parts[1] + switch option { + case "cidr": + ipv4, _, err := net.ParseCIDR(value) + if err != nil || ipv4.To4() == nil { + return errors.Errorf("invalid cidr %q", value) } - option, value := parts[0], parts[1] - switch option { - case "cidr": - ipv4, _, err := net.ParseCIDR(value) - if err != nil || ipv4.To4() == nil { - return errors.Errorf("invalid cidr %q", value) - } - cidr = value - case "port_handler": - switch value { - case "slirp4netns": - isSlirpHostForward = true - case "rootlesskit": - isSlirpHostForward = false - default: - return errors.Errorf("unknown port_handler for slirp4netns: %q", value) - } - case "allow_host_loopback": - switch value { - case "true": - disableHostLoopback = false - case "false": - disableHostLoopback = true - default: - return errors.Errorf("invalid value of allow_host_loopback for slirp4netns: %q", value) - } - case "enable_ipv6": - switch value { - case "true": - enableIPv6 = true - case "false": - enableIPv6 = false - default: - return errors.Errorf("invalid value of enable_ipv6 for slirp4netns: %q", value) - } - case "outbound_addr": - ipv4 := net.ParseIP(value) - if ipv4 == nil || ipv4.To4() == nil { - _, err := net.InterfaceByName(value) - if err != nil { - return errors.Errorf("invalid outbound_addr %q", value) - } + cidr = value + case "port_handler": + switch value { + case "slirp4netns": + isSlirpHostForward = true + case "rootlesskit": + isSlirpHostForward = false + default: + return errors.Errorf("unknown port_handler for slirp4netns: %q", value) + } + case "allow_host_loopback": + switch value { + case "true": + disableHostLoopback = false + case "false": + disableHostLoopback = true + default: + return errors.Errorf("invalid value of allow_host_loopback for slirp4netns: %q", value) + } + case "enable_ipv6": + switch value { + case "true": + enableIPv6 = true + case "false": + enableIPv6 = false + default: + return errors.Errorf("invalid value of enable_ipv6 for slirp4netns: %q", value) + } + case "outbound_addr": + ipv4 := net.ParseIP(value) + if ipv4 == nil || ipv4.To4() == nil { + _, err := net.InterfaceByName(value) + if err != nil { + return errors.Errorf("invalid outbound_addr %q", value) } - outboundAddr = value - case "outbound_addr6": - ipv6 := net.ParseIP(value) - if ipv6 == nil || ipv6.To4() != nil { - _, err := net.InterfaceByName(value) - if err != nil { - return errors.Errorf("invalid outbound_addr6: %q", value) - } + } + outboundAddr = value + case "outbound_addr6": + ipv6 := net.ParseIP(value) + if ipv6 == nil || ipv6.To4() != nil { + _, err := net.InterfaceByName(value) + if err != nil { + return errors.Errorf("invalid outbound_addr6: %q", value) } - outboundAddr6 = value - default: - return errors.Errorf("unknown option for slirp4netns: %q", o) } + outboundAddr6 = value + default: + return errors.Errorf("unknown option for slirp4netns: %q", o) } } diff --git a/libpod/oci_conmon_exec_linux.go b/libpod/oci_conmon_exec_linux.go index 7068bf87a..f8e7020f7 100644 --- a/libpod/oci_conmon_exec_linux.go +++ b/libpod/oci_conmon_exec_linux.go @@ -231,7 +231,7 @@ func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, t // Wait for the PID to stop if err := waitPidStop(pid, time.Duration(timeout)*time.Second); err != nil { - logrus.Warnf("Timed out waiting for container %s exec session %s to stop, resorting to SIGKILL", ctr.ID(), sessionID) + logrus.Infof("Timed out waiting for container %s exec session %s to stop, resorting to SIGKILL: %v", ctr.ID(), sessionID, err) } else { // No error, container is dead return nil diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index bd58610a2..307b9bc54 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -442,7 +442,7 @@ func (r *ConmonOCIRuntime) StopContainer(ctr *Container, timeout uint, all bool) } if err := waitContainerStop(ctr, time.Duration(timeout)*time.Second); err != nil { - logrus.Warnf("Timed out stopping container %s, resorting to SIGKILL", ctr.ID()) + logrus.Infof("Timed out stopping container %s, resorting to SIGKILL: %v", ctr.ID(), err) } else { // No error, the container is dead return nil @@ -1009,7 +1009,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co if ctr.config.SdNotifyMode == define.SdNotifyModeIgnore { if err := os.Unsetenv("NOTIFY_SOCKET"); err != nil { - logrus.Warnf("Error unsetting NOTIFY_SOCKET %s", err.Error()) + logrus.Warnf("Error unsetting NOTIFY_SOCKET %v", err) } } @@ -1155,14 +1155,14 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co conmonPID, err := readConmonPidFile(ctr.config.ConmonPidFile) if err != nil { - logrus.Warnf("error reading conmon pid file for container %s: %s", ctr.ID(), err.Error()) + logrus.Warnf("error reading conmon pid file for container %s: %v", ctr.ID(), err) } else if conmonPID > 0 { // conmon not having a pid file is a valid state, so don't set it if we don't have it logrus.Infof("Got Conmon PID as %d", conmonPID) ctr.state.ConmonPID = conmonPID if ctr.config.SdNotifyMode != define.SdNotifyModeIgnore { if sent, err := daemon.SdNotify(false, fmt.Sprintf("MAINPID=%d", conmonPID)); err != nil { - logrus.Errorf("Error notifying systemd of Conmon PID: %s", err.Error()) + logrus.Errorf("Error notifying systemd of Conmon PID: %v", err) } else if sent { logrus.Debugf("Notify MAINPID sent successfully") } diff --git a/libpod/options.go b/libpod/options.go index 0f55f34a3..bd12c0c34 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1547,12 +1547,19 @@ func WithVolumeDriver(driver string) VolumeCreateOption { if volume.valid { return define.ErrVolumeFinalized } - // only local driver is possible rn + + // Uncomment when volume plugins are ready for use. + // if driver != define.VolumeDriverLocal { + // if _, err := plugin.GetVolumePlugin(driver); err != nil { + // return err + // } + // } + if driver != define.VolumeDriverLocal { return define.ErrNotImplemented - } - volume.config.Driver = define.VolumeDriverLocal + + volume.config.Driver = driver return nil } } diff --git a/libpod/plugin/volume_api.go b/libpod/plugin/volume_api.go new file mode 100644 index 000000000..2500a4f36 --- /dev/null +++ b/libpod/plugin/volume_api.go @@ -0,0 +1,454 @@ +package plugin + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/containers/podman/v2/libpod/define" + "github.com/docker/go-plugins-helpers/sdk" + "github.com/docker/go-plugins-helpers/volume" + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +// TODO: We should add syntax for specifying plugins to containers.conf, and +// support for loading based on that. + +// Copied from docker/go-plugins-helpers/volume/api.go - not exported, so we +// need to do this to get at them. +// These are well-established paths that should not change unless the plugin API +// version changes. +var ( + activatePath = "/Plugin.Activate" + createPath = "/VolumeDriver.Create" + getPath = "/VolumeDriver.Get" + listPath = "/VolumeDriver.List" + removePath = "/VolumeDriver.Remove" + hostVirtualPath = "/VolumeDriver.Path" + mountPath = "/VolumeDriver.Mount" + unmountPath = "/VolumeDriver.Unmount" + // nolint + capabilitiesPath = "/VolumeDriver.Capabilities" +) + +const ( + defaultTimeout = 5 * time.Second + defaultPath = "/run/docker/plugins" + volumePluginType = "VolumeDriver" +) + +var ( + ErrNotPlugin = errors.New("target does not appear to be a valid plugin") + ErrNotVolumePlugin = errors.New("plugin is not a volume plugin") + ErrPluginRemoved = errors.New("plugin is no longer available (shut down?)") + + // This stores available, initialized volume plugins. + pluginsLock sync.Mutex + plugins map[string]*VolumePlugin +) + +// VolumePlugin is a single volume plugin. +type VolumePlugin struct { + // Name is the name of the volume plugin. This will be used to refer to + // it. + Name string + // SocketPath is the unix socket at which the plugin is accessed. + SocketPath string +} + +// This is the response from the activate endpoint of the API. +type activateResponse struct { + Implements []string +} + +// Validate that the given plugin is good to use. +// Add it to available plugins if so. +func validatePlugin(newPlugin *VolumePlugin) error { + // It's a socket. Is it a plugin? + // Hit the Activate endpoint to find out if it is, and if so what kind + req, err := http.NewRequest("POST", activatePath, nil) + if err != nil { + return errors.Wrapf(err, "error making request to volume plugin %s activation endpoint", newPlugin.Name) + } + + req.Header.Set("Host", newPlugin.getURI()) + req.Header.Set("Content-Type", sdk.DefaultContentTypeV1_1) + + client := new(http.Client) + client.Timeout = defaultTimeout + resp, err := client.Do(req) + if err != nil { + return errors.Wrapf(err, "error sending request to plugin %s activation endpoint", newPlugin.Name) + } + defer resp.Body.Close() + + // Response code MUST be 200. Anything else, we have to assume it's not + // a valid plugin. + if resp.StatusCode != 200 { + return errors.Wrapf(ErrNotPlugin, "got status code %d from activation endpoint for plugin %s", resp.StatusCode, newPlugin.Name) + } + + // Read and decode the body so we can tell if this is a volume plugin. + respBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return errors.Wrapf(err, "error reading activation response body from plugin %s", newPlugin.Name) + } + + respStruct := new(activateResponse) + if err := json.Unmarshal(respBytes, respStruct); err != nil { + return errors.Wrapf(err, "error unmarshalling plugin %s activation response", newPlugin.Name) + } + + foundVolume := false + for _, pluginType := range respStruct.Implements { + if pluginType == volumePluginType { + foundVolume = true + break + } + } + + if !foundVolume { + return errors.Wrapf(ErrNotVolumePlugin, "plugin %s does not implement volume plugin, instead provides %s", newPlugin.Name, strings.Join(respStruct.Implements, ", ")) + } + + plugins[newPlugin.Name] = newPlugin + + return nil +} + +// GetVolumePlugin gets a single volume plugin by path. +// TODO: We should not be auto-completing based on a default path; we should +// require volumes to have been pre-specified in containers.conf (will need a +// function to pre-populate the plugins list, and we should probably do a lazy +// initialization there to not slow things down too much). +func GetVolumePlugin(name string) (*VolumePlugin, error) { + pluginsLock.Lock() + defer pluginsLock.Unlock() + + plugin, exists := plugins[name] + if exists { + return plugin, nil + } + + // It's not cached. We need to get it. + + newPlugin := new(VolumePlugin) + newPlugin.Name = name + newPlugin.SocketPath = filepath.Join(defaultPath, fmt.Sprintf("%s.sock", name)) + + stat, err := os.Stat(newPlugin.SocketPath) + if err != nil { + return nil, errors.Wrapf(err, "cannot access plugin %s socket %q", name, newPlugin.SocketPath) + } + if stat.Mode()&os.ModeSocket == 0 { + return nil, errors.Wrapf(ErrNotPlugin, "volume %s path %q is not a unix socket", name, newPlugin.SocketPath) + } + + if err := validatePlugin(newPlugin); err != nil { + return nil, err + } + + return newPlugin, nil +} + +func (p *VolumePlugin) getURI() string { + return "unix://" + p.SocketPath +} + +// Verify the plugin is still available. +// TODO: Do we want to ping with an HTTP request? There's no ping endpoint so +// we'd need to hit Activate or Capabilities? +func (p *VolumePlugin) verifyReachable() error { + if _, err := os.Stat(p.SocketPath); err != nil { + if os.IsNotExist(err) { + pluginsLock.Lock() + defer pluginsLock.Unlock() + delete(plugins, p.Name) + return errors.Wrapf(ErrPluginRemoved, p.Name) + } + + return errors.Wrapf(err, "error accessing plugin %s", p.Name) + } + return nil +} + +// Send a request to the volume plugin for handling. +func (p *VolumePlugin) sendRequest(toJSON interface{}, hasBody bool, endpoint string) (*http.Response, error) { + var ( + reqJSON []byte + err error + ) + + if hasBody { + reqJSON, err = json.Marshal(toJSON) + if err != nil { + return nil, errors.Wrapf(err, "error marshalling request JSON for volume plugin %s endpoint %s", p.Name, endpoint) + } + } + + req, err := http.NewRequest("POST", endpoint, bytes.NewReader(reqJSON)) + if err != nil { + return nil, errors.Wrapf(err, "error making request to volume plugin %s endpoint %s", p.Name, endpoint) + } + + req.Header.Set("Host", p.getURI()) + req.Header.Set("Content-Type", sdk.DefaultContentTypeV1_1) + + client := new(http.Client) + client.Timeout = defaultTimeout + resp, err := client.Do(req) + if err != nil { + return nil, errors.Wrapf(err, "error sending request to volume plugin %s endpoint %s", p.Name, endpoint) + } + defer resp.Body.Close() + + return resp, nil +} + +// Turn an error response from a volume plugin into a well-formatted Go error. +func (p *VolumePlugin) makeErrorResponse(err, endpoint, volName string) error { + if err == "" { + err = "empty error from plugin" + } + if volName != "" { + return errors.Wrapf(errors.New(err), "error on %s on volume %s in volume plugin %s", endpoint, volName, p.Name) + } else { + return errors.Wrapf(errors.New(err), "error on %s in volume plugin %s", endpoint, p.Name) + } +} + +// Handle error responses from plugin +func (p *VolumePlugin) handleErrorResponse(resp *http.Response, endpoint, volName string) error { + // The official plugin reference implementation uses HTTP 500 for + // errors, but I don't think we can guarantee all plugins do that. + // Let's interpret anything other than 200 as an error. + // If there isn't an error, don't even bother decoding the response. + if resp.StatusCode != 200 { + errResp, err := ioutil.ReadAll(resp.Body) + if err != nil { + return errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name) + } + + errStruct := new(volume.ErrorResponse) + if err := json.Unmarshal(errResp, errStruct); err != nil { + return errors.Wrapf(err, "error unmarshalling JSON response from volume plugin %s", p.Name) + } + + return p.makeErrorResponse(errStruct.Err, endpoint, volName) + } + + return nil +} + +// CreateVolume creates a volume in the plugin. +func (p *VolumePlugin) CreateVolume(req *volume.CreateRequest) error { + if req == nil { + return errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to CreateVolume") + } + + if err := p.verifyReachable(); err != nil { + return err + } + + logrus.Infof("Creating volume %s using plugin %s", req.Name, p.Name) + + resp, err := p.sendRequest(req, true, createPath) + if err != nil { + return err + } + defer resp.Body.Close() + + return p.handleErrorResponse(resp, createPath, req.Name) +} + +// ListVolumes lists volumes available in the plugin. +func (p *VolumePlugin) ListVolumes() ([]*volume.Volume, error) { + if err := p.verifyReachable(); err != nil { + return nil, err + } + + logrus.Infof("Listing volumes using plugin %s", p.Name) + + resp, err := p.sendRequest(nil, false, listPath) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if err := p.handleErrorResponse(resp, listPath, ""); err != nil { + return nil, err + } + + // TODO: Can probably unify response reading under a helper + volumeRespBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name) + } + + volumeResp := new(volume.ListResponse) + if err := json.Unmarshal(volumeRespBytes, volumeResp); err != nil { + return nil, errors.Wrapf(err, "error unmarshalling volume plugin %s list response", p.Name) + } + + return volumeResp.Volumes, nil +} + +// GetVolume gets a single volume from the plugin. +func (p *VolumePlugin) GetVolume(req *volume.GetRequest) (*volume.Volume, error) { + if req == nil { + return nil, errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to GetVolume") + } + + if err := p.verifyReachable(); err != nil { + return nil, err + } + + logrus.Infof("Getting volume %s using plugin %s", req.Name, p.Name) + + resp, err := p.sendRequest(req, true, getPath) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if err := p.handleErrorResponse(resp, getPath, req.Name); err != nil { + return nil, err + } + + getRespBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name) + } + + getResp := new(volume.GetResponse) + if err := json.Unmarshal(getRespBytes, getResp); err != nil { + return nil, errors.Wrapf(err, "error unmarshalling volume plugin %s get response", p.Name) + } + + return getResp.Volume, nil +} + +// RemoveVolume removes a single volume from the plugin. +func (p *VolumePlugin) RemoveVolume(req *volume.RemoveRequest) error { + if req == nil { + return errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to RemoveVolume") + } + + if err := p.verifyReachable(); err != nil { + return err + } + + logrus.Infof("Removing volume %s using plugin %s", req.Name, p.Name) + + resp, err := p.sendRequest(req, true, removePath) + if err != nil { + return err + } + defer resp.Body.Close() + + return p.handleErrorResponse(resp, removePath, req.Name) +} + +// GetVolumePath gets the path the given volume is mounted at. +func (p *VolumePlugin) GetVolumePath(req *volume.PathRequest) (string, error) { + if req == nil { + return "", errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to GetVolumePath") + } + + if err := p.verifyReachable(); err != nil { + return "", err + } + + logrus.Infof("Getting volume %s path using plugin %s", req.Name, p.Name) + + resp, err := p.sendRequest(req, true, hostVirtualPath) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if err := p.handleErrorResponse(resp, hostVirtualPath, req.Name); err != nil { + return "", err + } + + pathRespBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name) + } + + pathResp := new(volume.PathResponse) + if err := json.Unmarshal(pathRespBytes, pathResp); err != nil { + return "", errors.Wrapf(err, "error unmarshalling volume plugin %s path response", p.Name) + } + + return pathResp.Mountpoint, nil +} + +// MountVolume mounts the given volume. The ID argument is the ID of the +// mounting container, used for internal record-keeping by the plugin. Returns +// the path the volume has been mounted at. +func (p *VolumePlugin) MountVolume(req *volume.MountRequest) (string, error) { + if req == nil { + return "", errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to MountVolume") + } + + if err := p.verifyReachable(); err != nil { + return "", err + } + + logrus.Infof("Mounting volume %s using plugin %s for container %s", req.Name, p.Name, req.ID) + + resp, err := p.sendRequest(req, true, mountPath) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if err := p.handleErrorResponse(resp, mountPath, req.Name); err != nil { + return "", err + } + + mountRespBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name) + } + + mountResp := new(volume.MountResponse) + if err := json.Unmarshal(mountRespBytes, mountResp); err != nil { + return "", errors.Wrapf(err, "error unmarshalling volume plugin %s path response", p.Name) + } + + return mountResp.Mountpoint, nil +} + +// UnmountVolume unmounts the given volume. The ID argument is the ID of the +// container that is unmounting, used for internal record-keeping by the plugin. +func (p *VolumePlugin) UnmountVolume(req *volume.UnmountRequest) error { + if req == nil { + return errors.Wrapf(define.ErrInvalidArg, "must provide non-nil request to UnmountVolume") + } + + if err := p.verifyReachable(); err != nil { + return err + } + + logrus.Infof("Unmounting volume %s using plugin %s for container %s", req.Name, p.Name, req.ID) + + resp, err := p.sendRequest(req, true, unmountPath) + if err != nil { + return err + } + defer resp.Body.Close() + + return p.handleErrorResponse(resp, unmountPath, req.Name) +} diff --git a/libpod/reset.go b/libpod/reset.go index f8828fed4..6d2842723 100644 --- a/libpod/reset.go +++ b/libpod/reset.go @@ -46,7 +46,7 @@ func (r *Runtime) Reset(ctx context.Context) error { } } - if err := stopPauseProcess(); err != nil { + if err := r.stopPauseProcess(); err != nil { logrus.Errorf("Error stopping pause process: %v", err) } diff --git a/libpod/runtime.go b/libpod/runtime.go index df3dfae2b..72bd34a5e 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -387,8 +387,8 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) { // Don't fatally error. // This will allow us to ship configs including optional // runtimes that might not be installed (crun, kata). - // Only a warnf so default configs don't spec errors. - logrus.Warnf("Error initializing configured OCI runtime %s: %v", name, err) + // Only a infof so default configs don't spec errors. + logrus.Infof("Error initializing configured OCI runtime %s: %v", name, err) continue } @@ -472,7 +472,7 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) { // we will need to access the storage. if os.Geteuid() != 0 { aliveLock.Unlock() // Unlock to avoid deadlock as BecomeRootInUserNS will reexec. - pausePid, err := util.GetRootlessPauseProcessPidPath() + pausePid, err := util.GetRootlessPauseProcessPidPathGivenDir(runtime.config.Engine.TmpDir) if err != nil { return errors.Wrapf(err, "could not get pause process pid file path") } @@ -538,6 +538,15 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) { return nil } +// TmpDir gets the current Libpod temporary files directory. +func (r *Runtime) TmpDir() (string, error) { + if !r.valid { + return "", define.ErrRuntimeStopped + } + + return r.config.Engine.TmpDir, nil +} + // GetConfig returns a copy of the configuration used by the runtime func (r *Runtime) GetConfig() (*config.Config, error) { r.lock.RLock() diff --git a/libpod/runtime_cstorage.go b/libpod/runtime_cstorage.go index 61fdd42d3..6ee8a9354 100644 --- a/libpod/runtime_cstorage.go +++ b/libpod/runtime_cstorage.go @@ -103,7 +103,7 @@ func (r *Runtime) removeStorageContainer(idOrName string, force bool) error { if errors.Cause(err) == storage.ErrContainerUnknown { // Container was removed from under us. // It's gone, so don't bother erroring. - logrus.Warnf("Storage for container %s already removed", ctr.ID) + logrus.Infof("Storage for container %s already removed", ctr.ID) return nil } return errors.Wrapf(err, "error looking up container %q mounts", idOrName) @@ -114,7 +114,7 @@ func (r *Runtime) removeStorageContainer(idOrName string, force bool) error { } else if _, err := r.store.Unmount(ctr.ID, true); err != nil { if errors.Cause(err) == storage.ErrContainerUnknown { // Container again gone, no error - logrus.Warnf("Storage for container %s already removed", ctr.ID) + logrus.Infof("Storage for container %s already removed", ctr.ID) return nil } return errors.Wrapf(err, "error unmounting container %q", idOrName) @@ -123,7 +123,7 @@ func (r *Runtime) removeStorageContainer(idOrName string, force bool) error { if err := r.store.DeleteContainer(ctr.ID); err != nil { if errors.Cause(err) == storage.ErrContainerUnknown { // Container again gone, no error - logrus.Warnf("Storage for container %s already removed", ctr.ID) + logrus.Infof("Storage for container %s already removed", ctr.ID) return nil } return errors.Wrapf(err, "error removing storage for container %q", idOrName) diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index e57890fa2..a2d9a875e 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -8,7 +8,6 @@ import ( "net/http" "net/url" "os" - "strings" "github.com/containers/buildah/imagebuildah" "github.com/containers/image/v5/directory" @@ -276,56 +275,47 @@ func DownloadFromFile(reader *os.File) (string, error) { } // LoadImage loads a container image into local storage -func (r *Runtime) LoadImage(ctx context.Context, name, inputFile string, writer io.Writer, signaturePolicy string) (string, error) { - var ( - newImages []*image.Image - err error - src types.ImageReference - ) +func (r *Runtime) LoadImage(ctx context.Context, inputFile string, writer io.Writer, signaturePolicy string) (string, error) { + if newImages, err := r.LoadAllImageFromArchive(ctx, writer, inputFile, signaturePolicy); err == nil { + return newImages, nil + } + return r.LoadImageFromSingleImageArchive(ctx, writer, inputFile, signaturePolicy) +} - if name == "" { - newImages, err = r.ImageRuntime().LoadAllImagesFromDockerArchive(ctx, inputFile, signaturePolicy, writer) - if err == nil { - return getImageNames(newImages), nil - } +// LoadAllImageFromArchive loads all images from the archive of multi-image that inputFile points to. +func (r *Runtime) LoadAllImageFromArchive(ctx context.Context, writer io.Writer, inputFile, signaturePolicy string) (string, error) { + newImages, err := r.ImageRuntime().LoadAllImagesFromDockerArchive(ctx, inputFile, signaturePolicy, writer) + if err == nil { + return getImageNames(newImages), nil } + return "", err +} +// LoadImageFromSingleImageArchive load image from the archive of single image that inputFile points to. +func (r *Runtime) LoadImageFromSingleImageArchive(ctx context.Context, writer io.Writer, inputFile, signaturePolicy string) (string, error) { + var err error for _, referenceFn := range []func() (types.ImageReference, error){ func() (types.ImageReference, error) { return dockerarchive.ParseReference(inputFile) }, func() (types.ImageReference, error) { - return ociarchive.NewReference(inputFile, name) // name may be "" - }, - func() (types.ImageReference, error) { - // prepend "localhost/" to support local image saved with this semantics - if !strings.Contains(name, "/") { - return ociarchive.NewReference(inputFile, fmt.Sprintf("%s/%s", image.DefaultLocalRegistry, name)) - } - return nil, nil + return ociarchive.NewReference(inputFile, "") }, func() (types.ImageReference, error) { return directory.NewReference(inputFile) }, func() (types.ImageReference, error) { - return layout.NewReference(inputFile, name) - }, - func() (types.ImageReference, error) { - // prepend "localhost/" to support local image saved with this semantics - if !strings.Contains(name, "/") { - return layout.NewReference(inputFile, fmt.Sprintf("%s/%s", image.DefaultLocalRegistry, name)) - } - return nil, nil + return layout.NewReference(inputFile, "") }, } { - src, err = referenceFn() + src, err := referenceFn() if err == nil && src != nil { - if newImages, err = r.ImageRuntime().LoadFromArchiveReference(ctx, src, signaturePolicy, writer); err == nil { + if newImages, err := r.ImageRuntime().LoadFromArchiveReference(ctx, src, signaturePolicy, writer); err == nil { return getImageNames(newImages), nil } } } - return "", errors.Wrapf(err, "error pulling %q", name) + return "", errors.Wrapf(err, "error pulling image") } func getImageNames(images []*image.Image) string { diff --git a/libpod/runtime_migrate.go b/libpod/runtime_migrate.go index 1ad32fe9c..f0f800ef0 100644 --- a/libpod/runtime_migrate.go +++ b/libpod/runtime_migrate.go @@ -18,9 +18,9 @@ import ( "github.com/sirupsen/logrus" ) -func stopPauseProcess() error { +func (r *Runtime) stopPauseProcess() error { if rootless.IsRootless() { - pausePidPath, err := util.GetRootlessPauseProcessPidPath() + pausePidPath, err := util.GetRootlessPauseProcessPidPathGivenDir(r.config.Engine.TmpDir) if err != nil { return errors.Wrapf(err, "could not get pause process pid file path") } @@ -98,5 +98,5 @@ func (r *Runtime) migrate(ctx context.Context) error { } } - return stopPauseProcess() + return r.stopPauseProcess() } diff --git a/libpod/runtime_migrate_unsupported.go b/libpod/runtime_migrate_unsupported.go index e362cca63..a9d351318 100644 --- a/libpod/runtime_migrate_unsupported.go +++ b/libpod/runtime_migrate_unsupported.go @@ -10,6 +10,6 @@ func (r *Runtime) migrate(ctx context.Context) error { return nil } -func stopPauseProcess() error { +func (r *Runtime) stopPauseProcess() error { return nil } diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index 76419587a..3e4185db1 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -34,40 +34,56 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm // Set Pod hostname g.Config.Hostname = p.config.Hostname + var options []CtrCreateOption + + // Command: If user-specified, use that preferentially. + // If not set and the config file is set, fall back to that. + var infraCtrCommand []string + if p.config.InfraContainer.InfraCommand != nil { + logrus.Debugf("User-specified infra container entrypoint %v", p.config.InfraContainer.InfraCommand) + infraCtrCommand = p.config.InfraContainer.InfraCommand + } else if r.config.Engine.InfraCommand != "" { + logrus.Debugf("Config-specified infra container entrypoint %s", r.config.Engine.InfraCommand) + infraCtrCommand = []string{r.config.Engine.InfraCommand} + } + // Only if set by the user or containers.conf, we set entrypoint for the + // infra container. + // This is only used by commit, so it shouldn't matter... But someone + // may eventually want to commit an infra container? + // TODO: Should we actually do this if set by containers.conf? + if infraCtrCommand != nil { + // Need to duplicate the array - we are going to add Cmd later + // so the current array will be changed. + newArr := make([]string, 0, len(infraCtrCommand)) + newArr = append(newArr, infraCtrCommand...) + options = append(options, WithEntrypoint(newArr)) + } + isRootless := rootless.IsRootless() - entrypointSet := len(p.config.InfraContainer.InfraCommand) > 0 - entryPoint := p.config.InfraContainer.InfraCommand - entryCmd := []string{} - var options []CtrCreateOption // I've seen circumstances where config is being passed as nil. // Let's err on the side of safety and make sure it's safe to use. if config != nil { - // default to entrypoint in image if there is one - if !entrypointSet { - if len(config.Entrypoint) > 0 { - entrypointSet = true - entryPoint = config.Entrypoint - entryCmd = config.Entrypoint + if infraCtrCommand == nil { + // If we have no entrypoint and command from the image, + // we can't go on - the infra container has no command. + if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 { + return nil, errors.Errorf("infra container has no command") } - } else { // so use the InfraCommand - entrypointSet = true - entryCmd = entryPoint - } - - if len(config.Cmd) > 0 { - // We can't use the default pause command, since we're - // sourcing from the image. If we didn't already set an - // entrypoint, set one now. - if !entrypointSet { + if len(config.Entrypoint) > 0 { + infraCtrCommand = config.Entrypoint + } else { // Use the Docker default "/bin/sh -c" // entrypoint, as we're overriding command. // If an image doesn't want this, it can // override entrypoint too. - entryCmd = []string{"/bin/sh", "-c"} + infraCtrCommand = []string{"/bin/sh", "-c"} } - entryCmd = append(entryCmd, config.Cmd...) } + if len(config.Cmd) > 0 { + infraCtrCommand = append(infraCtrCommand, config.Cmd...) + } + if len(config.Env) > 0 { for _, nameValPair := range config.Env { nameValSlice := strings.Split(nameValPair, "=") @@ -127,9 +143,9 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm } g.SetRootReadonly(true) - g.SetProcessArgs(entryCmd) + g.SetProcessArgs(infraCtrCommand) - logrus.Debugf("Using %q as infra container entrypoint", entryCmd) + logrus.Debugf("Using %q as infra container command", infraCtrCommand) g.RemoveMount("/dev/shm") if isRootless { @@ -148,9 +164,6 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm options = append(options, WithRootFSFromImage(imgID, imgName, rawImageName)) options = append(options, WithName(containerName)) options = append(options, withIsInfra()) - if entrypointSet { - options = append(options, WithEntrypoint(entryPoint)) - } if len(p.config.InfraContainer.ConmonPidFile) > 0 { options = append(options, WithConmonPidFile(p.config.InfraContainer.ConmonPidFile)) } diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go index 25598ce4d..1eb42660c 100644 --- a/libpod/runtime_pod_linux.go +++ b/libpod/runtime_pod_linux.go @@ -117,7 +117,7 @@ func (r *Runtime) NewPod(ctx context.Context, options ...PodCreateOption) (_ *Po return nil, errors.Errorf("Pods must have an infra container to share namespaces") } if pod.HasInfraContainer() && !pod.SharesNamespaces() { - logrus.Warnf("Pod has an infra container, but shares no namespaces") + logrus.Infof("Pod has an infra container, but shares no namespaces") } if err := r.state.AddPod(pod); err != nil { @@ -212,7 +212,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool) // Don't try if we failed to retrieve the cgroup if err == nil { if err := conmonCgroup.Update(resLimits); err != nil { - logrus.Warnf("Error updating pod %s conmon cgroup %s PID limit: %v", p.ID(), conmonCgroupPath, err) + logrus.Warnf("Error updating pod %s conmon cgroup PID limit: %v", p.ID(), err) } } } |