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

supabase / cli / 19497490251

19 Nov 2025 10:07AM UTC coverage: 55.082% (+0.006%) from 55.076%
19497490251

Pull #4420

github

web-flow
Merge 7439d428c into 28425f504
Pull Request #4420: feat: support append mode when updating network restrictions

25 of 38 new or added lines in 3 files covered. (65.79%)

57 existing lines in 3 files now uncovered.

6530 of 11855 relevant lines covered (55.08%)

6.26 hits per line

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

65.52
/internal/start/start.go
1
package start
2

3
import (
4
        "bytes"
5
        "context"
6
        _ "embed"
7
        "fmt"
8
        "net"
9
        "net/url"
10
        "os"
11
        "path"
12
        "path/filepath"
13
        "slices"
14
        "strconv"
15
        "strings"
16
        "text/template"
17
        "time"
18

19
        "github.com/docker/docker/api/types/container"
20
        "github.com/docker/docker/api/types/network"
21
        "github.com/docker/docker/client"
22
        "github.com/docker/go-connections/nat"
23
        "github.com/go-errors/errors"
24
        "github.com/jackc/pgconn"
25
        "github.com/jackc/pgx/v4"
26
        "github.com/spf13/afero"
27
        "github.com/supabase/cli/internal/db/start"
28
        "github.com/supabase/cli/internal/functions/serve"
29
        "github.com/supabase/cli/internal/seed/buckets"
30
        "github.com/supabase/cli/internal/services"
31
        "github.com/supabase/cli/internal/status"
32
        "github.com/supabase/cli/internal/utils"
33
        "github.com/supabase/cli/internal/utils/flags"
34
        "github.com/supabase/cli/pkg/config"
35
)
36

37
func Run(ctx context.Context, fsys afero.Fs, excludedContainers []string, ignoreHealthCheck bool) error {
3✔
38
        // Sanity checks.
3✔
39
        {
6✔
40
                if err := flags.LoadConfig(fsys); err != nil {
4✔
41
                        return err
1✔
42
                }
1✔
43
                if err := utils.AssertSupabaseDbIsRunning(); err == nil {
3✔
44
                        fmt.Fprintln(os.Stderr, utils.Aqua("supabase start")+" is already running.")
1✔
45
                        names := status.CustomName{}
1✔
46
                        return status.Run(ctx, names, utils.OutputPretty, fsys)
1✔
47
                } else if !errors.Is(err, utils.ErrNotRunning) {
3✔
48
                        return err
1✔
49
                }
1✔
50
                if err := flags.LoadProjectRef(fsys); err == nil {
×
51
                        _ = services.CheckVersions(ctx, fsys)
×
52
                }
×
53
        }
54

55
        dbConfig := pgconn.Config{
×
56
                Host:     utils.DbId,
×
57
                Port:     5432,
×
58
                User:     "postgres",
×
59
                Password: utils.Config.Db.Password,
×
60
                Database: "postgres",
×
61
        }
×
62
        if err := run(ctx, fsys, excludedContainers, dbConfig); err != nil {
×
63
                if ignoreHealthCheck && start.IsUnhealthyError(err) {
×
64
                        fmt.Fprintln(os.Stderr, err)
×
65
                } else {
×
66
                        if err := utils.DockerRemoveAll(context.Background(), os.Stderr, utils.Config.ProjectId); err != nil {
×
67
                                fmt.Fprintln(os.Stderr, err)
×
68
                        }
×
69
                        return err
×
70
                }
71
        }
72

73
        fmt.Fprintf(os.Stderr, "Started %s local development setup.\n\n", utils.Aqua("supabase"))
×
74
        status.PrettyPrint(os.Stdout, excludedContainers...)
×
75
        return nil
×
76
}
77

78
type kongConfig struct {
79
        GotrueId      string
80
        RestId        string
81
        RealtimeId    string
82
        StorageId     string
83
        StudioId      string
84
        PgmetaId      string
85
        EdgeRuntimeId string
86
        LogflareId    string
87
        PoolerId      string
88
        ApiHost       string
89
        ApiPort       uint16
90
        BearerToken   string
91
        QueryToken    string
92
}
93

94
var (
95
        //go:embed templates/kong.yml
96
        kongConfigEmbed    string
97
        kongConfigTemplate = template.Must(template.New("kongConfig").Parse(kongConfigEmbed))
98

99
        //go:embed templates/custom_nginx.template
100
        nginxConfigEmbed string
101
        // Hardcoded configs which match nginxConfigEmbed
102
        nginxEmailTemplateDir   = "/home/kong/templates/email"
103
        nginxTemplateServerPort = 8088
104
)
105

106
type vectorConfig struct {
107
        ApiKey        string
108
        VectorId      string
109
        LogflareId    string
110
        KongId        string
111
        GotrueId      string
112
        RestId        string
113
        RealtimeId    string
114
        StorageId     string
115
        EdgeRuntimeId string
116
        DbId          string
117
}
118

119
var (
120
        //go:embed templates/vector.yaml
121
        vectorConfigEmbed    string
122
        vectorConfigTemplate = template.Must(template.New("vectorConfig").Parse(vectorConfigEmbed))
123
)
124

125
type poolerTenant struct {
126
        DbHost            string
127
        DbPort            uint16
128
        DbDatabase        string
129
        DbPassword        string
130
        ExternalId        string
131
        ModeType          config.PoolMode
132
        DefaultMaxClients uint
133
        DefaultPoolSize   uint
134
}
135

136
var (
137
        //go:embed templates/pooler.exs
138
        poolerTenantEmbed    string
139
        poolerTenantTemplate = template.Must(template.New("poolerTenant").Parse(poolerTenantEmbed))
140
)
141

142
var serviceTimeout = 30 * time.Second
143

144
func run(ctx context.Context, fsys afero.Fs, excludedContainers []string, dbConfig pgconn.Config, options ...func(*pgx.ConnConfig)) error {
2✔
145
        excluded := make(map[string]bool)
2✔
146
        for _, name := range excludedContainers {
17✔
147
                excluded[name] = true
15✔
148
        }
15✔
149

150
        jwks, err := utils.Config.Auth.ResolveJWKS(ctx)
2✔
151
        if err != nil {
2✔
152
                return err
×
153
        }
×
154

155
        // Start Postgres.
156
        if dbConfig.Host == utils.DbId {
4✔
157
                if err := start.StartDatabase(ctx, "", fsys, os.Stderr, options...); err != nil {
2✔
158
                        return err
×
159
                }
×
160
        }
161

162
        var started []string
2✔
163
        var isStorageEnabled = utils.Config.Storage.Enabled && !isContainerExcluded(utils.Config.Storage.Image, excluded)
2✔
164
        var isImgProxyEnabled = utils.Config.Storage.ImageTransformation != nil &&
2✔
165
                utils.Config.Storage.ImageTransformation.Enabled && !isContainerExcluded(utils.Config.Storage.ImgProxyImage, excluded)
2✔
166
        fmt.Fprintln(os.Stderr, "Starting containers...")
2✔
167

2✔
168
        // Start Logflare
2✔
169
        if utils.Config.Analytics.Enabled && !isContainerExcluded(utils.Config.Analytics.Image, excluded) {
3✔
170
                env := []string{
1✔
171
                        "DB_DATABASE=_supabase",
1✔
172
                        "DB_HOSTNAME=" + dbConfig.Host,
1✔
173
                        fmt.Sprintf("DB_PORT=%d", dbConfig.Port),
1✔
174
                        "DB_SCHEMA=_analytics",
1✔
175
                        "DB_USERNAME=" + utils.SUPERUSER_ROLE,
1✔
176
                        "DB_PASSWORD=" + dbConfig.Password,
1✔
177
                        "LOGFLARE_MIN_CLUSTER_SIZE=1",
1✔
178
                        "LOGFLARE_SINGLE_TENANT=true",
1✔
179
                        "LOGFLARE_SUPABASE_MODE=true",
1✔
180
                        "LOGFLARE_PRIVATE_ACCESS_TOKEN=" + utils.Config.Analytics.ApiKey,
1✔
181
                        "LOGFLARE_LOG_LEVEL=warn",
1✔
182
                        "LOGFLARE_NODE_HOST=127.0.0.1",
1✔
183
                        "LOGFLARE_FEATURE_FLAG_OVERRIDE='multibackend=true'",
1✔
184
                        "RELEASE_COOKIE=cookie",
1✔
185
                }
1✔
186
                bind := []string{}
1✔
187

1✔
188
                switch utils.Config.Analytics.Backend {
1✔
189
                case config.LogflareBigQuery:
×
190
                        workdir, err := os.Getwd()
×
191
                        if err != nil {
×
192
                                return errors.Errorf("failed to get working directory: %w", err)
×
193
                        }
×
194
                        hostJwtPath := filepath.Join(workdir, utils.Config.Analytics.GcpJwtPath)
×
195
                        bind = append(bind, hostJwtPath+":/opt/app/rel/logflare/bin/gcloud.json")
×
196
                        // This is hardcoded in studio frontend
×
197
                        env = append(env,
×
198
                                "GOOGLE_DATASET_ID_APPEND=_prod",
×
199
                                "GOOGLE_PROJECT_ID="+utils.Config.Analytics.GcpProjectId,
×
200
                                "GOOGLE_PROJECT_NUMBER="+utils.Config.Analytics.GcpProjectNumber,
×
201
                        )
×
202
                case config.LogflarePostgres:
1✔
203
                        env = append(env,
1✔
204
                                fmt.Sprintf("POSTGRES_BACKEND_URL=postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, "_supabase"),
1✔
205
                                "POSTGRES_BACKEND_SCHEMA=_analytics",
1✔
206
                        )
1✔
207
                }
208

209
                if _, err := utils.DockerStart(
1✔
210
                        ctx,
1✔
211
                        container.Config{
1✔
212
                                Hostname: "127.0.0.1",
1✔
213
                                Image:    utils.Config.Analytics.Image,
1✔
214
                                Env:      env,
1✔
215
                                // Original entrypoint conflicts with healthcheck due to 15 seconds sleep:
1✔
216
                                // https://github.com/Logflare/logflare/blob/staging/run.sh#L35
1✔
217
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > run.sh && sh run.sh
1✔
218
./logflare eval Logflare.Release.migrate
1✔
219
./logflare start --sname logflare
1✔
220
EOF
1✔
221
`},
1✔
222
                                Healthcheck: &container.HealthConfig{
1✔
223
                                        Test: []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null",
1✔
224
                                                "http://127.0.0.1:4000/health",
1✔
225
                                        },
1✔
226
                                        Interval:    10 * time.Second,
1✔
227
                                        Timeout:     2 * time.Second,
1✔
228
                                        Retries:     3,
1✔
229
                                        StartPeriod: 10 * time.Second,
1✔
230
                                },
1✔
231
                                ExposedPorts: nat.PortSet{"4000/tcp": {}},
1✔
232
                        },
1✔
233
                        container.HostConfig{
1✔
234
                                Binds:         bind,
1✔
235
                                PortBindings:  nat.PortMap{"4000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Analytics.Port), 10)}}},
1✔
236
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
237
                        },
1✔
238
                        network.NetworkingConfig{
1✔
239
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
240
                                        utils.NetId: {
1✔
241
                                                Aliases: utils.LogflareAliases,
1✔
242
                                        },
1✔
243
                                },
1✔
244
                        },
1✔
245
                        utils.LogflareId,
1✔
246
                ); err != nil {
1✔
247
                        return err
×
248
                }
×
249
                started = append(started, utils.LogflareId)
1✔
250
        }
251

252
        // Start vector
253
        if utils.Config.Analytics.Enabled && !isContainerExcluded(utils.Config.Analytics.VectorImage, excluded) {
3✔
254
                var vectorConfigBuf bytes.Buffer
1✔
255
                if err := vectorConfigTemplate.Option("missingkey=error").Execute(&vectorConfigBuf, vectorConfig{
1✔
256
                        ApiKey:        utils.Config.Analytics.ApiKey,
1✔
257
                        VectorId:      utils.VectorId,
1✔
258
                        LogflareId:    utils.LogflareId,
1✔
259
                        KongId:        utils.KongId,
1✔
260
                        GotrueId:      utils.GotrueId,
1✔
261
                        RestId:        utils.RestId,
1✔
262
                        RealtimeId:    utils.RealtimeId,
1✔
263
                        StorageId:     utils.StorageId,
1✔
264
                        EdgeRuntimeId: utils.EdgeRuntimeId,
1✔
265
                        DbId:          utils.DbId,
1✔
266
                }); err != nil {
1✔
267
                        return errors.Errorf("failed to exec template: %w", err)
×
268
                }
×
269
                var binds, env, securityOpts []string
1✔
270
                // Special case for GitLab pipeline
1✔
271
                parsed, err := client.ParseHostURL(utils.Docker.DaemonHost())
1✔
272
                if err != nil {
1✔
273
                        return errors.Errorf("failed to parse docker host: %w", err)
×
274
                }
×
275
                // Ref: https://vector.dev/docs/reference/configuration/sources/docker_logs/#docker_host
276
                dindHost := &url.URL{Scheme: "http", Host: net.JoinHostPort(utils.DinDHost, "2375")}
1✔
277
                switch parsed.Scheme {
1✔
278
                case "tcp":
×
279
                        if _, port, err := net.SplitHostPort(parsed.Host); err == nil {
×
280
                                dindHost.Host = net.JoinHostPort(utils.DinDHost, port)
×
281
                        }
×
282
                        env = append(env, "DOCKER_HOST="+dindHost.String())
×
283
                case "npipe":
×
284
                        const dockerDaemonNeededErr = "Analytics on Windows requires Docker daemon exposed on tcp://localhost:2375.\nSee https://supabase.com/docs/guides/local-development/cli/getting-started?queryGroups=platform&platform=windows#running-supabase-locally for more details."
×
285
                        fmt.Fprintln(os.Stderr, utils.Yellow("WARNING:"), dockerDaemonNeededErr)
×
286
                        env = append(env, "DOCKER_HOST="+dindHost.String())
×
287
                case "unix":
×
288
                        if dindHost, err = client.ParseHostURL(client.DefaultDockerHost); err != nil {
×
289
                                return errors.Errorf("failed to parse default host: %w", err)
×
290
                        } else if strings.HasSuffix(parsed.Host, "/.docker/run/docker.sock") {
×
291
                                fmt.Fprintln(os.Stderr, utils.Yellow("WARNING:"), "analytics requires mounting default docker socket:", dindHost.Host)
×
292
                                binds = append(binds, fmt.Sprintf("%[1]s:%[1]s:ro", dindHost.Host))
×
293
                        } else {
×
294
                                // Podman and OrbStack can mount root-less socket without issue
×
295
                                binds = append(binds, fmt.Sprintf("%s:%s:ro", parsed.Host, dindHost.Host))
×
296
                                securityOpts = append(securityOpts, "label:disable")
×
297
                        }
×
298
                }
299
                if _, err := utils.DockerStart(
1✔
300
                        ctx,
1✔
301
                        container.Config{
1✔
302
                                Image: utils.Config.Analytics.VectorImage,
1✔
303
                                Env:   env,
1✔
304
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > /etc/vector/vector.yaml && vector --config /etc/vector/vector.yaml
1✔
305
` + vectorConfigBuf.String() + `
1✔
306
EOF
1✔
307
`},
1✔
308
                                Healthcheck: &container.HealthConfig{
1✔
309
                                        Test: []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider",
1✔
310
                                                "http://127.0.0.1:9001/health",
1✔
311
                                        },
1✔
312
                                        Interval: 10 * time.Second,
1✔
313
                                        Timeout:  2 * time.Second,
1✔
314
                                        Retries:  3,
1✔
315
                                },
1✔
316
                        },
1✔
317
                        container.HostConfig{
1✔
318
                                Binds:         binds,
1✔
319
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
320
                                SecurityOpt:   securityOpts,
1✔
321
                        },
1✔
322
                        network.NetworkingConfig{
1✔
323
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
324
                                        utils.NetId: {
1✔
325
                                                Aliases: utils.VectorAliases,
1✔
326
                                        },
1✔
327
                                },
1✔
328
                        },
1✔
329
                        utils.VectorId,
1✔
330
                ); err != nil {
1✔
331
                        return err
×
332
                }
×
333
                if parsed.Scheme != "npipe" {
2✔
334
                        started = append(started, utils.VectorId)
1✔
335
                }
1✔
336
        }
337

338
        // Start Kong.
339
        if !isContainerExcluded(utils.Config.Api.KongImage, excluded) {
3✔
340
                var kongConfigBuf bytes.Buffer
1✔
341
                if err := kongConfigTemplate.Option("missingkey=error").Execute(&kongConfigBuf, kongConfig{
1✔
342
                        GotrueId:      utils.GotrueId,
1✔
343
                        RestId:        utils.RestId,
1✔
344
                        RealtimeId:    utils.Config.Realtime.TenantId,
1✔
345
                        StorageId:     utils.StorageId,
1✔
346
                        StudioId:      utils.StudioId,
1✔
347
                        PgmetaId:      utils.PgmetaId,
1✔
348
                        EdgeRuntimeId: utils.EdgeRuntimeId,
1✔
349
                        LogflareId:    utils.LogflareId,
1✔
350
                        PoolerId:      utils.PoolerId,
1✔
351
                        ApiHost:       utils.Config.Hostname,
1✔
352
                        ApiPort:       utils.Config.Api.Port,
1✔
353
                        BearerToken: fmt.Sprintf(
1✔
354
                                // If Authorization header is set to a self-minted JWT, we want to pass it down.
1✔
355
                                // Legacy supabase-js may set Authorization header to Bearer <apikey>. We must remove it
1✔
356
                                // to avoid failing JWT validation.
1✔
357
                                // If Authorization header is missing, we want to match against apikey header to set the
1✔
358
                                // default JWT for downstream services.
1✔
359
                                // Finally, the apikey header may be set to a legacy JWT. In that case, we want to copy
1✔
360
                                // it to Authorization header for backwards compatibility.
1✔
361
                                `$((headers.authorization ~= nil and headers.authorization:sub(1, 10) ~= 'Bearer sb_' and headers.authorization) or (headers.apikey == '%s' and 'Bearer %s') or (headers.apikey == '%s' and 'Bearer %s') or headers.apikey)`,
1✔
362
                                utils.Config.Auth.SecretKey.Value,
1✔
363
                                utils.Config.Auth.ServiceRoleKey.Value,
1✔
364
                                utils.Config.Auth.PublishableKey.Value,
1✔
365
                                utils.Config.Auth.AnonKey.Value,
1✔
366
                        ),
1✔
367
                        QueryToken: fmt.Sprintf(
1✔
368
                                `$((query_params.apikey == '%s' and '%s') or (query_params.apikey == '%s' and '%s') or query_params.apikey)`,
1✔
369
                                utils.Config.Auth.SecretKey.Value,
1✔
370
                                utils.Config.Auth.ServiceRoleKey.Value,
1✔
371
                                utils.Config.Auth.PublishableKey.Value,
1✔
372
                                utils.Config.Auth.AnonKey.Value,
1✔
373
                        ),
1✔
374
                }); err != nil {
1✔
375
                        return errors.Errorf("failed to exec template: %w", err)
×
376
                }
×
377

378
                binds := []string{}
1✔
379
                for id, tmpl := range utils.Config.Auth.Email.Template {
1✔
380
                        if len(tmpl.ContentPath) == 0 {
×
381
                                continue
×
382
                        }
383
                        hostPath := tmpl.ContentPath
×
384
                        if !filepath.IsAbs(tmpl.ContentPath) {
×
385
                                var err error
×
386
                                hostPath, err = filepath.Abs(hostPath)
×
387
                                if err != nil {
×
388
                                        return errors.Errorf("failed to resolve absolute path: %w", err)
×
389
                                }
×
390
                        }
391
                        dockerPath := path.Join(nginxEmailTemplateDir, id+filepath.Ext(hostPath))
×
392
                        binds = append(binds, fmt.Sprintf("%s:%s:rw", hostPath, dockerPath))
×
393
                }
394

395
                dockerPort := uint16(8000)
1✔
396
                if utils.Config.Api.Tls.Enabled {
1✔
397
                        dockerPort = 8443
×
398
                }
×
399
                if _, err := utils.DockerStart(
1✔
400
                        ctx,
1✔
401
                        container.Config{
1✔
402
                                Image: utils.Config.Api.KongImage,
1✔
403
                                Env: []string{
1✔
404
                                        "KONG_DATABASE=off",
1✔
405
                                        "KONG_DECLARATIVE_CONFIG=/home/kong/kong.yml",
1✔
406
                                        "KONG_DNS_ORDER=LAST,A,CNAME", // https://github.com/supabase/cli/issues/14
1✔
407
                                        "KONG_PLUGINS=request-transformer,cors",
1✔
408
                                        fmt.Sprintf("KONG_PORT_MAPS=%d:8000", utils.Config.Api.Port),
1✔
409
                                        // Need to increase the nginx buffers in kong to avoid it rejecting the rather
1✔
410
                                        // sizeable response headers azure can generate
1✔
411
                                        // Ref: https://github.com/Kong/kong/issues/3974#issuecomment-482105126
1✔
412
                                        "KONG_NGINX_PROXY_PROXY_BUFFER_SIZE=160k",
1✔
413
                                        "KONG_NGINX_PROXY_PROXY_BUFFERS=64 160k",
1✔
414
                                        "KONG_NGINX_WORKER_PROCESSES=1",
1✔
415
                                        // Use modern TLS certificate
1✔
416
                                        "KONG_SSL_CERT=/home/kong/localhost.crt",
1✔
417
                                        "KONG_SSL_CERT_KEY=/home/kong/localhost.key",
1✔
418
                                },
1✔
419
                                Entrypoint: []string{"sh", "-c", `cat <<'EOF' > /home/kong/kong.yml && \
1✔
420
cat <<'EOF' > /home/kong/custom_nginx.template && \
1✔
421
cat <<'EOF' > /home/kong/localhost.crt && \
1✔
422
cat <<'EOF' > /home/kong/localhost.key && \
1✔
423
./docker-entrypoint.sh kong docker-start --nginx-conf /home/kong/custom_nginx.template
1✔
424
` + kongConfigBuf.String() + `
1✔
425
EOF
1✔
426
` + nginxConfigEmbed + `
1✔
427
EOF
1✔
428
` + string(utils.Config.Api.Tls.CertContent) + `
1✔
429
EOF
1✔
430
` + string(utils.Config.Api.Tls.KeyContent) + `
1✔
431
EOF
1✔
432
`},
1✔
433
                                ExposedPorts: nat.PortSet{
1✔
434
                                        "8000/tcp": {},
1✔
435
                                        "8443/tcp": {},
1✔
436
                                        nat.Port(fmt.Sprintf("%d/tcp", nginxTemplateServerPort)): {},
1✔
437
                                },
1✔
438
                        },
1✔
439
                        container.HostConfig{
1✔
440
                                Binds: binds,
1✔
441
                                PortBindings: nat.PortMap{nat.Port(fmt.Sprintf("%d/tcp", dockerPort)): []nat.PortBinding{{
1✔
442
                                        HostPort: strconv.FormatUint(uint64(utils.Config.Api.Port), 10)},
1✔
443
                                }},
1✔
444
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
445
                        },
1✔
446
                        network.NetworkingConfig{
1✔
447
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
448
                                        utils.NetId: {
1✔
449
                                                Aliases: utils.KongAliases,
1✔
450
                                        },
1✔
451
                                },
1✔
452
                        },
1✔
453
                        utils.KongId,
1✔
454
                ); err != nil {
1✔
455
                        return err
×
456
                }
×
457
                started = append(started, utils.KongId)
1✔
458
        }
459

460
        // Start GoTrue.
461
        if utils.Config.Auth.Enabled && !isContainerExcluded(utils.Config.Auth.Image, excluded) {
3✔
462
                var testOTP bytes.Buffer
1✔
463
                if len(utils.Config.Auth.Sms.TestOTP) > 0 {
1✔
464
                        formatMapForEnvConfig(utils.Config.Auth.Sms.TestOTP, &testOTP)
×
465
                }
×
466

467
                env := []string{
1✔
468
                        "API_EXTERNAL_URL=" + utils.Config.Api.ExternalUrl,
1✔
469

1✔
470
                        "GOTRUE_API_HOST=0.0.0.0",
1✔
471
                        "GOTRUE_API_PORT=9999",
1✔
472

1✔
473
                        "GOTRUE_DB_DRIVER=postgres",
1✔
474
                        fmt.Sprintf("GOTRUE_DB_DATABASE_URL=postgresql://supabase_auth_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
475

1✔
476
                        "GOTRUE_SITE_URL=" + utils.Config.Auth.SiteUrl,
1✔
477
                        "GOTRUE_URI_ALLOW_LIST=" + strings.Join(utils.Config.Auth.AdditionalRedirectUrls, ","),
1✔
478
                        fmt.Sprintf("GOTRUE_DISABLE_SIGNUP=%v", !utils.Config.Auth.EnableSignup),
1✔
479

1✔
480
                        "GOTRUE_JWT_ADMIN_ROLES=service_role",
1✔
481
                        "GOTRUE_JWT_AUD=authenticated",
1✔
482
                        "GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated",
1✔
483
                        fmt.Sprintf("GOTRUE_JWT_EXP=%v", utils.Config.Auth.JwtExpiry),
1✔
484
                        "GOTRUE_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
485
                        "GOTRUE_JWT_ISSUER=" + utils.Config.Auth.JwtIssuer,
1✔
486

1✔
487
                        fmt.Sprintf("GOTRUE_EXTERNAL_EMAIL_ENABLED=%v", utils.Config.Auth.Email.EnableSignup),
1✔
488
                        fmt.Sprintf("GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=%v", utils.Config.Auth.Email.DoubleConfirmChanges),
1✔
489
                        fmt.Sprintf("GOTRUE_MAILER_AUTOCONFIRM=%v", !utils.Config.Auth.Email.EnableConfirmations),
1✔
490
                        fmt.Sprintf("GOTRUE_MAILER_OTP_LENGTH=%v", utils.Config.Auth.Email.OtpLength),
1✔
491
                        fmt.Sprintf("GOTRUE_MAILER_OTP_EXP=%v", utils.Config.Auth.Email.OtpExpiry),
1✔
492

1✔
493
                        fmt.Sprintf("GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=%v", utils.Config.Auth.EnableAnonymousSignIns),
1✔
494

1✔
495
                        fmt.Sprintf("GOTRUE_SMTP_MAX_FREQUENCY=%v", utils.Config.Auth.Email.MaxFrequency),
1✔
496

1✔
497
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_INVITE=%s/verify", utils.Config.Auth.JwtIssuer),
1✔
498
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_CONFIRMATION=%s/verify", utils.Config.Auth.JwtIssuer),
1✔
499
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_RECOVERY=%s/verify", utils.Config.Auth.JwtIssuer),
1✔
500
                        fmt.Sprintf("GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=%s/verify", utils.Config.Auth.JwtIssuer),
1✔
501
                        "GOTRUE_RATE_LIMIT_EMAIL_SENT=360000",
1✔
502

1✔
503
                        fmt.Sprintf("GOTRUE_EXTERNAL_PHONE_ENABLED=%v", utils.Config.Auth.Sms.EnableSignup),
1✔
504
                        fmt.Sprintf("GOTRUE_SMS_AUTOCONFIRM=%v", !utils.Config.Auth.Sms.EnableConfirmations),
1✔
505
                        fmt.Sprintf("GOTRUE_SMS_MAX_FREQUENCY=%v", utils.Config.Auth.Sms.MaxFrequency),
1✔
506
                        "GOTRUE_SMS_OTP_EXP=6000",
1✔
507
                        "GOTRUE_SMS_OTP_LENGTH=6",
1✔
508
                        fmt.Sprintf("GOTRUE_SMS_TEMPLATE=%v", utils.Config.Auth.Sms.Template),
1✔
509
                        "GOTRUE_SMS_TEST_OTP=" + testOTP.String(),
1✔
510

1✔
511
                        fmt.Sprintf("GOTRUE_PASSWORD_MIN_LENGTH=%v", utils.Config.Auth.MinimumPasswordLength),
1✔
512
                        fmt.Sprintf("GOTRUE_PASSWORD_REQUIRED_CHARACTERS=%v", utils.Config.Auth.PasswordRequirements.ToChar()),
1✔
513
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation),
1✔
514
                        fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval),
1✔
515
                        fmt.Sprintf("GOTRUE_SECURITY_MANUAL_LINKING_ENABLED=%v", utils.Config.Auth.EnableManualLinking),
1✔
516
                        fmt.Sprintf("GOTRUE_SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION=%v", utils.Config.Auth.Email.SecurePasswordChange),
1✔
517
                        fmt.Sprintf("GOTRUE_MFA_PHONE_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.Phone.EnrollEnabled),
1✔
518
                        fmt.Sprintf("GOTRUE_MFA_PHONE_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.Phone.VerifyEnabled),
1✔
519
                        fmt.Sprintf("GOTRUE_MFA_TOTP_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.TOTP.EnrollEnabled),
1✔
520
                        fmt.Sprintf("GOTRUE_MFA_TOTP_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.TOTP.VerifyEnabled),
1✔
521
                        fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.EnrollEnabled),
1✔
522
                        fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.VerifyEnabled),
1✔
523
                        fmt.Sprintf("GOTRUE_MFA_MAX_ENROLLED_FACTORS=%v", utils.Config.Auth.MFA.MaxEnrolledFactors),
1✔
524

1✔
525
                        // Add rate limit configurations
1✔
526
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_ANONYMOUS_USERS=%v", utils.Config.Auth.RateLimit.AnonymousUsers),
1✔
527
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_TOKEN_REFRESH=%v", utils.Config.Auth.RateLimit.TokenRefresh),
1✔
528
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_OTP=%v", utils.Config.Auth.RateLimit.SignInSignUps),
1✔
529
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_VERIFY=%v", utils.Config.Auth.RateLimit.TokenVerifications),
1✔
530
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_SMS_SENT=%v", utils.Config.Auth.RateLimit.SmsSent),
1✔
531
                        fmt.Sprintf("GOTRUE_RATE_LIMIT_WEB3=%v", utils.Config.Auth.RateLimit.Web3),
1✔
532
                }
1✔
533

1✔
534
                // Since signing key is validated by ResolveJWKS, simply read the key file.
1✔
535
                if keys, err := afero.ReadFile(fsys, utils.Config.Auth.SigningKeysPath); err == nil && len(keys) > 0 {
1✔
536
                        env = append(env, "GOTRUE_JWT_KEYS="+string(keys))
×
537
                }
×
538

539
                if utils.Config.Auth.Email.Smtp != nil && utils.Config.Auth.Email.Smtp.Enabled {
1✔
540
                        env = append(env,
×
541
                                fmt.Sprintf("GOTRUE_RATE_LIMIT_EMAIL_SENT=%v", utils.Config.Auth.RateLimit.EmailSent),
×
542
                                fmt.Sprintf("GOTRUE_SMTP_HOST=%s", utils.Config.Auth.Email.Smtp.Host),
×
543
                                fmt.Sprintf("GOTRUE_SMTP_PORT=%d", utils.Config.Auth.Email.Smtp.Port),
×
544
                                fmt.Sprintf("GOTRUE_SMTP_USER=%s", utils.Config.Auth.Email.Smtp.User),
×
545
                                fmt.Sprintf("GOTRUE_SMTP_PASS=%s", utils.Config.Auth.Email.Smtp.Pass.Value),
×
546
                                fmt.Sprintf("GOTRUE_SMTP_ADMIN_EMAIL=%s", utils.Config.Auth.Email.Smtp.AdminEmail),
×
547
                                fmt.Sprintf("GOTRUE_SMTP_SENDER_NAME=%s", utils.Config.Auth.Email.Smtp.SenderName),
×
548
                        )
×
549
                } else if utils.Config.Inbucket.Enabled {
2✔
550
                        env = append(env,
1✔
551
                                "GOTRUE_SMTP_HOST="+utils.InbucketId,
1✔
552
                                "GOTRUE_SMTP_PORT=1025",
1✔
553
                                fmt.Sprintf("GOTRUE_SMTP_ADMIN_EMAIL=%s", utils.Config.Inbucket.AdminEmail),
1✔
554
                                fmt.Sprintf("GOTRUE_SMTP_SENDER_NAME=%s", utils.Config.Inbucket.SenderName),
1✔
555
                        )
1✔
556
                }
1✔
557

558
                if utils.Config.Auth.Sessions.Timebox > 0 {
1✔
559
                        env = append(env, fmt.Sprintf("GOTRUE_SESSIONS_TIMEBOX=%v", utils.Config.Auth.Sessions.Timebox))
×
560
                }
×
561
                if utils.Config.Auth.Sessions.InactivityTimeout > 0 {
1✔
562
                        env = append(env, fmt.Sprintf("GOTRUE_SESSIONS_INACTIVITY_TIMEOUT=%v", utils.Config.Auth.Sessions.InactivityTimeout))
×
563
                }
×
564

565
                for id, tmpl := range utils.Config.Auth.Email.Template {
1✔
566
                        if len(tmpl.ContentPath) > 0 {
×
567
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_TEMPLATES_%s=http://%s:%d/email/%s",
×
568
                                        strings.ToUpper(id),
×
569
                                        utils.KongId,
×
570
                                        nginxTemplateServerPort,
×
571
                                        id+filepath.Ext(tmpl.ContentPath),
×
572
                                ))
×
573
                        }
×
574
                        if tmpl.Subject != nil {
×
575
                                env = append(env, fmt.Sprintf("GOTRUE_MAILER_SUBJECTS_%s=%s",
×
576
                                        strings.ToUpper(id),
×
577
                                        *tmpl.Subject,
×
578
                                ))
×
579
                        }
×
580
                }
581

582
                switch {
1✔
583
                case utils.Config.Auth.Sms.Twilio.Enabled:
×
584
                        env = append(
×
585
                                env,
×
586
                                "GOTRUE_SMS_PROVIDER=twilio",
×
587
                                "GOTRUE_SMS_TWILIO_ACCOUNT_SID="+utils.Config.Auth.Sms.Twilio.AccountSid,
×
588
                                "GOTRUE_SMS_TWILIO_AUTH_TOKEN="+utils.Config.Auth.Sms.Twilio.AuthToken.Value,
×
589
                                "GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.Twilio.MessageServiceSid,
×
590
                        )
×
591
                case utils.Config.Auth.Sms.TwilioVerify.Enabled:
×
592
                        env = append(
×
593
                                env,
×
594
                                "GOTRUE_SMS_PROVIDER=twilio_verify",
×
595
                                "GOTRUE_SMS_TWILIO_VERIFY_ACCOUNT_SID="+utils.Config.Auth.Sms.TwilioVerify.AccountSid,
×
596
                                "GOTRUE_SMS_TWILIO_VERIFY_AUTH_TOKEN="+utils.Config.Auth.Sms.TwilioVerify.AuthToken.Value,
×
597
                                "GOTRUE_SMS_TWILIO_VERIFY_MESSAGE_SERVICE_SID="+utils.Config.Auth.Sms.TwilioVerify.MessageServiceSid,
×
598
                        )
×
599
                case utils.Config.Auth.Sms.Messagebird.Enabled:
×
600
                        env = append(
×
601
                                env,
×
602
                                "GOTRUE_SMS_PROVIDER=messagebird",
×
603
                                "GOTRUE_SMS_MESSAGEBIRD_ACCESS_KEY="+utils.Config.Auth.Sms.Messagebird.AccessKey.Value,
×
604
                                "GOTRUE_SMS_MESSAGEBIRD_ORIGINATOR="+utils.Config.Auth.Sms.Messagebird.Originator,
×
605
                        )
×
606
                case utils.Config.Auth.Sms.Textlocal.Enabled:
×
607
                        env = append(
×
608
                                env,
×
609
                                "GOTRUE_SMS_PROVIDER=textlocal",
×
610
                                "GOTRUE_SMS_TEXTLOCAL_API_KEY="+utils.Config.Auth.Sms.Textlocal.ApiKey.Value,
×
611
                                "GOTRUE_SMS_TEXTLOCAL_SENDER="+utils.Config.Auth.Sms.Textlocal.Sender,
×
612
                        )
×
613
                case utils.Config.Auth.Sms.Vonage.Enabled:
×
614
                        env = append(
×
615
                                env,
×
616
                                "GOTRUE_SMS_PROVIDER=vonage",
×
617
                                "GOTRUE_SMS_VONAGE_API_KEY="+utils.Config.Auth.Sms.Vonage.ApiKey,
×
618
                                "GOTRUE_SMS_VONAGE_API_SECRET="+utils.Config.Auth.Sms.Vonage.ApiSecret.Value,
×
619
                                "GOTRUE_SMS_VONAGE_FROM="+utils.Config.Auth.Sms.Vonage.From,
×
620
                        )
×
621
                }
622

623
                if captcha := utils.Config.Auth.Captcha; captcha != nil {
1✔
624
                        env = append(
×
625
                                env,
×
626
                                fmt.Sprintf("GOTRUE_SECURITY_CAPTCHA_ENABLED=%v", captcha.Enabled),
×
627
                                fmt.Sprintf("GOTRUE_SECURITY_CAPTCHA_PROVIDER=%v", captcha.Provider),
×
628
                                fmt.Sprintf("GOTRUE_SECURITY_CAPTCHA_SECRET=%v", captcha.Secret.Value),
×
629
                        )
×
630
                }
×
631

632
                if hook := utils.Config.Auth.Hook.MFAVerificationAttempt; hook != nil && hook.Enabled {
1✔
633
                        env = append(
×
634
                                env,
×
635
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED=true",
×
636
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI="+hook.URI,
×
637
                                "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_SECRETS="+hook.Secrets.Value,
×
638
                        )
×
639
                }
×
640
                if hook := utils.Config.Auth.Hook.PasswordVerificationAttempt; hook != nil && hook.Enabled {
1✔
641
                        env = append(
×
642
                                env,
×
643
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED=true",
×
644
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="+hook.URI,
×
645
                                "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_SECRETS="+hook.Secrets.Value,
×
646
                        )
×
647
                }
×
648
                if hook := utils.Config.Auth.Hook.CustomAccessToken; hook != nil && hook.Enabled {
1✔
649
                        env = append(
×
650
                                env,
×
651
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED=true",
×
652
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="+hook.URI,
×
653
                                "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS="+hook.Secrets.Value,
×
654
                        )
×
655
                }
×
656
                if hook := utils.Config.Auth.Hook.SendSMS; hook != nil && hook.Enabled {
1✔
657
                        env = append(
×
658
                                env,
×
659
                                "GOTRUE_HOOK_SEND_SMS_ENABLED=true",
×
660
                                "GOTRUE_HOOK_SEND_SMS_URI="+hook.URI,
×
661
                                "GOTRUE_HOOK_SEND_SMS_SECRETS="+hook.Secrets.Value,
×
662
                        )
×
663
                }
×
664
                if hook := utils.Config.Auth.Hook.SendEmail; hook != nil && hook.Enabled {
1✔
665
                        env = append(
×
666
                                env,
×
667
                                "GOTRUE_HOOK_SEND_EMAIL_ENABLED=true",
×
668
                                "GOTRUE_HOOK_SEND_EMAIL_URI="+hook.URI,
×
669
                                "GOTRUE_HOOK_SEND_EMAIL_SECRETS="+hook.Secrets.Value,
×
670
                        )
×
671
                }
×
672
                if hook := utils.Config.Auth.Hook.BeforeUserCreated; hook != nil && hook.Enabled {
1✔
673
                        env = append(
×
674
                                env,
×
675
                                "GOTRUE_HOOK_BEFORE_USER_CREATED_ENABLED=true",
×
676
                                "GOTRUE_HOOK_BEFORE_USER_CREATED_URI="+hook.URI,
×
677
                                "GOTRUE_HOOK_BEFORE_USER_CREATED_SECRETS="+hook.Secrets.Value,
×
678
                        )
×
679
                }
×
680

681
                if utils.Config.Auth.MFA.Phone.EnrollEnabled || utils.Config.Auth.MFA.Phone.VerifyEnabled {
1✔
682
                        env = append(
×
683
                                env,
×
684
                                "GOTRUE_MFA_PHONE_TEMPLATE="+utils.Config.Auth.MFA.Phone.Template,
×
685
                                fmt.Sprintf("GOTRUE_MFA_PHONE_OTP_LENGTH=%v", utils.Config.Auth.MFA.Phone.OtpLength),
×
686
                                fmt.Sprintf("GOTRUE_MFA_PHONE_MAX_FREQUENCY=%v", utils.Config.Auth.MFA.Phone.MaxFrequency),
×
687
                        )
×
688
                }
×
689

690
                for name, config := range utils.Config.Auth.External {
2✔
691
                        env = append(
1✔
692
                                env,
1✔
693
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_ENABLED=%v", strings.ToUpper(name), config.Enabled),
1✔
694
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_CLIENT_ID=%s", strings.ToUpper(name), config.ClientId),
1✔
695
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SECRET=%s", strings.ToUpper(name), config.Secret.Value),
1✔
696
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_SKIP_NONCE_CHECK=%t", strings.ToUpper(name), config.SkipNonceCheck),
1✔
697
                                fmt.Sprintf("GOTRUE_EXTERNAL_%s_EMAIL_OPTIONAL=%t", strings.ToUpper(name), config.EmailOptional),
1✔
698
                        )
1✔
699

1✔
700
                        redirectUri := config.RedirectUri
1✔
701
                        if redirectUri == "" {
2✔
702
                                redirectUri = utils.Config.Auth.JwtIssuer + "/callback"
1✔
703
                        }
1✔
704
                        env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=%s", strings.ToUpper(name), redirectUri))
1✔
705

1✔
706
                        if config.Url != "" {
1✔
707
                                env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_%s_URL=%s", strings.ToUpper(name), config.Url))
×
708
                        }
×
709
                }
710
                env = append(env, fmt.Sprintf("GOTRUE_EXTERNAL_WEB3_SOLANA_ENABLED=%v", utils.Config.Auth.Web3.Solana.Enabled))
1✔
711

1✔
712
                // OAuth server configuration
1✔
713
                if utils.Config.Auth.OAuthServer.Enabled {
1✔
714
                        env = append(env,
×
715
                                fmt.Sprintf("GOTRUE_OAUTH_SERVER_ENABLED=%v", utils.Config.Auth.OAuthServer.Enabled),
×
716
                                "GOTRUE_OAUTH_SERVER_AUTHORIZATION_PATH="+utils.Config.Auth.OAuthServer.AuthorizationUrlPath,
×
717
                                fmt.Sprintf("GOTRUE_OAUTH_SERVER_ALLOW_DYNAMIC_REGISTRATION=%v", utils.Config.Auth.OAuthServer.AllowDynamicRegistration),
×
718
                        )
×
719
                }
×
720

721
                if _, err := utils.DockerStart(
1✔
722
                        ctx,
1✔
723
                        container.Config{
1✔
724
                                Image:        utils.Config.Auth.Image,
1✔
725
                                Env:          env,
1✔
726
                                ExposedPorts: nat.PortSet{"9999/tcp": {}},
1✔
727
                                Healthcheck: &container.HealthConfig{
1✔
728
                                        Test: []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider",
1✔
729
                                                "http://127.0.0.1:9999/health",
1✔
730
                                        },
1✔
731
                                        Interval: 10 * time.Second,
1✔
732
                                        Timeout:  2 * time.Second,
1✔
733
                                        Retries:  3,
1✔
734
                                },
1✔
735
                        },
1✔
736
                        container.HostConfig{
1✔
737
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
738
                        },
1✔
739
                        network.NetworkingConfig{
1✔
740
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
741
                                        utils.NetId: {
1✔
742
                                                Aliases: utils.GotrueAliases,
1✔
743
                                        },
1✔
744
                                },
1✔
745
                        },
1✔
746
                        utils.GotrueId,
1✔
747
                ); err != nil {
1✔
748
                        return err
×
749
                }
×
750
                started = append(started, utils.GotrueId)
1✔
751
        }
752

753
        // Start Mailpit
754
        if utils.Config.Inbucket.Enabled && !isContainerExcluded(utils.Config.Inbucket.Image, excluded) {
3✔
755
                inbucketPortBindings := nat.PortMap{"8025/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Port), 10)}}}
1✔
756
                if utils.Config.Inbucket.SmtpPort != 0 {
1✔
757
                        inbucketPortBindings["1025/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.SmtpPort), 10)}}
×
758
                }
×
759
                if utils.Config.Inbucket.Pop3Port != 0 {
1✔
760
                        inbucketPortBindings["1110/tcp"] = []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Inbucket.Pop3Port), 10)}}
×
761
                }
×
762
                if _, err := utils.DockerStart(
1✔
763
                        ctx,
1✔
764
                        container.Config{
1✔
765
                                Image: utils.Config.Inbucket.Image,
1✔
766
                                Env: []string{
1✔
767
                                        // Disable reverse DNS lookups in Mailpit to avoid slow/delayed DNS resolution
1✔
768
                                        "MP_SMTP_DISABLE_RDNS=true",
1✔
769
                                },
1✔
770
                                Healthcheck: &container.HealthConfig{
1✔
771
                                        Test:     []string{"CMD", "/mailpit", "readyz"},
1✔
772
                                        Interval: 10 * time.Second,
1✔
773
                                        Timeout:  2 * time.Second,
1✔
774
                                        Retries:  3,
1✔
775
                                        // StartPeriod taken from upstream Dockerfile
1✔
776
                                        StartPeriod: 10 * time.Second,
1✔
777
                                },
1✔
778
                        },
1✔
779
                        container.HostConfig{
1✔
780
                                PortBindings:  inbucketPortBindings,
1✔
781
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
782
                        },
1✔
783
                        network.NetworkingConfig{
1✔
784
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
785
                                        utils.NetId: {
1✔
786
                                                Aliases: utils.InbucketAliases,
1✔
787
                                        },
1✔
788
                                },
1✔
789
                        },
1✔
790
                        utils.InbucketId,
1✔
791
                ); err != nil {
1✔
UNCOV
792
                        return err
×
UNCOV
793
                }
×
794
                started = append(started, utils.InbucketId)
1✔
795
        }
796

797
        // Start Realtime.
798
        if utils.Config.Realtime.Enabled && !isContainerExcluded(utils.Config.Realtime.Image, excluded) {
3✔
799
                if _, err := utils.DockerStart(
1✔
800
                        ctx,
1✔
801
                        container.Config{
1✔
802
                                Image: utils.Config.Realtime.Image,
1✔
803
                                Env: []string{
1✔
804
                                        "PORT=4000",
1✔
805
                                        "DB_HOST=" + dbConfig.Host,
1✔
806
                                        fmt.Sprintf("DB_PORT=%d", dbConfig.Port),
1✔
807
                                        "DB_USER=" + utils.SUPERUSER_ROLE,
1✔
808
                                        "DB_PASSWORD=" + dbConfig.Password,
1✔
809
                                        "DB_NAME=" + dbConfig.Database,
1✔
810
                                        "DB_AFTER_CONNECT_QUERY=SET search_path TO _realtime",
1✔
811
                                        "DB_ENC_KEY=" + utils.Config.Realtime.EncryptionKey,
1✔
812
                                        "API_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
813
                                        fmt.Sprintf("API_JWT_JWKS=%s", jwks),
1✔
814
                                        "METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
815
                                        "APP_NAME=realtime",
1✔
816
                                        "SECRET_KEY_BASE=" + utils.Config.Realtime.SecretKeyBase,
1✔
817
                                        "ERL_AFLAGS=" + utils.ToRealtimeEnv(utils.Config.Realtime.IpVersion),
1✔
818
                                        "DNS_NODES=''",
1✔
819
                                        "RLIMIT_NOFILE=",
1✔
820
                                        "SEED_SELF_HOST=true",
1✔
821
                                        "RUN_JANITOR=true",
1✔
822
                                        fmt.Sprintf("MAX_HEADER_LENGTH=%d", utils.Config.Realtime.MaxHeaderLength),
1✔
823
                                },
1✔
824
                                ExposedPorts: nat.PortSet{"4000/tcp": {}},
1✔
825
                                Healthcheck: &container.HealthConfig{
1✔
826
                                        // Podman splits command by spaces unless it's quoted, but curl header can't be quoted.
1✔
827
                                        Test: []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null",
1✔
828
                                                "-H", "Host:" + utils.Config.Realtime.TenantId,
1✔
829
                                                "http://127.0.0.1:4000/api/ping",
1✔
830
                                        },
1✔
831
                                        Interval: 10 * time.Second,
1✔
832
                                        Timeout:  2 * time.Second,
1✔
833
                                        Retries:  3,
1✔
834
                                },
1✔
835
                        },
1✔
836
                        container.HostConfig{
1✔
837
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
838
                        },
1✔
839
                        network.NetworkingConfig{
1✔
840
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
841
                                        utils.NetId: {
1✔
842
                                                Aliases: utils.RealtimeAliases,
1✔
843
                                        },
1✔
844
                                },
1✔
845
                        },
1✔
846
                        utils.RealtimeId,
1✔
847
                ); err != nil {
1✔
UNCOV
848
                        return err
×
UNCOV
849
                }
×
850
                started = append(started, utils.RealtimeId)
1✔
851
        }
852

853
        // Start PostgREST.
854
        if utils.Config.Api.Enabled && !isContainerExcluded(utils.Config.Api.Image, excluded) {
3✔
855
                if _, err := utils.DockerStart(
1✔
856
                        ctx,
1✔
857
                        container.Config{
1✔
858
                                Image: utils.Config.Api.Image,
1✔
859
                                Env: []string{
1✔
860
                                        fmt.Sprintf("PGRST_DB_URI=postgresql://authenticator:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
861
                                        "PGRST_DB_SCHEMAS=" + strings.Join(utils.Config.Api.Schemas, ","),
1✔
862
                                        "PGRST_DB_EXTRA_SEARCH_PATH=" + strings.Join(utils.Config.Api.ExtraSearchPath, ","),
1✔
863
                                        fmt.Sprintf("PGRST_DB_MAX_ROWS=%d", utils.Config.Api.MaxRows),
1✔
864
                                        "PGRST_DB_ANON_ROLE=anon",
1✔
865
                                        fmt.Sprintf("PGRST_JWT_SECRET=%s", jwks),
1✔
866
                                        "PGRST_ADMIN_SERVER_PORT=3001",
1✔
867
                                },
1✔
868
                                // PostgREST does not expose a shell for health check
1✔
869
                        },
1✔
870
                        container.HostConfig{
1✔
871
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
872
                        },
1✔
873
                        network.NetworkingConfig{
1✔
874
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
875
                                        utils.NetId: {
1✔
876
                                                Aliases: utils.RestAliases,
1✔
877
                                        },
1✔
878
                                },
1✔
879
                        },
1✔
880
                        utils.RestId,
1✔
881
                ); err != nil {
1✔
UNCOV
882
                        return err
×
UNCOV
883
                }
×
884
                started = append(started, utils.RestId)
1✔
885
        }
886

887
        // Start Storage.
888
        if isStorageEnabled {
3✔
889
                dockerStoragePath := "/mnt"
1✔
890
                if _, err := utils.DockerStart(
1✔
891
                        ctx,
1✔
892
                        container.Config{
1✔
893
                                Image: utils.Config.Storage.Image,
1✔
894
                                Env: []string{
1✔
895
                                        "DB_MIGRATIONS_FREEZE_AT=" + utils.Config.Storage.TargetMigration,
1✔
896
                                        "ANON_KEY=" + utils.Config.Auth.AnonKey.Value,
1✔
897
                                        "SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey.Value,
1✔
898
                                        "AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
899
                                        fmt.Sprintf("JWT_JWKS=%s", jwks),
1✔
900
                                        fmt.Sprintf("DATABASE_URL=postgresql://supabase_storage_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
1✔
901
                                        fmt.Sprintf("FILE_SIZE_LIMIT=%v", utils.Config.Storage.FileSizeLimit),
1✔
902
                                        "STORAGE_BACKEND=file",
1✔
903
                                        "FILE_STORAGE_BACKEND_PATH=" + dockerStoragePath,
1✔
904
                                        "TENANT_ID=stub",
1✔
905
                                        // TODO: https://github.com/supabase/storage-api/issues/55
1✔
906
                                        "STORAGE_S3_REGION=" + utils.Config.Storage.S3Credentials.Region,
1✔
907
                                        "GLOBAL_S3_BUCKET=stub",
1✔
908
                                        fmt.Sprintf("ENABLE_IMAGE_TRANSFORMATION=%t", isImgProxyEnabled),
1✔
909
                                        fmt.Sprintf("IMGPROXY_URL=http://%s:5001", utils.ImgProxyId),
1✔
910
                                        "TUS_URL_PATH=/storage/v1/upload/resumable",
1✔
911
                                        "S3_PROTOCOL_ACCESS_KEY_ID=" + utils.Config.Storage.S3Credentials.AccessKeyId,
1✔
912
                                        "S3_PROTOCOL_ACCESS_KEY_SECRET=" + utils.Config.Storage.S3Credentials.SecretAccessKey,
1✔
913
                                        "S3_PROTOCOL_PREFIX=/storage/v1",
1✔
914
                                        "UPLOAD_FILE_SIZE_LIMIT=52428800000",
1✔
915
                                        "UPLOAD_FILE_SIZE_LIMIT_STANDARD=5242880000",
1✔
916
                                        "SIGNED_UPLOAD_URL_EXPIRATION_TIME=7200",
1✔
917
                                },
1✔
918
                                Healthcheck: &container.HealthConfig{
1✔
919
                                        // For some reason, localhost resolves to IPv6 address on GitPod which breaks healthcheck.
1✔
920
                                        Test: []string{"CMD", "wget", "--no-verbose", "--tries=1", "--spider",
1✔
921
                                                "http://127.0.0.1:5000/status",
1✔
922
                                        },
1✔
923
                                        Interval: 10 * time.Second,
1✔
924
                                        Timeout:  2 * time.Second,
1✔
925
                                        Retries:  3,
1✔
926
                                },
1✔
927
                        },
1✔
928
                        container.HostConfig{
1✔
929
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
930
                                Binds:         []string{utils.StorageId + ":" + dockerStoragePath},
1✔
931
                        },
1✔
932
                        network.NetworkingConfig{
1✔
933
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
934
                                        utils.NetId: {
1✔
935
                                                Aliases: utils.StorageAliases,
1✔
936
                                        },
1✔
937
                                },
1✔
938
                        },
1✔
939
                        utils.StorageId,
1✔
940
                ); err != nil {
1✔
UNCOV
941
                        return err
×
UNCOV
942
                }
×
943
                started = append(started, utils.StorageId)
1✔
944
        }
945

946
        // Start Storage ImgProxy.
947
        if isStorageEnabled && isImgProxyEnabled {
2✔
948
                if _, err := utils.DockerStart(
×
949
                        ctx,
×
950
                        container.Config{
×
951
                                Image: utils.Config.Storage.ImgProxyImage,
×
952
                                Env: []string{
×
953
                                        "IMGPROXY_BIND=:5001",
×
954
                                        "IMGPROXY_LOCAL_FILESYSTEM_ROOT=/",
×
955
                                        "IMGPROXY_USE_ETAG=/",
×
956
                                        "IMGPROXY_MAX_SRC_RESOLUTION=50",
×
957
                                        "IMGPROXY_MAX_SRC_FILE_SIZE=25000000",
×
958
                                        "IMGPROXY_MAX_ANIMATION_FRAMES=60",
×
959
                                        "IMGPROXY_ENABLE_WEBP_DETECTION=true",
×
960
                                        "IMGPROXY_PRESETS=default=width:3000/height:8192",
×
961
                                        "IMGPROXY_FORMAT_QUALITY=jpeg=80,avif=62,webp=80",
×
962
                                },
×
963
                                Healthcheck: &container.HealthConfig{
×
964
                                        Test:     []string{"CMD", "imgproxy", "health"},
×
965
                                        Interval: 10 * time.Second,
×
966
                                        Timeout:  2 * time.Second,
×
967
                                        Retries:  3,
×
968
                                },
×
969
                        },
×
970
                        container.HostConfig{
×
971
                                VolumesFrom:   []string{utils.StorageId},
×
972
                                RestartPolicy: container.RestartPolicy{Name: "always"},
×
973
                        },
×
974
                        network.NetworkingConfig{
×
975
                                EndpointsConfig: map[string]*network.EndpointSettings{
×
976
                                        utils.NetId: {
×
977
                                                Aliases: utils.ImgProxyAliases,
×
978
                                        },
×
979
                                },
×
980
                        },
×
981
                        utils.ImgProxyId,
×
UNCOV
982
                ); err != nil {
×
UNCOV
983
                        return err
×
UNCOV
984
                }
×
UNCOV
985
                started = append(started, utils.ImgProxyId)
×
986
        }
987

988
        // Start all functions.
989
        if utils.Config.EdgeRuntime.Enabled && !isContainerExcluded(utils.Config.EdgeRuntime.Image, excluded) {
3✔
990
                dbUrl := fmt.Sprintf("postgresql://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database)
1✔
991
                if err := serve.ServeFunctions(ctx, "", nil, "", dbUrl, serve.RuntimeOption{}, fsys); err != nil {
1✔
UNCOV
992
                        return err
×
UNCOV
993
                }
×
994
                started = append(started, utils.EdgeRuntimeId)
1✔
995
        }
996

997
        // Start pg-meta.
998
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.Config.Studio.PgmetaImage, excluded) {
3✔
999
                if _, err := utils.DockerStart(
1✔
1000
                        ctx,
1✔
1001
                        container.Config{
1✔
1002
                                Image: utils.Config.Studio.PgmetaImage,
1✔
1003
                                Env: []string{
1✔
1004
                                        "PG_META_PORT=8080",
1✔
1005
                                        "PG_META_DB_HOST=" + dbConfig.Host,
1✔
1006
                                        "PG_META_DB_NAME=" + dbConfig.Database,
1✔
1007
                                        "PG_META_DB_USER=" + dbConfig.User,
1✔
1008
                                        fmt.Sprintf("PG_META_DB_PORT=%d", dbConfig.Port),
1✔
1009
                                        "PG_META_DB_PASSWORD=" + dbConfig.Password,
1✔
1010
                                },
1✔
1011
                                Healthcheck: &container.HealthConfig{
1✔
1012
                                        Test:     []string{"CMD-SHELL", `node --eval="fetch('http://127.0.0.1:8080/health').then((r) => {if (!r.ok) throw new Error(r.status)})"`},
1✔
1013
                                        Interval: 10 * time.Second,
1✔
1014
                                        Timeout:  2 * time.Second,
1✔
1015
                                        Retries:  3,
1✔
1016
                                },
1✔
1017
                        },
1✔
1018
                        container.HostConfig{
1✔
1019
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
1020
                        },
1✔
1021
                        network.NetworkingConfig{
1✔
1022
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
1023
                                        utils.NetId: {
1✔
1024
                                                Aliases: utils.PgmetaAliases,
1✔
1025
                                        },
1✔
1026
                                },
1✔
1027
                        },
1✔
1028
                        utils.PgmetaId,
1✔
1029
                ); err != nil {
1✔
UNCOV
1030
                        return err
×
UNCOV
1031
                }
×
1032
                started = append(started, utils.PgmetaId)
1✔
1033
        }
1034

1035
        // Start Studio.
1036
        if utils.Config.Studio.Enabled && !isContainerExcluded(utils.Config.Studio.Image, excluded) {
3✔
1037
                if _, err := utils.DockerStart(
1✔
1038
                        ctx,
1✔
1039
                        container.Config{
1✔
1040
                                Image: utils.Config.Studio.Image,
1✔
1041
                                Env: []string{
1✔
1042
                                        "CURRENT_CLI_VERSION=" + utils.Version,
1✔
1043
                                        "STUDIO_PG_META_URL=http://" + utils.PgmetaId + ":8080",
1✔
1044
                                        "POSTGRES_PASSWORD=" + dbConfig.Password,
1✔
1045
                                        "SUPABASE_URL=http://" + utils.KongId + ":8000",
1✔
1046
                                        "SUPABASE_PUBLIC_URL=" + utils.Config.Studio.ApiUrl,
1✔
1047
                                        "AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
1✔
1048
                                        "SUPABASE_ANON_KEY=" + utils.Config.Auth.AnonKey.Value,
1✔
1049
                                        "SUPABASE_SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey.Value,
1✔
1050
                                        "LOGFLARE_PRIVATE_ACCESS_TOKEN=" + utils.Config.Analytics.ApiKey,
1✔
1051
                                        "OPENAI_API_KEY=" + utils.Config.Studio.OpenaiApiKey.Value,
1✔
1052
                                        fmt.Sprintf("LOGFLARE_URL=http://%v:4000", utils.LogflareId),
1✔
1053
                                        fmt.Sprintf("NEXT_PUBLIC_ENABLE_LOGS=%v", utils.Config.Analytics.Enabled),
1✔
1054
                                        fmt.Sprintf("NEXT_ANALYTICS_BACKEND_PROVIDER=%v", utils.Config.Analytics.Backend),
1✔
1055
                                        // Ref: https://github.com/vercel/next.js/issues/51684#issuecomment-1612834913
1✔
1056
                                        "HOSTNAME=0.0.0.0",
1✔
1057
                                },
1✔
1058
                                Healthcheck: &container.HealthConfig{
1✔
1059
                                        Test:     []string{"CMD-SHELL", `node --eval="fetch('http://127.0.0.1:3000/api/platform/profile').then((r) => {if (!r.ok) throw new Error(r.status)})"`},
1✔
1060
                                        Interval: 10 * time.Second,
1✔
1061
                                        Timeout:  2 * time.Second,
1✔
1062
                                        Retries:  3,
1✔
1063
                                },
1✔
1064
                        },
1✔
1065
                        container.HostConfig{
1✔
1066
                                PortBindings:  nat.PortMap{"3000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Studio.Port), 10)}}},
1✔
1067
                                RestartPolicy: container.RestartPolicy{Name: "always"},
1✔
1068
                        },
1✔
1069
                        network.NetworkingConfig{
1✔
1070
                                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
1071
                                        utils.NetId: {
1✔
1072
                                                Aliases: utils.StudioAliases,
1✔
1073
                                        },
1✔
1074
                                },
1✔
1075
                        },
1✔
1076
                        utils.StudioId,
1✔
1077
                ); err != nil {
1✔
UNCOV
1078
                        return err
×
UNCOV
1079
                }
×
1080
                started = append(started, utils.StudioId)
1✔
1081
        }
1082

1083
        // Start pooler.
1084
        if utils.Config.Db.Pooler.Enabled && !isContainerExcluded(utils.Config.Db.Pooler.Image, excluded) {
2✔
1085
                portSession := uint16(5432)
×
1086
                portTransaction := uint16(6543)
×
UNCOV
1087
                dockerPort := portTransaction
×
1088
                if utils.Config.Db.Pooler.PoolMode == config.SessionMode {
×
1089
                        dockerPort = portSession
×
1090
                }
×
1091
                // Create pooler tenant
1092
                var poolerTenantBuf bytes.Buffer
×
1093
                if err := poolerTenantTemplate.Option("missingkey=error").Execute(&poolerTenantBuf, poolerTenant{
×
1094
                        DbHost:            dbConfig.Host,
×
1095
                        DbPort:            dbConfig.Port,
×
1096
                        DbDatabase:        dbConfig.Database,
×
1097
                        DbPassword:        dbConfig.Password,
×
1098
                        ExternalId:        utils.Config.Db.Pooler.TenantId,
×
1099
                        ModeType:          utils.Config.Db.Pooler.PoolMode,
×
1100
                        DefaultMaxClients: utils.Config.Db.Pooler.MaxClientConn,
×
1101
                        DefaultPoolSize:   utils.Config.Db.Pooler.DefaultPoolSize,
×
1102
                }); err != nil {
×
1103
                        return errors.Errorf("failed to exec template: %w", err)
×
1104
                }
×
1105
                if _, err := utils.DockerStart(
×
1106
                        ctx,
×
1107
                        container.Config{
×
1108
                                Image: utils.Config.Db.Pooler.Image,
×
1109
                                Env: []string{
×
1110
                                        "PORT=4000",
×
1111
                                        fmt.Sprintf("PROXY_PORT_SESSION=%d", portSession),
×
1112
                                        fmt.Sprintf("PROXY_PORT_TRANSACTION=%d", portTransaction),
×
1113
                                        fmt.Sprintf("DATABASE_URL=ecto://%s:%s@%s:%d/%s", dbConfig.User, dbConfig.Password, dbConfig.Host, dbConfig.Port, "_supabase"),
×
1114
                                        "CLUSTER_POSTGRES=true",
×
1115
                                        "SECRET_KEY_BASE=" + utils.Config.Db.Pooler.SecretKeyBase,
×
1116
                                        "VAULT_ENC_KEY=" + utils.Config.Db.Pooler.EncryptionKey,
×
1117
                                        "API_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
×
1118
                                        "METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret.Value,
×
1119
                                        "REGION=local",
×
1120
                                        "RUN_JANITOR=true",
×
1121
                                        "ERL_AFLAGS=-proto_dist inet_tcp",
×
1122
                                        "RLIMIT_NOFILE=",
×
1123
                                },
×
1124
                                Cmd: []string{
×
1125
                                        "/bin/sh", "-c",
×
1126
                                        fmt.Sprintf("/app/bin/migrate && /app/bin/supavisor eval '%s' && /app/bin/server", poolerTenantBuf.String()),
×
1127
                                },
×
1128
                                ExposedPorts: nat.PortSet{
×
1129
                                        "4000/tcp": {},
×
1130
                                        nat.Port(fmt.Sprintf("%d/tcp", portSession)):     {},
×
1131
                                        nat.Port(fmt.Sprintf("%d/tcp", portTransaction)): {},
×
1132
                                },
×
1133
                                Healthcheck: &container.HealthConfig{
×
1134
                                        Test:     []string{"CMD", "curl", "-sSfL", "--head", "-o", "/dev/null", "http://127.0.0.1:4000/api/health"},
×
1135
                                        Interval: 10 * time.Second,
×
1136
                                        Timeout:  2 * time.Second,
×
1137
                                        Retries:  3,
×
1138
                                },
×
1139
                        },
×
1140
                        container.HostConfig{
×
1141
                                PortBindings: nat.PortMap{nat.Port(fmt.Sprintf("%d/tcp", dockerPort)): []nat.PortBinding{{
×
1142
                                        HostPort: strconv.FormatUint(uint64(utils.Config.Db.Pooler.Port), 10)},
×
1143
                                }},
×
1144
                                RestartPolicy: container.RestartPolicy{Name: "always"},
×
1145
                        },
×
1146
                        network.NetworkingConfig{
×
1147
                                EndpointsConfig: map[string]*network.EndpointSettings{
×
1148
                                        utils.NetId: {
×
1149
                                                Aliases: utils.PoolerAliases,
×
1150
                                        },
×
1151
                                },
×
1152
                        },
×
UNCOV
1153
                        utils.PoolerId,
×
UNCOV
1154
                ); err != nil {
×
UNCOV
1155
                        return err
×
UNCOV
1156
                }
×
UNCOV
1157
                started = append(started, utils.PoolerId)
×
1158
        }
1159

1160
        fmt.Fprintln(os.Stderr, "Waiting for health checks...")
2✔
1161
        if utils.NoBackupVolume && slices.Contains(started, utils.StorageId) {
3✔
1162
                if err := start.WaitForHealthyService(ctx, serviceTimeout, utils.StorageId); err != nil {
1✔
1163
                        return err
×
UNCOV
1164
                }
×
1165
                // Disable prompts when seeding
1166
                if err := buckets.Run(ctx, "", false, fsys); err != nil {
1✔
UNCOV
1167
                        return err
×
UNCOV
1168
                }
×
1169
        }
1170
        return start.WaitForHealthyService(ctx, serviceTimeout, started...)
2✔
1171
}
1172

1173
func isContainerExcluded(imageName string, excluded map[string]bool) bool {
22✔
1174
        short := utils.ShortContainerImageName(imageName)
22✔
1175
        val, ok := excluded[short]
22✔
1176
        return ok && val
22✔
1177
}
22✔
1178

1179
func ExcludableContainers() []string {
1✔
1180
        names := []string{}
1✔
1181
        for _, image := range config.Images.Services() {
14✔
1182
                names = append(names, utils.ShortContainerImageName(image))
13✔
1183
        }
13✔
1184
        return names
1✔
1185
}
1186

1187
func formatMapForEnvConfig(input map[string]string, output *bytes.Buffer) {
5✔
1188
        numOfKeyPairs := len(input)
5✔
1189
        i := 0
5✔
1190
        for k, v := range input {
15✔
1191
                output.WriteString(k)
10✔
1192
                output.WriteString(":")
10✔
1193
                output.WriteString(v)
10✔
1194
                i++
10✔
1195
                if i < numOfKeyPairs {
16✔
1196
                        output.WriteString(",")
6✔
1197
                }
6✔
1198
        }
1199
}
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