blob: 284cced10d187af8ca1c1a285fa2bb5adeeb279d [file] [log] [blame]
package daemon
import (
"context"
"errors"
"fmt"
"runtime"
"time"
containertypes "github.com/moby/moby/api/types/container"
networktypes "github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/storage"
"github.com/moby/moby/v2/daemon/config"
"github.com/moby/moby/v2/daemon/container"
"github.com/moby/moby/v2/daemon/server/backend"
"github.com/moby/moby/v2/errdefs"
)
// 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(ctx context.Context, name string, options backend.ContainerInspectOptions) (_ *containertypes.InspectResponse, desiredMACAddress networktypes.HardwareAddr, _ error) {
ctr, err := daemon.GetContainer(name)
if err != nil {
return nil, nil, err
}
ctr.Lock()
base, desiredMACAddress, err := daemon.getInspectData(&daemon.config().Config, ctr)
if err != nil {
ctr.Unlock()
return nil, nil, err
}
// TODO(thaJeztah): do we need a deep copy here? Otherwise we could use maps.Clone (see https://github.com/moby/moby/commit/7917a36cc787ada58987320e67cc6d96858f3b55)
ports := make(networktypes.PortMap, len(ctr.NetworkSettings.Ports))
for k, pm := range ctr.NetworkSettings.Ports {
ports[k] = pm
}
apiNetworks := make(map[string]*networktypes.EndpointSettings)
for nwName, epConf := range ctr.NetworkSettings.Networks {
if epConf.EndpointSettings != nil {
// We must make a copy of this pointer object otherwise it can race with other operations
apiNetworks[nwName] = epConf.EndpointSettings.Copy()
}
}
networkSettings := &containertypes.NetworkSettings{
SandboxID: ctr.NetworkSettings.SandboxID,
SandboxKey: ctr.NetworkSettings.SandboxKey,
Ports: ports,
Networks: apiNetworks,
}
mountPoints := ctr.GetMountPoints()
// Don’t hold container lock for size calculation (see https://github.com/moby/moby/issues/31158)
ctr.Unlock()
if options.Size {
sizeRw, sizeRootFs, err := daemon.imageService.GetContainerLayerSize(ctx, base.ID)
if err != nil {
return nil, nil, err
}
base.SizeRw = &sizeRw
base.SizeRootFs = &sizeRootFs
}
imageManifest := ctr.ImageManifest
if imageManifest != nil && imageManifest.Platform == nil {
// Copy the image manifest to avoid mutating the original
c := *imageManifest
imageManifest = &c
imageManifest.Platform = &ctr.ImagePlatform
}
base.Mounts = mountPoints
base.NetworkSettings = networkSettings
base.ImageManifestDescriptor = imageManifest
return base, desiredMACAddress, nil
}
func (daemon *Daemon) getInspectData(daemonCfg *config.Config, ctr *container.Container) (_ *containertypes.InspectResponse, desiredMACAddress networktypes.HardwareAddr, _ error) {
// make a copy to play with
hostConfig := *ctr.HostConfig
// Add information for legacy links
children := daemon.linkIndex.children(ctr)
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, daemonCfg)
// Migrate the container's default network's MacAddress to the top-level
// Config.MacAddress field for older API versions (< 1.44). We set it here
// unconditionally, to keep backward compatibility with clients that use
// unversioned API endpoints.
var macAddress networktypes.HardwareAddr
if ctr.Config != nil {
if nwm := hostConfig.NetworkMode; nwm.IsBridge() || nwm.IsUserDefined() {
if epConf, ok := ctr.NetworkSettings.Networks[nwm.NetworkName()]; ok {
macAddress = epConf.DesiredMacAddress
}
}
}
var containerHealth *containertypes.Health
if ctr.State.Health != nil {
containerHealth = &containertypes.Health{
Status: ctr.State.Health.Status(),
FailingStreak: ctr.State.Health.Health.FailingStreak,
Log: append([]*containertypes.HealthcheckResult{}, ctr.State.Health.Health.Log...),
}
}
inspectResponse := &containertypes.InspectResponse{
ID: ctr.ID,
Created: ctr.Created.Format(time.RFC3339Nano),
Path: ctr.Path,
Args: ctr.Args,
State: &containertypes.State{
Status: ctr.State.State(),
Running: ctr.State.Running,
Paused: ctr.State.Paused,
Restarting: ctr.State.Restarting,
OOMKilled: ctr.State.OOMKilled,
Dead: ctr.State.Dead,
Pid: ctr.State.Pid,
ExitCode: ctr.State.ExitCode,
Error: ctr.State.ErrorMsg,
StartedAt: ctr.State.StartedAt.Format(time.RFC3339Nano),
FinishedAt: ctr.State.FinishedAt.Format(time.RFC3339Nano),
Health: containerHealth,
},
Image: ctr.ImageID.String(),
LogPath: ctr.LogPath,
Name: ctr.Name,
RestartCount: ctr.RestartCount,
Driver: ctr.Driver,
Platform: ctr.ImagePlatform.OS,
MountLabel: ctr.MountLabel,
ProcessLabel: ctr.ProcessLabel,
ExecIDs: ctr.GetExecIDs(),
HostConfig: &hostConfig,
Config: ctr.Config,
}
// Now set any platform-specific fields
inspectResponse = setPlatformSpecificContainerFields(ctr, inspectResponse)
if daemon.UsesSnapshotter() {
inspectResponse.Storage = &storage.Storage{
RootFS: &storage.RootFSStorage{
Snapshot: &storage.RootFSStorageSnapshot{
Name: ctr.Driver,
},
},
}
// Additional information only applies to graphDrivers, so we're done.
return inspectResponse, macAddress, nil
}
inspectResponse.GraphDriver = &storage.DriverData{
Name: ctr.Driver,
}
if ctr.RWLayer == nil {
if ctr.State.Dead {
return inspectResponse, macAddress, nil
}
return nil, nil, errdefs.System(errors.New("RWLayer of container " + ctr.ID + " is unexpectedly nil"))
}
graphDriverData, err := ctr.RWLayer.Metadata()
if err != nil {
if ctr.State.Dead {
// container is marked as Dead, and its graphDriver metadata may
// have been removed; we can ignore errors.
return inspectResponse, macAddress, nil
}
return nil, nil, errdefs.System(err)
}
inspectResponse.GraphDriver.Data = graphDriverData
return inspectResponse, macAddress, 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) (*containertypes.ExecInspectResponse, error) {
e := daemon.execCommands.Get(id)
if e == nil {
return nil, errExecNotFound(id)
}
if ctr := daemon.containers.Get(e.Container.ID); ctr == nil {
return nil, errExecNotFound(id)
}
e.Lock()
defer e.Unlock()
var pid int
if e.Process != nil {
pid = int(e.Process.Pid())
}
var privileged *bool
if runtime.GOOS != "windows" || e.Privileged {
// Privileged is not used on Windows, so should always be false
// (and omitted in the response), but set it if it happened to
// be true. On non-Windows, we always set it, and the field should
// not be omitted.
privileged = &e.Privileged
}
return &containertypes.ExecInspectResponse{
ID: e.ID,
Running: e.Running,
ExitCode: e.ExitCode,
ProcessConfig: &containertypes.ExecProcessConfig{
Tty: e.Tty,
Entrypoint: e.Entrypoint,
Arguments: e.Args,
Privileged: privileged, // Privileged is not used on Windows
User: e.User, // User is not used on Windows
},
OpenStdin: e.OpenStdin,
OpenStdout: e.OpenStdout,
OpenStderr: e.OpenStderr,
CanRemove: e.CanRemove,
ContainerID: e.Container.ID,
DetachKeys: e.DetachKeys,
Pid: pid,
}, nil
}