Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/cli-go/internal/functions/serve/serve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ func TestServeFunctions(t *testing.T) {
require.NoError(t, utils.Config.Load("testdata/config.toml", testdata))
utils.UpdateDockerIds()

t.Run("starts main service with regular remote module imports", func(t *testing.T) {
assert.Contains(t, mainFuncEmbed, `from "https://deno.land/std/http/status.ts"`)
assert.Contains(t, mainFuncEmbed, `from "https://deno.land/std/path/posix/mod.ts"`)
Comment thread
jgoux marked this conversation as resolved.
assert.Contains(t, mainFuncEmbed, `from "jsr:@panva/jose@6"`)
assert.Contains(t, mainFuncEmbed, `pathname === "/_internal/health"`)
})

t.Run("runs inspect mode", func(t *testing.T) {
// Setup in-memory fs
fsys := afero.FromIOFS{FS: testdata}
Expand Down
17 changes: 11 additions & 6 deletions apps/cli-go/internal/functions/serve/templates/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { STATUS_CODE, STATUS_TEXT } from "https://deno.land/std/http/status.ts";
import * as posix from "https://deno.land/std/path/posix/mod.ts";

import * as jose from "jsr:@panva/jose@6";

const SB_SPECIFIC_ERROR_CODE = {
Expand Down Expand Up @@ -145,18 +144,24 @@ async function isValidLegacyJWT(jwtSecret: string, jwt: string): Promise<boolean
return true;
}

// Lazy-loading JWKs
let jwks = (() => {
let jwks: any | undefined;

function getJwks(jose: any) {
if (jwks !== undefined) {
return jwks;
}
try {
// using injected JWKS from cli
return jose.createLocalJWKSet(JSON.parse(Deno.env.get('SUPABASE_JWKS')));
jwks = jose.createLocalJWKSet(JSON.parse(Deno.env.get('SUPABASE_JWKS')));
} catch (error) {
return null
jwks = null;
}
})();
return jwks;
}

async function isValidJWT(jwksUrl: URL, jwt: string): Promise<boolean> {
try {
jwks = getJwks(jose);
if (!jwks) {
// Loading from remote-url on fly
jwks = jose.createRemoteJWKSet(new URL(jwksUrl));
Expand Down
20 changes: 12 additions & 8 deletions apps/cli-go/internal/start/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func Run(ctx context.Context, fsys afero.Fs, excludedContainers []string, ignore
Password: utils.Config.Db.Password,
Database: "postgres",
}
if err := run(ctx, fsys, excludedContainers, dbConfig); err != nil {
if err := run(ctx, fsys, excludedContainers, dbConfig, ignoreHealthCheck); err != nil {
if ignoreHealthCheck && start.IsUnhealthyError(err) {
fmt.Fprintln(os.Stderr, err)
} else {
Expand Down Expand Up @@ -215,7 +215,7 @@ func pullImagesUsingCompose(ctx context.Context, project types.Project) error {
return service.Pull(ctx, &project, api.PullOptions{IgnoreFailures: true})
}

func run(ctx context.Context, fsys afero.Fs, excludedContainers []string, dbConfig pgconn.Config, options ...func(*pgx.ConnConfig)) error {
func run(ctx context.Context, fsys afero.Fs, excludedContainers []string, dbConfig pgconn.Config, ignoreHealthCheck bool, options ...func(*pgx.ConnConfig)) error {
excluded := make(map[string]bool)
for _, name := range excludedContainers {
excluded[name] = true
Expand Down Expand Up @@ -1214,18 +1214,22 @@ EOF
}

fmt.Fprintln(os.Stderr, "Waiting for health checks...")
if utils.NoBackupVolume && slices.Contains(started, utils.StorageId) {
if err := start.WaitForHealthyService(ctx, serviceTimeout, utils.StorageId); err != nil {
return err
if err := start.WaitForHealthyService(ctx, serviceTimeout, started...); err != nil {
if ignoreHealthCheck && utils.NoBackupVolume && slices.Contains(started, utils.StorageId) {
if storageErr := start.WaitForHealthyService(ctx, serviceTimeout, utils.StorageId); storageErr == nil {
if seedErr := buckets.Run(ctx, "", false, fsys); seedErr != nil {
return seedErr
}
}
}
return err
Comment thread
jgoux marked this conversation as resolved.
}
if utils.NoBackupVolume && slices.Contains(started, utils.StorageId) {
// Disable prompts when seeding
if err := buckets.Run(ctx, "", false, fsys); err != nil {
return err
}
}
if err := start.WaitForHealthyService(ctx, serviceTimeout, started...); err != nil {
return err
}
_ = phtelemetry.FromContext(ctx).Capture(ctx, phtelemetry.EventStackStarted, nil, nil)
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions apps/cli-go/internal/start/start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ func TestDatabaseStart(t *testing.T) {
Reply(http.StatusOK).
JSON(storage.ListVectorBucketsResponse{})
// Run test
err = run(ctx, fsys, []string{}, pgconn.Config{Host: utils.DbId}, conn.Intercept)
err = run(ctx, fsys, []string{}, pgconn.Config{Host: utils.DbId}, false, conn.Intercept)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
Expand Down Expand Up @@ -301,7 +301,7 @@ func TestDatabaseStart(t *testing.T) {
// Run test
exclude := ExcludableContainers()
exclude = append(exclude, "invalid", exclude[0])
err := run(context.Background(), fsys, exclude, pgconn.Config{Host: utils.DbId})
err := run(context.Background(), fsys, exclude, pgconn.Config{Host: utils.DbId}, false)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
Expand Down
32 changes: 32 additions & 0 deletions apps/cli-go/pkg/fetcher/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"encoding/json"
"io"
"net/http"
"net/url"
"slices"
"strings"

"github.com/go-errors/errors"
)
Expand Down Expand Up @@ -92,6 +94,9 @@ func (s *Fetcher) Send(ctx context.Context, method, path string, reqBody any, re
// Sends request
resp, err := s.client.Do(req)
if err != nil {
if hint := localGatewayHint(s.server, err); len(hint) > 0 {
return nil, errors.Errorf("failed to execute http request: %w\n\n%s", err, hint)
}
return nil, errors.Errorf("failed to execute http request: %w", err)
}
if slices.Contains(s.status, resp.StatusCode) {
Expand All @@ -109,6 +114,33 @@ func (s *Fetcher) Send(ctx context.Context, method, path string, reqBody any, re
return resp, nil
}

func localGatewayHint(server string, err error) string {
if err == nil {
return ""
}
parsed, parseErr := url.Parse(server)
if parseErr != nil {
return ""
}
host := parsed.Hostname()
if host != "127.0.0.1" && host != "localhost" && host != "::1" {
return ""
}
message := err.Error()
if !strings.Contains(message, "malformed HTTP response") &&
!strings.Contains(message, "Client.Timeout exceeded while awaiting headers") &&
!strings.Contains(message, "context deadline exceeded") {
return ""
}
port := parsed.Port()
if len(port) == 0 {
return ""
}
return "The local Supabase API gateway did not return a valid HTTP response. " +
"Another process may be listening on the configured API port " + port + ". " +
"Check the port with `lsof -nP -iTCP:" + port + " -sTCP:LISTEN`, then stop the conflicting process or set a different `api.port` in supabase/config.toml."
}

func ParseJSON[T any](r io.ReadCloser) (T, error) {
defer r.Close()
var data T
Expand Down
37 changes: 37 additions & 0 deletions apps/cli-go/pkg/fetcher/http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package fetcher

import (
"context"
"net"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestSendSuggestsApiPortConflictForMalformedLocalResponse(t *testing.T) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
defer listener.Close()

done := make(chan struct{})
go func() {
defer close(done)
conn, err := listener.Accept()
if err != nil {
return
}
defer conn.Close()
_, _ = conn.Write([]byte(`{"type":"Tier1","version":"1.0"}`))
}()

api := NewFetcher("http://" + listener.Addr().String())
_, err = api.Send(context.Background(), "GET", "/storage/v1/bucket", nil)
require.Error(t, err)
assert.Contains(t, err.Error(), "malformed HTTP response")
assert.Contains(t, err.Error(), "Another process may be listening on the configured API port")
assert.Contains(t, err.Error(), "lsof -nP -iTCP:")
assert.Contains(t, err.Error(), "api.port")

<-done
}
Loading