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

supabase / cli / 23224184388

18 Mar 2026 01:10AM UTC coverage: 60.017% (-1.8%) from 61.863%
23224184388

Pull #4970

github

web-flow
Merge 61991c119 into 24b7304bd
Pull Request #4970: feat: add apple-container runtime support

829 of 1660 new or added lines in 15 files covered. (49.94%)

8 existing lines in 3 files now uncovered.

8607 of 14341 relevant lines covered (60.02%)

7.63 hits per line

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

60.24
/internal/utils/runtime.go
1
package utils
2

3
import (
4
        "context"
5
        "encoding/base64"
6
        "encoding/json"
7
        "io"
8
        "strings"
9

10
        "github.com/containerd/errdefs"
11
        "github.com/docker/docker/api/types"
12
        "github.com/docker/docker/api/types/container"
13
        "github.com/docker/docker/api/types/network"
14
        "github.com/docker/docker/api/types/volume"
15
        "github.com/docker/docker/client"
16
        "github.com/go-errors/errors"
17
)
18

19
// healthcheckLabel stores the container's health-check command as a
20
// base64-encoded JSON array inside a label.  Apple containers do not support
21
// native health-checks, so the CLI runs the check itself via `container exec`.
22
const healthcheckLabel = "com.supabase.cli.healthcheck"
23

24
type ContainerMount struct {
25
        Source   string
26
        Target   string
27
        Type     string
28
        ReadOnly bool
29
}
30

31
type ContainerInfo struct {
32
        ID           string
33
        Names        []string
34
        Labels       map[string]string
35
        Status       string
36
        Running      bool
37
        HealthStatus string
38
        Mounts       []ContainerMount
39
        NetworkIPs   map[string]string
40
}
41

42
type VolumeInfo struct {
43
        Name   string
44
        Labels map[string]string
45
}
46

47
type NetworkInfo struct {
48
        Name   string
49
        Labels map[string]string
50
}
51

52
func applyContainerLabels(config *container.Config) {
74✔
53
        if config.Labels == nil {
148✔
54
                config.Labels = make(map[string]string, 3)
74✔
55
        }
74✔
56
        config.Labels[CliProjectLabel] = Config.ProjectId
74✔
57
        config.Labels[composeProjectLabel] = Config.ProjectId
74✔
58
        if encoded := encodeHealthcheck(config.Healthcheck); len(encoded) > 0 {
96✔
59
                config.Labels[healthcheckLabel] = encoded
22✔
60
        }
22✔
61
}
62

63
func encodeHealthcheck(check *container.HealthConfig) string {
75✔
64
        if check == nil || len(check.Test) == 0 {
127✔
65
                return ""
52✔
66
        }
52✔
67
        payload, err := json.Marshal(check.Test)
23✔
68
        if err != nil {
23✔
NEW
69
                return ""
×
NEW
70
        }
×
71
        // Apple container labels reject "=" padding in values.
72
        return base64.RawStdEncoding.EncodeToString(payload)
23✔
73
}
74

75
func decodeHealthcheck(encoded string) ([]string, error) {
3✔
76
        var payload []byte
3✔
77
        var err error
3✔
78
        for _, value := range []string{encoded, strings.TrimRight(encoded, "=")} {
6✔
79
                payload, err = base64.StdEncoding.DecodeString(value)
3✔
80
                if err == nil {
5✔
81
                        break
2✔
82
                }
83
                payload, err = base64.RawStdEncoding.DecodeString(value)
1✔
84
                if err == nil {
2✔
85
                        break
1✔
86
                }
NEW
87
                payload, err = base64.URLEncoding.DecodeString(value)
×
NEW
88
                if err == nil {
×
NEW
89
                        break
×
90
                }
NEW
91
                payload, err = base64.RawURLEncoding.DecodeString(value)
×
NEW
92
                if err == nil {
×
NEW
93
                        break
×
94
                }
95
        }
96
        if err != nil {
3✔
NEW
97
                return nil, err
×
NEW
98
        }
×
99
        var test []string
3✔
100
        if err := json.Unmarshal(payload, &test); err != nil {
3✔
NEW
101
                return nil, err
×
NEW
102
        }
×
103
        return test, nil
3✔
104
}
105

106
// The runtime dispatcher functions below use a simple if/else pattern rather
107
// than an interface because:
108
//   - There are only two runtimes (Docker, Apple Container).
109
//   - Each Apple implementation is a thin wrapper around the `container` CLI,
110
//     keeping the logic co-located and easy to follow.
111
//   - An interface would require threading a runtime instance through many
112
//     call sites that currently use package-level helpers.
113

114
func DockerStart(ctx context.Context, config container.Config, hostConfig container.HostConfig, networkingConfig network.NetworkingConfig, containerName string) (string, error) {
35✔
115
        if UsesAppleContainerRuntime() {
35✔
NEW
116
                return appleStart(ctx, config, hostConfig, networkingConfig, containerName)
×
NEW
117
        }
×
118
        return dockerStart(ctx, config, hostConfig, networkingConfig, containerName)
35✔
119
}
120

121
func DockerRemoveAll(ctx context.Context, w io.Writer, projectId string) error {
8✔
122
        if UsesAppleContainerRuntime() {
8✔
NEW
123
                return appleRemoveAll(ctx, w, projectId)
×
NEW
124
        }
×
125
        return dockerRemoveAll(ctx, w, projectId)
8✔
126
}
127

128
func DockerRemove(containerId string) {
8✔
129
        if UsesAppleContainerRuntime() {
8✔
NEW
130
                _ = appleRemoveContainer(context.Background(), containerId, true)
×
NEW
131
                return
×
NEW
132
        }
×
133
        dockerRemove(containerId)
8✔
134
}
135

136
func DockerRunOnceWithConfig(ctx context.Context, config container.Config, hostConfig container.HostConfig, networkingConfig network.NetworkingConfig, containerName string, stdout, stderr io.Writer) error {
51✔
137
        if UsesAppleContainerRuntime() {
51✔
NEW
138
                return appleRunOnceWithConfig(ctx, config, hostConfig, networkingConfig, containerName, stdout, stderr)
×
NEW
139
        }
×
140
        return dockerRunOnceWithConfig(ctx, config, hostConfig, networkingConfig, containerName, stdout, stderr)
51✔
141
}
142

143
func DockerStreamLogs(ctx context.Context, containerId string, stdout, stderr io.Writer, opts ...func(*container.LogsOptions)) error {
9✔
144
        if UsesAppleContainerRuntime() {
9✔
NEW
145
                return appleStreamLogs(ctx, containerId, stdout, stderr)
×
NEW
146
        }
×
147
        return dockerStreamLogs(ctx, containerId, stdout, stderr, opts...)
9✔
148
}
149

150
func DockerStreamLogsOnce(ctx context.Context, containerId string, stdout, stderr io.Writer) error {
3✔
151
        if UsesAppleContainerRuntime() {
3✔
NEW
152
                return appleStreamLogsOnce(ctx, containerId, stdout, stderr)
×
NEW
153
        }
×
154
        return dockerStreamLogsOnce(ctx, containerId, stdout, stderr)
3✔
155
}
156

NEW
157
func DockerExecOnceWithStream(ctx context.Context, containerId, workdir string, env, cmd []string, stdout, stderr io.Writer) error {
×
NEW
158
        if UsesAppleContainerRuntime() {
×
NEW
159
                return appleExecOnceWithStream(ctx, containerId, workdir, env, cmd, stdout, stderr)
×
NEW
160
        }
×
NEW
161
        return dockerExecOnceWithStream(ctx, containerId, workdir, env, cmd, stdout, stderr)
×
162
}
163

164
func RemoveContainer(ctx context.Context, containerId string, removeVolumes, force bool) error {
5✔
165
        if UsesAppleContainerRuntime() {
5✔
NEW
166
                return appleRemoveContainer(ctx, containerId, force)
×
NEW
167
        }
×
168
        return Docker.ContainerRemove(ctx, containerId, container.RemoveOptions{
5✔
169
                RemoveVolumes: removeVolumes,
5✔
170
                Force:         force,
5✔
171
        })
5✔
172
}
173

174
func RemoveVolume(ctx context.Context, volumeName string, force bool) error {
1✔
175
        if UsesAppleContainerRuntime() {
1✔
NEW
176
                return appleRemoveVolume(ctx, volumeName, force)
×
NEW
177
        }
×
178
        return Docker.VolumeRemove(ctx, volumeName, force)
1✔
179
}
180

181
func RestartContainer(ctx context.Context, containerId string) error {
22✔
182
        if UsesAppleContainerRuntime() {
22✔
NEW
183
                return appleRestartContainer(ctx, containerId)
×
NEW
184
        }
×
185
        return Docker.ContainerRestart(ctx, containerId, container.StopOptions{})
22✔
186
}
187

188
func InspectContainer(ctx context.Context, containerId string) (ContainerInfo, error) {
59✔
189
        if UsesAppleContainerRuntime() {
59✔
NEW
190
                return appleInspectContainer(ctx, containerId)
×
NEW
191
        }
×
192
        resp, err := Docker.ContainerInspect(ctx, containerId)
59✔
193
        if err != nil {
71✔
194
                return ContainerInfo{}, err
12✔
195
        }
12✔
196
        name := ""
47✔
197
        if resp.ContainerJSONBase != nil {
78✔
198
                name = resp.Name
31✔
199
        }
31✔
200
        info := ContainerInfo{
47✔
201
                ID:         name,
47✔
202
                Labels:     map[string]string{},
47✔
203
                Status:     "",
47✔
204
                Running:    false,
47✔
205
                Names:      nil,
47✔
206
                NetworkIPs: map[string]string{},
47✔
207
        }
47✔
208
        if len(name) > 0 {
47✔
NEW
209
                info.Names = []string{name}
×
NEW
210
        }
×
211
        if len(info.ID) > 0 && info.ID[0] == '/' {
47✔
NEW
212
                info.ID = info.ID[1:]
×
NEW
213
                info.Names = []string{name}
×
NEW
214
        }
×
215
        if resp.Config != nil && resp.Config.Labels != nil {
47✔
NEW
216
                info.Labels = resp.Config.Labels
×
NEW
217
        }
×
218
        if resp.ContainerJSONBase != nil && resp.State != nil {
78✔
219
                info.Status = resp.State.Status
31✔
220
                info.Running = resp.State.Running
31✔
221
                if resp.State.Health != nil {
57✔
222
                        info.HealthStatus = resp.State.Health.Status
26✔
223
                }
26✔
224
        }
225
        if resp.NetworkSettings != nil {
47✔
NEW
226
                for name, details := range resp.NetworkSettings.Networks {
×
NEW
227
                        if details != nil && len(details.IPAddress) > 0 {
×
NEW
228
                                info.NetworkIPs[name] = details.IPAddress
×
NEW
229
                        }
×
230
                }
231
        }
232
        if len(resp.Mounts) > 0 {
47✔
NEW
233
                info.Mounts = make([]ContainerMount, 0, len(resp.Mounts))
×
NEW
234
                for _, m := range resp.Mounts {
×
NEW
235
                        info.Mounts = append(info.Mounts, ContainerMount{
×
NEW
236
                                Source:   m.Name,
×
NEW
237
                                Target:   m.Destination,
×
NEW
238
                                Type:     string(m.Type),
×
NEW
239
                                ReadOnly: !m.RW,
×
NEW
240
                        })
×
NEW
241
                }
×
242
        }
243
        return info, nil
47✔
244
}
245

NEW
246
func GetContainerIP(ctx context.Context, containerId, networkName string) (string, error) {
×
NEW
247
        info, err := InspectContainer(ctx, containerId)
×
NEW
248
        if err != nil {
×
NEW
249
                return "", err
×
NEW
250
        }
×
NEW
251
        if len(networkName) > 0 {
×
NEW
252
                if ip, ok := info.NetworkIPs[networkName]; ok && len(ip) > 0 {
×
NEW
253
                        return strings.TrimSuffix(ip, "/24"), nil
×
NEW
254
                }
×
255
        }
NEW
256
        for _, ip := range info.NetworkIPs {
×
NEW
257
                if len(ip) > 0 {
×
NEW
258
                        return strings.TrimSuffix(ip, "/24"), nil
×
NEW
259
                }
×
260
        }
NEW
261
        return "", errors.Errorf("failed to detect IP address for container: %s", containerId)
×
262
}
263

264
func ListContainers(ctx context.Context, all bool) ([]ContainerInfo, error) {
7✔
265
        if UsesAppleContainerRuntime() {
7✔
NEW
266
                return appleListContainers(ctx, all)
×
NEW
267
        }
×
268
        resp, err := Docker.ContainerList(ctx, container.ListOptions{All: all})
7✔
269
        if err != nil {
8✔
270
                return nil, err
1✔
271
        }
1✔
272
        result := make([]ContainerInfo, 0, len(resp))
6✔
273
        for _, item := range resp {
71✔
274
                id := item.ID
65✔
275
                if len(id) == 0 && len(item.Names) > 0 {
130✔
276
                        id = item.Names[0]
65✔
277
                }
65✔
278
                if len(id) > 0 && id[0] == '/' {
78✔
279
                        id = id[1:]
13✔
280
                }
13✔
281
                result = append(result, ContainerInfo{
65✔
282
                        ID:      id,
65✔
283
                        Names:   item.Names,
65✔
284
                        Labels:  item.Labels,
65✔
285
                        Status:  item.State,
65✔
286
                        Running: item.State == "running",
65✔
287
                })
65✔
288
        }
289
        return result, nil
6✔
290
}
291

292
func ListVolumes(ctx context.Context) ([]VolumeInfo, error) {
4✔
293
        if UsesAppleContainerRuntime() {
4✔
NEW
294
                return appleListVolumes(ctx)
×
NEW
295
        }
×
296
        resp, err := Docker.VolumeList(ctx, volume.ListOptions{})
4✔
297
        if err != nil {
4✔
NEW
298
                return nil, err
×
NEW
299
        }
×
300
        result := make([]VolumeInfo, 0, len(resp.Volumes))
4✔
301
        for _, item := range resp.Volumes {
7✔
302
                result = append(result, VolumeInfo{Name: item.Name, Labels: item.Labels})
3✔
303
        }
3✔
304
        return result, nil
4✔
305
}
306

307
func ListNetworks(ctx context.Context) ([]NetworkInfo, error) {
2✔
308
        if UsesAppleContainerRuntime() {
2✔
NEW
309
                resp, err := appleListNetworks(ctx)
×
NEW
310
                if err != nil {
×
NEW
311
                        return nil, err
×
NEW
312
                }
×
NEW
313
                result := make([]NetworkInfo, 0, len(resp))
×
NEW
314
                for _, item := range resp {
×
NEW
315
                        result = append(result, NetworkInfo{Name: item.ID, Labels: item.Config.Labels})
×
NEW
316
                }
×
NEW
317
                return result, nil
×
318
        }
319
        resp, err := Docker.NetworkList(ctx, network.ListOptions{})
2✔
320
        if err != nil {
2✔
NEW
321
                return nil, err
×
NEW
322
        }
×
323
        result := make([]NetworkInfo, 0, len(resp))
2✔
324
        for _, item := range resp {
2✔
NEW
325
                result = append(result, NetworkInfo{Name: item.Name, Labels: item.Labels})
×
NEW
326
        }
×
327
        return result, nil
2✔
328
}
329

330
func VolumeExists(ctx context.Context, name string) (bool, error) {
7✔
331
        if UsesAppleContainerRuntime() {
7✔
NEW
332
                return appleVolumeExists(ctx, name)
×
NEW
333
        }
×
334
        if _, err := Docker.VolumeInspect(ctx, name); err == nil {
9✔
335
                return true, nil
2✔
336
        } else if errdefs.IsNotFound(err) {
10✔
337
                return false, nil
3✔
338
        } else {
5✔
339
                return false, err
2✔
340
        }
2✔
341
}
342

343
func AssertServiceHealthy(ctx context.Context, containerId string) error {
31✔
344
        info, err := InspectContainer(ctx, containerId)
31✔
345
        if err != nil {
32✔
346
                if errdefs.IsNotFound(err) {
1✔
NEW
347
                        return errors.New(ErrNotRunning)
×
NEW
348
                }
×
349
                if client.IsErrConnectionFailed(err) {
2✔
350
                        if UsesAppleContainerRuntime() {
1✔
NEW
351
                                CmdSuggestion = suggestAppleContainerInstall
×
352
                        } else {
1✔
353
                                CmdSuggestion = suggestDockerInstall
1✔
354
                        }
1✔
355
                }
356
                return errors.Errorf("failed to inspect service: %w", err)
1✔
357
        }
358
        if !info.Running {
33✔
359
                return errors.Errorf("%s container is not running: %s", containerId, info.Status)
3✔
360
        }
3✔
361
        if UsesAppleContainerRuntime() {
27✔
NEW
362
                if encoded, ok := info.Labels[healthcheckLabel]; ok && len(encoded) > 0 {
×
NEW
363
                        test, err := decodeHealthcheck(encoded)
×
NEW
364
                        if err != nil {
×
NEW
365
                                return errors.Errorf("failed to decode service healthcheck: %w", err)
×
NEW
366
                        }
×
NEW
367
                        if err := appleRunHealthcheck(ctx, containerId, test); err != nil {
×
NEW
368
                                return errors.Errorf("%s container is not ready: %w", containerId, err)
×
NEW
369
                        }
×
370
                }
NEW
371
                return nil
×
372
        }
373
        if len(info.HealthStatus) > 0 && info.HealthStatus != types.Healthy {
27✔
NEW
374
                return errors.Errorf("%s container is not ready: %s", containerId, info.HealthStatus)
×
NEW
375
        }
×
376
        return nil
27✔
377
}
378

379
func ListProjectVolumes(ctx context.Context, projectId string) ([]VolumeInfo, error) {
4✔
380
        volumes, err := ListVolumes(ctx)
4✔
381
        if err != nil {
4✔
NEW
382
                return nil, err
×
NEW
383
        }
×
384
        var result []VolumeInfo
4✔
385
        for _, item := range volumes {
7✔
386
                if matchesProjectLabel(item.Labels, projectId) || matchesProjectName(item.Name, projectId) {
5✔
387
                        result = append(result, item)
2✔
388
                }
2✔
389
        }
390
        return result, nil
4✔
391
}
392

393
func ListProjectNetworks(ctx context.Context, projectId string) ([]NetworkInfo, error) {
2✔
394
        networks, err := ListNetworks(ctx)
2✔
395
        if err != nil {
2✔
NEW
396
                return nil, err
×
NEW
397
        }
×
398
        var result []NetworkInfo
2✔
399
        for _, item := range networks {
2✔
NEW
400
                if matchesProjectLabel(item.Labels, projectId) || matchesProjectName(item.Name, projectId) {
×
NEW
401
                        result = append(result, item)
×
NEW
402
                }
×
403
        }
404
        return result, nil
2✔
405
}
406

407
func ListProjectContainers(ctx context.Context, projectId string, all bool) ([]ContainerInfo, error) {
7✔
408
        containers, err := ListContainers(ctx, all)
7✔
409
        if err != nil {
8✔
410
                return nil, err
1✔
411
        }
1✔
412
        var result []ContainerInfo
6✔
413
        for _, item := range containers {
71✔
414
                if matchesProjectLabel(item.Labels, projectId) || matchesProjectContainer(item, projectId) {
104✔
415
                        result = append(result, item)
39✔
416
                }
39✔
417
        }
418
        return result, nil
6✔
419
}
420

421
func matchesProjectLabel(labels map[string]string, projectId string) bool {
68✔
422
        if len(labels) == 0 {
134✔
423
                return false
66✔
424
        }
66✔
425
        value, ok := labels[CliProjectLabel]
2✔
426
        if !ok {
2✔
NEW
427
                return false
×
NEW
428
        }
×
429
        return len(projectId) == 0 || value == projectId
2✔
430
}
431

432
func matchesProjectContainer(info ContainerInfo, projectId string) bool {
65✔
433
        if matchesProjectName(info.ID, projectId) {
104✔
434
                return true
39✔
435
        }
39✔
436
        for _, name := range info.Names {
52✔
437
                if matchesProjectName(name, projectId) {
26✔
NEW
438
                        return true
×
NEW
439
                }
×
440
        }
441
        return false
26✔
442
}
443

444
func matchesProjectName(name, projectId string) bool {
92✔
445
        if len(projectId) == 0 || len(name) == 0 {
93✔
446
                return false
1✔
447
        }
1✔
448
        trimmed := strings.TrimPrefix(name, "/")
91✔
449
        return strings.HasSuffix(trimmed, "_"+projectId) || strings.HasSuffix(trimmed, "-"+projectId)
91✔
450
}
451

NEW
452
func filterProjectVolumes(volumes []VolumeInfo, projectId string) []VolumeInfo {
×
NEW
453
        var result []VolumeInfo
×
NEW
454
        for _, item := range volumes {
×
NEW
455
                if matchesProjectLabel(item.Labels, projectId) {
×
NEW
456
                        result = append(result, item)
×
NEW
457
                }
×
458
        }
NEW
459
        return result
×
460
}
461

NEW
462
func filterProjectContainers(containers []ContainerInfo, projectId string) []ContainerInfo {
×
NEW
463
        var result []ContainerInfo
×
NEW
464
        for _, item := range containers {
×
NEW
465
                if matchesProjectLabel(item.Labels, projectId) {
×
NEW
466
                        result = append(result, item)
×
NEW
467
                }
×
468
        }
NEW
469
        return result
×
470
}
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