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  }