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