mirror of https://github.com/containers/podman.git
				
				
				
			
		
			
				
	
	
		
			514 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			514 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
| package libpod
 | |
| 
 | |
| import (
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/containers/image/manifest"
 | |
| 	"github.com/containers/libpod/libpod/driver"
 | |
| 	"github.com/cri-o/ocicni/pkg/ocicni"
 | |
| 	spec "github.com/opencontainers/runtime-spec/specs-go"
 | |
| 	"github.com/pkg/errors"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| )
 | |
| 
 | |
| // 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"`
 | |
| 	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"`
 | |
| }
 | |
| 
 | |
| // 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"`
 | |
| }
 | |
| 
 | |
| // 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"`
 | |
| }
 | |
| 
 | |
| // 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"`
 | |
| 	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
 | |
| 	spec, err := c.specFromState()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// Process is allowed to be nil in the spec
 | |
| 	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
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	mounts, err := c.getInspectMounts(spec)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	data := &InspectContainerData{
 | |
| 		ID:      config.ID,
 | |
| 		Created: config.CreatedTime,
 | |
| 		Path:    path,
 | |
| 		Args:    args,
 | |
| 		State: &InspectContainerState{
 | |
| 			OciVersion: spec.Version,
 | |
| 			Status:     runtimeInfo.State.String(),
 | |
| 			Running:    runtimeInfo.State == ContainerStateRunning,
 | |
| 			Paused:     runtimeInfo.State == ContainerStatePaused,
 | |
| 			OOMKilled:  runtimeInfo.OOMKilled,
 | |
| 			Dead:       runtimeInfo.State.String() == "bad state",
 | |
| 			Pid:        runtimeInfo.PID,
 | |
| 			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,
 | |
| 		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:   spec.Process.Capabilities.Effective,
 | |
| 		BoundingCaps:    spec.Process.Capabilities.Bounding,
 | |
| 		AppArmorProfile: spec.Process.ApparmorProfile,
 | |
| 		ExecIDs:         execIDs,
 | |
| 		GraphDriver:     driverData,
 | |
| 		Mounts:          mounts,
 | |
| 		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(spec)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	data.Config = inspectConfig
 | |
| 
 | |
| 	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) ([]InspectMount, error) {
 | |
| 	inspectMounts := []InspectMount{}
 | |
| 
 | |
| 	// No mounts, return early
 | |
| 	if len(c.config.UserVolumes) == 0 {
 | |
| 		return inspectMounts, nil
 | |
| 	}
 | |
| 
 | |
| 	// We need to parse all named volumes and mounts into maps, so we don't
 | |
| 	// end up with repeated lookups for each user volume.
 | |
| 	// Map destination to struct, as destination is what is stored in
 | |
| 	// UserVolumes.
 | |
| 	namedVolumes := make(map[string]*ContainerNamedVolume)
 | |
| 	mounts := make(map[string]spec.Mount)
 | |
| 	for _, namedVol := range c.config.NamedVolumes {
 | |
| 		namedVolumes[namedVol.Dest] = namedVol
 | |
| 	}
 | |
| 	for _, mount := range ctrSpec.Mounts {
 | |
| 		mounts[mount.Destination] = mount
 | |
| 	}
 | |
| 
 | |
| 	for _, vol := range c.config.UserVolumes {
 | |
| 		// We need to look up the volumes.
 | |
| 		// First: is it a named volume?
 | |
| 		if volume, ok := namedVolumes[vol]; ok {
 | |
| 			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)
 | |
| 		} else if mount, ok := mounts[vol]; ok {
 | |
| 			// 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)
 | |
| 		}
 | |
| 		// We couldn't find a mount. Log a warning.
 | |
| 		logrus.Warnf("Could not find mount at destination %q when building inspect output for container %s", vol, c.ID())
 | |
| 	}
 | |
| 
 | |
| 	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{}
 | |
| 		for _, val := range spec.Process.Env {
 | |
| 			ctrConfig.Env = append(ctrConfig.Env, val)
 | |
| 		}
 | |
| 		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{}
 | |
| 		for _, val := range c.config.Command {
 | |
| 			ctrConfig.Cmd = append(ctrConfig.Cmd, val)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// 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
 | |
| }
 |