package libpod import ( "fmt" "strings" "time" "github.com/containers/image/manifest" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/driver" "github.com/containers/libpod/pkg/util" "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/runtime-tools/validate" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/syndtr/gocapability/capability" ) const ( // InspectAnnotationCIDFile is used by Inspect to determine if a // container ID file was created for the container. // If an annotation with this key is found in the OCI spec, it will be // used in the output of Inspect(). InspectAnnotationCIDFile = "io.podman.annotations.cid-file" // InspectAnnotationAutoremove is used by Inspect to determine if a // container will be automatically removed on exit. // If an annotation with this key is found in the OCI spec and is one of // the two supported boolean values (InspectResponseTrue and // InspectResponseFalse) it will be used in the output of Inspect(). InspectAnnotationAutoremove = "io.podman.annotations.autoremove" // InspectAnnotationVolumesFrom is used by Inspect to identify // containers whose volumes are are being used by this container. // It is expected to be a comma-separated list of container names and/or // IDs. // If an annotation with this key is found in the OCI spec, it will be // used in the output of Inspect(). InspectAnnotationVolumesFrom = "io.podman.annotations.volumes-from" // InspectAnnotationPrivileged is used by Inspect to identify containers // which are privileged (IE, running with elevated privileges). // It is expected to be a boolean, populated by one of // InspectResponseTrue or InspectResponseFalse. // If an annotation with this key is found in the OCI spec, it will be // used in the output of Inspect(). InspectAnnotationPrivileged = "io.podman.annotations.privileged" // InspectAnnotationPublishAll is used by Inspect to identify containers // which have all the ports from their image published. // It is expected to be a boolean, populated by one of // InspectResponseTrue or InspectResponseFalse. // If an annotation with this key is found in the OCI spec, it will be // used in the output of Inspect(). InspectAnnotationPublishAll = "io.podman.annotations.publish-all" // InspectAnnotationInit is used by Inspect to identify containers that // mount an init binary in. // It is expected to be a boolean, populated by one of // InspectResponseTrue or InspectResponseFalse. // If an annotation with this key is found in the OCI spec, it will be // used in the output of Inspect(). InspectAnnotationInit = "io.podman.annotations.init" // InspectAnnotationLabel is used by Inspect to identify containers with // special SELinux-related settings. It is used to populate the output // of the SecurityOpt setting. // If an annotation with this key is found in the OCI spec, it will be // used in the output of Inspect(). InspectAnnotationLabel = "io.podman.annotations.label" // InspectAnnotationSeccomp is used by Inspect to identify containers // with special Seccomp-related settings. It is used to populate the // output of the SecurityOpt setting in Inspect. // If an annotation with this key is found in the OCI spec, it will be // used in the output of Inspect(). InspectAnnotationSeccomp = "io.podman.annotations.seccomp" // InspectAnnotationApparmor is used by Inspect to identify containers // with special Apparmor-related settings. It is used to populate the // output of the SecurityOpt setting. // If an annotation with this key is found in the OCI spec, it will be // used in the output of Inspect(). InspectAnnotationApparmor = "io.podman.annotations.apparmor" // InspectResponseTrue is a boolean True response for an inspect // annotation. InspectResponseTrue = "TRUE" // InspectResponseFalse is a boolean False response for an inspect // annotation. InspectResponseFalse = "FALSE" ) // InspectContainerData provides a detailed record of a container's configuration // and state as viewed by Libpod. // Large portions of this structure are defined such that the output is // compatible with `docker inspect` JSON, but additional fields have been added // as required to share information not in the original output. type InspectContainerData struct { ID string `json:"Id"` Created time.Time `json:"Created"` Path string `json:"Path"` Args []string `json:"Args"` State *InspectContainerState `json:"State"` ImageID string `json:"Image"` ImageName string `json:"ImageName"` Rootfs string `json:"Rootfs"` Pod string `json:"Pod"` ResolvConfPath string `json:"ResolvConfPath"` HostnamePath string `json:"HostnamePath"` HostsPath string `json:"HostsPath"` StaticDir string `json:"StaticDir"` OCIConfigPath string `json:"OCIConfigPath,omitempty"` OCIRuntime string `json:"OCIRuntime,omitempty"` LogPath string `json:"LogPath"` ConmonPidFile string `json:"ConmonPidFile"` Name string `json:"Name"` RestartCount int32 `json:"RestartCount"` Driver string `json:"Driver"` MountLabel string `json:"MountLabel"` ProcessLabel string `json:"ProcessLabel"` AppArmorProfile string `json:"AppArmorProfile"` EffectiveCaps []string `json:"EffectiveCaps"` BoundingCaps []string `json:"BoundingCaps"` ExecIDs []string `json:"ExecIDs"` GraphDriver *driver.Data `json:"GraphDriver"` SizeRw int64 `json:"SizeRw,omitempty"` SizeRootFs int64 `json:"SizeRootFs,omitempty"` Mounts []InspectMount `json:"Mounts"` Dependencies []string `json:"Dependencies"` NetworkSettings *InspectNetworkSettings `json:"NetworkSettings"` //TODO ExitCommand []string `json:"ExitCommand"` Namespace string `json:"Namespace"` IsInfra bool `json:"IsInfra"` Config *InspectContainerConfig `json:"Config"` HostConfig *InspectContainerHostConfig `json:"HostConfig"` } // InspectContainerConfig holds further data about how a container was initially // configured. type InspectContainerConfig struct { // Container hostname Hostname string `json:"Hostname"` // Container domain name - unused at present DomainName string `json:"Domainname"` // User the container was launched with User string `json:"User"` // Unused, at present AttachStdin bool `json:"AttachStdin"` // Unused, at present AttachStdout bool `json:"AttachStdout"` // Unused, at present AttachStderr bool `json:"AttachStderr"` // Whether the container creates a TTY Tty bool `json:"Tty"` // Whether the container leaves STDIN open OpenStdin bool `json:"OpenStdin"` // Whether STDIN is only left open once. // Presently not supported by Podman, unused. StdinOnce bool `json:"StdinOnce"` // Container environment variables Env []string `json:"Env"` // Container command Cmd []string `json:"Cmd"` // Container image Image string `json:"Image"` // Unused, at present. I've never seen this field populated. Volumes map[string]struct{} `json:"Volumes"` // Container working directory WorkingDir string `json:"WorkingDir"` // Container entrypoint Entrypoint string `json:"Entrypoint"` // On-build arguments - presently unused. More of Buildah's domain. OnBuild *string `json:"OnBuild"` // Container labels Labels map[string]string `json:"Labels"` // Container annotations Annotations map[string]string `json:"Annotations"` // Container stop signal StopSignal uint `json:"StopSignal"` // Configured healthcheck for the container Healthcheck *manifest.Schema2HealthConfig `json:"Healthcheck,omitempty"` } // InspectContainerHostConfig holds information used when the container was // created. // It's very much a Docker-specific struct, retained (mostly) as-is for // compatibility. We fill individual fields as best as we can, inferring as much // as possible from the spec and container config. // Some things cannot be inferred. These will be populated by spec annotations // (if available). // Field names are fixed for compatibility and cannot be changed. // As such, silence lint warnings about them. //nolint type InspectContainerHostConfig struct { // Binds contains an array of user-added mounts. // Both volume mounts and named volumes are included. // Tmpfs mounts are NOT included. // In 'docker inspect' this is separated into 'Binds' and 'Mounts' based // on how a mount was added. We do not make this distinction and do not // include a Mounts field in inspect. // Format: :[:] Binds []string `json:"Binds"` // ContainerIDFile is a file created during container creation to hold // the ID of the created container. // This is not handled within libpod and is stored in an annotation. ContainerIDFile string `json:"ContainerIDFile"` // LogConfig contains information on the container's logging backend LogConfig *InspectLogConfig `json:"LogConfig"` // NetworkMode is the configuration of the container's network // namespace. // Populated as follows: // default - A network namespace is being created and configured via CNI // none - A network namespace is being created, not configured via CNI // host - No network namespace created // container: - Using another container's network namespace // ns: - A path to a network namespace has been specified NetworkMode string `json:"NetworkMode"` // PortBindings contains the container's port bindings. // It is formatted as map[string][]InspectHostPort. // The string key here is formatted as / // and represents the container port. A single container port may be // bound to multiple host ports (on different IPs). PortBindings map[string][]InspectHostPort `json:"PortBindings"` // RestartPolicy contains the container's restart policy. RestartPolicy *InspectRestartPolicy `json:"RestartPolicy"` // AutoRemove is whether the container will be automatically removed on // exiting. // It is not handled directly within libpod and is stored in an // annotation. AutoRemove bool `json:"AutoRemove"` // VolumeDriver is presently unused and is retained for Docker // compatibility. VolumeDriver string `json:"VolumeDriver"` // VolumesFrom is a list of containers which this container uses volumes // from. This is not handled directly within libpod and is stored in an // annotation. // It is formatted as an array of container names and IDs. VolumesFrom []string `json:"VolumesFrom"` // CapAdd is a list of capabilities added to the container. // It is not directly stored by Libpod, and instead computed from the // capabilities listed in the container's spec, compared against a set // of default capabilities. CapAdd []string `json:"CapAdd"` // CapDrop is a list of capabilities removed from the container. // It is not directly stored by libpod, and instead computed from the // capabilities listed in the container's spec, compared against a set // of default capabilities. CapDrop []string `json:"CapDrop"` // Dns is a list of DNS nameservers that will be added to the // container's resolv.conf Dns []string `json:"Dns"` // DnsOptions is a list of DNS options that will be set in the // container's resolv.conf DnsOptions []string `json:"DnsOptions"` // DnsSearch is a list of DNS search domains that will be set in the // container's resolv.conf DnsSearch []string `json:"DnsSearch"` // ExtraHosts contains hosts that will be aded to the container's // /etc/hosts. ExtraHosts []string `json:"ExtraHosts"` // GroupAdd contains groups that the user inside the container will be // added to. GroupAdd []string `json:"GroupAdd"` // IpcMode represents the configuration of the container's IPC // namespace. // Populated as follows: // "" (empty string) - Default, an IPC namespace will be created // host - No IPC namespace created // container: - Using another container's IPC namespace // ns: - A path to an IPC namespace has been specified IpcMode string `json:"IpcMode"` // Cgroup contains the container's cgroup. It is presently not // populated. // TODO. Cgroup string `json:"Cgroup"` // Links is unused, and provided purely for Docker compatibility. Links []string `json:"Links"` // OOMScoreAdj is an adjustment that will be made to the container's OOM // score. OomScoreAdj int `json:"OomScoreAdj"` // PidMode represents the configuration of the container's PID // namespace. // Populated as follows: // "" (empty string) - Default, a PID namespace will be created // host - No PID namespace created // container: - Using another container's PID namespace // ns: - A path to a PID namespace has been specified PidMode string `json:"PidMode"` // Privileged indicates whether the container is running with elevated // privileges. // This has a very specific meaning in the Docker sense, so it's very // difficult to decode from the spec and config, and so is stored as an // annotation. Privileged bool `json:"Privileged"` // PublishAllPorts indicates whether image ports are being published. // This is not directly stored in libpod and is saved as an annotation. PublishAllPorts bool `json:"PublishAllPorts"` // ReadonlyRootfs is whether the container will be mounted read-only. ReadonlyRootfs bool `json:"ReadonlyRootfs"` // SecurityOpt is a list of security-related options that are set in the // container. SecurityOpt []string `json:"SecurityOpt"` // Tmpfs is a list of tmpfs filesystems that will be mounted into the // container. // It is a map of destination path to options for the mount. Tmpfs map[string]string `json:"Tmpfs"` // UTSMode represents the configuration of the container's UID // namespace. // Populated as follows: // "" (empty string) - Default, a UTS namespace will be created // host - no UTS namespace created // container: - Using another container's UTS namespace // ns: - A path to a UTS namespace has been specified UTSMode string `json:"UTSMode"` // UsernsMode represents the configuration of the container's user // namespace. // When running rootless, a user namespace is created outside of libpod // to allow some privileged operations. This will not be reflected here. // Populated as follows: // "" (empty string) - No user namespace will be created // private - The container will be run in a user namespace // container: - Using another container's user namespace // ns: - A path to a user namespace has been specified // TODO Rootless has an additional 'keep-id' option, presently not // reflected here. UsernsMode string `json:"UsernsMode"` // ShmSize is the size of the container's SHM device. ShmSize int64 `json:"ShmSize"` // Runtime is provided purely for Docker compatibility. // It is set unconditionally to "oci" as Podman does not presently // support non-OCI runtimes. Runtime string `json:"Runtime"` // ConsoleSize is an array of 2 integers showing the size of the // container's console. // It is only set if the container is creating a terminal. // TODO. ConsoleSize []uint `json:"ConsoleSize"` // Isolation is presently unused and provided solely for Docker // compatibility. Isolation string `json:"Isolation"` // CpuShares indicates the CPU resources allocated to the container. // It is a relative weight in the scheduler for assigning CPU time // versus other CGroups. CpuShares uint64 `json:"CpuShares"` // Memory indicates the memory resources allocated to the container. // This is the limit (in bytes) of RAM the container may use. Memory int64 `json:"Memory"` // NanoCpus indicates number of CPUs allocated to the container. // It is an integer where one full CPU is indicated by 1000000000 (one // billion). // Thus, 2.5 CPUs (fractional portions of CPUs are allowed) would be // 2500000000 (2.5 billion). // In 'docker inspect' this is set exclusively of two further options in // the output (CpuPeriod and CpuQuota) which are both used to implement // this functionality. // We can't distinguish here, so if CpuQuota is set to the default of // 100000, we will set both CpuQuota, CpuPeriod, and NanoCpus. If // CpuQuota is not the default, we will not set NanoCpus. NanoCpus int64 `json:"NanoCpus"` // CgroupParent is the CGroup parent of the container. // Only set if not default. CgroupParent string `json:"CgroupParent"` // BlkioWeight indicates the I/O resources allocated to the container. // It is a relative weight in the scheduler for assigning I/O time // versus other CGroups. BlkioWeight uint16 `json:"BlkioWeight"` // BlkioWeightDevice is an array of I/O resource priorities for // individual device nodes. // Unfortunately, the spec only stores the device's Major/Minor numbers // and not the path, which is used here. // Fortunately, the kernel provides an interface for retrieving the path // of a given node by major:minor at /sys/dev/. However, the exact path // in use may not be what was used in the original CLI invocation - // though it is guaranteed that the device node will be the same, and // using the given path will be functionally identical. BlkioWeightDevice []InspectBlkioWeightDevice `json:"BlkioWeightDevice"` // BlkioDeviceReadBps is an array of I/O throttle parameters for // individual device nodes. // This specifically sets read rate cap in bytes per second for device // nodes. // As with BlkioWeightDevice, we pull the path from /sys/dev, and we // don't guarantee the path will be identical to the original (though // the node will be). BlkioDeviceReadBps []InspectBlkioThrottleDevice `json:"BlkioDeviceReadBps"` // BlkioDeviceWriteBps is an array of I/O throttle parameters for // individual device nodes. // this specifically sets write rate cap in bytes per second for device // nodes. // as with BlkioWeightDevice, we pull the path from /sys/dev, and we // don't guarantee the path will be identical to the original (though // the node will be). BlkioDeviceWriteBps []InspectBlkioThrottleDevice `json:"BlkioDeviceWriteBps"` // BlkioDeviceReadIOps is an array of I/O throttle parameters for // individual device nodes. // This specifically sets the read rate cap in iops per second for // device nodes. // As with BlkioWeightDevice, we pull the path from /sys/dev, and we // don't guarantee the path will be identical to the original (though // the node will be). BlkioDeviceReadIOps []InspectBlkioThrottleDevice `json:"BlkioDeviceReadIOps"` // BlkioDeviceWriteIOps is an array of I/O throttle parameters for // individual device nodes. // This specifically sets the write rate cap in iops per second for // device nodes. // As with BlkioWeightDevice, we pull the path from /sys/dev, and we // don't guarantee the path will be identical to the original (though // the node will be). BlkioDeviceWriteIOps []InspectBlkioThrottleDevice `json:"BlkioDeviceWriteIOps"` // CpuPeriod is the length of a CPU period in microseconds. // It relates directly to CpuQuota. CpuPeriod uint64 `json:"CpuPeriod"` // CpuPeriod is the amount of time (in microseconds) that a container // can use the CPU in every CpuPeriod. CpuQuota int64 `json:"CpuQuota"` // CpuRealtimePeriod is the length of time (in microseconds) of the CPU // realtime period. If set to 0, no time will be allocated to realtime // tasks. CpuRealtimePeriod uint64 `json:"CpuRealtimePeriod"` // CpuRealtimeRuntime is the length of time (in microseconds) allocated // for realtime tasks within every CpuRealtimePeriod. CpuRealtimeRuntime int64 `json:"CpuRealtimeRuntime"` // CpusetCpus is the is the set of CPUs that the container will execute // on. Formatted as `0-3` or `0,2`. Default (if unset) is all CPUs. CpusetCpus string `json:"CpusetCpus"` // CpusetMems is the set of memory nodes the container will use. // Formatted as `0-3` or `0,2`. Default (if unset) is all memory nodes. CpusetMems string `json:"CpusetMems"` // Devices is a list of device nodes that will be added to the // container. // These are stored in the OCI spec only as type, major, minor while we // display the host path. We convert this with /sys/dev, but we cannot // guarantee that the host path will be identical - only that the actual // device will be. Devices []InspectDevice `json:"Devices"` // DiskQuota is the maximum amount of disk space the container may use // (in bytes). // Presently not populated. // TODO. DiskQuota uint64 `json:"DiskQuota"` // KernelMemory is the maximum amount of memory the kernel will devote // to the container. KernelMemory int64 `json:"KernelMemory"` // MemoryReservation is the reservation (soft limit) of memory available // to the container. Soft limits are warnings only and can be exceeded. MemoryReservation int64 `json:"MemoryReservation"` // MemorySwap is the total limit for all memory available to the // container, including swap. 0 indicates that there is no limit to the // amount of memory available. MemorySwap int64 `json:"MemorySwap"` // MemorySwappiness is the willingness of the kernel to page container // memory to swap. It is an integer from 0 to 100, with low numbers // being more likely to be put into swap. // -1, the default, will not set swappiness and use the system defaults. MemorySwappiness int64 `json:"MemorySwappiness"` // OomKillDisable indicates whether the kernel OOM killer is disabled // for the container. OomKillDisable bool `json:"OomKillDisable"` // Init indicates whether the container has an init mounted into it. Init bool `json:"Init,omitempty"` // PidsLimit is the maximum number of PIDs what may be created within // the container. 0, the default, indicates no limit. PidsLimit int64 `json:"PidsLimit"` // Ulimits is a set of ulimits that will be set within the container. Ulimits []InspectUlimit `json:"Ulimits"` // CpuCount is Windows-only and not presently implemented. CpuCount uint64 `json:"CpuCount"` // CpuPercent is Windows-only and not presently implemented. CpuPercent uint64 `json:"CpuPercent"` // IOMaximumIOps is Windows-only and not presently implemented. IOMaximumIOps uint64 `json:"IOMaximumIOps"` // IOMaximumBandwidth is Windows-only and not presently implemented. IOMaximumBandwidth uint64 `json:"IOMaximumBandwidth"` } // InspectLogConfig holds information about a container's configured log driver // and is presently unused. It is retained for Docker compatibility. type InspectLogConfig struct { Type string `json:"Type"` Config map[string]string `json:"Config"` //idk type, TODO } // InspectRestartPolicy holds information about the container's restart policy. type InspectRestartPolicy struct { // Name contains the container's restart policy. // Allowable values are "no" or "" (take no action), // "on-failure" (restart on non-zero exit code, with an optional max // retry count), and "always" (always restart on container stop, unless // explicitly requested by API). // Note that this is NOT actually a name of any sort - the poor naming // is for Docker compatibility. Name string `json:"Name"` // MaximumRetryCount is the maximum number of retries allowed if the // "on-failure" restart policy is in use. Not used if "on-failure" is // not set. MaximumRetryCount uint `json:"MaximumRetryCount"` } // InspectBlkioWeightDevice holds information about the relative weight // of an individual device node. Weights are used in the I/O scheduler to give // relative priority to some accesses. type InspectBlkioWeightDevice struct { // Path is the path to the device this applies to. Path string `json:"Path"` // Weight is the relative weight the scheduler will use when scheduling // I/O. Weight uint16 `json:"Weight"` } // InspectBlkioThrottleDevice holds information about a speed cap for a device // node. This cap applies to a specific operation (read, write, etc) on the given // node. type InspectBlkioThrottleDevice struct { // Path is the path to the device this applies to. Path string `json:"Path"` // Rate is the maximum rate. It is in either bytes per second or iops // per second, determined by where it is used - documentation will // indicate which is appropriate. Rate uint64 `json:"Rate"` } // InspectUlimit is a ulimit that will be applied to the container. type InspectUlimit struct { // Name is the name (type) of the ulimit. Name string `json:"Name"` // Soft is the soft limit that will be applied. Soft uint64 `json:"Soft"` // Hard is the hard limit that will be applied. Hard uint64 `json:"Hard"` } // InspectMount provides a record of a single mount in a container. It contains // fields for both named and normal volumes. Only user-specified volumes will be // included, and tmpfs volumes are not included even if the user specified them. type InspectMount struct { // Whether the mount is a volume or bind mount. Allowed values are // "volume" and "bind". Type string `json:"Type"` // The name of the volume. Empty for bind mounts. Name string `json:"Name,omptempty"` // The source directory for the volume. Source string `json:"Source"` // The destination directory for the volume. Specified as a path within // the container, as it would be passed into the OCI runtime. Destination string `json:"Destination"` // The driver used for the named volume. Empty for bind mounts. Driver string `json:"Driver"` // Contains SELinux :z/:Z mount options. Unclear what, if anything, else // goes in here. Mode string `json:"Mode"` // All remaining mount options. Additional data, not present in the // original output. Options []string `json:"Options"` // Whether the volume is read-write RW bool `json:"RW"` // Mount propagation for the mount. Can be empty if not specified, but // is always printed - no omitempty. Propagation string `json:"Propagation"` } // InspectDevice is a single device that will be mounted into the container. type InspectDevice struct { // PathOnHost is the path of the device on the host. PathOnHost string `json:"PathOnHost"` // PathInContainer is the path of the device within the container. PathInContainer string `json:"PathInContainer"` // CgroupPermissions is the permissions of the mounted device. // Presently not populated. // TODO. CgroupPermissions string `json:"CgroupPermissions"` } // InspectHostPort provides information on a port on the host that a container's // port is bound to. type InspectHostPort struct { // IP on the host we are bound to. "" if not specified (binding to all // IPs). HostIP string `json:"HostIp"` // Port on the host we are bound to. No special formatting - just an // integer stuffed into a string. HostPort string `json:"HostPort"` } // InspectContainerState provides a detailed record of a container's current // state. It is returned as part of InspectContainerData. // As with InspectContainerData, many portions of this struct are matched to // 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"` Healthcheck HealthCheckResults `json:"Healthcheck,omitempty"` } // InspectNetworkSettings holds information about the network settings of the // container. // Many fields are maintained only for compatibility with `docker inspect` and // are unused within Libpod. type InspectNetworkSettings struct { Bridge string `json:"Bridge"` SandboxID string `json:"SandboxID"` HairpinMode bool `json:"HairpinMode"` LinkLocalIPv6Address string `json:"LinkLocalIPv6Address"` LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen"` Ports []ocicni.PortMapping `json:"Ports"` SandboxKey string `json:"SandboxKey"` SecondaryIPAddresses []string `json:"SecondaryIPAddresses"` SecondaryIPv6Addresses []string `json:"SecondaryIPv6Addresses"` EndpointID string `json:"EndpointID"` Gateway string `json:"Gateway"` GlobalIPv6Address string `json:"GlobalIPv6Address"` GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen"` IPAddress string `json:"IPAddress"` IPPrefixLen int `json:"IPPrefixLen"` IPv6Gateway string `json:"IPv6Gateway"` MacAddress string `json:"MacAddress"` } // Inspect a container for low-level information func (c *Container) Inspect(size bool) (*InspectContainerData, error) { if !c.batched { c.lock.Lock() defer c.lock.Unlock() if err := c.syncContainer(); err != nil { return nil, err } } storeCtr, err := c.runtime.store.Container(c.ID()) if err != nil { return nil, errors.Wrapf(err, "error getting container from store %q", c.ID()) } layer, err := c.runtime.store.Layer(storeCtr.LayerID) if err != nil { return nil, errors.Wrapf(err, "error reading information about layer %q", storeCtr.LayerID) } driverData, err := driver.GetDriverData(c.runtime.store, layer.ID) if err != nil { return nil, errors.Wrapf(err, "error getting graph driver info %q", c.ID()) } return c.getContainerInspectData(size, driverData) } func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) (*InspectContainerData, error) { config := c.config runtimeInfo := c.state ctrSpec, err := c.specFromState() if err != nil { return nil, err } // Process is allowed to be nil in the stateSpec args := []string{} if config.Spec.Process != nil { args = config.Spec.Process.Args } var path string if len(args) > 0 { path = args[0] } if len(args) > 1 { args = args[1:] } execIDs := []string{} for id := range c.state.ExecSessions { execIDs = append(execIDs, id) } resolvPath := "" hostsPath := "" hostnamePath := "" if c.state.BindMounts != nil { if getPath, ok := c.state.BindMounts["/etc/resolv.conf"]; ok { resolvPath = getPath } if getPath, ok := c.state.BindMounts["/etc/hosts"]; ok { hostsPath = getPath } if getPath, ok := c.state.BindMounts["/etc/hostname"]; ok { hostnamePath = getPath } } namedVolumes, mounts := c.sortUserVolumes(ctrSpec) inspectMounts, err := c.getInspectMounts(ctrSpec, namedVolumes, mounts) if err != nil { return nil, err } data := &InspectContainerData{ ID: config.ID, Created: config.CreatedTime, Path: path, Args: args, State: &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, }, ImageID: config.RootfsImageID, ImageName: config.RootfsImageName, ExitCommand: config.ExitCommand, Namespace: config.Namespace, Rootfs: config.Rootfs, Pod: config.Pod, ResolvConfPath: resolvPath, HostnamePath: hostnamePath, HostsPath: hostsPath, StaticDir: config.StaticDir, LogPath: config.LogPath, OCIRuntime: config.OCIRuntime, ConmonPidFile: config.ConmonPidFile, Name: config.Name, RestartCount: int32(runtimeInfo.RestartCount), Driver: driverData.Name, MountLabel: config.MountLabel, ProcessLabel: config.ProcessLabel, EffectiveCaps: ctrSpec.Process.Capabilities.Effective, BoundingCaps: ctrSpec.Process.Capabilities.Bounding, AppArmorProfile: ctrSpec.Process.ApparmorProfile, ExecIDs: execIDs, GraphDriver: driverData, Mounts: inspectMounts, Dependencies: c.Dependencies(), NetworkSettings: &InspectNetworkSettings{ Bridge: "", // TODO SandboxID: "", // TODO - is this even relevant? HairpinMode: false, // TODO LinkLocalIPv6Address: "", // TODO - do we even support IPv6? LinkLocalIPv6PrefixLen: 0, // TODO - do we even support IPv6? Ports: []ocicni.PortMapping{}, // TODO - maybe worth it to put this in Docker format? SandboxKey: "", // Network namespace path SecondaryIPAddresses: nil, // TODO - do we support this? SecondaryIPv6Addresses: nil, // TODO - do we support this? EndpointID: "", // TODO - is this even relevant? Gateway: "", // TODO GlobalIPv6Address: "", GlobalIPv6PrefixLen: 0, IPAddress: "", IPPrefixLen: 0, IPv6Gateway: "", MacAddress: "", // TODO }, IsInfra: c.IsInfra(), } if c.state.ConfigPath != "" { data.OCIConfigPath = c.state.ConfigPath } if c.config.HealthCheckConfig != nil { // This container has a healthcheck defined in it; we need to add it's state healthCheckState, err := c.GetHealthCheckLog() if err != nil { // An error here is not considered fatal; no health state will be displayed logrus.Error(err) } else { data.State.Healthcheck = healthCheckState } } // Copy port mappings into network settings if config.PortMappings != nil { data.NetworkSettings.Ports = config.PortMappings } // Get information on the container's network namespace (if present) data = c.getContainerNetworkInfo(data) inspectConfig, err := c.generateInspectContainerConfig(ctrSpec) if err != nil { return nil, err } data.Config = inspectConfig hostConfig, err := c.generateInspectContainerHostConfig(ctrSpec, namedVolumes, mounts) if err != nil { return nil, err } data.HostConfig = hostConfig if size { rootFsSize, err := c.rootFsSize() if err != nil { logrus.Errorf("error getting rootfs size %q: %v", config.ID, err) } rwSize, err := c.rwSize() if err != nil { logrus.Errorf("error getting rw size %q: %v", config.ID, err) } data.SizeRootFs = rootFsSize data.SizeRw = rwSize } return data, nil } // Get inspect-formatted mounts list. // Only includes user-specified mounts. Only includes bind mounts and named // volumes, not tmpfs volumes. func (c *Container) getInspectMounts(ctrSpec *spec.Spec, namedVolumes []*ContainerNamedVolume, mounts []spec.Mount) ([]InspectMount, error) { inspectMounts := []InspectMount{} // No mounts, return early if len(c.config.UserVolumes) == 0 { return inspectMounts, nil } for _, volume := range namedVolumes { mountStruct := InspectMount{} mountStruct.Type = "volume" mountStruct.Destination = volume.Dest mountStruct.Name = volume.Name // For src and driver, we need to look up the named // volume. volFromDB, err := c.runtime.state.Volume(volume.Name) if err != nil { return nil, errors.Wrapf(err, "error looking up volume %s in container %s config", volume.Name, c.ID()) } mountStruct.Driver = volFromDB.Driver() mountStruct.Source = volFromDB.MountPoint() parseMountOptionsForInspect(volume.Options, &mountStruct) inspectMounts = append(inspectMounts, mountStruct) } for _, mount := range mounts { // It's a mount. // Is it a tmpfs? If so, discard. if mount.Type == "tmpfs" { continue } mountStruct := InspectMount{} mountStruct.Type = "bind" mountStruct.Source = mount.Source mountStruct.Destination = mount.Destination parseMountOptionsForInspect(mount.Options, &mountStruct) inspectMounts = append(inspectMounts, mountStruct) } return inspectMounts, nil } // Parse mount options so we can populate them in the mount structure. // The mount passed in will be modified. func parseMountOptionsForInspect(options []string, mount *InspectMount) { isRW := true mountProp := "" zZ := "" otherOpts := []string{} // Some of these may be overwritten if the user passes us garbage opts // (for example, [ro,rw]) // We catch these on the Podman side, so not a problem there, but other // users of libpod who do not properly validate mount options may see // this. // Not really worth dealing with on our end - garbage in, garbage out. for _, opt := range options { switch opt { case "ro": isRW = false case "rw": // Do nothing, silently discard case "shared", "slave", "private", "rshared", "rslave", "rprivate": mountProp = opt case "z", "Z": zZ = opt default: otherOpts = append(otherOpts, opt) } } mount.RW = isRW mount.Propagation = mountProp mount.Mode = zZ mount.Options = otherOpts } // Generate the InspectContainerConfig struct for the Config field of Inspect. func (c *Container) generateInspectContainerConfig(spec *spec.Spec) (*InspectContainerConfig, error) { ctrConfig := new(InspectContainerConfig) ctrConfig.Hostname = c.Hostname() ctrConfig.User = c.config.User if spec.Process != nil { ctrConfig.Tty = spec.Process.Terminal ctrConfig.Env = []string{} ctrConfig.Env = append(ctrConfig.Env, spec.Process.Env...) ctrConfig.WorkingDir = spec.Process.Cwd } ctrConfig.OpenStdin = c.config.Stdin ctrConfig.Image = c.config.RootfsImageName // Leave empty is not explicitly overwritten by user if len(c.config.Command) != 0 { ctrConfig.Cmd = []string{} ctrConfig.Cmd = append(ctrConfig.Cmd, c.config.Command...) } // Leave empty if not explicitly overwritten by user if len(c.config.Entrypoint) != 0 { ctrConfig.Entrypoint = strings.Join(c.config.Entrypoint, " ") } if len(c.config.Labels) != 0 { ctrConfig.Labels = make(map[string]string) for k, v := range c.config.Labels { ctrConfig.Labels[k] = v } } if len(spec.Annotations) != 0 { ctrConfig.Annotations = make(map[string]string) for k, v := range spec.Annotations { ctrConfig.Annotations[k] = v } } ctrConfig.StopSignal = c.config.StopSignal // TODO: should JSON deep copy this to ensure internal pointers don't // leak. ctrConfig.Healthcheck = c.config.HealthCheckConfig return ctrConfig, nil } // Generate the InspectContainerHostConfig struct for the HostConfig field of // Inspect. func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, namedVolumes []*ContainerNamedVolume, mounts []spec.Mount) (*InspectContainerHostConfig, error) { hostConfig := new(InspectContainerHostConfig) logConfig := new(InspectLogConfig) logConfig.Type = c.config.LogDriver hostConfig.LogConfig = logConfig restartPolicy := new(InspectRestartPolicy) restartPolicy.Name = c.config.RestartPolicy restartPolicy.MaximumRetryCount = c.config.RestartRetries hostConfig.RestartPolicy = restartPolicy hostConfig.Dns = make([]string, 0, len(c.config.DNSServer)) for _, dns := range c.config.DNSServer { hostConfig.Dns = append(hostConfig.Dns, dns.String()) } hostConfig.DnsOptions = make([]string, 0, len(c.config.DNSOption)) hostConfig.DnsOptions = append(hostConfig.DnsOptions, c.config.DNSOption...) hostConfig.DnsSearch = make([]string, 0, len(c.config.DNSSearch)) hostConfig.DnsSearch = append(hostConfig.DnsSearch, c.config.DNSSearch...) hostConfig.ExtraHosts = make([]string, 0, len(c.config.HostAdd)) hostConfig.ExtraHosts = append(hostConfig.ExtraHosts, c.config.HostAdd...) 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.ReadonlyRootfs = ctrSpec.Root.Readonly hostConfig.ShmSize = c.config.ShmSize hostConfig.Runtime = "oci" // This is very expensive to initialize. // So we don't want to initialize it unless we absolutely have to - IE, // there are things that require a major:minor to path translation. var deviceNodes map[string]string // Annotations if ctrSpec.Annotations != nil { hostConfig.ContainerIDFile = ctrSpec.Annotations[InspectAnnotationCIDFile] if ctrSpec.Annotations[InspectAnnotationAutoremove] == InspectResponseTrue { hostConfig.AutoRemove = true } if ctrs, ok := ctrSpec.Annotations[InspectAnnotationVolumesFrom]; ok { hostConfig.VolumesFrom = strings.Split(ctrs, ",") } if ctrSpec.Annotations[InspectAnnotationPrivileged] == InspectResponseTrue { hostConfig.Privileged = true } if ctrSpec.Annotations[InspectAnnotationInit] == InspectResponseTrue { hostConfig.Init = true } if label, ok := ctrSpec.Annotations[InspectAnnotationLabel]; ok { hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("label=%s", label)) } if seccomp, ok := ctrSpec.Annotations[InspectAnnotationSeccomp]; ok { hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("seccomp=%s", seccomp)) } if apparmor, ok := ctrSpec.Annotations[InspectAnnotationApparmor]; ok { hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("apparmor=%s", apparmor)) } } // Resource limits if ctrSpec.Linux != nil { if ctrSpec.Linux.Resources != nil { if ctrSpec.Linux.Resources.CPU != nil { if ctrSpec.Linux.Resources.CPU.Shares != nil { hostConfig.CpuShares = *ctrSpec.Linux.Resources.CPU.Shares } if ctrSpec.Linux.Resources.CPU.Period != nil { hostConfig.CpuPeriod = *ctrSpec.Linux.Resources.CPU.Period } if ctrSpec.Linux.Resources.CPU.Quota != nil { hostConfig.CpuQuota = *ctrSpec.Linux.Resources.CPU.Quota } if ctrSpec.Linux.Resources.CPU.RealtimePeriod != nil { hostConfig.CpuRealtimePeriod = *ctrSpec.Linux.Resources.CPU.RealtimePeriod } if ctrSpec.Linux.Resources.CPU.RealtimeRuntime != nil { hostConfig.CpuRealtimeRuntime = *ctrSpec.Linux.Resources.CPU.RealtimeRuntime } hostConfig.CpusetCpus = ctrSpec.Linux.Resources.CPU.Cpus hostConfig.CpusetMems = ctrSpec.Linux.Resources.CPU.Mems } if ctrSpec.Linux.Resources.Memory != nil { 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 } if ctrSpec.Linux.Resources.Memory.Swap != nil { hostConfig.MemorySwap = *ctrSpec.Linux.Resources.Memory.Swap } if ctrSpec.Linux.Resources.Memory.Swappiness != nil { hostConfig.MemorySwappiness = int64(*ctrSpec.Linux.Resources.Memory.Swappiness) } else { // Swappiness has a default of -1 hostConfig.MemorySwappiness = -1 } if ctrSpec.Linux.Resources.Memory.DisableOOMKiller != nil { hostConfig.OomKillDisable = *ctrSpec.Linux.Resources.Memory.DisableOOMKiller } } if ctrSpec.Linux.Resources.Pids != nil { hostConfig.PidsLimit = ctrSpec.Linux.Resources.Pids.Limit } if ctrSpec.Linux.Resources.BlockIO != nil { if ctrSpec.Linux.Resources.BlockIO.Weight != nil { hostConfig.BlkioWeight = *ctrSpec.Linux.Resources.BlockIO.Weight } hostConfig.BlkioWeightDevice = []InspectBlkioWeightDevice{} for _, dev := range ctrSpec.Linux.Resources.BlockIO.WeightDevice { key := fmt.Sprintf("%d:%d", dev.Major, dev.Minor) // TODO: how do we handle LeafWeight vs // Weight? For now, ignore anything // without Weight set. if dev.Weight == nil { logrus.Warnf("Ignoring weight device %s as it lacks a weight", key) continue } if deviceNodes == nil { nodes, err := util.FindDeviceNodes() if err != nil { return nil, err } deviceNodes = nodes } path, ok := deviceNodes[key] if !ok { logrus.Warnf("Could not locate weight device %s in system devices", key) continue } weightDev := InspectBlkioWeightDevice{} weightDev.Path = path weightDev.Weight = *dev.Weight hostConfig.BlkioWeightDevice = append(hostConfig.BlkioWeightDevice, weightDev) } handleThrottleDevice := func(devs []spec.LinuxThrottleDevice) ([]InspectBlkioThrottleDevice, error) { out := []InspectBlkioThrottleDevice{} for _, dev := range devs { key := fmt.Sprintf("%d:%d", dev.Major, dev.Minor) if deviceNodes == nil { nodes, err := util.FindDeviceNodes() if err != nil { return nil, err } deviceNodes = nodes } path, ok := deviceNodes[key] if !ok { logrus.Warnf("Could not locate throttle device %s in system devices", key) continue } throttleDev := InspectBlkioThrottleDevice{} throttleDev.Path = path throttleDev.Rate = dev.Rate out = append(out, throttleDev) } return out, nil } readBps, err := handleThrottleDevice(ctrSpec.Linux.Resources.BlockIO.ThrottleReadBpsDevice) if err != nil { return nil, err } hostConfig.BlkioDeviceReadBps = readBps writeBps, err := handleThrottleDevice(ctrSpec.Linux.Resources.BlockIO.ThrottleWriteBpsDevice) if err != nil { return nil, err } hostConfig.BlkioDeviceWriteBps = writeBps readIops, err := handleThrottleDevice(ctrSpec.Linux.Resources.BlockIO.ThrottleReadIOPSDevice) if err != nil { return nil, err } hostConfig.BlkioDeviceReadIOps = readIops writeIops, err := handleThrottleDevice(ctrSpec.Linux.Resources.BlockIO.ThrottleWriteIOPSDevice) if err != nil { return nil, err } hostConfig.BlkioDeviceWriteIOps = writeIops } } } // NanoCPUs. // This is only calculated if CpuPeriod == 100000. // It is given in nanoseconds, versus the microseconds used elsewhere - // so multiply by 10000 (not sure why, but 1000 is off by 10). if hostConfig.CpuPeriod == 100000 { hostConfig.NanoCpus = 10000 * hostConfig.CpuQuota } // Bind mounts, formatted as src:dst. // We'll be appending some options that aren't necessarily in the // original command line... but no helping that from inside libpod. binds := []string{} tmpfs := make(map[string]string) for _, namedVol := range namedVolumes { if len(namedVol.Options) > 0 { binds = append(binds, fmt.Sprintf("%s:%s:%s", namedVol.Name, namedVol.Dest, strings.Join(namedVol.Options, ","))) } else { binds = append(binds, fmt.Sprintf("%s:%s", namedVol.Name, namedVol.Dest)) } } for _, mount := range mounts { if mount.Type == "tmpfs" { tmpfs[mount.Destination] = strings.Join(mount.Options, ",") } else { // TODO - maybe we should parse for empty source/destination // here. Would be confusing if we print just a bare colon. if len(mount.Options) > 0 { binds = append(binds, fmt.Sprintf("%s:%s:%s", mount.Source, mount.Destination, strings.Join(mount.Options, ","))) } else { binds = append(binds, fmt.Sprintf("%s:%s", mount.Source, mount.Destination)) } } } hostConfig.Binds = binds hostConfig.Tmpfs = tmpfs // Network mode parsing. networkMode := "" if c.config.CreateNetNS { networkMode = "default" } else if c.config.NetNsCtr != "" { networkMode = fmt.Sprintf("container:%s", c.config.NetNsCtr) } else { // Find the spec's network namespace. // If there is none, it's host networking. // If there is one and it has a path, it's "ns:". foundNetNS := false for _, ns := range ctrSpec.Linux.Namespaces { if ns.Type == spec.NetworkNamespace { foundNetNS = true if ns.Path != "" { networkMode = fmt.Sprintf("ns:%s", ns.Path) } else { networkMode = "none" } break } } if !foundNetNS { networkMode = "host" } } hostConfig.NetworkMode = networkMode // Port bindings. // Only populate if we're using CNI to configure the network. portBindings := make(map[string][]InspectHostPort) if c.config.CreateNetNS { for _, port := range c.config.PortMappings { key := fmt.Sprintf("%d/%s", port.ContainerPort, port.Protocol) hostPorts := portBindings[key] if hostPorts == nil { hostPorts = []InspectHostPort{} } hostPorts = append(hostPorts, InspectHostPort{ HostIP: port.HostIP, HostPort: fmt.Sprintf("%d", port.HostPort), }) portBindings[key] = hostPorts } } hostConfig.PortBindings = portBindings // Cap add and cap drop. // We need a default set of capabilities to compare against. // The OCI generate package has one, and is commonly used, so we'll // use it. // Problem: there are 5 sets of capabilities. // Use the bounding set for this computation, it's the most encompassing // (but still not perfect). capAdd := []string{} capDrop := []string{} // No point in continuing if we got a spec without a Process block... if ctrSpec.Process != nil { // Max an O(1) lookup table for default bounding caps. boundingCaps := make(map[string]bool) g, err := generate.New("linux") if err != nil { return nil, err } if !hostConfig.Privileged { for _, cap := range g.Config.Process.Capabilities.Bounding { boundingCaps[cap] = true } } else { // If we are privileged, use all caps. for _, cap := range capability.List() { if g.HostSpecific && cap > validate.LastCap() { continue } boundingCaps[fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String()))] = true } } // Iterate through spec caps. // If it's not in default bounding caps, it was added. // If it is, delete from the default set. Whatever remains after // we finish are the dropped caps. for _, cap := range ctrSpec.Process.Capabilities.Bounding { if _, ok := boundingCaps[cap]; ok { delete(boundingCaps, cap) } else { capAdd = append(capAdd, cap) } } for cap := range boundingCaps { capDrop = append(capDrop, cap) } } hostConfig.CapAdd = capAdd hostConfig.CapDrop = capDrop // IPC Namespace mode ipcMode := "" if c.config.IPCNsCtr != "" { ipcMode = fmt.Sprintf("container:%s", c.config.IPCNsCtr) } else { // Locate the spec's IPC namespace. // If there is none, it's ipc=host. // If there is one and it has a path, it's "ns:". // If no path, it's default - the empty string. foundIPCNS := false for _, ns := range ctrSpec.Linux.Namespaces { if ns.Type == spec.IPCNamespace { foundIPCNS = true if ns.Path != "" { ipcMode = fmt.Sprintf("ns:%s", ns.Path) } break } } if !foundIPCNS { ipcMode = "host" } } hostConfig.IpcMode = ipcMode // CGroup parent // Need to check if it's the default, and not print if so. defaultCgroupParent := "" switch c.runtime.config.CgroupManager { case CgroupfsCgroupsManager: defaultCgroupParent = CgroupfsDefaultCgroupParent case SystemdCgroupsManager: defaultCgroupParent = SystemdDefaultCgroupParent } if c.config.CgroupParent != defaultCgroupParent { hostConfig.CgroupParent = c.config.CgroupParent } // PID namespace mode pidMode := "" if c.config.PIDNsCtr != "" { pidMode = fmt.Sprintf("container:%s", c.config.PIDNsCtr) } else { // Locate the spec's PID namespace. // If there is none, it's pid=host. // If there is one and it has a path, it's "ns:". // If there is no path, it's default - the empty string. foundPIDNS := false for _, ns := range ctrSpec.Linux.Namespaces { if ns.Type == spec.PIDNamespace { foundPIDNS = true if ns.Path != "" { pidMode = fmt.Sprintf("ns:%s", ns.Path) } break } } if !foundPIDNS { pidMode = "host" } } hostConfig.PidMode = pidMode // UTS namespace mode utsMode := "" if c.config.UTSNsCtr != "" { utsMode = fmt.Sprintf("container:%s", c.config.UTSNsCtr) } else { // Locate the spec's UTS namespace. // If there is none, it's uts=host. // If there is one and it has a path, it's "ns:". // If there is no path, it's default - the empty string. foundUTSNS := false for _, ns := range ctrSpec.Linux.Namespaces { if ns.Type == spec.UTSNamespace { foundUTSNS = true if ns.Path != "" { utsMode = fmt.Sprintf("ns:%s", ns.Path) } break } } if !foundUTSNS { utsMode = "host" } } hostConfig.UTSMode = utsMode // User namespace mode usernsMode := "" if c.config.UserNsCtr != "" { usernsMode = fmt.Sprintf("container:%s", c.config.UserNsCtr) } else { // Locate the spec's user namespace. // If there is none, it's default - the empty string. // If there is one, it's "private" if no path, or "ns:" if // there's a path. for _, ns := range ctrSpec.Linux.Namespaces { if ns.Type == spec.UserNamespace { if ns.Path != "" { usernsMode = fmt.Sprintf("ns:%s", ns.Path) } else { usernsMode = "private" } } } } hostConfig.UsernsMode = usernsMode // Devices // Do not include if privileged - assumed that all devices will be // included. hostConfig.Devices = []InspectDevice{} if ctrSpec.Linux != nil && !hostConfig.Privileged { for _, dev := range ctrSpec.Linux.Devices { key := fmt.Sprintf("%d:%d", dev.Major, dev.Minor) if deviceNodes == nil { nodes, err := util.FindDeviceNodes() if err != nil { return nil, err } deviceNodes = nodes } path, ok := deviceNodes[key] if !ok { logrus.Warnf("Could not locate device %s on host", key) continue } newDev := InspectDevice{} newDev.PathOnHost = path newDev.PathInContainer = dev.Path hostConfig.Devices = append(hostConfig.Devices, newDev) } } // Ulimits hostConfig.Ulimits = []InspectUlimit{} if ctrSpec.Process != nil { for _, limit := range ctrSpec.Process.Rlimits { newLimit := InspectUlimit{} newLimit.Name = limit.Type newLimit.Soft = limit.Soft newLimit.Hard = limit.Hard hostConfig.Ulimits = append(hostConfig.Ulimits, newLimit) } } // Terminal size // We can't actually get this for now... // So default to something sane. // TODO: Populate this. hostConfig.ConsoleSize = []uint{0, 0} return hostConfig, nil }