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

supabase / cli / 3572813994

04 Dec 2022 06:36PM UTC coverage: 54.923% (-2.8%) from 57.76%
3572813994

Pull #648

github

Kevin Saliou
chore: remove all tabs & trailing spaces from SQL files
Pull Request #648: chore: remove all tabs & trailing spaces from SQL files

3057 of 5566 relevant lines covered (54.92%)

498.14 hits per line

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

66.79
/internal/utils/docker.go
1
package utils
2

3
import (
4
        "archive/tar"
5
        "bytes"
6
        "context"
7
        "encoding/base64"
8
        "encoding/json"
9
        "errors"
10
        "fmt"
11
        "io"
12
        "os"
13
        "strings"
14
        "sync"
15
        "time"
16

17
        dockerConfig "github.com/docker/cli/cli/config"
18
        "github.com/docker/docker/api/types"
19
        "github.com/docker/docker/api/types/container"
20
        "github.com/docker/docker/client"
21
        "github.com/docker/docker/errdefs"
22
        "github.com/docker/docker/pkg/jsonmessage"
23
        "github.com/docker/docker/pkg/stdcopy"
24
        "github.com/spf13/viper"
25
)
26

27
// TODO: refactor to initialise lazily
28
var Docker = NewDocker()
29

30
func NewDocker() *client.Client {
35✔
31
        docker, err := client.NewClientWithOpts(
35✔
32
                client.WithAPIVersionNegotiation(),
35✔
33
                // Support env (e.g. for mock setup or rootless docker)
35✔
34
                client.FromEnv,
35✔
35
        )
35✔
36
        if err != nil {
35✔
37
                fmt.Fprintln(os.Stderr, "Failed to initialize Docker client:", err)
×
38
                os.Exit(1)
×
39
        }
×
40
        return docker
35✔
41
}
42

43
func AssertDockerIsRunning() error {
7✔
44
        if _, err := Docker.Ping(context.Background()); err != nil {
9✔
45
                return NewError(err.Error())
2✔
46
        }
2✔
47

48
        return nil
5✔
49
}
50

51
func DockerNetworkCreateIfNotExists(ctx context.Context, networkId string) error {
29✔
52
        _, err := Docker.NetworkCreate(
29✔
53
                ctx,
29✔
54
                networkId,
29✔
55
                types.NetworkCreate{
29✔
56
                        CheckDuplicate: true,
29✔
57
                        Labels: map[string]string{
29✔
58
                                "com.supabase.cli.project":   Config.ProjectId,
29✔
59
                                "com.docker.compose.project": Config.ProjectId,
29✔
60
                        },
29✔
61
                },
29✔
62
        )
29✔
63
        // if error is network already exists, no need to propagate to user
29✔
64
        if errdefs.IsConflict(err) {
29✔
65
                return nil
×
66
        }
×
67
        return err
29✔
68
}
69

70
func DockerExec(ctx context.Context, container string, cmd []string) (io.Reader, error) {
×
71
        exec, err := Docker.ContainerExecCreate(
×
72
                ctx,
×
73
                container,
×
74
                types.ExecConfig{Cmd: cmd, AttachStderr: true, AttachStdout: true},
×
75
        )
×
76
        if err != nil {
×
77
                return nil, err
×
78
        }
×
79

80
        resp, err := Docker.ContainerExecAttach(ctx, exec.ID, types.ExecStartCheck{})
×
81
        if err != nil {
×
82
                return nil, err
×
83
        }
×
84

85
        return resp.Reader, nil
×
86
}
87

88
// NOTE: There's a risk of data race with reads & writes from `DockerRun` and
89
// reads from `DockerRemoveAll`, but since they're expected to be run on the
90
// same thread, this is fine.
91
var containers []string
92

93
func DockerRun(
94
        ctx context.Context,
95
        name string,
96
        config *container.Config,
97
        hostConfig *container.HostConfig,
98
) (io.Reader, error) {
×
99
        config.Image = GetRegistryImageUrl(config.Image)
×
100
        container, err := Docker.ContainerCreate(ctx, config, hostConfig, nil, nil, name)
×
101
        if err != nil {
×
102
                return nil, err
×
103
        }
×
104
        containers = append(containers, name)
×
105

×
106
        resp, err := Docker.ContainerAttach(ctx, container.ID, types.ContainerAttachOptions{Stream: true, Stdout: true, Stderr: true})
×
107
        if err != nil {
×
108
                return nil, err
×
109
        }
×
110

111
        if err := Docker.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}); err != nil {
×
112
                return nil, err
×
113
        }
×
114

115
        return resp.Reader, nil
×
116
}
117

118
func DockerRemoveContainers(ctx context.Context, containers []string) {
4✔
119
        var wg sync.WaitGroup
4✔
120

4✔
121
        for _, container := range containers {
6✔
122
                wg.Add(1)
2✔
123

2✔
124
                go func(container string) {
4✔
125
                        if err := Docker.ContainerRemove(ctx, container, types.ContainerRemoveOptions{
2✔
126
                                RemoveVolumes: true,
2✔
127
                                Force:         true,
2✔
128
                        }); err != nil {
2✔
129
                                // TODO: Handle errors
×
130
                                // fmt.Fprintln(os.Stderr, err)
×
131
                                _ = err
×
132
                        }
×
133

134
                        wg.Done()
2✔
135
                }(container)
136
        }
137

138
        wg.Wait()
4✔
139
}
140

141
func DockerRemoveAll(ctx context.Context, netId string) {
1✔
142
        DockerRemoveContainers(ctx, containers)
1✔
143
        _ = Docker.NetworkRemove(ctx, netId)
1✔
144
}
1✔
145

146
func DockerAddFile(ctx context.Context, container string, fileName string, content []byte) error {
×
147
        var buf bytes.Buffer
×
148
        tw := tar.NewWriter(&buf)
×
149
        err := tw.WriteHeader(&tar.Header{
×
150
                Name: fileName,
×
151
                Mode: 0777,
×
152
                Size: int64(len(content)),
×
153
        })
×
154

×
155
        if err != nil {
×
156
                return fmt.Errorf("failed to copy file: %v", err)
×
157
        }
×
158

159
        _, err = tw.Write(content)
×
160

×
161
        if err != nil {
×
162
                return fmt.Errorf("failed to copy file: %v", err)
×
163
        }
×
164

165
        err = tw.Close()
×
166

×
167
        if err != nil {
×
168
                return fmt.Errorf("failed to copy file: %v", err)
×
169
        }
×
170

171
        err = Docker.CopyToContainer(ctx, container, "/tmp", &buf, types.CopyToContainerOptions{})
×
172
        if err != nil {
×
173
                return fmt.Errorf("failed to copy file: %v", err)
×
174
        }
×
175
        return nil
×
176
}
177

178
var (
179
        // Only supports one registry per command invocation
180
        registryAuth string
181
        registryOnce sync.Once
182
)
183

184
func GetRegistryAuth() string {
5✔
185
        registryOnce.Do(func() {
7✔
186
                config := dockerConfig.LoadDefaultConfigFile(os.Stderr)
2✔
187
                // Ref: https://docs.docker.com/engine/api/sdk/examples/#pull-an-image-with-authentication
2✔
188
                auth, err := config.GetAuthConfig(getRegistry())
2✔
189
                if err != nil {
2✔
190
                        fmt.Fprintln(os.Stderr, "Failed to load registry credentials:", err)
×
191
                        return
×
192
                }
×
193
                encoded, err := json.Marshal(auth)
2✔
194
                if err != nil {
2✔
195
                        fmt.Fprintln(os.Stderr, "Failed to serialise auth config:", err)
×
196
                        return
×
197
                }
×
198
                registryAuth = base64.URLEncoding.EncodeToString(encoded)
2✔
199
        })
200
        return registryAuth
5✔
201
}
202

203
// Defaults to Supabase public ECR for faster image pull
204
const defaultRegistry = "public.ecr.aws/supabase"
205

206
func getRegistry() string {
110✔
207
        registry := viper.GetString("INTERNAL_IMAGE_REGISTRY")
110✔
208
        if len(registry) == 0 {
201✔
209
                return defaultRegistry
91✔
210
        }
91✔
211
        return strings.ToLower(registry)
19✔
212
}
213

214
func GetRegistryImageUrl(imageName string) string {
108✔
215
        registry := getRegistry()
108✔
216
        if registry == "docker.io" {
126✔
217
                return imageName
18✔
218
        }
18✔
219
        if registry == defaultRegistry {
180✔
220
                parts := strings.Split(imageName, "/")
90✔
221
                imageName = parts[len(parts)-1]
90✔
222
        }
90✔
223
        return registry + "/" + imageName
90✔
224
}
225

226
func DockerImagePullWithRetry(ctx context.Context, image string, retries int) (io.ReadCloser, error) {
3✔
227
        out, err := Docker.ImagePull(ctx, image, types.ImagePullOptions{
3✔
228
                RegistryAuth: GetRegistryAuth(),
3✔
229
        })
3✔
230
        for i := time.Duration(1); retries > 0; retries-- {
7✔
231
                if err == nil {
6✔
232
                        break
2✔
233
                }
234
                fmt.Fprintln(os.Stderr, err)
2✔
235
                fmt.Fprintf(os.Stderr, "Retrying after %d seconds...\n", i)
2✔
236
                time.Sleep(i * time.Second)
2✔
237
                out, err = Docker.ImagePull(ctx, image, types.ImagePullOptions{
2✔
238
                        RegistryAuth: GetRegistryAuth(),
2✔
239
                })
2✔
240
                i *= 2
2✔
241
        }
242
        return out, err
3✔
243
}
244

245
func DockerPullImageIfNotCached(ctx context.Context, imageName string) error {
35✔
246
        imageUrl := GetRegistryImageUrl(imageName)
35✔
247
        if _, _, err := Docker.ImageInspectWithRaw(ctx, imageUrl); err == nil {
63✔
248
                return nil
28✔
249
        } else if !client.IsErrNotFound(err) {
40✔
250
                return err
5✔
251
        }
5✔
252
        out, err := DockerImagePullWithRetry(ctx, imageUrl, 2)
2✔
253
        if err != nil {
3✔
254
                return err
1✔
255
        }
1✔
256
        defer out.Close()
1✔
257
        fmt.Fprintln(os.Stderr, "Pulling docker image:", imageUrl)
1✔
258
        if viper.GetBool("DEBUG") {
1✔
259
                return jsonmessage.DisplayJSONMessagesStream(out, os.Stderr, os.Stderr.Fd(), true, nil)
×
260
        }
×
261
        _, err = io.Copy(io.Discard, out)
1✔
262
        return err
1✔
263
}
264

265
func DockerStop(containerID string) {
3✔
266
        stopContainer(Docker, containerID)
3✔
267
}
3✔
268

269
func stopContainer(docker *client.Client, containerID string) {
4✔
270
        if err := docker.ContainerStop(context.Background(), containerID, nil); err != nil {
5✔
271
                fmt.Fprintln(os.Stderr, "Failed to stop container:", containerID, err)
1✔
272
        }
1✔
273
}
274

275
func DockerStart(ctx context.Context, config container.Config, hostConfig container.HostConfig, containerName string) (string, error) {
31✔
276
        // Pull container image
31✔
277
        if err := DockerPullImageIfNotCached(ctx, config.Image); err != nil {
35✔
278
                return "", err
4✔
279
        }
4✔
280
        // Setup default config
281
        config.Image = GetRegistryImageUrl(config.Image)
27✔
282
        if config.Labels == nil {
54✔
283
                config.Labels = map[string]string{}
27✔
284
        }
27✔
285
        config.Labels["com.supabase.cli.project"] = Config.ProjectId
27✔
286
        config.Labels["com.docker.compose.project"] = Config.ProjectId
27✔
287
        if len(hostConfig.NetworkMode) == 0 {
54✔
288
                hostConfig.NetworkMode = container.NetworkMode(NetId)
27✔
289
        }
27✔
290
        // Create network with name
291
        if err := DockerNetworkCreateIfNotExists(ctx, string(hostConfig.NetworkMode)); err != nil {
27✔
292
                return "", err
×
293
        }
×
294
        // Create container from image
295
        resp, err := Docker.ContainerCreate(ctx, &config, &hostConfig, nil, nil, containerName)
27✔
296
        if err != nil {
28✔
297
                return "", err
1✔
298
        }
1✔
299
        containers = append(containers, resp.ID)
26✔
300
        // Run container in background
26✔
301
        return resp.ID, Docker.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
26✔
302
}
303

304
// Runs a container image exactly once, returning stdout and throwing error on non-zero exit code.
305
func DockerRunOnce(ctx context.Context, image string, env []string, cmd []string) (string, error) {
16✔
306
        container, err := DockerStart(ctx, container.Config{
16✔
307
                Image: image,
16✔
308
                Env:   env,
16✔
309
                Cmd:   cmd,
16✔
310
        }, container.HostConfig{AutoRemove: true}, "")
16✔
311
        if err != nil {
21✔
312
                return "", err
5✔
313
        }
5✔
314
        // Propagate cancellation to terminate early
315
        go func() {
22✔
316
                <-ctx.Done()
11✔
317
                if ctx.Err() != nil {
12✔
318
                        stopContainer(NewDocker(), container)
1✔
319
                }
1✔
320
        }()
321
        // Stream logs
322
        logs, err := Docker.ContainerLogs(ctx, container, types.ContainerLogsOptions{
11✔
323
                ShowStdout: true,
11✔
324
                ShowStderr: viper.GetBool("DEBUG"),
11✔
325
                Follow:     true,
11✔
326
        })
11✔
327
        if err != nil {
13✔
328
                return "", err
2✔
329
        }
2✔
330
        defer logs.Close()
9✔
331
        var out bytes.Buffer
9✔
332
        if _, err := stdcopy.StdCopy(&out, os.Stderr, logs); err != nil {
10✔
333
                return "", err
1✔
334
        }
1✔
335
        // Check exit code
336
        resp, err := Docker.ContainerInspect(ctx, container)
8✔
337
        if err != nil {
9✔
338
                return "", err
1✔
339
        }
1✔
340
        if resp.State.ExitCode > 0 {
8✔
341
                return "", errors.New("error running container")
1✔
342
        }
1✔
343
        return out.String(), nil
6✔
344
}
345

346
// Exec a command once inside a container, returning stdout and throwing error on non-zero exit code.
347
func DockerExecOnce(ctx context.Context, container string, env []string, cmd []string) (string, error) {
4✔
348
        // Reset shadow database
4✔
349
        exec, err := Docker.ContainerExecCreate(ctx, container, types.ExecConfig{
4✔
350
                Env:          env,
4✔
351
                Cmd:          cmd,
4✔
352
                AttachStderr: viper.GetBool("DEBUG"),
4✔
353
                AttachStdout: true,
4✔
354
        })
4✔
355
        if err != nil {
7✔
356
                return "", err
3✔
357
        }
3✔
358
        // Read exec output
359
        resp, err := Docker.ContainerExecAttach(ctx, exec.ID, types.ExecStartCheck{})
1✔
360
        if err != nil {
2✔
361
                return "", err
1✔
362
        }
1✔
363
        defer resp.Close()
×
364
        // Capture error details
×
365
        var out bytes.Buffer
×
366
        if _, err := stdcopy.StdCopy(&out, os.Stderr, resp.Reader); err != nil {
×
367
                return "", err
×
368
        }
×
369
        // Get the exit code
370
        iresp, err := Docker.ContainerExecInspect(ctx, exec.ID)
×
371
        if err != nil {
×
372
                return "", err
×
373
        }
×
374
        if iresp.ExitCode > 0 {
×
375
                err = errors.New("error executing command")
×
376
        }
×
377
        return out.String(), err
×
378
}
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

© 2025 Coveralls, Inc