github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/container-utils/podman/podman.go (about) 1 // Copyright 2023 The Inspektor Gadget authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package podman 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "net" 22 "net/http" 23 "net/url" 24 "time" 25 26 log "github.com/sirupsen/logrus" 27 28 runtimeclient "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils/runtime-client" 29 "github.com/inspektor-gadget/inspektor-gadget/pkg/types" 30 ) 31 32 const ( 33 defaultConnectionTimeout = 2 * time.Second 34 containerListAllURL = "http://d/v4.0.0/libpod/containers/json?all=true" 35 containerInspectURL = "http://d/v4.0.0/libpod/containers/%s/json" 36 ) 37 38 type PodmanClient struct { 39 client http.Client 40 } 41 42 func NewPodmanClient(socketPath string) runtimeclient.ContainerRuntimeClient { 43 if socketPath == "" { 44 socketPath = runtimeclient.PodmanDefaultSocketPath 45 } 46 47 return &PodmanClient{ 48 client: http.Client{ 49 Transport: &http.Transport{ 50 DialContext: func(ctx context.Context, _, _ string) (conn net.Conn, err error) { 51 return net.Dial("unix", socketPath) 52 }, 53 }, 54 Timeout: defaultConnectionTimeout, 55 }, 56 } 57 } 58 59 func (p *PodmanClient) listContainers(containerID string) ([]*runtimeclient.ContainerData, error) { 60 var filters string 61 if containerID != "" { 62 f, err := json.Marshal(map[string][]string{"id": {containerID}}) 63 if err != nil { 64 return nil, fmt.Errorf("setting up filters: %w", err) 65 } 66 filters = "&filters=" + url.QueryEscape(string(f)) 67 } 68 69 resp, err := p.client.Get(containerListAllURL + filters) 70 if err != nil { 71 return nil, fmt.Errorf("listing containers: %w", err) 72 } 73 defer resp.Body.Close() 74 75 if resp.StatusCode != http.StatusOK { 76 return nil, fmt.Errorf("listing containers via rest api: %s", resp.Status) 77 } 78 79 var containers []struct { 80 ID string `json:"Id"` 81 Names []string `json:"Names"` 82 State string `json:"State"` 83 } 84 if err = json.NewDecoder(resp.Body).Decode(&containers); err != nil { 85 return nil, fmt.Errorf("decoding containers: %w", err) 86 } 87 88 ret := make([]*runtimeclient.ContainerData, len(containers)) 89 for i, c := range containers { 90 ret[i] = &runtimeclient.ContainerData{ 91 Runtime: runtimeclient.RuntimeContainerData{ 92 BasicRuntimeMetadata: types.BasicRuntimeMetadata{ 93 ContainerID: c.ID, 94 ContainerName: c.Names[0], 95 RuntimeName: types.RuntimeNamePodman, 96 }, 97 State: containerStatusStateToRuntimeClientState(c.State), 98 }, 99 } 100 } 101 return ret, nil 102 } 103 104 func (p *PodmanClient) GetContainers() ([]*runtimeclient.ContainerData, error) { 105 return p.listContainers("") 106 } 107 108 func (p *PodmanClient) GetContainer(containerID string) (*runtimeclient.ContainerData, error) { 109 containers, err := p.listContainers(containerID) 110 if err != nil { 111 return nil, err 112 } 113 if len(containers) == 0 { 114 return nil, fmt.Errorf("container %q not found", containerID) 115 } 116 if len(containers) > 1 { 117 log.Warnf("PodmanClient: multiple containers (%d) with ID %q. Taking the first one: %+v", 118 len(containers), containerID, containers) 119 } 120 return containers[0], nil 121 } 122 123 func (p *PodmanClient) GetContainerDetails(containerID string) (*runtimeclient.ContainerDetailsData, error) { 124 resp, err := p.client.Get(fmt.Sprintf(containerInspectURL, containerID)) 125 if err != nil { 126 return nil, fmt.Errorf("inspecting container %q: %w", containerID, err) 127 } 128 defer resp.Body.Close() 129 130 if resp.StatusCode != http.StatusOK { 131 return nil, fmt.Errorf("inspecting container via rest api %q: %s", containerID, resp.Status) 132 } 133 134 var container struct { 135 ID string `json:"Id"` 136 Name string `json:"Name"` 137 State struct { 138 Status string `json:"Status"` 139 Pid int `json:"Pid"` 140 CgroupPath string `json:"CgroupPath"` 141 } `json:"State"` 142 } 143 144 if err = json.NewDecoder(resp.Body).Decode(&container); err != nil { 145 return nil, fmt.Errorf("decoding container %q: %w", containerID, err) 146 } 147 148 return &runtimeclient.ContainerDetailsData{ 149 ContainerData: runtimeclient.ContainerData{ 150 Runtime: runtimeclient.RuntimeContainerData{ 151 BasicRuntimeMetadata: types.BasicRuntimeMetadata{ 152 ContainerID: container.ID, 153 ContainerName: container.Name, 154 RuntimeName: types.RuntimeNamePodman, 155 }, 156 State: containerStatusStateToRuntimeClientState(container.State.Status), 157 }, 158 }, 159 Pid: container.State.Pid, 160 CgroupsPath: container.State.CgroupPath, 161 }, nil 162 } 163 164 func (p *PodmanClient) Close() error { 165 return nil 166 } 167 168 func containerStatusStateToRuntimeClientState(containerState string) string { 169 switch containerState { 170 case "created": 171 return runtimeclient.StateCreated 172 case "running": 173 return runtimeclient.StateRunning 174 case "exited": 175 return runtimeclient.StateExited 176 case "dead": 177 return runtimeclient.StateExited 178 default: 179 return runtimeclient.StateUnknown 180 } 181 }