diff --git a/api/types/types.go b/api/types/types.go index efe5ea28be..5a63faad37 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -5,6 +5,7 @@ import ( "time" "github.com/docker/docker/daemon/network" + "github.com/docker/docker/pkg/nat" "github.com/docker/docker/pkg/version" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" @@ -255,7 +256,6 @@ type ContainerJSONBase struct { Args []string State *ContainerState Image string - NetworkSettings *network.Settings ResolvConfPath string HostnamePath string HostsPath string @@ -277,8 +277,28 @@ type ContainerJSONBase struct { // ContainerJSON is newly used struct along with MountPoint type ContainerJSON struct { *ContainerJSONBase - Mounts []MountPoint - Config *runconfig.Config + Mounts []MountPoint + Config *runconfig.Config + NetworkSettings *NetworkSettings +} + +// NetworkSettings exposes the network settings in the api +type NetworkSettings struct { + NetworkSettingsBase + Networks map[string]*network.EndpointSettings +} + +// NetworkSettingsBase holds basic information about networks +type NetworkSettingsBase struct { + Bridge string + SandboxID string + HairpinMode bool + LinkLocalIPv6Address string + LinkLocalIPv6PrefixLen int + Ports nat.PortMap + SandboxKey string + SecondaryIPAddresses []network.Address + SecondaryIPv6Addresses []network.Address } // MountPoint represents a mount point configuration inside the container. diff --git a/api/types/versions/v1p19/types.go b/api/types/versions/v1p19/types.go index da31b5ca20..a66aa9d5e5 100644 --- a/api/types/versions/v1p19/types.go +++ b/api/types/versions/v1p19/types.go @@ -3,6 +3,7 @@ package v1p19 import ( "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/versions/v1p20" "github.com/docker/docker/pkg/nat" "github.com/docker/docker/runconfig" ) @@ -11,9 +12,10 @@ import ( // Note this is not used by the Windows daemon. type ContainerJSON struct { *types.ContainerJSONBase - Volumes map[string]string - VolumesRW map[string]bool - Config *ContainerConfig + Volumes map[string]string + VolumesRW map[string]bool + Config *ContainerConfig + NetworkSettings *v1p20.NetworkSettings } // ContainerConfig is a backcompatibility struct for APIs prior to 1.20. diff --git a/api/types/versions/v1p20/types.go b/api/types/versions/v1p20/types.go index 3255204dac..7b2c25d08a 100644 --- a/api/types/versions/v1p20/types.go +++ b/api/types/versions/v1p20/types.go @@ -10,8 +10,9 @@ import ( // ContainerJSON is a backcompatibility struct for the API 1.20 type ContainerJSON struct { *types.ContainerJSONBase - Mounts []types.MountPoint - Config *ContainerConfig + Mounts []types.MountPoint + Config *ContainerConfig + NetworkSettings *NetworkSettings } // ContainerConfig is a backcompatibility struct used in ContainerJSON for the API 1.20 @@ -31,3 +32,16 @@ type StatsJSON struct { types.Stats Network types.NetworkStats `json:"network,omitempty"` } + +// NetworkSettings is a backward compatible struct for APIs prior to 1.21 +type NetworkSettings struct { + types.NetworkSettingsBase + EndpointID string + Gateway string + GlobalIPv6Address string + GlobalIPv6PrefixLen int + IPAddress string + IPPrefixLen int + IPv6Gateway string + MacAddress string +} diff --git a/daemon/container.go b/daemon/container.go index 2cc0ecb3bd..480bd8eb96 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -332,10 +332,6 @@ func (streamConfig *streamConfig) StderrPipe() io.ReadCloser { return ioutils.NewBufReader(reader) } -func (container *Container) isNetworkAllocated() bool { - return container.NetworkSettings.IPAddress != "" -} - // cleanup releases any network resources allocated to the container along with any rules // around how containers are linked together. It also unmounts the container's root filesystem. func (container *Container) cleanup() { diff --git a/daemon/container_unix.go b/daemon/container_unix.go index d6766f73e9..5c48019ef5 100644 --- a/daemon/container_unix.go +++ b/daemon/container_unix.go @@ -88,15 +88,25 @@ func (container *Container) setupLinkedContainers() ([]string, error) { return nil, err } + bridgeSettings := container.NetworkSettings.Networks["bridge"] + if bridgeSettings == nil { + return nil, nil + } + if len(children) > 0 { for linkAlias, child := range children { if !child.IsRunning() { return nil, derr.ErrorCodeLinkNotRunning.WithArgs(child.Name, linkAlias) } + childBridgeSettings := child.NetworkSettings.Networks["bridge"] + if childBridgeSettings == nil { + return nil, fmt.Errorf("container %d not attached to default bridge network", child.ID) + } + link := links.NewLink( - container.NetworkSettings.IPAddress, - child.NetworkSettings.IPAddress, + bridgeSettings.IPAddress, + childBridgeSettings.IPAddress, linkAlias, child.Config.Env, child.Config.ExposedPorts, @@ -542,13 +552,14 @@ func (container *Container) buildSandboxOptions(n libnetwork.Network) ([]libnetw if alias != child.Name[1:] { aliasList = aliasList + " " + child.Name[1:] } - sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(aliasList, child.NetworkSettings.IPAddress)) + sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(aliasList, child.NetworkSettings.Networks["bridge"].IPAddress)) cEndpoint, _ := child.getEndpointInNetwork(n) if cEndpoint != nil && cEndpoint.ID() != "" { childEndpoints = append(childEndpoints, cEndpoint.ID()) } } + bridgeSettings := container.NetworkSettings.Networks["bridge"] refs := container.daemon.containerGraph().RefPaths(container.ID) for _, ref := range refs { if ref.ParentID == "0" { @@ -561,8 +572,8 @@ func (container *Container) buildSandboxOptions(n libnetwork.Network) ([]libnetw } if c != nil && !container.daemon.configStore.DisableBridge && container.hostConfig.NetworkMode.IsPrivate() { - logrus.Debugf("Update /etc/hosts of %s for alias %s with ip %s", c.ID, ref.Name, container.NetworkSettings.IPAddress) - sboxOptions = append(sboxOptions, libnetwork.OptionParentUpdate(c.ID, ref.Name, container.NetworkSettings.IPAddress)) + logrus.Debugf("Update /etc/hosts of %s for alias %s with ip %s", c.ID, ref.Name, bridgeSettings.IPAddress) + sboxOptions = append(sboxOptions, libnetwork.OptionParentUpdate(c.ID, ref.Name, bridgeSettings.IPAddress)) if ep.ID() != "" { parentEndpoints = append(parentEndpoints, ep.ID()) } @@ -583,12 +594,8 @@ func (container *Container) buildSandboxOptions(n libnetwork.Network) ([]libnetw func isLinkable(child *Container) bool { // A container is linkable only if it belongs to the default network - for _, nw := range child.NetworkSettings.Networks { - if nw == "bridge" { - return true - } - } - return false + _, ok := child.NetworkSettings.Networks["bridge"] + return ok } func (container *Container) getEndpointInNetwork(n libnetwork.Network) (libnetwork.Endpoint, error) { @@ -663,6 +670,9 @@ func (container *Container) buildEndpointInfo(n libnetwork.Network, ep libnetwor return networkSettings, nil } + if _, ok := networkSettings.Networks[n.Name()]; !ok { + networkSettings.Networks[n.Name()] = new(network.EndpointSettings) + } networkSettings.Networks[n.Name()].EndpointID = ep.ID() iface := epInfo.Iface() @@ -670,25 +680,14 @@ func (container *Container) buildEndpointInfo(n libnetwork.Network, ep libnetwor return networkSettings, nil } - if networkSettings.EndpointID == "" { - networkSettings.EndpointID = ep.ID() - } if iface.Address() != nil { ones, _ := iface.Address().Mask.Size() - if networkSettings.IPAddress == "" || networkSettings.IPPrefixLen == 0 { - networkSettings.IPAddress = iface.Address().IP.String() - networkSettings.IPPrefixLen = ones - } networkSettings.Networks[n.Name()].IPAddress = iface.Address().IP.String() networkSettings.Networks[n.Name()].IPPrefixLen = ones } if iface.AddressIPv6() != nil && iface.AddressIPv6().IP.To16() != nil { onesv6, _ := iface.AddressIPv6().Mask.Size() - if networkSettings.GlobalIPv6Address == "" || networkSettings.GlobalIPv6PrefixLen == 0 { - networkSettings.GlobalIPv6Address = iface.AddressIPv6().IP.String() - networkSettings.GlobalIPv6PrefixLen = onesv6 - } networkSettings.Networks[n.Name()].GlobalIPv6Address = iface.AddressIPv6().IP.String() networkSettings.Networks[n.Name()].GlobalIPv6PrefixLen = onesv6 } @@ -703,9 +702,6 @@ func (container *Container) buildEndpointInfo(n libnetwork.Network, ep libnetwor return networkSettings, nil } if mac, ok := driverInfo[netlabel.MacAddress]; ok { - if networkSettings.MacAddress == "" { - networkSettings.MacAddress = mac.(net.HardwareAddr).String() - } networkSettings.Networks[n.Name()].MacAddress = mac.(net.HardwareAddr).String() } @@ -718,14 +714,8 @@ func (container *Container) updateJoinInfo(n libnetwork.Network, ep libnetwork.E // It is not an error to get an empty endpoint info return nil } - if container.NetworkSettings.Gateway == "" { - container.NetworkSettings.Gateway = epInfo.Gateway().String() - } container.NetworkSettings.Networks[n.Name()].Gateway = epInfo.Gateway().String() if epInfo.GatewayIPv6().To16() != nil { - if container.NetworkSettings.IPv6Gateway == "" { - container.NetworkSettings.IPv6Gateway = epInfo.GatewayIPv6().String() - } container.NetworkSettings.Networks[n.Name()].IPv6Gateway = epInfo.GatewayIPv6().String() } @@ -1231,17 +1221,6 @@ func (container *Container) disconnectFromNetwork(n libnetwork.Network) error { return fmt.Errorf("endpoint delete failed for container %s on network %s: %v", container.ID, n.Name(), err) } - if container.NetworkSettings.EndpointID == container.NetworkSettings.Networks[n.Name()].EndpointID { - container.NetworkSettings.EndpointID = "" - container.NetworkSettings.Gateway = "" - container.NetworkSettings.GlobalIPv6Address = "" - container.NetworkSettings.GlobalIPv6PrefixLen = 0 - container.NetworkSettings.IPAddress = "" - container.NetworkSettings.IPPrefixLen = 0 - container.NetworkSettings.IPv6Gateway = "" - container.NetworkSettings.MacAddress = "" - - } delete(container.NetworkSettings.Networks, n.Name()) return nil } diff --git a/daemon/inspect.go b/daemon/inspect.go index 8bac0408fb..bd1f07fdca 100644 --- a/daemon/inspect.go +++ b/daemon/inspect.go @@ -6,6 +6,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/versions/v1p20" + "github.com/docker/docker/daemon/network" ) // ContainerInspect returns low-level information about a @@ -26,8 +27,22 @@ func (daemon *Daemon) ContainerInspect(name string, size bool) (*types.Container } mountPoints := addMountPoints(container) + networkSettings := &types.NetworkSettings{ + 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, + }, + container.NetworkSettings.Networks, + } - return &types.ContainerJSON{base, mountPoints, container.Config}, nil + return &types.ContainerJSON{base, mountPoints, container.Config, networkSettings}, nil } // ContainerInspect120 serializes the master version of a container into a json type. @@ -53,8 +68,9 @@ func (daemon *Daemon) ContainerInspect120(name string) (*v1p20.ContainerJSON, er container.Config.ExposedPorts, container.hostConfig.VolumeDriver, } + networkSettings := getBackwardsCompatibleNetworkSettings(container.NetworkSettings) - return &v1p20.ContainerJSON{base, mountPoints, config}, nil + return &v1p20.ContainerJSON{base, mountPoints, config, networkSettings}, nil } func (daemon *Daemon) getInspectData(container *Container, size bool) (*types.ContainerJSONBase, error) { @@ -91,22 +107,21 @@ func (daemon *Daemon) getInspectData(container *Container, size bool) (*types.Co } contJSONBase := &types.ContainerJSONBase{ - ID: container.ID, - Created: container.Created.Format(time.RFC3339Nano), - Path: container.Path, - Args: container.Args, - State: containerState, - Image: container.ImageID, - NetworkSettings: container.NetworkSettings, - LogPath: container.LogPath, - Name: container.Name, - RestartCount: container.RestartCount, - Driver: container.Driver, - ExecDriver: container.ExecDriver, - MountLabel: container.MountLabel, - ProcessLabel: container.ProcessLabel, - ExecIDs: container.getExecIDs(), - HostConfig: &hostConfig, + ID: container.ID, + Created: container.Created.Format(time.RFC3339Nano), + Path: container.Path, + Args: container.Args, + State: containerState, + Image: container.ImageID, + LogPath: container.LogPath, + Name: container.Name, + RestartCount: container.RestartCount, + Driver: container.Driver, + ExecDriver: container.ExecDriver, + MountLabel: container.MountLabel, + ProcessLabel: container.ProcessLabel, + ExecIDs: container.getExecIDs(), + HostConfig: &hostConfig, } var ( @@ -151,3 +166,30 @@ func (daemon *Daemon) VolumeInspect(name string) (*types.Volume, error) { } return volumeToAPIType(v), nil } + +func 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, + }, + } + if bridgeSettings := settings.Networks["bridge"]; bridgeSettings != nil { + result.EndpointID = bridgeSettings.EndpointID + result.Gateway = bridgeSettings.Gateway + result.GlobalIPv6Address = bridgeSettings.GlobalIPv6Address + result.GlobalIPv6PrefixLen = bridgeSettings.GlobalIPv6PrefixLen + result.IPAddress = bridgeSettings.IPAddress + result.IPPrefixLen = bridgeSettings.IPPrefixLen + result.IPv6Gateway = bridgeSettings.IPv6Gateway + result.MacAddress = bridgeSettings.MacAddress + } + return result +} diff --git a/daemon/inspect_unix.go b/daemon/inspect_unix.go index 1122259ff9..d2d8794d04 100644 --- a/daemon/inspect_unix.go +++ b/daemon/inspect_unix.go @@ -50,8 +50,9 @@ func (daemon *Daemon) ContainerInspectPre120(name string) (*v1p19.ContainerJSON, container.hostConfig.CPUShares, container.hostConfig.CpusetCpus, } + networkSettings := getBackwardsCompatibleNetworkSettings(container.NetworkSettings) - return &v1p19.ContainerJSON{base, volumes, volumesRW, config}, nil + return &v1p19.ContainerJSON{base, volumes, volumesRW, config, networkSettings}, nil } func addMountPoints(container *Container) []types.MountPoint { diff --git a/daemon/network/settings.go b/daemon/network/settings.go index ecfecc60c6..2fe23c7866 100644 --- a/daemon/network/settings.go +++ b/daemon/network/settings.go @@ -26,18 +26,10 @@ type IPAMConfig struct { // TODO Windows. Many of these fields can be factored out., type Settings struct { Bridge string - EndpointID string // this is for backward compatibility SandboxID string - Gateway string // this is for backward compatibility - GlobalIPv6Address string // this is for backward compatibility - GlobalIPv6PrefixLen int // this is for backward compatibility HairpinMode bool - IPAddress string // this is for backward compatibility - IPPrefixLen int // this is for backward compatibility - IPv6Gateway string // this is for backward compatibility LinkLocalIPv6Address string LinkLocalIPv6PrefixLen int - MacAddress string // this is for backward compatibility Networks map[string]*EndpointSettings Ports nat.PortMap SandboxKey string diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 1f5a2b9c8c..d344ef68a4 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -303,7 +303,7 @@ func (s *DockerSuite) TestDaemonIPv6Enabled(c *check.C) { c.Fatalf("Could not run container: %s, %v", out, err) } - out, err := d.Cmd("inspect", "--format", "'{{.NetworkSettings.LinkLocalIPv6Address}}'", "ipv6test") + out, err := d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.LinkLocalIPv6Address}}'", "ipv6test") out = strings.Trim(out, " \r\n'") if err != nil { @@ -314,7 +314,7 @@ func (s *DockerSuite) TestDaemonIPv6Enabled(c *check.C) { c.Fatalf("Container should have a link-local IPv6 address") } - out, err = d.Cmd("inspect", "--format", "'{{.NetworkSettings.GlobalIPv6Address}}'", "ipv6test") + out, err = d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.GlobalIPv6Address}}'", "ipv6test") out = strings.Trim(out, " \r\n'") if err != nil { @@ -351,7 +351,7 @@ func (s *DockerSuite) TestDaemonIPv6FixedCIDR(c *check.C) { c.Fatalf("Could not run container: %s, %v", out, err) } - out, err := d.Cmd("inspect", "--format", "'{{.NetworkSettings.LinkLocalIPv6Address}}'", "ipv6test") + out, err := d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.LinkLocalIPv6Address}}'", "ipv6test") out = strings.Trim(out, " \r\n'") if err != nil { @@ -362,7 +362,7 @@ func (s *DockerSuite) TestDaemonIPv6FixedCIDR(c *check.C) { c.Fatalf("Container should have a link-local IPv6 address") } - out, err = d.Cmd("inspect", "--format", "'{{.NetworkSettings.GlobalIPv6Address}}'", "ipv6test") + out, err = d.Cmd("inspect", "--format", "'{{.NetworkSettings.Networks.bridge.GlobalIPv6Address}}'", "ipv6test") out = strings.Trim(out, " \r\n'") if err != nil { diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index b49bf7d882..4bf5562054 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -135,7 +135,7 @@ func (s *DockerSuite) TestLinksUpdateOnRestart(c *check.C) { out, _ := dockerCmd(c, "run", "-d", "--name", "two", "--link", "one:onetwo", "--link", "one:one", "busybox", "top") id := strings.TrimSpace(string(out)) - realIP, err := inspectField("one", "NetworkSettings.IPAddress") + realIP, err := inspectField("one", "NetworkSettings.Networks.bridge.IPAddress") if err != nil { c.Fatal(err) } @@ -156,7 +156,7 @@ func (s *DockerSuite) TestLinksUpdateOnRestart(c *check.C) { c.Assert(ip, checker.Equals, realIP) dockerCmd(c, "restart", "one") - realIP, err = inspectField("one", "NetworkSettings.IPAddress") + realIP, err = inspectField("one", "NetworkSettings.Networks.bridge.IPAddress") c.Assert(err, checker.IsNil) content, err = readContainerFileWithExec(id, "/etc/hosts") diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 7b86eb20f0..0d203afd99 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -184,7 +184,7 @@ func (s *DockerSuite) TestRunLinksContainerWithContainerName(c *check.C) { testRequires(c, DaemonIsLinux) dockerCmd(c, "run", "-i", "-t", "-d", "--name", "parent", "busybox") - ip, err := inspectField("parent", "NetworkSettings.IPAddress") + ip, err := inspectField("parent", "NetworkSettings.Networks.bridge.IPAddress") c.Assert(err, check.IsNil) out, _ := dockerCmd(c, "run", "--link", "parent:test", "busybox", "/bin/cat", "/etc/hosts") @@ -201,7 +201,7 @@ func (s *DockerSuite) TestRunLinksContainerWithContainerId(c *check.C) { cID, _ := dockerCmd(c, "run", "-i", "-t", "-d", "busybox") cID = strings.TrimSpace(cID) - ip, err := inspectField(cID, "NetworkSettings.IPAddress") + ip, err := inspectField(cID, "NetworkSettings.Networks.bridge.IPAddress") c.Assert(err, check.IsNil) out, _ := dockerCmd(c, "run", "--link", cID+":test", "busybox", "/bin/cat", "/etc/hosts") @@ -1833,7 +1833,7 @@ func (s *DockerSuite) TestRunInspectMacAddress(c *check.C) { out, _ := dockerCmd(c, "run", "-d", "--mac-address="+mac, "busybox", "top") id := strings.TrimSpace(out) - inspectedMac, err := inspectField(id, "NetworkSettings.MacAddress") + inspectedMac, err := inspectField(id, "NetworkSettings.Networks.bridge.MacAddress") c.Assert(err, check.IsNil) if inspectedMac != mac { c.Fatalf("docker inspect outputs wrong MAC address: %q, should be: %q", inspectedMac, mac) @@ -1856,7 +1856,7 @@ func (s *DockerSuite) TestRunDeallocatePortOnMissingIptablesRule(c *check.C) { out, _ := dockerCmd(c, "run", "-d", "-p", "23:23", "busybox", "top") id := strings.TrimSpace(out) - ip, err := inspectField(id, "NetworkSettings.IPAddress") + ip, err := inspectField(id, "NetworkSettings.Networks.bridge.IPAddress") c.Assert(err, check.IsNil) iptCmd := exec.Command("iptables", "-D", "DOCKER", "-d", fmt.Sprintf("%s/32", ip), "!", "-i", "docker0", "-o", "docker0", "-p", "tcp", "-m", "tcp", "--dport", "23", "-j", "ACCEPT") @@ -3403,7 +3403,7 @@ func (s *DockerSuite) TestRunNetworkNotInitializedNoneMode(c *check.C) { testRequires(c, DaemonIsLinux) out, _ := dockerCmd(c, "run", "-d", "--net=none", "busybox", "top") id := strings.TrimSpace(out) - res, err := inspectField(id, "NetworkSettings.IPAddress") + res, err := inspectField(id, "NetworkSettings.Networks.none.IPAddress") c.Assert(err, check.IsNil) if res != "" { c.Fatalf("For 'none' mode network must not be initialized, but container got IP: %s", res) diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 67627aff93..98de35f6c6 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -788,7 +788,11 @@ func findContainerIP(c *check.C, id string, network string) string { } func (d *Daemon) findContainerIP(id string) string { - return findContainerIP(d.c, id, "--host") + out, err := d.Cmd("inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.bridge.IPAddress }}'"), id) + if err != nil { + d.c.Log(err) + } + return strings.Trim(out, " \r\n'") } func getContainerCount() (int, error) {