1
2
3
4
5
6
7
8
9
10
11
12
13
14 package crio
15
16 import (
17 "context"
18 "encoding/json"
19 "errors"
20 "fmt"
21 "net"
22 "net/http"
23 "syscall"
24 "time"
25 )
26
27 const (
28 InspectContainersEndpoint = "/containers"
29
30 crioProtocolPrefix = "cri-o://"
31 maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path)
32 )
33
34
35 type CrioClient struct {
36 client *http.Client
37 socketPath string
38 }
39
40
41 func (c CrioClient) FormatContainerID(ctx context.Context, containerID string) (string, error) {
42 if len(containerID) < len(crioProtocolPrefix) {
43 return "", fmt.Errorf("container id %s is not a crio container id", containerID)
44 }
45 if containerID[0:len(crioProtocolPrefix)] != crioProtocolPrefix {
46 return "", fmt.Errorf("expected %s but got %s", crioProtocolPrefix, containerID[0:len(crioProtocolPrefix)])
47 }
48 return containerID[len(crioProtocolPrefix):], nil
49 }
50
51
52
53
54 func (c CrioClient) GetPidFromContainerID(ctx context.Context, containerID string) (uint32, error) {
55 id, err := c.FormatContainerID(ctx, containerID)
56 if err != nil {
57 return 0, err
58 }
59
60 req, err := c.getRequest(ctx, InspectContainersEndpoint+"/"+id)
61 if err != nil {
62 return 0, err
63 }
64 resp, err := c.client.Do(req)
65 if err != nil {
66 return 0, err
67 }
68 defer resp.Body.Close()
69 cInfo := make(map[string]interface{})
70 if err := json.NewDecoder(resp.Body).Decode(&cInfo); err != nil {
71 return 0, err
72 }
73
74 pid := cInfo["pid"]
75 if pid, ok := pid.(float64); ok {
76 return uint32(pid), nil
77 }
78
79 return 0, errors.New("fail to get pid from container info")
80 }
81
82
83 func (c CrioClient) ContainerKillByContainerID(ctx context.Context, containerID string) error {
84 pid, err := c.GetPidFromContainerID(ctx, containerID)
85 if err != nil {
86 return err
87 }
88 return syscall.Kill(int(pid), syscall.SIGKILL)
89 }
90
91 func New(socketPath string) (*CrioClient, error) {
92 tr := new(http.Transport)
93 if err := configureUnixTransport(tr, "unix", socketPath); err != nil {
94 return nil, err
95 }
96 c := &http.Client{
97 Transport: tr,
98 }
99 return &CrioClient{
100 client: c,
101 socketPath: socketPath,
102 }, nil
103 }
104
105 func configureUnixTransport(tr *http.Transport, proto, addr string) error {
106 if len(addr) > maxUnixSocketPathSize {
107 return fmt.Errorf("unix socket path %q is too long", addr)
108 }
109
110 tr.DisableCompression = true
111 tr.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
112 return net.DialTimeout(proto, addr, 32*time.Second)
113 }
114 return nil
115 }
116
117 func (c *CrioClient) getRequest(ctx context.Context, path string) (*http.Request, error) {
118 req, err := http.NewRequest("GET", path, nil)
119 if err != nil {
120 return nil, err
121 }
122
123
124 req.Host = "crio"
125 req.URL.Host = c.socketPath
126 req.URL.Scheme = "http"
127 req = req.WithContext(ctx)
128 return req, nil
129 }
130