diff --git a/cmd/status.go b/cmd/status.go index 11b774854..3e15d52b9 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -18,7 +18,7 @@ var ( statusCmd = &cobra.Command{ GroupID: groupLocalDev, Use: "status", - Short: "Show status of local Supabase containers", + Short: "Show status of your Supabase project", PreRunE: func(cmd *cobra.Command, args []string) error { es, err := env.EnvironToEnvSet(override) if err != nil { @@ -38,5 +38,9 @@ var ( func init() { flags := statusCmd.Flags() flags.StringSliceVar(&override, "override-name", []string{}, "Override specific variable names.") + flags.String("db-url", "", "Shows the database status specified by the connection string (must be percent-encoded).") + flags.Bool("linked", false, "Shows status of the linked project.") + flags.Bool("local", true, "Shows status of the local database.") + statusCmd.MarkFlagsMutuallyExclusive("db-url", "linked", "local") rootCmd.AddCommand(statusCmd) } diff --git a/internal/status/status.go b/internal/status/status.go index 62532d7ea..d72910162 100644 --- a/internal/status/status.go +++ b/internal/status/status.go @@ -21,8 +21,10 @@ import ( "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" "github.com/spf13/afero" + "github.com/supabase/cli/internal/projects/apiKeys" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" + "github.com/supabase/cli/pkg/api" "github.com/supabase/cli/pkg/fetcher" ) @@ -49,7 +51,7 @@ type CustomName struct { func (c *CustomName) toValues(exclude ...string) map[string]string { values := map[string]string{ - c.DbURL: fmt.Sprintf("postgresql://%s@%s:%d/postgres", url.UserPassword("postgres", utils.Config.Db.Password), utils.Config.Hostname, utils.Config.Db.Port), + c.DbURL: utils.ToPostgresURL(flags.DbConfig), } apiEnabled := utils.Config.Api.Enabled && !slices.Contains(exclude, utils.RestId) && !slices.Contains(exclude, utils.ShortContainerImageName(utils.Config.Api.Image)) @@ -98,7 +100,18 @@ func Run(ctx context.Context, names CustomName, format string, fsys afero.Fs) er if err := flags.LoadConfig(fsys); err != nil { return err } - if err := assertContainerHealthy(ctx, utils.DbId); err != nil { + if !utils.IsLocalDatabase(flags.DbConfig) { + apiUrl := url.URL{Scheme: "https", Host: utils.GetSupabaseHost(flags.ProjectRef)} + utils.Config.Api.ExternalUrl = apiUrl.String() + if err := updateApiKey(ctx); err != nil { + return err + } + if format == utils.OutputPretty { + PrettyPrint(os.Stdout) + return nil + } + return printStatus(names, format, os.Stdout) + } else if err := assertContainerHealthy(ctx, utils.DbId); err != nil { return err } stopped, err := checkServiceHealth(ctx) @@ -116,6 +129,37 @@ func Run(ctx context.Context, names CustomName, format string, fsys afero.Fs) er return printStatus(names, format, os.Stdout, stopped...) } +func updateApiKey(ctx context.Context) error { + keys, err := apiKeys.RunGetApiKeys(ctx, flags.ProjectRef) + if err != nil { + return err + } + for _, k := range keys { + kt, err := k.Type.Get() + if err != nil { + continue + } + kv, err := k.ApiKey.Get() + if err != nil { + continue + } + switch kt { + case api.ApiKeyResponseTypePublishable: + utils.Config.Auth.PublishableKey.Value = kv + case api.ApiKeyResponseTypeSecret: + utils.Config.Auth.SecretKey.Value = kv + case api.ApiKeyResponseTypeLegacy: + switch k.Name { + case "anon": + utils.Config.Auth.AnonKey.Value = kv + case "service_role": + utils.Config.Auth.ServiceRoleKey.Value = kv + } + } + } + return nil +} + func checkServiceHealth(ctx context.Context) ([]string, error) { resp, err := utils.Docker.ContainerList(ctx, container.ListOptions{ Filters: utils.CliProjectFilter(utils.Config.ProjectId), diff --git a/internal/status/status_test.go b/internal/status/status_test.go index c7bfc1bc4..0498ecb89 100644 --- a/internal/status/status_test.go +++ b/internal/status/status_test.go @@ -14,6 +14,8 @@ import ( "github.com/stretchr/testify/require" "github.com/supabase/cli/internal/testing/apitest" "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/internal/utils/flags" + "github.com/supabase/cli/pkg/api" ) func TestStatusCommand(t *testing.T) { @@ -176,3 +178,64 @@ func TestPrintStatus(t *testing.T) { assert.Equal(t, "DB_URL = \"postgresql://postgres:postgres@127.0.0.1:0/postgres\"\n", stdout.String()) }) } + +func TestRemoteStatusCommand(t *testing.T) { + t.Run("shows remote health status", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + projectRef := apitest.RandomProjectRef() + require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) + // Setup access token + token := apitest.RandomAccessToken(t) + t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) + // Setup mock API + defer gock.OffAll() + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + projectRef + "/health"). + ParamPresent("services"). + Reply(http.StatusOK). + JSON([]api.V1ServiceHealthResponse{ + { + Name: api.V1ServiceHealthResponseNameAuth, + Healthy: true, + Status: api.ACTIVEHEALTHY, + }, + { + Name: api.V1ServiceHealthResponseNameRealtime, + Healthy: true, + Status: api.ACTIVEHEALTHY, + }, + { + Name: api.V1ServiceHealthResponseNameRest, + Healthy: true, + Status: api.ACTIVEHEALTHY, + }, + { + Name: api.V1ServiceHealthResponseNameStorage, + Healthy: true, + Status: api.ACTIVEHEALTHY, + }, + { + Name: api.V1ServiceHealthResponseNameDb, + Healthy: true, + Status: api.ACTIVEHEALTHY, + }, + }) + // Run test + assert.NoError(t, Run(context.Background(), CustomName{}, utils.OutputPretty, fsys)) + // Check error + assert.Empty(t, apitest.ListUnmatchedRequests()) + }) + + t.Run("throws error on missing project ref", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + defer gock.OffAll() + // Reset global state + flags.ProjectRef = "" + // Run test + err := Run(context.Background(), CustomName{}, utils.OutputPretty, fsys) + // Check error + assert.ErrorContains(t, err, "project ref") + }) +}