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

supabase / cli / 19140348754

06 Nov 2025 03:13PM UTC coverage: 54.622% (+0.02%) from 54.604%
19140348754

Pull #4320

github

web-flow
Merge 1aa4e5564 into d736cfd4d
Pull Request #4320: show api keys with deprecated flag

1 of 9 new or added lines in 3 files covered. (11.11%)

100 existing lines in 8 files now uncovered.

6376 of 11673 relevant lines covered (54.62%)

6.11 hits per line

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

75.0
/internal/status/status.go
1
package status
2

3
import (
4
        "context"
5
        "crypto/tls"
6
        "crypto/x509"
7
        _ "embed"
8
        "fmt"
9
        "io"
10
        "net/http"
11
        "net/url"
12
        "os"
13
        "reflect"
14
        "slices"
15
        "sync"
16
        "time"
17

18
        "github.com/docker/docker/api/types"
19
        "github.com/docker/docker/api/types/container"
20
        "github.com/go-errors/errors"
21
        "github.com/spf13/afero"
22
        "github.com/supabase/cli/internal/utils"
23
        "github.com/supabase/cli/internal/utils/flags"
24
        "github.com/supabase/cli/pkg/fetcher"
25
)
26

27
type CustomName struct {
28
        ApiURL                   string `env:"api.url,default=API_URL"`
29
        GraphqlURL               string `env:"api.graphql_url,default=GRAPHQL_URL"`
30
        StorageS3URL             string `env:"api.storage_s3_url,default=STORAGE_S3_URL"`
31
        McpURL                   string `env:"api.mcp_url,default=MCP_URL"`
32
        DbURL                    string `env:"db.url,default=DB_URL"`
33
        StudioURL                string `env:"studio.url,default=STUDIO_URL"`
34
        InbucketURL              string `env:"inbucket.url,default=INBUCKET_URL,deprecated"`
35
        MailpitURL               string `env:"mailpit.url,default=MAILPIT_URL"`
36
        PublishableKey           string `env:"auth.publishable_key,default=PUBLISHABLE_KEY"`
37
        SecretKey                string `env:"auth.secret_key,default=SECRET_KEY"`
38
        JWTSecret                string `env:"auth.jwt_secret,default=JWT_SECRET,deprecated"`
39
        AnonKey                  string `env:"auth.anon_key,default=ANON_KEY,deprecated"`
40
        ServiceRoleKey           string `env:"auth.service_role_key,default=SERVICE_ROLE_KEY,deprecated"`
41
        StorageS3AccessKeyId     string `env:"storage.s3_access_key_id,default=S3_PROTOCOL_ACCESS_KEY_ID"`
42
        StorageS3SecretAccessKey string `env:"storage.s3_secret_access_key,default=S3_PROTOCOL_ACCESS_KEY_SECRET"`
43
        StorageS3Region          string `env:"storage.s3_region,default=S3_PROTOCOL_REGION"`
44
}
45

46
func (c *CustomName) toValues(exclude ...string) map[string]string {
6✔
47
        values := map[string]string{
6✔
48
                c.DbURL: fmt.Sprintf("postgresql://%s@%s:%d/postgres", url.UserPassword("postgres", utils.Config.Db.Password), utils.Config.Hostname, utils.Config.Db.Port),
6✔
49
        }
6✔
50

6✔
51
        apiEnabled := utils.Config.Api.Enabled && !slices.Contains(exclude, utils.RestId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Api.Image))
6✔
52
        studioEnabled := utils.Config.Studio.Enabled && !slices.Contains(exclude, utils.StudioId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Studio.Image))
6✔
53
        authEnabled := utils.Config.Auth.Enabled && !slices.Contains(exclude, utils.GotrueId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Auth.Image))
6✔
54
        inbucketEnabled := utils.Config.Inbucket.Enabled && !slices.Contains(exclude, utils.InbucketId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Inbucket.Image))
6✔
55
        storageEnabled := utils.Config.Storage.Enabled && !slices.Contains(exclude, utils.StorageId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Storage.Image))
6✔
56

6✔
57
        if apiEnabled {
6✔
58
                values[c.ApiURL] = utils.Config.Api.ExternalUrl
×
59
                values[c.GraphqlURL] = utils.GetApiUrl("/graphql/v1")
×
60
                if studioEnabled {
×
61
                        values[c.McpURL] = utils.GetApiUrl("/mcp")
×
62
                }
×
63
        }
64
        if studioEnabled {
6✔
65
                values[c.StudioURL] = fmt.Sprintf("http://%s:%d", utils.Config.Hostname, utils.Config.Studio.Port)
×
66
        }
×
67
        if authEnabled {
6✔
68
                values[c.PublishableKey] = utils.Config.Auth.PublishableKey.Value
×
69
                values[c.SecretKey] = utils.Config.Auth.SecretKey.Value
×
70
        }
×
71
        if inbucketEnabled {
6✔
72
                values[c.MailpitURL] = fmt.Sprintf("http://%s:%d", utils.Config.Hostname, utils.Config.Inbucket.Port)
×
73
        }
×
74
        if storageEnabled {
6✔
75
                values[c.StorageS3URL] = utils.GetApiUrl("/storage/v1/s3")
×
76
                values[c.StorageS3AccessKeyId] = utils.Config.Storage.S3Credentials.AccessKeyId
×
77
                values[c.StorageS3SecretAccessKey] = utils.Config.Storage.S3Credentials.SecretAccessKey
×
78
                values[c.StorageS3Region] = utils.Config.Storage.S3Credentials.Region
×
79
        }
×
80
        return values
6✔
81
}
82

83
func Run(ctx context.Context, names CustomName, format string, fsys afero.Fs) error {
4✔
84
        // Sanity checks.
4✔
85
        if err := flags.LoadConfig(fsys); err != nil {
5✔
86
                return err
1✔
87
        }
1✔
88
        if err := assertContainerHealthy(ctx, utils.DbId); err != nil {
4✔
89
                return err
1✔
90
        }
1✔
91
        stopped, err := checkServiceHealth(ctx)
2✔
92
        if err != nil {
2✔
93
                return err
×
94
        }
×
95
        if len(stopped) > 0 {
4✔
96
                fmt.Fprintln(os.Stderr, "Stopped services:", stopped)
2✔
97
        }
2✔
98
        // Set suggestion to use new keys instead of deprecated ones
99
        if utils.Config.Auth.Enabled && !slices.Contains(stopped, utils.GotrueId) && !slices.Contains(stopped, utils.ShortContainerImageName(utils.Config.Auth.Image)) {
2✔
NEW
100
                utils.CmdSuggestion = utils.Orange("Please use publishable and secret key instead of anon and service role key.")
×
NEW
101
        }
×
102
        if format == utils.OutputPretty {
4✔
103
                fmt.Fprintf(os.Stderr, "%s local development setup is running.\n\n", utils.Aqua("supabase"))
2✔
104
                PrettyPrint(os.Stdout, stopped...)
2✔
105
                return nil
2✔
106
        }
2✔
107
        return printStatus(names, format, os.Stdout, stopped...)
×
108
}
109

110
func checkServiceHealth(ctx context.Context) ([]string, error) {
5✔
111
        resp, err := utils.Docker.ContainerList(ctx, container.ListOptions{
5✔
112
                Filters: utils.CliProjectFilter(utils.Config.ProjectId),
5✔
113
        })
5✔
114
        if err != nil {
6✔
115
                return nil, errors.Errorf("failed to list running containers: %w", err)
1✔
116
        }
1✔
117
        running := make(map[string]struct{}, len(resp))
4✔
118
        for _, c := range resp {
43✔
119
                for _, n := range c.Names {
78✔
120
                        running[n] = struct{}{}
39✔
121
                }
39✔
122
        }
123
        var stopped []string
4✔
124
        for _, containerId := range utils.GetDockerIds() {
56✔
125
                if _, ok := running["/"+containerId]; !ok {
91✔
126
                        stopped = append(stopped, containerId)
39✔
127
                }
39✔
128
        }
129
        return stopped, nil
4✔
130
}
131

132
func assertContainerHealthy(ctx context.Context, container string) error {
30✔
133
        if resp, err := utils.Docker.ContainerInspect(ctx, container); err != nil {
31✔
134
                return errors.Errorf("failed to inspect container health: %w", err)
1✔
135
        } else if !resp.State.Running {
33✔
136
                return errors.Errorf("%s container is not running: %s", container, resp.State.Status)
3✔
137
        } else if resp.State.Health != nil && resp.State.Health.Status != types.Healthy {
29✔
138
                return errors.Errorf("%s container is not ready: %s", container, resp.State.Health.Status)
×
139
        }
×
140
        return nil
26✔
141
}
142

143
func IsServiceReady(ctx context.Context, container string) error {
29✔
144
        if container == utils.RestId {
30✔
145
                // PostgREST does not support native health checks
1✔
146
                return checkHTTPHead(ctx, "/rest-admin/v1/ready")
1✔
147
        }
1✔
148
        if container == utils.EdgeRuntimeId {
29✔
149
                // Native health check logs too much hyper::Error(IncompleteMessage)
1✔
150
                return checkHTTPHead(ctx, "/functions/v1/_internal/health")
1✔
151
        }
1✔
152
        return assertContainerHealthy(ctx, container)
27✔
153
}
154

155
// To regenerate local certificate pair:
156
//
157
//        openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 \
158
//          -nodes -keyout kong.local.key -out kong.local.crt -subj "/CN=localhost" \
159
//          -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
160
func NewKongClient() *http.Client {
5✔
161
        client := &http.Client{
5✔
162
                Timeout: 10 * time.Second,
5✔
163
        }
5✔
164
        if t, ok := http.DefaultTransport.(*http.Transport); ok {
5✔
165
                pool, err := x509.SystemCertPool()
×
166
                if err != nil {
×
167
                        fmt.Fprintln(utils.GetDebugLogger(), err)
×
168
                        pool = x509.NewCertPool()
×
169
                }
×
170
                // No need to replace TLS config if we fail to append cert
171
                if pool.AppendCertsFromPEM(utils.Config.Api.Tls.CertContent) {
×
172
                        rt := t.Clone()
×
173
                        rt.TLSClientConfig = &tls.Config{
×
174
                                MinVersion: tls.VersionTLS12,
×
175
                                RootCAs:    pool,
×
176
                        }
×
177
                        client.Transport = rt
×
178
                }
×
179
        }
180
        return client
5✔
181
}
182

183
var (
184
        healthClient *fetcher.Fetcher
185
        healthOnce   sync.Once
186
)
187

188
func checkHTTPHead(ctx context.Context, path string) error {
2✔
189
        healthOnce.Do(func() {
3✔
190
                healthClient = fetcher.NewServiceGateway(
1✔
191
                        utils.Config.Api.ExternalUrl,
1✔
192
                        utils.Config.Auth.SecretKey.Value,
1✔
193
                        fetcher.WithHTTPClient(NewKongClient()),
1✔
194
                        fetcher.WithUserAgent("SupabaseCLI/"+utils.Version),
1✔
195
                )
1✔
196
        })
1✔
197
        // HEAD method does not return response body
198
        resp, err := healthClient.Send(ctx, http.MethodHead, path, nil)
2✔
199
        if err != nil {
2✔
200
                return err
×
201
        }
×
202
        defer resp.Body.Close()
2✔
203
        return nil
2✔
204
}
205

206
func printStatus(names CustomName, format string, w io.Writer, exclude ...string) (err error) {
4✔
207
        values := names.toValues(exclude...)
4✔
208
        return utils.EncodeOutput(format, w, values)
4✔
209
}
4✔
210

211
func PrettyPrint(w io.Writer, exclude ...string) {
2✔
212
        names := CustomName{
2✔
213
                ApiURL:                   "         " + utils.Aqua("API URL"),
2✔
214
                GraphqlURL:               "     " + utils.Aqua("GraphQL URL"),
2✔
215
                StorageS3URL:             "  " + utils.Aqua("S3 Storage URL"),
2✔
216
                McpURL:                   "         " + utils.Aqua("MCP URL"),
2✔
217
                DbURL:                    "    " + utils.Aqua("Database URL"),
2✔
218
                StudioURL:                "      " + utils.Aqua("Studio URL"),
2✔
219
                MailpitURL:               "     " + utils.Aqua("Mailpit URL"),
2✔
220
                PublishableKey:           " " + utils.Aqua("Publishable key"),
2✔
221
                SecretKey:                "      " + utils.Aqua("Secret key"),
2✔
222
                StorageS3AccessKeyId:     "   " + utils.Aqua("S3 Access Key"),
2✔
223
                StorageS3SecretAccessKey: "   " + utils.Aqua("S3 Secret Key"),
2✔
224
                StorageS3Region:          "       " + utils.Aqua("S3 Region"),
2✔
225
        }
2✔
226
        values := names.toValues(exclude...)
2✔
227
        // Iterate through map in order of declared struct fields
2✔
228
        val := reflect.ValueOf(names)
2✔
229
        for i := 0; i < val.NumField(); i++ {
34✔
230
                k := val.Field(i).String()
32✔
231
                if v, ok := values[k]; ok {
34✔
232
                        fmt.Fprintf(w, "%s: %s\n", k, v)
2✔
233
                }
2✔
234
        }
235
}
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