Merge pull request #1221 from timstclair/tests

Cleanup manager package
This commit is contained in:
Vish Kannan 2016-05-02 16:05:24 -07:00
commit 04a0a007ba
13 changed files with 238 additions and 322 deletions

163
container/docker/docker.go Normal file
View File

@ -0,0 +1,163 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Provides global docker information.
package docker
import (
"fmt"
"strconv"
"strings"
dockertypes "github.com/docker/engine-api/types"
"golang.org/x/net/context"
"github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/machine"
)
func Status() (v1.DockerStatus, error) {
client, err := Client()
if err != nil {
return v1.DockerStatus{}, fmt.Errorf("unable to communicate with docker daemon: %v", err)
}
dockerInfo, err := client.Info(context.Background())
if err != nil {
return v1.DockerStatus{}, err
}
out := v1.DockerStatus{}
out.Version = VersionString()
out.KernelVersion = machine.KernelVersion()
out.OS = dockerInfo.OperatingSystem
out.Hostname = dockerInfo.Name
out.RootDir = dockerInfo.DockerRootDir
out.Driver = dockerInfo.Driver
out.ExecDriver = dockerInfo.ExecutionDriver
out.NumImages = dockerInfo.Images
out.NumContainers = dockerInfo.Containers
out.DriverStatus = make(map[string]string, len(dockerInfo.DriverStatus))
for _, v := range dockerInfo.DriverStatus {
out.DriverStatus[v[0]] = v[1]
}
return out, nil
}
func Images() ([]v1.DockerImage, error) {
client, err := Client()
if err != nil {
return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err)
}
images, err := client.ImageList(context.Background(), dockertypes.ImageListOptions{All: false})
if err != nil {
return nil, err
}
out := []v1.DockerImage{}
const unknownTag = "<none>:<none>"
for _, image := range images {
if len(image.RepoTags) == 1 && image.RepoTags[0] == unknownTag {
// images with repo or tags are uninteresting.
continue
}
di := v1.DockerImage{
ID: image.ID,
RepoTags: image.RepoTags,
Created: image.Created,
VirtualSize: image.VirtualSize,
Size: image.Size,
}
out = append(out, di)
}
return out, nil
}
// Checks whether the dockerInfo reflects a valid docker setup, and returns it if it does, or an
// error otherwise.
func ValidateInfo() (*dockertypes.Info, error) {
client, err := Client()
if err != nil {
return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err)
}
dockerInfo, err := client.Info(context.Background())
if err != nil {
return nil, fmt.Errorf("failed to detect Docker info: %v", err)
}
// Fall back to version API if ServerVersion is not set in info.
if dockerInfo.ServerVersion == "" {
version, err := client.ServerVersion(context.Background())
if err != nil {
return nil, fmt.Errorf("unable to get docker version: %v", err)
}
dockerInfo.ServerVersion = version.Version
}
version, err := parseDockerVersion(dockerInfo.ServerVersion)
if err != nil {
return nil, err
}
if version[0] < 1 {
return nil, fmt.Errorf("cAdvisor requires docker version %v or above but we have found version %v reported as %q", []int{1, 0, 0}, version, dockerInfo.ServerVersion)
}
// Check that the libcontainer execdriver is used if the version is < 1.11
// (execution drivers are no longer supported as of 1.11).
if version[0] <= 1 && version[1] <= 10 &&
!strings.HasPrefix(dockerInfo.ExecutionDriver, "native") {
return nil, fmt.Errorf("docker found, but not using native exec driver")
}
if dockerInfo.Driver == "" {
return nil, fmt.Errorf("failed to find docker storage driver")
}
return &dockerInfo, nil
}
func Version() ([]int, error) {
return parseDockerVersion(VersionString())
}
func VersionString() string {
docker_version := "Unknown"
client, err := Client()
if err == nil {
version, err := client.ServerVersion(context.Background())
if err == nil {
docker_version = version.Version
}
}
return docker_version
}
// TODO: switch to a semantic versioning library.
func parseDockerVersion(full_version_string string) ([]int, error) {
matches := version_re.FindAllStringSubmatch(full_version_string, -1)
if len(matches) != 1 {
return nil, fmt.Errorf("version string \"%v\" doesn't match expected regular expression: \"%v\"", full_version_string, version_regexp_string)
}
version_string_array := matches[0][1:]
version_array := make([]int, 3)
for index, version_string := range version_string_array {
version, err := strconv.Atoi(version_string)
if err != nil {
return nil, fmt.Errorf("error while parsing \"%v\" in \"%v\"", version_string, full_version_string)
}
version_array[index] = version
}
return version_array, nil
}

View File

@ -19,7 +19,6 @@ import (
"fmt"
"path"
"regexp"
"strconv"
"strings"
"github.com/google/cadvisor/container"
@ -164,24 +163,6 @@ var (
version_re = regexp.MustCompile(version_regexp_string)
)
// TODO: switch to a semantic versioning library.
func parseDockerVersion(full_version_string string) ([]int, error) {
matches := version_re.FindAllStringSubmatch(full_version_string, -1)
if len(matches) != 1 {
return nil, fmt.Errorf("version string \"%v\" doesn't match expected regular expression: \"%v\"", full_version_string, version_regexp_string)
}
version_string_array := matches[0][1:]
version_array := make([]int, 3)
for index, version_string := range version_string_array {
version, err := strconv.Atoi(version_string)
if err != nil {
return nil, fmt.Errorf("error while parsing \"%v\" in \"%v\"", version_string, full_version_string)
}
version_array[index] = version
}
return version_array, nil
}
// Register root container before running this function!
func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics container.MetricSet) error {
client, err := Client()

View File

@ -29,7 +29,6 @@ import (
info "github.com/google/cadvisor/info/v1"
docker "github.com/docker/engine-api/client"
dockertypes "github.com/docker/engine-api/types"
dockercontainer "github.com/docker/engine-api/types/container"
"github.com/opencontainers/runc/libcontainer/cgroups"
cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
@ -349,63 +348,3 @@ func (self *dockerContainerHandler) StopWatchingSubcontainers() error {
func (self *dockerContainerHandler) Exists() bool {
return common.CgroupExists(self.cgroupPaths)
}
func DockerInfo() (dockertypes.Info, error) {
client, err := Client()
if err != nil {
return dockertypes.Info{}, fmt.Errorf("unable to communicate with docker daemon: %v", err)
}
return client.Info(context.Background())
}
func DockerImages() ([]dockertypes.Image, error) {
client, err := Client()
if err != nil {
return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err)
}
return client.ImageList(context.Background(), dockertypes.ImageListOptions{All: false})
}
// Checks whether the dockerInfo reflects a valid docker setup, and returns it if it does, or an
// error otherwise.
func ValidateInfo() (*dockertypes.Info, error) {
client, err := Client()
if err != nil {
return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err)
}
dockerInfo, err := client.Info(context.Background())
if err != nil {
return nil, fmt.Errorf("failed to detect Docker info: %v", err)
}
// Fall back to version API if ServerVersion is not set in info.
if dockerInfo.ServerVersion == "" {
version, err := client.ServerVersion(context.Background())
if err != nil {
return nil, fmt.Errorf("unable to get docker version: %v", err)
}
dockerInfo.ServerVersion = version.Version
}
version, err := parseDockerVersion(dockerInfo.ServerVersion)
if err != nil {
return nil, err
}
if version[0] < 1 {
return nil, fmt.Errorf("cAdvisor requires docker version %v or above but we have found version %v reported as %q", []int{1, 0, 0}, version, dockerInfo.ServerVersion)
}
// Check that the libcontainer execdriver is used if the version is < 1.11
// (execution drivers are no longer supported as of 1.11).
if version[0] <= 1 && version[1] <= 10 &&
!strings.HasPrefix(dockerInfo.ExecutionDriver, "native") {
return nil, fmt.Errorf("docker found, but not using native exec driver")
}
if dockerInfo.Driver == "" {
return nil, fmt.Errorf("failed to find docker storage driver")
}
return &dockerInfo, nil
}

View File

@ -26,7 +26,7 @@ import (
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/utils/machine"
"github.com/google/cadvisor/machine"
"github.com/golang/glog"
"github.com/opencontainers/runc/libcontainer/cgroups"

37
info/v1/docker.go Normal file
View File

@ -0,0 +1,37 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Types used for docker containers.
package v1
type DockerStatus struct {
Version string `json:"version"`
KernelVersion string `json:"kernel_version"`
OS string `json:"os"`
Hostname string `json:"hostname"`
RootDir string `json:"root_dir"`
Driver string `json:"driver"`
DriverStatus map[string]string `json:"driver_status"`
ExecDriver string `json:"exec_driver"`
NumImages int `json:"num_images"`
NumContainers int `json:"num_containers"`
}
type DockerImage struct {
ID string `json:"id"`
RepoTags []string `json:"repo_tags"` // repository name and tags.
Created int64 `json:"created"` // unix time since creation.
VirtualSize int64 `json:"virtual_size"`
Size int64 `json:"size"`
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package manager
package machine
import (
"bytes"
@ -22,17 +22,13 @@ import (
"strings"
"syscall"
"github.com/google/cadvisor/container/docker"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/utils/cloudinfo"
"github.com/google/cadvisor/utils/machine"
"github.com/google/cadvisor/utils/sysfs"
"github.com/google/cadvisor/utils/sysinfo"
version "github.com/google/cadvisor/version"
"github.com/golang/glog"
"golang.org/x/net/context"
)
var machineIdFilePath = flag.String("machine_id_file", "/etc/machine-id,/var/lib/dbus/machine-id", "Comma-separated list of files to check for machine-id. Use the first one that exists.")
@ -52,19 +48,19 @@ func getInfoFromFiles(filePaths string) string {
return ""
}
func getMachineInfo(sysFs sysfs.SysFs, fsInfo fs.FsInfo, inHostNamespace bool) (*info.MachineInfo, error) {
func Info(sysFs sysfs.SysFs, fsInfo fs.FsInfo, inHostNamespace bool) (*info.MachineInfo, error) {
rootFs := "/"
if !inHostNamespace {
rootFs = "/rootfs"
}
cpuinfo, err := ioutil.ReadFile(filepath.Join(rootFs, "/proc/cpuinfo"))
clockSpeed, err := machine.GetClockSpeed(cpuinfo)
clockSpeed, err := GetClockSpeed(cpuinfo)
if err != nil {
return nil, err
}
memoryCapacity, err := machine.GetMachineMemoryCapacity()
memoryCapacity, err := GetMachineMemoryCapacity()
if err != nil {
return nil, err
}
@ -84,7 +80,7 @@ func getMachineInfo(sysFs sysfs.SysFs, fsInfo fs.FsInfo, inHostNamespace bool) (
glog.Errorf("Failed to get network devices: %v", err)
}
topology, numCores, err := machine.GetTopology(sysFs, string(cpuinfo))
topology, numCores, err := GetTopology(sysFs, string(cpuinfo))
if err != nil {
glog.Errorf("Failed to get topology information: %v", err)
}
@ -121,22 +117,7 @@ func getMachineInfo(sysFs sysfs.SysFs, fsInfo fs.FsInfo, inHostNamespace bool) (
return machineInfo, nil
}
func getVersionInfo() (*info.VersionInfo, error) {
kernel_version := getKernelVersion()
container_os := getContainerOsVersion()
docker_version := getDockerVersion()
return &info.VersionInfo{
KernelVersion: kernel_version,
ContainerOsVersion: container_os,
DockerVersion: docker_version,
CadvisorVersion: version.Info["version"],
CadvisorRevision: version.Info["revision"],
}, nil
}
func getContainerOsVersion() string {
func ContainerOsVersion() string {
container_os := "Unknown"
os_release, err := ioutil.ReadFile("/etc/os-release")
if err == nil {
@ -153,19 +134,7 @@ func getContainerOsVersion() string {
return container_os
}
func getDockerVersion() string {
docker_version := "Unknown"
client, err := docker.Client()
if err == nil {
version, err := client.ServerVersion(context.Background())
if err == nil {
docker_version = version.Version
}
}
return docker_version
}
func getKernelVersion() string {
func KernelVersion() string {
uname := &syscall.Utsname{}
if err := syscall.Uname(uname); err != nil {

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// The machine package contains functions that extract machine-level specs.
package machine
import (
@ -33,8 +34,6 @@ import (
"github.com/golang/glog"
)
// The utils/machine package contains functions that extract machine-level specs.
var (
cpuRegExp = regexp.MustCompile(`^processor\s*:\s*([0-9]+)$`)
coreRegExp = regexp.MustCompile(`^core id\s*:\s*([0-9]+)$`)

View File

@ -36,9 +36,11 @@ import (
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/info/v2"
"github.com/google/cadvisor/machine"
"github.com/google/cadvisor/utils/cpuload"
"github.com/google/cadvisor/utils/oomparser"
"github.com/google/cadvisor/utils/sysfs"
"github.com/google/cadvisor/version"
"github.com/golang/glog"
"github.com/opencontainers/runc/libcontainer/cgroups"
@ -110,10 +112,10 @@ type Manager interface {
CloseEventChannel(watch_id int)
// Get status information about docker.
DockerInfo() (DockerStatus, error)
DockerInfo() (info.DockerStatus, error)
// Get details about interesting docker images.
DockerImages() ([]DockerImage, error)
DockerImages() ([]info.DockerImage, error)
// Returns debugging information. Map of lines per category.
DebugInfo() map[string][]string
@ -132,7 +134,7 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingIn
}
glog.Infof("cAdvisor running in container: %q", selfContainer)
dockerInfo, err := dockerInfo()
dockerStatus, err := docker.Status()
if err != nil {
glog.Warningf("Unable to connect to Docker: %v", err)
}
@ -144,8 +146,8 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingIn
context := fs.Context{
Docker: fs.DockerContext{
Root: docker.RootDir(),
Driver: dockerInfo.Driver,
DriverStatus: dockerInfo.DriverStatus,
Driver: dockerStatus.Driver,
DriverStatus: dockerStatus.DriverStatus,
},
RktPath: rktPath,
}
@ -174,7 +176,7 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingIn
ignoreMetrics: ignoreMetricsSet,
}
machineInfo, err := getMachineInfo(sysfs, fsInfo, inHostNamespace)
machineInfo, err := machine.Info(sysfs, fsInfo, inHostNamespace)
if err != nil {
return nil, err
}
@ -239,9 +241,6 @@ func (self *manager) Start() error {
glog.Errorf("Registration of the raw container factory failed: %v", err)
}
self.DockerInfo()
self.DockerImages()
if *enableLoadReader {
// Create cpu load reader.
cpuLoadReader, err := cpuload.New()
@ -1125,79 +1124,12 @@ func parseEventsStoragePolicy() events.StoragePolicy {
return policy
}
type DockerStatus struct {
Version string `json:"version"`
KernelVersion string `json:"kernel_version"`
OS string `json:"os"`
Hostname string `json:"hostname"`
RootDir string `json:"root_dir"`
Driver string `json:"driver"`
DriverStatus map[string]string `json:"driver_status"`
ExecDriver string `json:"exec_driver"`
NumImages int `json:"num_images"`
NumContainers int `json:"num_containers"`
func (m *manager) DockerImages() ([]info.DockerImage, error) {
return docker.Images()
}
type DockerImage struct {
ID string `json:"id"`
RepoTags []string `json:"repo_tags"` // repository name and tags.
Created int64 `json:"created"` // unix time since creation.
VirtualSize int64 `json:"virtual_size"`
Size int64 `json:"size"`
}
func (m *manager) DockerImages() ([]DockerImage, error) {
images, err := docker.DockerImages()
if err != nil {
return nil, err
}
out := []DockerImage{}
const unknownTag = "<none>:<none>"
for _, image := range images {
if len(image.RepoTags) == 1 && image.RepoTags[0] == unknownTag {
// images with repo or tags are uninteresting.
continue
}
di := DockerImage{
ID: image.ID,
RepoTags: image.RepoTags,
Created: image.Created,
VirtualSize: image.VirtualSize,
Size: image.Size,
}
out = append(out, di)
}
return out, nil
}
func (m *manager) DockerInfo() (DockerStatus, error) {
return dockerInfo()
}
func dockerInfo() (DockerStatus, error) {
dockerInfo, err := docker.DockerInfo()
if err != nil {
return DockerStatus{}, err
}
versionInfo, err := getVersionInfo()
if err != nil {
return DockerStatus{}, err
}
out := DockerStatus{}
out.Version = versionInfo.DockerVersion
out.KernelVersion = dockerInfo.KernelVersion
out.OS = dockerInfo.OperatingSystem
out.Hostname = dockerInfo.Name
out.RootDir = dockerInfo.DockerRootDir
out.Driver = dockerInfo.Driver
out.ExecDriver = dockerInfo.ExecutionDriver
out.NumImages = dockerInfo.Images
out.NumContainers = dockerInfo.Containers
out.DriverStatus = make(map[string]string, len(dockerInfo.DriverStatus))
for _, v := range dockerInfo.DriverStatus {
out.DriverStatus[v[0]] = v[1]
}
return out, nil
func (m *manager) DockerInfo() (info.DockerStatus, error) {
return docker.Status()
}
func (m *manager) DebugInfo() map[string][]string {
@ -1234,3 +1166,18 @@ func (m *manager) DebugInfo() map[string][]string {
debugInfo["Managed containers"] = lines
return debugInfo
}
func getVersionInfo() (*info.VersionInfo, error) {
kernel_version := machine.KernelVersion()
container_os := machine.ContainerOsVersion()
docker_version := docker.VersionString()
return &info.VersionInfo{
KernelVersion: kernel_version,
ContainerOsVersion: container_os,
DockerVersion: docker_version,
CadvisorVersion: version.Info["version"],
CadvisorRevision: version.Info["revision"],
}, nil
}

View File

@ -1,119 +0,0 @@
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build test
package manager
import (
"github.com/google/cadvisor/events"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/info/v2"
"github.com/stretchr/testify/mock"
)
type ManagerMock struct {
mock.Mock
}
func (c *ManagerMock) Start() error {
args := c.Called()
return args.Error(0)
}
func (c *ManagerMock) Stop() error {
args := c.Called()
return args.Error(0)
}
func (c *ManagerMock) GetContainerInfo(name string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
args := c.Called(name, query)
return args.Get(0).(*info.ContainerInfo), args.Error(1)
}
func (c *ManagerMock) SubcontainersInfo(containerName string, query *info.ContainerInfoRequest) ([]*info.ContainerInfo, error) {
args := c.Called(containerName, query)
return args.Get(0).([]*info.ContainerInfo), args.Error(1)
}
func (c *ManagerMock) AllDockerContainers(query *info.ContainerInfoRequest) (map[string]info.ContainerInfo, error) {
args := c.Called(query)
return args.Get(0).(map[string]info.ContainerInfo), args.Error(1)
}
func (c *ManagerMock) DockerContainer(name string, query *info.ContainerInfoRequest) (info.ContainerInfo, error) {
args := c.Called(name, query)
return args.Get(0).(info.ContainerInfo), args.Error(1)
}
func (c *ManagerMock) GetContainerSpec(containerName string, options v2.RequestOptions) (map[string]v2.ContainerSpec, error) {
args := c.Called(containerName, options)
return args.Get(0).(map[string]v2.ContainerSpec), args.Error(1)
}
func (c *ManagerMock) GetDerivedStats(containerName string, options v2.RequestOptions) (map[string]v2.DerivedStats, error) {
args := c.Called(containerName, options)
return args.Get(0).(map[string]v2.DerivedStats), args.Error(1)
}
func (c *ManagerMock) GetRequestedContainersInfo(containerName string, options v2.RequestOptions) (map[string]*info.ContainerInfo, error) {
args := c.Called(containerName, options)
return args.Get(0).(map[string]*info.ContainerInfo), args.Error(1)
}
func (c *ManagerMock) Exists(name string) bool {
args := c.Called(name)
return args.Get(0).(bool)
}
func (c *ManagerMock) WatchForEvents(queryuest *events.Request, passedChannel chan *info.Event) error {
args := c.Called(queryuest, passedChannel)
return args.Error(0)
}
func (c *ManagerMock) GetPastEvents(queryuest *events.Request) ([]*info.Event, error) {
args := c.Called(queryuest)
return args.Get(0).([]*info.Event), args.Error(1)
}
func (c *ManagerMock) GetMachineInfo() (*info.MachineInfo, error) {
args := c.Called()
return args.Get(0).(*info.MachineInfo), args.Error(1)
}
func (c *ManagerMock) GetVersionInfo() (*info.VersionInfo, error) {
args := c.Called()
return args.Get(0).(*info.VersionInfo), args.Error(1)
}
func (c *ManagerMock) GetFsInfo() ([]v2.FsInfo, error) {
args := c.Called()
return args.Get(0).([]v2.FsInfo), args.Error(1)
}
func (c *ManagerMock) GetProcessList(name string, options v2.RequestOptions) ([]v2.ProcessInfo, error) {
args := c.Called()
return args.Get(0).([]v2.ProcessInfo), args.Error(1)
}
func (c *ManagerMock) DockerInfo() (DockerStatus, error) {
args := c.Called()
return args.Get(0).(DockerStatus), args.Error(1)
}
func (c *ManagerMock) DockerImages() ([]DockerImage, error) {
args := c.Called()
return args.Get(0).([]DockerImage), args.Error(1)
}

View File

@ -31,7 +31,7 @@ import (
const DockerPage = "/docker/"
func toStatusKV(status manager.DockerStatus) ([]keyVal, []keyVal) {
func toStatusKV(status info.DockerStatus) ([]keyVal, []keyVal) {
ds := []keyVal{
{Key: "Driver", Value: status.Driver},
}

View File

@ -62,7 +62,7 @@ type pageData struct {
Root string
DockerStatus []keyVal
DockerDriverStatus []keyVal
DockerImages []manager.DockerImage
DockerImages []info.DockerImage
}
func init() {