diff --git a/container/container.go b/container/container.go index 8414efc6a0..4c435a0e81 100644 --- a/container/container.go +++ b/container/container.go @@ -35,6 +35,7 @@ const ( ContainerTypeCrio ContainerTypeContainerd ContainerTypeMesos + ContainerTypePodman ) // Interface for container operation handlers. diff --git a/container/podman/client.go b/container/podman/client.go new file mode 100644 index 0000000000..af33ebeaf5 --- /dev/null +++ b/container/podman/client.go @@ -0,0 +1,58 @@ +// Copyright 2021 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. + +package podman + +import ( + "context" + "fmt" + "net" + "net/http" + urllib "net/url" +) + +type clientKey struct{} + +func (c clientKey) String() string { + return "client" +} + +type Connection struct { + URI *urllib.URL + Client *http.Client +} + +func client(ctx *context.Context) (*Connection, error) { + url, err := urllib.Parse(*endpointFlag) + if err != nil { + return nil, err + } + + switch url.Scheme { + case "unix": + connection := Connection{URI: url} + connection.Client = &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + return (&net.Dialer{}).DialContext(ctx, "unix", url.Path) + }, + DisableCompression: true, + }, + } + *ctx = context.WithValue(*ctx, clientKey{}, &connection) + return &connection, nil + } + + return nil, fmt.Errorf("couldn't get podman client") +} diff --git a/container/podman/factory.go b/container/podman/factory.go new file mode 100644 index 0000000000..ed0cd0b987 --- /dev/null +++ b/container/podman/factory.go @@ -0,0 +1,113 @@ +// Copyright 2021 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. + +package podman + +import ( + "flag" + "fmt" + "path" + "sync" + "time" + + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/container/docker" + dockerutil "github.com/google/cadvisor/container/docker/utils" + "github.com/google/cadvisor/devicemapper" + "github.com/google/cadvisor/fs" + info "github.com/google/cadvisor/info/v1" + "github.com/google/cadvisor/zfs" +) + +const ( + rootDirRetries = 5 + rootDirRetryPeriod = time.Second + containerBaseName = "container" +) + +var ( + endpointFlag = flag.String("podman", "unix:///var/run/podman/podman.sock", "podman endpoint") +) + +var ( + rootDir string + rootDirOnce sync.Once +) + +func RootDir() string { + rootDirOnce.Do(func() { + for i := 0; i < rootDirRetries; i++ { + status, err := Status() + if err == nil && status.RootDir != "" { + rootDir = status.RootDir + break + } else { + time.Sleep(rootDirRetryPeriod) + } + } + }) + return rootDir +} + +type podmanFactory struct { + // Information about the mounted cgroup subsystems. + machineInfoFactory info.MachineInfoFactory + + storageDriver docker.StorageDriver + storageDir string + + cgroupSubsystem map[string]string + + fsInfo fs.FsInfo + + metrics container.MetricSet + + thinPoolName string + thinPoolWatcher *devicemapper.ThinPoolWatcher + + zfsWatcher *zfs.ZfsWatcher +} + +func (f *podmanFactory) CanHandleAndAccept(name string) (handle bool, accept bool, err error) { + // Rootless + if path.Base(name) == containerBaseName { + name, _ = path.Split(name) + } + if !dockerutil.IsContainerName(name) { + return false, false, nil + } + + id := dockerutil.ContainerNameToId(name) + + ctnr, err := InspectContainer(id) + if err != nil || !ctnr.State.Running { + return false, true, fmt.Errorf("error inspecting container: %v", err) + } + + return true, true, nil +} + +func (f *podmanFactory) DebugInfo() map[string][]string { + return map[string][]string{} +} + +func (f *podmanFactory) String() string { + return "podman" +} + +func (f *podmanFactory) NewContainerHandler(name string, metadataEnvAllowList []string, inHostNamespace bool) (handler container.ContainerHandler, err error) { + return newPodmanContainerHandler(name, f.machineInfoFactory, f.fsInfo, + f.storageDriver, f.storageDir, f.cgroupSubsystem, inHostNamespace, + metadataEnvAllowList, f.metrics, f.thinPoolName, f.thinPoolWatcher, f.zfsWatcher) +} diff --git a/container/podman/fs.go b/container/podman/fs.go new file mode 100644 index 0000000000..e714e900c3 --- /dev/null +++ b/container/podman/fs.go @@ -0,0 +1,54 @@ +// Copyright 2022 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. + +package podman + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/google/cadvisor/container/docker" +) + +const ( + containersJSONFilename = "containers.json" +) + +type containersJSON struct { + ID string `json:"id"` + Layer string `json:"layer"` + // rest in unnecessary +} + +func rwLayerID(storageDriver docker.StorageDriver, storageDir string, containerID string) (string, error) { + data, err := os.ReadFile(filepath.Join(storageDir, string(storageDriver)+"-containers", containersJSONFilename)) + if err != nil { + return "", err + } + var containers []containersJSON + err = json.Unmarshal(data, &containers) + if err != nil { + return "", err + } + + for _, c := range containers { + if c.ID == containerID { + return c.Layer, nil + } + } + + return "", fmt.Errorf("unable to determine %v rw layer id", containerID) +} diff --git a/container/podman/handler.go b/container/podman/handler.go new file mode 100644 index 0000000000..203d9b4eb9 --- /dev/null +++ b/container/podman/handler.go @@ -0,0 +1,309 @@ +// Copyright 2021 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. + +package podman + +import ( + "fmt" + "path" + "path/filepath" + "strings" + "time" + + dockercontainer "github.com/docker/docker/api/types/container" + "github.com/opencontainers/runc/libcontainer/cgroups" + + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/container/common" + "github.com/google/cadvisor/container/docker" + dockerutil "github.com/google/cadvisor/container/docker/utils" + containerlibcontainer "github.com/google/cadvisor/container/libcontainer" + "github.com/google/cadvisor/devicemapper" + "github.com/google/cadvisor/fs" + info "github.com/google/cadvisor/info/v1" + "github.com/google/cadvisor/zfs" +) + +type podmanContainerHandler struct { + // machineInfoFactory provides info.MachineInfo + machineInfoFactory info.MachineInfoFactory + + // Absolute path to the cgroup hierarchies of this container. + // (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test") + cgroupPaths map[string]string + + storageDriver docker.StorageDriver + fsInfo fs.FsInfo + rootfsStorageDir string + + creationTime time.Time + + // Metadata associated with the container. + envs map[string]string + labels map[string]string + + image string + + networkMode dockercontainer.NetworkMode + + fsHandler common.FsHandler + + ipAddress string + + metrics container.MetricSet + + thinPoolName string + + zfsParent string + + reference info.ContainerReference + + libcontainerHandler *containerlibcontainer.Handler +} + +func newPodmanContainerHandler( + name string, + machineInfoFactory info.MachineInfoFactory, + fsInfo fs.FsInfo, + storageDriver docker.StorageDriver, + storageDir string, + cgroupSubsystems map[string]string, + inHostNamespace bool, + metadataEnvAllowList []string, + metrics container.MetricSet, + thinPoolName string, + thinPoolWatcher *devicemapper.ThinPoolWatcher, + zfsWatcher *zfs.ZfsWatcher, +) (container.ContainerHandler, error) { + // Create the cgroup paths. + cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems, name) + + cgroupManager, err := containerlibcontainer.NewCgroupManager(name, cgroupPaths) + if err != nil { + return nil, err + } + + rootFs := "/" + if !inHostNamespace { + rootFs = "/rootfs" + storageDir = path.Join(rootFs, storageDir) + } + + rootless := path.Base(name) == containerBaseName + if rootless { + name, _ = path.Split(name) + } + + id := dockerutil.ContainerNameToId(name) + + // We assume that if Inspect fails then the container is not known to Podman. + ctnr, err := InspectContainer(id) + if err != nil { + return nil, err + } + + rwLayerID, err := rwLayerID(storageDriver, storageDir, id) + if err != nil { + return nil, err + } + + rootfsStorageDir, zfsParent, zfsFilesystem, err := determineDeviceStorage(storageDriver, storageDir, rwLayerID) + if err != nil { + return nil, err + } + + otherStorageDir := filepath.Join(storageDir, string(storageDriver)+"-containers", id) + + handler := &podmanContainerHandler{ + machineInfoFactory: machineInfoFactory, + cgroupPaths: cgroupPaths, + storageDriver: storageDriver, + fsInfo: fsInfo, + rootfsStorageDir: rootfsStorageDir, + ipAddress: ctnr.NetworkSettings.IPAddress, + envs: make(map[string]string), + labels: ctnr.Config.Labels, + image: ctnr.Config.Image, + networkMode: ctnr.HostConfig.NetworkMode, + fsHandler: common.NewFsHandler(common.DefaultPeriod, rootfsStorageDir, otherStorageDir, fsInfo), + metrics: metrics, + thinPoolName: thinPoolName, + zfsParent: zfsParent, + reference: info.ContainerReference{ + Id: id, + Name: name, + Aliases: []string{strings.TrimPrefix(ctnr.Name, "/"), id}, + Namespace: Namespace, + }, + libcontainerHandler: containerlibcontainer.NewHandler(cgroupManager, rootFs, ctnr.State.Pid, metrics), + } + + handler.creationTime, err = time.Parse(time.RFC3339, ctnr.Created) + if err != nil { + return nil, fmt.Errorf("failed to parse the create timestamp %q for container %q: %v", ctnr.Created, id, err) + } + + if ctnr.RestartCount > 0 { + handler.labels["restartcount"] = fmt.Sprint(ctnr.RestartCount) + } + + // Obtain the IP address for the container. + // If the NetworkMode starts with 'container:' then we need to use the IP address of the container specified. + // This happens in cases such as kubernetes where the containers doesn't have an IP address itself and we need to use the pod's address + networkMode := string(handler.networkMode) + if handler.ipAddress == "" && strings.HasPrefix(networkMode, "container:") { + id := strings.TrimPrefix(networkMode, "container:") + ctnr, err := InspectContainer(id) + if err != nil { + return nil, err + } + handler.ipAddress = ctnr.NetworkSettings.IPAddress + } + + if metrics.Has(container.DiskUsageMetrics) { + handler.fsHandler = &docker.FsHandler{ + FsHandler: common.NewFsHandler(common.DefaultPeriod, rootfsStorageDir, otherStorageDir, fsInfo), + ThinPoolWatcher: thinPoolWatcher, + ZfsWatcher: zfsWatcher, + DeviceID: ctnr.GraphDriver.Data["DeviceId"], + ZfsFilesystem: zfsFilesystem, + } + } + + // Split env vars to get metadata map. + for _, exposedEnv := range metadataEnvAllowList { + if exposedEnv == "" { + continue + } + + for _, envVar := range ctnr.Config.Env { + if envVar != "" { + splits := strings.SplitN(envVar, "=", 2) + if len(splits) == 2 && strings.HasPrefix(splits[0], exposedEnv) { + handler.envs[strings.ToLower(splits[0])] = splits[1] + } + } + } + } + + return handler, nil +} + +func determineDeviceStorage(storageDriver docker.StorageDriver, storageDir string, rwLayerID string) ( + rootfsStorageDir string, zfsFilesystem string, zfsParent string, err error) { + switch storageDriver { + // Podman aliased the driver names together. + case docker.OverlayStorageDriver, docker.Overlay2StorageDriver: + rootfsStorageDir = path.Join(storageDir, "overlay", rwLayerID, "diff") + return + default: + return docker.DetermineDeviceStorage(storageDriver, storageDir, rwLayerID) + } +} + +func (p podmanContainerHandler) ContainerReference() (info.ContainerReference, error) { + return p.reference, nil +} + +func (p podmanContainerHandler) needNet() bool { + if p.metrics.Has(container.NetworkUsageMetrics) { + p.networkMode.IsContainer() + return !p.networkMode.IsContainer() + } + return false +} + +func (p podmanContainerHandler) GetSpec() (info.ContainerSpec, error) { + hasFilesystem := p.metrics.Has(container.DiskUsageMetrics) + + spec, err := common.GetSpec(p.cgroupPaths, p.machineInfoFactory, p.needNet(), hasFilesystem) + if err != nil { + return info.ContainerSpec{}, err + } + + spec.Labels = p.labels + spec.Envs = p.envs + spec.Image = p.image + spec.CreationTime = p.creationTime + + return spec, nil +} + +func (p podmanContainerHandler) GetStats() (*info.ContainerStats, error) { + stats, err := p.libcontainerHandler.GetStats() + if err != nil { + return stats, err + } + + if !p.needNet() { + stats.Network = info.NetworkStats{} + } + + err = docker.FsStats(stats, p.machineInfoFactory, p.metrics, p.storageDriver, + p.fsHandler, p.fsInfo, p.thinPoolName, p.rootfsStorageDir, p.zfsParent) + if err != nil { + return stats, err + } + + return stats, nil +} + +func (p podmanContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) { + return []info.ContainerReference{}, nil +} + +func (p podmanContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { + return p.libcontainerHandler.GetProcesses() +} + +func (p podmanContainerHandler) GetCgroupPath(resource string) (string, error) { + var res string + if !cgroups.IsCgroup2UnifiedMode() { + res = resource + } + path, ok := p.cgroupPaths[res] + if !ok { + return "", fmt.Errorf("couldn't find path for resource %q for container %q", resource, p.reference.Name) + } + + return path, nil +} + +func (p podmanContainerHandler) GetContainerLabels() map[string]string { + return p.labels +} + +func (p podmanContainerHandler) GetContainerIPAddress() string { + return p.ipAddress +} + +func (p podmanContainerHandler) Exists() bool { + return common.CgroupExists(p.cgroupPaths) +} + +func (p podmanContainerHandler) Cleanup() { + if p.fsHandler != nil { + p.fsHandler.Stop() + } +} + +func (p podmanContainerHandler) Start() { + if p.fsHandler != nil { + p.fsHandler.Start() + } +} + +func (p podmanContainerHandler) Type() container.ContainerType { + return container.ContainerTypePodman +} diff --git a/container/podman/install/install.go b/container/podman/install/install.go new file mode 100644 index 0000000000..72a273ef02 --- /dev/null +++ b/container/podman/install/install.go @@ -0,0 +1,29 @@ +// Copyright 2021 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. + +package install + +import ( + "k8s.io/klog/v2" + + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/container/podman" +) + +func init() { + err := container.RegisterPlugin("podman", podman.NewPlugin()) + if err != nil { + klog.Fatalf("Failed to register podman plugin: %v", err) + } +} diff --git a/container/podman/plugin.go b/container/podman/plugin.go new file mode 100644 index 0000000000..1aac12b3e8 --- /dev/null +++ b/container/podman/plugin.go @@ -0,0 +1,109 @@ +// Copyright 2021 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. + +package podman + +import ( + "fmt" + + "github.com/opencontainers/runc/libcontainer/cgroups" + "k8s.io/klog/v2" + + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/container/docker" + dockerutil "github.com/google/cadvisor/container/docker/utils" + "github.com/google/cadvisor/container/libcontainer" + "github.com/google/cadvisor/devicemapper" + "github.com/google/cadvisor/fs" + info "github.com/google/cadvisor/info/v1" + "github.com/google/cadvisor/watcher" + "github.com/google/cadvisor/zfs" +) + +func NewPlugin() container.Plugin { + return &plugin{} +} + +type plugin struct{} + +func (p *plugin) InitializeFSContext(context *fs.Context) error { + context.Podman = fs.PodmanContext{ + Root: "", + Driver: "", + DriverStatus: map[string]string{}, + } + + return nil +} + +func (p *plugin) Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics container.MetricSet) (watcher.ContainerWatcher, error) { + return Register(factory, fsInfo, includedMetrics) +} + +func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, metrics container.MetricSet) (watcher.ContainerWatcher, error) { + cgroupSubsystem, err := libcontainer.GetCgroupSubsystems(metrics) + if err != nil { + return nil, fmt.Errorf("failed to get cgroup subsystems: %v", err) + } + + validatedInfo, err := docker.ValidateInfo(GetInfo, VersionString) + if err != nil { + return nil, fmt.Errorf("failed to validate Podman info: %v", err) + } + + var ( + thinPoolName string + thinPoolWatcher *devicemapper.ThinPoolWatcher + zfsWatcher *zfs.ZfsWatcher + ) + if metrics.Has(container.DiskUsageMetrics) { + switch docker.StorageDriver(validatedInfo.Driver) { + case docker.DevicemapperStorageDriver: + thinPoolWatcher, err = docker.StartThinPoolWatcher(validatedInfo) + if err != nil { + klog.Errorf("devicemapper filesystem stats will not be reported: %v", err) + } + + status, _ := docker.StatusFromDockerInfo(*validatedInfo) + thinPoolName = status.DriverStatus[dockerutil.DriverStatusPoolName] + case docker.ZfsStorageDriver: + zfsWatcher, err = docker.StartZfsWatcher(validatedInfo) + if err != nil { + klog.Errorf("zfs filesystem stats will not be reported: %v", err) + } + } + } + + // Register Podman container handler factory. + klog.V(1).Info("Registering Podman factory") + f := &podmanFactory{ + machineInfoFactory: factory, + storageDriver: docker.StorageDriver(validatedInfo.Driver), + storageDir: RootDir(), + cgroupSubsystem: cgroupSubsystem, + fsInfo: fsInfo, + metrics: metrics, + thinPoolName: thinPoolName, + thinPoolWatcher: thinPoolWatcher, + zfsWatcher: zfsWatcher, + } + + container.RegisterContainerHandlerFactory(f, []watcher.ContainerWatchSource{watcher.Raw}) + + if !cgroups.IsCgroup2UnifiedMode() { + klog.Warning("Podman rootless containers not working with cgroups v1!") + } + + return nil, nil +} diff --git a/container/podman/podman.go b/container/podman/podman.go new file mode 100644 index 0000000000..4f2cc7b95f --- /dev/null +++ b/container/podman/podman.go @@ -0,0 +1,132 @@ +// Copyright 2021 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. + +package podman + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + dockertypes "github.com/docker/docker/api/types" + "github.com/pkg/errors" + + "github.com/google/cadvisor/container/docker" + "github.com/google/cadvisor/container/docker/utils" + v1 "github.com/google/cadvisor/info/v1" +) + +const ( + Namespace = "podman" +) + +var timeout = 10 * time.Second + +func validateResponse(gotError error, response *http.Response) error { + var err error + switch { + case response == nil: + err = fmt.Errorf("response not present") + case response.StatusCode == http.StatusNotFound: + err = fmt.Errorf("item not found") + case response.StatusCode == http.StatusNotImplemented: + err = fmt.Errorf("query not implemented") + default: + return gotError + } + + if gotError != nil { + err = errors.Wrap(gotError, err.Error()) + } + + return err +} + +func apiGetRequest(url string, item interface{}) error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + conn, err := client(&ctx) + if err != nil { + return err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return err + } + + resp, err := conn.Client.Do(req) + err = validateResponse(err, resp) + if err != nil { + return err + } + defer resp.Body.Close() + + data, err := io.ReadAll(resp.Body) + if err != nil { + return err + + } + + err = json.Unmarshal(data, item) + if err != nil { + return err + } + + return ctx.Err() +} + +func Images() ([]v1.DockerImage, error) { + var summaries []dockertypes.ImageSummary + err := apiGetRequest("http://d/v1.0.0/images/json", &summaries) + if err != nil { + return nil, err + } + return utils.SummariesToImages(summaries) +} + +func Status() (v1.DockerStatus, error) { + podmanInfo, err := GetInfo() + if err != nil { + return v1.DockerStatus{}, err + } + + return docker.StatusFromDockerInfo(*podmanInfo) +} + +func GetInfo() (*dockertypes.Info, error) { + var info dockertypes.Info + err := apiGetRequest("http://d/v1.0.0/info", &info) + return &info, err +} + +func VersionString() (string, error) { + var version dockertypes.Version + err := apiGetRequest("http://d/v1.0.0/version", &version) + if err != nil { + return "Unknown", err + } + + return version.Version, nil +} + +func InspectContainer(id string) (dockertypes.ContainerJSON, error) { + var data dockertypes.ContainerJSON + err := apiGetRequest(fmt.Sprintf("http://d/v1.0.0/containers/%s/json", id), &data) + return data, err +} diff --git a/container/podman/podman_test.go b/container/podman/podman_test.go new file mode 100644 index 0000000000..9414760329 --- /dev/null +++ b/container/podman/podman_test.go @@ -0,0 +1,72 @@ +// Copyright 2022 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. + +package podman + +import ( + "errors" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidateResponse(t *testing.T) { + for _, tc := range []struct { + response *http.Response + err error + expected string + }{ + { + response: nil, + err: nil, + expected: "response not present", + }, + { + response: &http.Response{ + StatusCode: http.StatusNotFound, + }, + err: errors.New("some error"), + expected: "item not found: some error", + }, + { + response: &http.Response{ + StatusCode: http.StatusNotImplemented, + }, + err: errors.New("some error"), + expected: "query not implemented: some error", + }, + { + response: &http.Response{ + StatusCode: http.StatusOK, + }, + err: errors.New("some error"), + expected: "some error", + }, + { + response: &http.Response{ + StatusCode: http.StatusOK, + }, + err: nil, + expected: "", + }, + } { + err := validateResponse(tc.err, tc.response) + if tc.expected != "" { + assert.EqualError(t, err, tc.expected) + } else { + assert.NoError(t, err) + } + } +} diff --git a/fs/types.go b/fs/types.go index 35268ace98..269aa8769a 100644 --- a/fs/types.go +++ b/fs/types.go @@ -22,6 +22,7 @@ type Context struct { // docker root directory. Docker DockerContext Crio CrioContext + Podman PodmanContext } type DockerContext struct { @@ -30,6 +31,12 @@ type DockerContext struct { DriverStatus map[string]string } +type PodmanContext struct { + Root string + Driver string + DriverStatus map[string]string +} + type CrioContext struct { Root string } diff --git a/info/v2/container.go b/info/v2/container.go index 15fb79b9ea..f0824027a3 100644 --- a/info/v2/container.go +++ b/info/v2/container.go @@ -25,6 +25,7 @@ import ( const ( TypeName = "name" TypeDocker = "docker" + TypePodman = "podman" ) type CpuSpec struct {