mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Make invalid states unrepresentable by moving away from stringly-typed MAC address values in API structs. As go.dev/issue/29678 has not yet been implemented, provide our own HardwareAddr byte-slice type which implements TextMarshaler and TextUnmarshaler to retain compatibility with the API wire format. When stdlib's net.HardwareAddr type implements TextMarshaler and TextUnmarshaler and GODEBUG=netmarshal becomes the default, we should be able to make the type a straight alias for stdlib net.HardwareAddr as a non-breaking change. Signed-off-by: Cory Snider <csnider@mirantis.com>
241 lines
7.4 KiB
Go
241 lines
7.4 KiB
Go
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.StateString(),
|
||
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
|
||
}
|