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 "fmt"
22 "net"
23 "net/http"
24 "syscall"
25 "time"
26
27 "github.com/pkg/errors"
28 "google.golang.org/grpc"
29 v1 "k8s.io/cri-api/pkg/apis/runtime/v1"
30 )
31
32 const (
33 InspectContainersEndpoint = "/containers"
34
35 crioProtocolPrefix = "cri-o://"
36 maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path)
37 )
38
39
40 type CrioClient struct {
41 client *http.Client
42 runtimeClient v1.RuntimeServiceClient
43 socketPath string
44 }
45
46
47 func (c CrioClient) FormatContainerID(ctx context.Context, containerID string) (string, error) {
48 if len(containerID) < len(crioProtocolPrefix) {
49 return "", errors.Errorf("container id %s is not a crio container id", containerID)
50 }
51 if containerID[0:len(crioProtocolPrefix)] != crioProtocolPrefix {
52 return "", errors.Errorf("expected %s but got %s", crioProtocolPrefix, containerID[0:len(crioProtocolPrefix)])
53 }
54 return containerID[len(crioProtocolPrefix):], nil
55 }
56
57
58
59
60 func (c CrioClient) GetPidFromContainerID(ctx context.Context, containerID string) (uint32, error) {
61 id, err := c.FormatContainerID(ctx, containerID)
62 if err != nil {
63 return 0, err
64 }
65
66 req, err := c.getRequest(ctx, InspectContainersEndpoint+"/"+id)
67 if err != nil {
68 return 0, err
69 }
70 resp, err := c.client.Do(req)
71 if err != nil {
72 return 0, err
73 }
74 defer resp.Body.Close()
75 cInfo := make(map[string]interface{})
76 if err := json.NewDecoder(resp.Body).Decode(&cInfo); err != nil {
77 return 0, err
78 }
79
80 pid := cInfo["pid"]
81 if pid, ok := pid.(float64); ok {
82 return uint32(pid), nil
83 }
84
85 return 0, errors.New("fail to get pid from container info")
86 }
87
88
89 func (c CrioClient) ContainerKillByContainerID(ctx context.Context, containerID string) error {
90 pid, err := c.GetPidFromContainerID(ctx, containerID)
91 if err != nil {
92 return err
93 }
94 return syscall.Kill(int(pid), syscall.SIGKILL)
95 }
96
97
98 func (c CrioClient) ListContainerIDs(ctx context.Context) ([]string, error) {
99 resp, err := c.runtimeClient.ListContainers(ctx, &v1.ListContainersRequest{})
100 if err != nil {
101 return nil, err
102 }
103
104 var ids []string
105 for _, container := range resp.Containers {
106 id := fmt.Sprintf("%s%s", crioProtocolPrefix, container.Id)
107 ids = append(ids, id)
108 }
109 return ids, nil
110 }
111
112
113 func (c CrioClient) GetLabelsFromContainerID(ctx context.Context, containerID string) (map[string]string, error) {
114 id, err := c.FormatContainerID(ctx, containerID)
115 if err != nil {
116 return nil, err
117 }
118
119 container, err := c.runtimeClient.ContainerStatus(ctx, &v1.ContainerStatusRequest{
120 ContainerId: id,
121 })
122 if err != nil {
123 return nil, err
124 }
125
126 return container.Status.Labels, nil
127 }
128
129 func buildRuntimeServiceClient(endpoint string) (v1.RuntimeServiceClient, error) {
130 addr := fmt.Sprintf("unix://%s", endpoint)
131 conn, err := grpc.Dial(addr, grpc.WithBlock(), grpc.WithInsecure())
132 if err != nil {
133 return nil, err
134 }
135
136 client := v1.NewRuntimeServiceClient(conn)
137 return client, err
138 }
139
140 func New(socketPath string) (*CrioClient, error) {
141 tr := new(http.Transport)
142 if err := configureUnixTransport(tr, "unix", socketPath); err != nil {
143 return nil, err
144 }
145 c := &http.Client{
146 Transport: tr,
147 }
148
149 runtimeClient, err := buildRuntimeServiceClient(socketPath)
150 if err != nil {
151 return nil, err
152 }
153
154 return &CrioClient{
155 client: c,
156 runtimeClient: runtimeClient,
157 socketPath: socketPath,
158 }, nil
159 }
160
161 func configureUnixTransport(tr *http.Transport, proto, addr string) error {
162 if len(addr) > maxUnixSocketPathSize {
163 return errors.Errorf("unix socket path %q is too long", addr)
164 }
165
166 tr.DisableCompression = true
167 tr.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
168 return net.DialTimeout(proto, addr, 32*time.Second)
169 }
170 return nil
171 }
172
173 func (c *CrioClient) getRequest(ctx context.Context, path string) (*http.Request, error) {
174 req, err := http.NewRequest("GET", path, nil)
175 if err != nil {
176 return nil, err
177 }
178
179
180 req.Host = "crio"
181 req.URL.Host = c.socketPath
182 req.URL.Scheme = "http"
183 req = req.WithContext(ctx)
184 return req, nil
185 }
186