• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tensorchord / envd / 13754521523

10 Mar 2025 01:00AM UTC coverage: 42.542% (+0.4%) from 42.15%
13754521523

Pull #1992

github

kemingy
fix lint

Signed-off-by: Keming <kemingyang@tensorchord.ai>
Pull Request #1992: chore: bump dep version, check dep monthly

26 of 32 new or added lines in 5 files covered. (81.25%)

2 existing lines in 1 file now uncovered.

5157 of 12122 relevant lines covered (42.54%)

158.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/pkg/driver/nerdctl/nerdctl.go
1
// Copyright 2023 The envd 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 nerdctl
16

17
import (
18
        "bytes"
19
        "context"
20
        "encoding/json"
21
        "fmt"
22
        "io"
23
        "os/exec"
24
        "time"
25

26
        "github.com/cockroachdb/errors"
27
        "github.com/docker/docker/api/types/container"
28
        dockerimage "github.com/docker/docker/api/types/image"
29
        "github.com/sirupsen/logrus"
30

31
        "github.com/tensorchord/envd/pkg/driver"
32
        containerType "github.com/tensorchord/envd/pkg/types"
33
        "github.com/tensorchord/envd/pkg/util/buildkitutil"
34
)
35

36
type nerdctlClient struct {
37
        bin string
38
}
39

40
var (
41
        waitingInterval = 1 * time.Second
42
)
43

44
func NewClient(ctx context.Context) (driver.Client, error) {
×
45
        bin, err := exec.LookPath("nerdctl")
×
46
        if err != nil {
×
47
                // for mac lima users, check nerdctl.lima
×
48
                bin, err = exec.LookPath("nerdctl.lima")
×
49
                if err != nil {
×
50
                        return nil, errors.New("can not found nerdctl(or nerdctl.lima for mac) in PATH")
×
51
                }
×
52
        }
53

54
        return &nerdctlClient{
×
55
                bin: bin,
×
56
        }, nil
×
57
}
58

59
func (nc *nerdctlClient) Load(ctx context.Context, r io.ReadCloser, quiet bool) error {
×
60
        cmd := exec.CommandContext(ctx, nc.bin, "load")
×
61
        cmd.Stdin = r
×
62
        var out bytes.Buffer
×
63
        cmd.Stdout = &out
×
64
        err := cmd.Run()
×
65
        if err != nil {
×
66
                return err
×
67
        }
×
68

69
        logrus.Debug(out.String())
×
70
        return nil
×
71
}
72

73
func (nc *nerdctlClient) StartBuildkitd(ctx context.Context, tag, name string, bc *buildkitutil.BuildkitConfig, timeout time.Duration) (string, error) {
×
74
        logger := logrus.WithFields(logrus.Fields{
×
75
                "tag":             tag,
×
76
                "container":       name,
×
77
                "buildkit-config": bc,
×
78
                "driver":          "nerdctl",
×
79
        })
×
80
        logger.Debug("starting buildkitd")
×
81

×
82
        if err := nc.imageInspect(ctx, tag); err != nil {
×
83
                if err := nc.imagePull(ctx, tag); err != nil {
×
84
                        return "", errors.Wrap(err, "pulling buildkitd image")
×
85
                }
×
86
        }
87

88
        existed, _ := nc.containerExists(ctx, name)
×
89
        if existed {
×
90
                status, err := nc.GetStatus(ctx, name)
×
91
                if err != nil {
×
92
                        return "", errors.Wrap(err, "failed to get container status")
×
93
                }
×
94

95
                err = nc.handleContainerCreated(ctx, name, status, timeout)
×
96
                if err != nil {
×
97
                        return "", errors.Wrap(err, "failed to handle container created condition")
×
98
                }
×
99

100
                // When status is StatusDead/StatusRemoving, we nened to create and start the container later(not to return directly).
101
                if status != containerType.StatusDead && status != containerType.StatusRemoving {
×
102
                        return name, nil
×
103
                }
×
104
        }
105

106
        buildkitdCmd := "buildkitd"
×
107
        // TODO: support mirror CA keypair
×
108
        if len(bc.Registries) > 0 {
×
109
                cfg, err := bc.String()
×
110
                if err != nil {
×
111
                        return "", errors.Wrap(err, "failed to generate buildkit config")
×
112
                }
×
113
                buildkitdCmd = fmt.Sprintf("mkdir /etc/buildkit && echo '%s' > /etc/buildkit/buildkitd.toml && buildkitd", cfg)
×
114
                logger.Debugf("setting buildkit config: %s", cfg)
×
115
        }
116

117
        out, err := nc.exec(ctx, "run", "-d",
×
118
                "--name", name,
×
119
                "--privileged",
×
120
                "--entrypoint", "sh",
×
121
                tag, "-c", buildkitdCmd)
×
122
        if err != nil {
×
123
                logger.WithError(err).Error("can not run buildkitd", out)
×
124
                return "", errors.Wrap(err, "running buildkitd")
×
125
        }
×
126

127
        err = nc.waitUntilRunning(ctx, name, timeout)
×
128

×
129
        return name, err
×
130
}
131

132
func (nc *nerdctlClient) Exec(ctx context.Context, cname string, cmd []string) error {
×
133
        return nil
×
134
}
×
135

136
func (nc *nerdctlClient) GetImageWithCacheHashLabel(ctx context.Context, image string, hash string) (dockerimage.Summary, error) {
×
137
        return dockerimage.Summary{}, nil
×
138
}
×
139
func (nc *nerdctlClient) RemoveImage(ctx context.Context, image string) error {
×
140
        return nil
×
141
}
×
142
func (nc *nerdctlClient) PushImage(ctx context.Context, image, platform string) error {
×
143
        return nil
×
144
}
×
145
func (nc *nerdctlClient) PruneImage(ctx context.Context) (dockerimage.PruneReport, error) {
×
146
        return dockerimage.PruneReport{}, nil
×
147
}
×
148
func (nc *nerdctlClient) Stats(ctx context.Context, cname string, statChan chan<- *driver.Stats, done <-chan bool) error {
×
149
        return nil
×
150
}
×
151

152
func (nc *nerdctlClient) GetStatus(ctx context.Context, cname string) (containerType.ContainerStatus, error) {
×
153
        container, err := nc.containerInspect(ctx, cname)
×
154
        if err != nil {
×
155
                return "", err
×
156
        }
×
157
        return containerType.ContainerStatus(container.State.Status), nil
×
158
}
159

160
// TODO(kweizh): use container engine to wrap docker and nerdctl
161
func (nc *nerdctlClient) waitUntilRunning(ctx context.Context,
162
        name string, timeout time.Duration) error {
×
163
        logger := logrus.WithField("container", name)
×
164
        logger.Debug("waiting to start")
×
165

×
166
        // First, wait for the container to be marked as started.
×
167
        ctxTimeout, cancel := context.WithTimeout(ctx, timeout)
×
168
        defer cancel()
×
169
        for {
×
170
                select {
×
171
                case <-time.After(waitingInterval):
×
172
                        _, err := nc.exec(ctx, "start", name)
×
173
                        if err != nil {
×
174
                                continue
×
175
                        }
176

177
                        c, err := nc.containerInspect(ctx, name)
×
178
                        if err != nil {
×
179
                                // Has not yet started. Keep waiting.
×
180
                                continue
×
181
                        }
182
                        if c.State.Running {
×
183
                                logger.Debug("the container is running")
×
184
                                return nil
×
185
                        }
×
186

187
                case <-ctxTimeout.Done():
×
188
                        container, err := nc.containerInspect(ctx, name)
×
189
                        if err != nil {
×
190
                                logger.Debugf("failed to inspect container %s", name)
×
191
                        }
×
192
                        state, err := json.Marshal(container.State)
×
193
                        if err != nil {
×
194
                                logger.Debug("failed to marshal container state")
×
195
                        }
×
196
                        logger.Debugf("container state: %s", state)
×
197
                        return errors.Errorf("timeout %s: container did not start", timeout)
×
198
                }
199
        }
200
}
201

202
func (nc *nerdctlClient) waitUntilRemoved(ctx context.Context,
203
        name string, timeout time.Duration) error {
×
204
        logger := logrus.WithField("container", name)
×
205
        logger.Debug("waiting to be removed")
×
206

×
207
        // Wait for the container to be removed
×
208
        ctxTimeout, cancel := context.WithTimeout(ctx, timeout)
×
209
        defer cancel()
×
210
        for {
×
211
                select {
×
212
                case <-time.After(waitingInterval):
×
213
                        exist, err := nc.containerExists(ctxTimeout, name)
×
214
                        if err != nil {
×
215
                                return errors.Wrap(err, "failed to check if container has been removed")
×
216
                        }
×
217
                        if !exist {
×
218
                                logger.Debug("the container has been removed")
×
219
                                return nil
×
220
                        }
×
221
                case <-ctxTimeout.Done():
×
222
                        container, err := nc.containerInspect(ctx, name)
×
223
                        if err != nil {
×
224
                                logger.Debugf("failed to inspect container %s", name)
×
225
                        }
×
226
                        state, err := json.Marshal(container.State)
×
227
                        if err != nil {
×
228
                                logger.Debug("failed to marshal container state")
×
229
                        }
×
230
                        logger.Debugf("container state: %s", state)
×
231
                        return errors.Errorf("timeout %s: container can't be removed", timeout)
×
232
                }
233
        }
234
}
235

236
func (nc *nerdctlClient) handleContainerCreated(ctx context.Context,
237
        cname string, status containerType.ContainerStatus, timeout time.Duration) error {
×
238
        logger := logrus.WithFields(logrus.Fields{
×
239
                "container": cname,
×
240
                "status":    status,
×
241
        })
×
242

×
243
        if status == containerType.StatusPaused {
×
244
                logger.Info("container was paused, unpause it now...")
×
245
                out, err := nc.exec(ctx, "unpause", cname)
×
246
                if err != nil {
×
247
                        logger.WithError(err).Error("can not run buildkitd", out)
×
248
                        return errors.Wrap(err, "failed to unpause container")
×
249
                }
×
250
        } else if status == containerType.StatusExited {
×
251
                logger.Info("container exited, try to start it...")
×
252
                out, err := nc.exec(ctx, "start", cname)
×
253
                if err != nil {
×
254
                        logger.WithError(err).Error("can not run buildkitd", out)
×
255
                        return errors.Wrap(err, "failed to start exited cotaniner")
×
256
                }
×
257
        } else if status == containerType.StatusDead {
×
258
                logger.Info("container is dead, try to remove it...")
×
259
                out, err := nc.exec(ctx, "remove", cname)
×
260
                if err != nil {
×
261
                        logger.WithError(err).Error("can not run buildkitd", out)
×
262
                        return errors.Wrap(err, "failed to remove cotaniner")
×
263
                }
×
264
        } else if status == containerType.StatusCreated {
×
265
                logger.Info("container is being created.")
×
266
                err := nc.waitUntilRunning(ctx, cname, timeout)
×
267
                if err != nil {
×
268
                        logger.WithError(err).Error("can not run buildkitd")
×
269
                        return errors.Wrap(err, "failed to start container")
×
270
                }
×
271
        } else if status == containerType.StatusRemoving {
×
272
                // The remaining condition is StatusRemoving, we just need to wait.
×
273
                logger.Info("container is being removed.")
×
274
                err := nc.waitUntilRemoved(ctx, cname, timeout)
×
275
                if err != nil {
×
276
                        logger.WithError(err).Error("can not run buildkitd")
×
277
                        return errors.Wrap(err, "failed to remove container")
×
278
                }
×
279
        }
280
        // No process for StatusRunning
281

282
        return nil
×
283
}
284

285
func (nc *nerdctlClient) containerExists(ctx context.Context, tag string) (bool, error) {
×
286
        _, err := nc.containerInspect(ctx, tag)
×
287
        return err == nil, err
×
288
}
×
289

NEW
290
func (nc *nerdctlClient) containerInspect(ctx context.Context, tag string) (*container.InspectResponse, error) {
×
291
        out, err := nc.exec(ctx, "inspect", tag)
×
292
        if err != nil {
×
293
                // TODO(kweizh): check not found
×
294
                return nil, err
×
295
        }
×
296

NEW
297
        cs := []container.InspectResponse{}
×
298
        err = json.Unmarshal([]byte(out), &cs)
×
299
        if err != nil {
×
300
                logrus.WithError(err).Error(cs)
×
301
                return nil, err
×
302
        }
×
303

304
        return &(cs[0]), nil
×
305
}
306

307
func (nc *nerdctlClient) exec(ctx context.Context, args ...string) (string, error) {
×
308
        cmd := exec.CommandContext(ctx, nc.bin, args...)
×
309

×
310
        var out bytes.Buffer
×
311
        cmd.Stdout = &out
×
312

×
313
        err := cmd.Run()
×
314
        if err != nil {
×
315
                return "", errors.Wrap(err, "nerdctlClient error")
×
316
        }
×
317

318
        return out.String(), nil
×
319
}
320

321
// TODO(kweizh): return inspect result
322
func (nc *nerdctlClient) imageInspect(ctx context.Context, tag string) error {
×
323
        cmd := exec.CommandContext(ctx, nc.bin, "image", "inspect", tag)
×
324
        var out bytes.Buffer
×
325
        cmd.Stdout = &out
×
326
        err := cmd.Run()
×
327
        if err != nil {
×
328
                // TODO(kweizh): check not found
×
329
                return err
×
330
        }
×
331

332
        return nil
×
333
}
334

335
// TODO(kweizh): return pull output
336
func (nc *nerdctlClient) imagePull(ctx context.Context, tag string) error {
×
337
        cmd := exec.CommandContext(ctx, nc.bin, "pull", tag)
×
338
        var out bytes.Buffer
×
339
        cmd.Stdout = &out
×
340
        err := cmd.Run()
×
341
        if err != nil {
×
342
                return err
×
343
        }
×
344

345
        return nil
×
346
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc