package daemon import ( "fmt" "time" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions/v1p20" "github.com/docker/docker/container" "github.com/docker/docker/daemon/network" ) // ContainerInspect returns low-level information about a // container. Returns an error if the container cannot be found, or if // there is an error getting the data. func (daemon *Daemon) ContainerInspect(name string, size bool, version string) (interface{}, error) { switch { case versions.LessThan(version, "1.20"): return daemon.containerInspectPre120(name) case versions.Equal(version, "1.20"): return daemon.containerInspect120(name) } return daemon.ContainerInspectCurrent(name, size) } // ContainerInspectCurrent returns low-level information about a // container in a most recent api version. func (daemon *Daemon) ContainerInspectCurrent(name string, size bool) (*types.ContainerJSON, error) { container, err := daemon.GetContainer(name) if err != nil { return nil, err } container.Lock() defer container.Unlock() base, err := daemon.getInspectData(container, size) if err != nil { return nil, err } apiNetworks := make(map[string]*networktypes.EndpointSettings) for name, epConf := range container.NetworkSettings.Networks { if epConf.EndpointSettings != nil { apiNetworks[name] = epConf.EndpointSettings } } mountPoints := addMountPoints(container) networkSettings := &types.NetworkSettings{ NetworkSettingsBase: types.NetworkSettingsBase{ Bridge: container.NetworkSettings.Bridge, SandboxID: container.NetworkSettings.SandboxID, HairpinMode: container.NetworkSettings.HairpinMode, LinkLocalIPv6Address: container.NetworkSettings.LinkLocalIPv6Address, LinkLocalIPv6PrefixLen: container.NetworkSettings.LinkLocalIPv6PrefixLen, Ports: container.NetworkSettings.Ports, SandboxKey: container.NetworkSettings.SandboxKey, SecondaryIPAddresses: container.NetworkSettings.SecondaryIPAddresses, SecondaryIPv6Addresses: container.NetworkSettings.SecondaryIPv6Addresses, }, DefaultNetworkSettings: daemon.getDefaultNetworkSettings(container.NetworkSettings.Networks), Networks: apiNetworks, } return &types.ContainerJSON{ ContainerJSONBase: base, Mounts: mountPoints, Config: container.Config, NetworkSettings: networkSettings, }, nil } // containerInspect120 serializes the master version of a container into a json type. func (daemon *Daemon) containerInspect120(name string) (*v1p20.ContainerJSON, error) { container, err := daemon.GetContainer(name) if err != nil { return nil, err } container.Lock() defer container.Unlock() base, err := daemon.getInspectData(container, false) if err != nil { return nil, err } mountPoints := addMountPoints(container) config := &v1p20.ContainerConfig{ Config: container.Config, MacAddress: container.Config.MacAddress, NetworkDisabled: container.Config.NetworkDisabled, ExposedPorts: container.Config.ExposedPorts, VolumeDriver: container.HostConfig.VolumeDriver, } networkSettings := daemon.getBackwardsCompatibleNetworkSettings(container.NetworkSettings) return &v1p20.ContainerJSON{ ContainerJSONBase: base, Mounts: mountPoints, Config: config, NetworkSettings: networkSettings, }, nil } func (daemon *Daemon) getInspectData(container *container.Container, size bool) (*types.ContainerJSONBase, error) { // make a copy to play with hostConfig := *container.HostConfig children := daemon.children(container) hostConfig.Links = nil // do not expose the internal structure for linkAlias, child := range children { hostConfig.Links = append(hostConfig.Links, fmt.Sprintf("%s:%s", child.Name, linkAlias)) } // We merge the Ulimits from hostConfig with daemon default daemon.mergeUlimits(&hostConfig) var containerHealth *types.Health if container.State.Health != nil { containerHealth = &types.Health{ Status: container.State.Health.Status, FailingStreak: container.State.Health.FailingStreak, Log: append([]*types.HealthcheckResult{}, container.State.Health.Log...), } } containerState := &types.ContainerState{ Status: container.State.StateString(), Running: container.State.Running, Paused: container.State.Paused, Restarting: container.State.Restarting, OOMKilled: container.State.OOMKilled, Dead: container.State.Dead, Pid: container.State.Pid, ExitCode: container.State.ExitCode(), Error: container.State.Error(), StartedAt: container.State.StartedAt.Format(time.RFC3339Nano), FinishedAt: container.State.FinishedAt.Format(time.RFC3339Nano), Health: containerHealth, } contJSONBase := &types.ContainerJSONBase{ ID: container.ID, Created: container.Created.Format(time.RFC3339Nano), Path: container.Path, Args: container.Args, State: containerState, Image: container.ImageID.String(), LogPath: container.LogPath, Name: container.Name, RestartCount: container.RestartCount, Driver: container.Driver, MountLabel: container.MountLabel, ProcessLabel: container.ProcessLabel, ExecIDs: container.GetExecIDs(), HostConfig: &hostConfig, } var ( sizeRw int64 sizeRootFs int64 ) if size { sizeRw, sizeRootFs = daemon.getSize(container) contJSONBase.SizeRw = &sizeRw contJSONBase.SizeRootFs = &sizeRootFs } // Now set any platform-specific fields contJSONBase = setPlatformSpecificContainerFields(container, contJSONBase) contJSONBase.GraphDriver.Name = container.Driver graphDriverData, err := container.RWLayer.Metadata() // If container is marked as Dead, the container's graphdriver metadata // could have been removed, it will cause error if we try to get the metadata, // we can ignore the error if the container is dead. if err != nil && !container.Dead { return nil, err } contJSONBase.GraphDriver.Data = graphDriverData return contJSONBase, nil } // ContainerExecInspect returns low-level information about the exec // command. An error is returned if the exec cannot be found. func (daemon *Daemon) ContainerExecInspect(id string) (*backend.ExecInspect, error) { e, err := daemon.getExecConfig(id) if err != nil { return nil, err } pc := inspectExecProcessConfig(e) return &backend.ExecInspect{ ID: e.ID, Running: e.Running, ExitCode: e.ExitCode, ProcessConfig: pc, OpenStdin: e.OpenStdin, OpenStdout: e.OpenStdout, OpenStderr: e.OpenStderr, CanRemove: e.CanRemove, ContainerID: e.ContainerID, DetachKeys: e.DetachKeys, Pid: e.Pid, }, nil } // VolumeInspect looks up a volume by name. An error is returned if // the volume cannot be found. func (daemon *Daemon) VolumeInspect(name string) (*types.Volume, error) { v, err := daemon.volumes.Get(name) if err != nil { return nil, err } apiV := volumeToAPIType(v) apiV.Mountpoint = v.Path() apiV.Status = v.Status() return apiV, nil } func (daemon *Daemon) getBackwardsCompatibleNetworkSettings(settings *network.Settings) *v1p20.NetworkSettings { result := &v1p20.NetworkSettings{ NetworkSettingsBase: types.NetworkSettingsBase{ Bridge: settings.Bridge, SandboxID: settings.SandboxID, HairpinMode: settings.HairpinMode, LinkLocalIPv6Address: settings.LinkLocalIPv6Address, LinkLocalIPv6PrefixLen: settings.LinkLocalIPv6PrefixLen, Ports: settings.Ports, SandboxKey: settings.SandboxKey, SecondaryIPAddresses: settings.SecondaryIPAddresses, SecondaryIPv6Addresses: settings.SecondaryIPv6Addresses, }, DefaultNetworkSettings: daemon.getDefaultNetworkSettings(settings.Networks), } return result } // getDefaultNetworkSettings creates the deprecated structure that holds the information // about the bridge network for a container. func (daemon *Daemon) getDefaultNetworkSettings(networks map[string]*network.EndpointSettings) types.DefaultNetworkSettings { var settings types.DefaultNetworkSettings if defaultNetwork, ok := networks["bridge"]; ok && defaultNetwork.EndpointSettings != nil { settings.EndpointID = defaultNetwork.EndpointID settings.Gateway = defaultNetwork.Gateway settings.GlobalIPv6Address = defaultNetwork.GlobalIPv6Address settings.GlobalIPv6PrefixLen = defaultNetwork.GlobalIPv6PrefixLen settings.IPAddress = defaultNetwork.IPAddress settings.IPPrefixLen = defaultNetwork.IPPrefixLen settings.IPv6Gateway = defaultNetwork.IPv6Gateway settings.MacAddress = defaultNetwork.MacAddress } return settings }