Skip to content

Commit

Permalink
feat: support deploying functions without docker
Browse files Browse the repository at this point in the history
  • Loading branch information
sweatybridge committed Feb 10, 2025
1 parent 0d1f040 commit e23d212
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 42 deletions.
18 changes: 12 additions & 6 deletions cmd/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ var (
},
}

useApi bool
useDocker bool
noVerifyJWT = new(bool)
useLegacyBundle bool
importMapPath string
Expand All @@ -65,7 +67,7 @@ var (
if !cmd.Flags().Changed("no-verify-jwt") {
noVerifyJWT = nil
}
return deploy.Run(cmd.Context(), args, flags.ProjectRef, noVerifyJWT, importMapPath, afero.NewOsFs())
return deploy.Run(cmd.Context(), args, useDocker, noVerifyJWT, importMapPath, afero.NewOsFs())
},
}

Expand Down Expand Up @@ -123,11 +125,15 @@ var (
func init() {
functionsListCmd.Flags().StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
functionsDeleteCmd.Flags().StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
functionsDeployCmd.Flags().BoolVar(noVerifyJWT, "no-verify-jwt", false, "Disable JWT verification for the Function.")
functionsDeployCmd.Flags().StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
functionsDeployCmd.Flags().BoolVar(&useLegacyBundle, "legacy-bundle", false, "Use legacy bundling mechanism.")
functionsDeployCmd.Flags().StringVar(&importMapPath, "import-map", "", "Path to import map file.")
cobra.CheckErr(functionsDeployCmd.Flags().MarkHidden("legacy-bundle"))
deployFlags := functionsDeployCmd.Flags()
deployFlags.BoolVar(&useApi, "use-api", true, "Use Management API to bundle functions.")
deployFlags.BoolVar(&useDocker, "use-docker", false, "Use Docker to bundle functions.")
functionsDeployCmd.MarkFlagsMutuallyExclusive("use-api", "use-docker")
deployFlags.BoolVar(noVerifyJWT, "no-verify-jwt", false, "Disable JWT verification for the Function.")
deployFlags.StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.")
deployFlags.BoolVar(&useLegacyBundle, "legacy-bundle", false, "Use legacy bundling mechanism.")
deployFlags.StringVar(&importMapPath, "import-map", "", "Path to import map file.")
cobra.CheckErr(deployFlags.MarkHidden("legacy-bundle"))
functionsServeCmd.Flags().BoolVar(noVerifyJWT, "no-verify-jwt", false, "Disable JWT verification for the Function.")
functionsServeCmd.Flags().StringVar(&envFilePath, "env-file", "", "Path to an env file to be populated to the Function environment.")
functionsServeCmd.Flags().StringVar(&importMapPath, "import-map", "", "Path to import map file.")
Expand Down
14 changes: 9 additions & 5 deletions internal/functions/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"github.com/supabase/cli/pkg/function"
)

func Run(ctx context.Context, slugs []string, projectRef string, noVerifyJWT *bool, importMapPath string, fsys afero.Fs) error {
func Run(ctx context.Context, slugs []string, useDocker bool, noVerifyJWT *bool, importMapPath string, fsys afero.Fs) error {
// Load function config and project id
if err := flags.LoadConfig(fsys); err != nil {
return err
Expand All @@ -37,12 +37,16 @@ func Run(ctx context.Context, slugs []string, projectRef string, noVerifyJWT *bo
if err != nil {
return err
}
api := function.NewEdgeRuntimeAPI(projectRef, *utils.GetSupabase(), NewDockerBundler(fsys))
if err := api.UpsertFunctions(ctx, functionConfig); err != nil {
if useDocker {
api := function.NewEdgeRuntimeAPI(flags.ProjectRef, *utils.GetSupabase(), NewDockerBundler(fsys))
if err := api.UpsertFunctions(ctx, functionConfig); err != nil {
return err
}
} else if err := deploy(ctx, functionConfig, fsys); err != nil {
return err
}
fmt.Printf("Deployed Functions on project %s: %s\n", utils.Aqua(projectRef), strings.Join(slugs, ", "))
url := fmt.Sprintf("%s/project/%v/functions", utils.GetSupabaseDashboardURL(), projectRef)
fmt.Printf("Deployed Functions on project %s: %s\n", utils.Aqua(flags.ProjectRef), strings.Join(slugs, ", "))
url := fmt.Sprintf("%s/project/%v/functions", utils.GetSupabaseDashboardURL(), flags.ProjectRef)
fmt.Println("You can inspect your deployment in the Dashboard: " + url)
return nil
}
Expand Down
46 changes: 19 additions & 27 deletions internal/functions/deploy/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ 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"
"github.com/supabase/cli/pkg/cast"
"github.com/supabase/cli/pkg/config"
)

func TestDeployCommand(t *testing.T) {
flags.ProjectRef = apitest.RandomProjectRef()
const slug = "test-func"
const containerId = "test-container"
imageUrl := utils.GetRegistryImageUrl(utils.Config.EdgeRuntime.Image)
Expand All @@ -29,8 +31,6 @@ func TestDeployCommand(t *testing.T) {
// Setup in-memory fs
fsys := afero.NewMemMapFs()
require.NoError(t, utils.WriteConfig(fsys, false))
// Setup valid project ref
project := apitest.RandomProjectRef()
// Setup valid access token
token := apitest.RandomAccessToken(t)
t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
Expand All @@ -40,13 +40,13 @@ func TestDeployCommand(t *testing.T) {
// Setup mock api
defer gock.OffAll()
gock.New(utils.DefaultApiHost).
Get("/v1/projects/" + project + "/functions").
Get("/v1/projects/" + flags.ProjectRef + "/functions").
Reply(http.StatusOK).
JSON([]api.FunctionResponse{})
for i := range functions {
// Do not match slug to avoid flakey tests
gock.New(utils.DefaultApiHost).
Post("/v1/projects/" + project + "/functions").
Post("/v1/projects/" + flags.ProjectRef + "/functions").
Reply(http.StatusCreated).
JSON(api.FunctionResponse{Id: fmt.Sprintf("%d", i)})
// Setup mock docker
Expand All @@ -61,7 +61,7 @@ func TestDeployCommand(t *testing.T) {
}
// Run test
noVerifyJWT := true
err = Run(context.Background(), functions, project, &noVerifyJWT, "", fsys)
err = Run(context.Background(), functions, true, &noVerifyJWT, "", fsys)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
Expand All @@ -88,8 +88,6 @@ import_map = "./import_map.json"
require.NoError(t, afero.WriteFile(fsys, entrypointPath, []byte{}, 0644))
ignorePath := filepath.Join(utils.FunctionsDir, "_ignore", "index.ts")
require.NoError(t, afero.WriteFile(fsys, ignorePath, []byte{}, 0644))
// Setup valid project ref
project := apitest.RandomProjectRef()
// Setup valid access token
token := apitest.RandomAccessToken(t)
t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
Expand All @@ -99,11 +97,11 @@ import_map = "./import_map.json"
// Setup mock api
defer gock.OffAll()
gock.New(utils.DefaultApiHost).
Get("/v1/projects/" + project + "/functions").
Get("/v1/projects/" + flags.ProjectRef + "/functions").
Reply(http.StatusOK).
JSON([]api.FunctionResponse{})
gock.New(utils.DefaultApiHost).
Post("/v1/projects/"+project+"/functions").
Post("/v1/projects/"+flags.ProjectRef+"/functions").
MatchParam("slug", slug).
ParamPresent("import_map_path").
Reply(http.StatusCreated).
Expand All @@ -116,7 +114,7 @@ import_map = "./import_map.json"
outputDir := filepath.Join(utils.TempDir, fmt.Sprintf(".output_%s", slug))
require.NoError(t, afero.WriteFile(fsys, filepath.Join(outputDir, "output.eszip"), []byte(""), 0644))
// Run test
err = Run(context.Background(), nil, project, nil, "", fsys)
err = Run(context.Background(), nil, true, nil, "", fsys)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
Expand All @@ -142,8 +140,6 @@ import_map = "./import_map.json"
// Setup function entrypoints
require.NoError(t, afero.WriteFile(fsys, filepath.Join(utils.FunctionsDir, "enabled-func", "index.ts"), []byte{}, 0644))
require.NoError(t, afero.WriteFile(fsys, filepath.Join(utils.FunctionsDir, "disabled-func", "index.ts"), []byte{}, 0644))
// Setup valid project ref
project := apitest.RandomProjectRef()
// Setup valid access token
token := apitest.RandomAccessToken(t)
t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
Expand All @@ -153,11 +149,11 @@ import_map = "./import_map.json"
// Setup mock api
defer gock.OffAll()
gock.New(utils.DefaultApiHost).
Get("/v1/projects/" + project + "/functions").
Get("/v1/projects/" + flags.ProjectRef + "/functions").
Reply(http.StatusOK).
JSON([]api.FunctionResponse{})
gock.New(utils.DefaultApiHost).
Post("/v1/projects/"+project+"/functions").
Post("/v1/projects/"+flags.ProjectRef+"/functions").
MatchParam("slug", "enabled-func").
Reply(http.StatusCreated).
JSON(api.FunctionResponse{Id: "1"})
Expand All @@ -168,7 +164,7 @@ import_map = "./import_map.json"
outputDir := filepath.Join(utils.TempDir, ".output_enabled-func")
require.NoError(t, afero.WriteFile(fsys, filepath.Join(outputDir, "output.eszip"), []byte(""), 0644))
// Run test
err = Run(context.Background(), nil, project, nil, "", fsys)
err = Run(context.Background(), nil, true, nil, "", fsys)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
Expand All @@ -179,7 +175,7 @@ import_map = "./import_map.json"
fsys := afero.NewMemMapFs()
require.NoError(t, utils.WriteConfig(fsys, false))
// Run test
err := Run(context.Background(), []string{"_invalid"}, "", nil, "", fsys)
err := Run(context.Background(), []string{"_invalid"}, true, nil, "", fsys)
// Check error
assert.ErrorContains(t, err, "Invalid Function name.")
})
Expand All @@ -189,7 +185,7 @@ import_map = "./import_map.json"
fsys := afero.NewMemMapFs()
require.NoError(t, utils.WriteConfig(fsys, false))
// Run test
err := Run(context.Background(), nil, "", nil, "", fsys)
err := Run(context.Background(), nil, true, nil, "", fsys)
// Check error
assert.ErrorContains(t, err, "No Functions specified or found in supabase/functions")
})
Expand All @@ -207,8 +203,6 @@ verify_jwt = false
`)
require.NoError(t, err)
require.NoError(t, f.Close())
// Setup valid project ref
project := apitest.RandomProjectRef()
// Setup valid access token
token := apitest.RandomAccessToken(t)
t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
Expand All @@ -218,11 +212,11 @@ verify_jwt = false
// Setup mock api
defer gock.OffAll()
gock.New(utils.DefaultApiHost).
Get("/v1/projects/" + project + "/functions").
Get("/v1/projects/" + flags.ProjectRef + "/functions").
Reply(http.StatusOK).
JSON([]api.FunctionResponse{})
gock.New(utils.DefaultApiHost).
Post("/v1/projects/"+project+"/functions").
Post("/v1/projects/"+flags.ProjectRef+"/functions").
MatchParam("verify_jwt", "false").
Reply(http.StatusCreated).
JSON(api.FunctionResponse{Id: "1"})
Expand All @@ -234,7 +228,7 @@ verify_jwt = false
outputDir := filepath.Join(utils.TempDir, fmt.Sprintf(".output_%s", slug))
require.NoError(t, afero.WriteFile(fsys, filepath.Join(outputDir, "output.eszip"), []byte(""), 0644))
// Run test
assert.NoError(t, Run(context.Background(), []string{slug}, project, nil, "", fsys))
assert.NoError(t, Run(context.Background(), []string{slug}, true, nil, "", fsys))
// Validate api
assert.Empty(t, apitest.ListUnmatchedRequests())
})
Expand All @@ -252,8 +246,6 @@ verify_jwt = false
`)
require.NoError(t, err)
require.NoError(t, f.Close())
// Setup valid project ref
project := apitest.RandomProjectRef()
// Setup valid access token
token := apitest.RandomAccessToken(t)
t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
Expand All @@ -263,11 +255,11 @@ verify_jwt = false
// Setup mock api
defer gock.OffAll()
gock.New(utils.DefaultApiHost).
Get("/v1/projects/" + project + "/functions").
Get("/v1/projects/" + flags.ProjectRef + "/functions").
Reply(http.StatusOK).
JSON([]api.FunctionResponse{})
gock.New(utils.DefaultApiHost).
Post("/v1/projects/"+project+"/functions").
Post("/v1/projects/"+flags.ProjectRef+"/functions").
MatchParam("verify_jwt", "true").
Reply(http.StatusCreated).
JSON(api.FunctionResponse{Id: "1"})
Expand All @@ -280,7 +272,7 @@ verify_jwt = false
require.NoError(t, afero.WriteFile(fsys, filepath.Join(outputDir, "output.eszip"), []byte(""), 0644))
// Run test
noVerifyJwt := false
assert.NoError(t, Run(context.Background(), []string{slug}, project, &noVerifyJwt, "", fsys))
assert.NoError(t, Run(context.Background(), []string{slug}, true, &noVerifyJwt, "", fsys))
// Validate api
assert.Empty(t, apitest.ListUnmatchedRequests())
})
Expand Down
Loading

0 comments on commit e23d212

Please sign in to comment.