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