243 lines
7.0 KiB
Go
243 lines
7.0 KiB
Go
package serve
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/api/types/network"
|
|
"github.com/docker/go-connections/nat"
|
|
"github.com/go-errors/errors"
|
|
"github.com/spf13/afero"
|
|
"github.com/spf13/viper"
|
|
"github.com/supabase/cli/internal/functions/deploy"
|
|
"github.com/supabase/cli/internal/secrets/set"
|
|
"github.com/supabase/cli/internal/utils"
|
|
"github.com/supabase/cli/internal/utils/flags"
|
|
)
|
|
|
|
type InspectMode string
|
|
|
|
const (
|
|
InspectModeRun InspectMode = "run"
|
|
InspectModeBrk InspectMode = "brk"
|
|
InspectModeWait InspectMode = "wait"
|
|
)
|
|
|
|
func (mode InspectMode) toFlag() string {
|
|
switch mode {
|
|
case InspectModeBrk:
|
|
return "inspect-brk"
|
|
case InspectModeWait:
|
|
return "inspect-wait"
|
|
case InspectModeRun:
|
|
fallthrough
|
|
default:
|
|
return "inspect"
|
|
}
|
|
}
|
|
|
|
type RuntimeOption struct {
|
|
InspectMode *InspectMode
|
|
InspectMain bool
|
|
}
|
|
|
|
func (i *RuntimeOption) toArgs() []string {
|
|
flags := []string{}
|
|
if i.InspectMode != nil {
|
|
flags = append(flags, fmt.Sprintf("--%s=0.0.0.0:%d", i.InspectMode.toFlag(), dockerRuntimeInspectorPort))
|
|
if i.InspectMain {
|
|
flags = append(flags, "--inspect-main")
|
|
}
|
|
}
|
|
return flags
|
|
}
|
|
|
|
const (
|
|
dockerRuntimeServerPort = 8081
|
|
dockerRuntimeInspectorPort = 8083
|
|
)
|
|
|
|
var (
|
|
//go:embed templates/main.ts
|
|
mainFuncEmbed string
|
|
)
|
|
|
|
func Run(ctx context.Context, envFilePath string, noVerifyJWT *bool, importMapPath string, runtimeOption RuntimeOption, fsys afero.Fs) error {
|
|
// 1. Sanity checks.
|
|
if err := flags.LoadConfig(fsys); err != nil {
|
|
return err
|
|
}
|
|
if err := utils.AssertSupabaseDbIsRunning(); err != nil {
|
|
return err
|
|
}
|
|
// 2. Remove existing container.
|
|
_ = utils.Docker.ContainerRemove(ctx, utils.EdgeRuntimeId, container.RemoveOptions{
|
|
RemoveVolumes: true,
|
|
Force: true,
|
|
})
|
|
// Use network alias because Deno cannot resolve `_` in hostname
|
|
dbUrl := fmt.Sprintf("postgresql://postgres:postgres@%s:5432/postgres", utils.DbAliases[0])
|
|
// 3. Serve and log to console
|
|
fmt.Fprintln(os.Stderr, "Setting up Edge Functions runtime...")
|
|
if err := ServeFunctions(ctx, envFilePath, noVerifyJWT, importMapPath, dbUrl, runtimeOption, fsys); err != nil {
|
|
return err
|
|
}
|
|
if err := utils.DockerStreamLogs(ctx, utils.EdgeRuntimeId, os.Stdout, os.Stderr); err != nil {
|
|
return err
|
|
}
|
|
fmt.Println("Stopped serving " + utils.Bold(utils.FunctionsDir))
|
|
return nil
|
|
}
|
|
|
|
func ServeFunctions(ctx context.Context, envFilePath string, noVerifyJWT *bool, importMapPath string, dbUrl string, runtimeOption RuntimeOption, fsys afero.Fs) error {
|
|
// 1. Load default values
|
|
if envFilePath == "" {
|
|
if f, err := fsys.Stat(utils.FallbackEnvFilePath); err == nil && !f.IsDir() {
|
|
envFilePath = utils.FallbackEnvFilePath
|
|
}
|
|
} else if !filepath.IsAbs(envFilePath) {
|
|
envFilePath = filepath.Join(utils.CurrentDirAbs, envFilePath)
|
|
}
|
|
// 2. Parse user defined env
|
|
env, err := parseEnvFile(envFilePath, fsys)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
env = append(env,
|
|
fmt.Sprintf("SUPABASE_URL=http://%s:8000", utils.KongAliases[0]),
|
|
"SUPABASE_ANON_KEY="+utils.Config.Auth.AnonKey,
|
|
"SUPABASE_SERVICE_ROLE_KEY="+utils.Config.Auth.ServiceRoleKey,
|
|
"SUPABASE_DB_URL="+dbUrl,
|
|
"SUPABASE_INTERNAL_JWT_SECRET="+utils.Config.Auth.JwtSecret,
|
|
fmt.Sprintf("SUPABASE_INTERNAL_HOST_PORT=%d", utils.Config.Api.Port),
|
|
)
|
|
if viper.GetBool("DEBUG") {
|
|
env = append(env, "SUPABASE_INTERNAL_DEBUG=true")
|
|
}
|
|
if runtimeOption.InspectMode != nil {
|
|
env = append(env, "SUPABASE_INTERNAL_WALLCLOCK_LIMIT_SEC=0")
|
|
}
|
|
// 3. Parse custom import map
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return errors.Errorf("failed to get working directory: %w", err)
|
|
}
|
|
binds, functionsConfigString, err := populatePerFunctionConfigs(cwd, importMapPath, noVerifyJWT, fsys)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
env = append(env, "SUPABASE_INTERNAL_FUNCTIONS_CONFIG="+functionsConfigString)
|
|
// 4. Parse entrypoint script
|
|
cmd := append([]string{
|
|
"edge-runtime",
|
|
"start",
|
|
"--main-service=/root",
|
|
fmt.Sprintf("--port=%d", dockerRuntimeServerPort),
|
|
fmt.Sprintf("--policy=%s", utils.Config.EdgeRuntime.Policy),
|
|
}, runtimeOption.toArgs()...)
|
|
if viper.GetBool("DEBUG") {
|
|
cmd = append(cmd, "--verbose")
|
|
}
|
|
cmdString := strings.Join(cmd, " ")
|
|
entrypoint := []string{"sh", "-c", `cat <<'EOF' > /root/index.ts && ` + cmdString + `
|
|
` + mainFuncEmbed + `
|
|
EOF
|
|
`}
|
|
// 5. Parse exposed ports
|
|
dockerRuntimePort := nat.Port(fmt.Sprintf("%d/tcp", dockerRuntimeServerPort))
|
|
exposedPorts := nat.PortSet{dockerRuntimePort: struct{}{}}
|
|
portBindings := nat.PortMap{}
|
|
if runtimeOption.InspectMode != nil {
|
|
dockerInspectorPort := nat.Port(fmt.Sprintf("%d/tcp", dockerRuntimeInspectorPort))
|
|
exposedPorts[dockerInspectorPort] = struct{}{}
|
|
portBindings[dockerInspectorPort] = []nat.PortBinding{{
|
|
HostPort: strconv.FormatUint(uint64(utils.Config.EdgeRuntime.InspectorPort), 10),
|
|
}}
|
|
}
|
|
// 6. Start container
|
|
_, err = utils.DockerStart(
|
|
ctx,
|
|
container.Config{
|
|
Image: utils.Config.EdgeRuntime.Image,
|
|
Env: env,
|
|
Entrypoint: entrypoint,
|
|
ExposedPorts: exposedPorts,
|
|
WorkingDir: utils.ToDockerPath(cwd),
|
|
// No tcp health check because edge runtime logs them as client connection error
|
|
},
|
|
container.HostConfig{
|
|
Binds: binds,
|
|
PortBindings: portBindings,
|
|
},
|
|
network.NetworkingConfig{
|
|
EndpointsConfig: map[string]*network.EndpointSettings{
|
|
utils.NetId: {
|
|
Aliases: utils.EdgeRuntimeAliases,
|
|
},
|
|
},
|
|
},
|
|
utils.EdgeRuntimeId,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func parseEnvFile(envFilePath string, fsys afero.Fs) ([]string, error) {
|
|
env := []string{}
|
|
if len(envFilePath) == 0 {
|
|
return env, nil
|
|
}
|
|
envMap, err := set.ParseEnvFile(envFilePath, fsys)
|
|
if err != nil {
|
|
return env, err
|
|
}
|
|
for name, value := range envMap {
|
|
if strings.HasPrefix(name, "SUPABASE_") {
|
|
fmt.Fprintln(os.Stderr, "Env name cannot start with SUPABASE_, skipping: "+name)
|
|
continue
|
|
}
|
|
env = append(env, name+"="+value)
|
|
}
|
|
return env, nil
|
|
}
|
|
|
|
func populatePerFunctionConfigs(cwd, importMapPath string, noVerifyJWT *bool, fsys afero.Fs) ([]string, string, error) {
|
|
slugs, err := deploy.GetFunctionSlugs(fsys)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
functionsConfig, err := deploy.GetFunctionConfig(slugs, importMapPath, noVerifyJWT, fsys)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
binds := []string{}
|
|
for slug, fc := range functionsConfig {
|
|
if !fc.Enabled {
|
|
fmt.Fprintln(os.Stderr, "Skipped serving Function:", slug)
|
|
continue
|
|
}
|
|
modules, err := deploy.GetBindMounts(cwd, utils.FunctionsDir, "", fc.Entrypoint, fc.ImportMap, fsys)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
binds = append(binds, modules...)
|
|
fc.ImportMap = utils.ToDockerPath(fc.ImportMap)
|
|
fc.Entrypoint = utils.ToDockerPath(fc.Entrypoint)
|
|
functionsConfig[slug] = fc
|
|
for i, val := range fc.StaticFiles {
|
|
fc.StaticFiles[i] = utils.ToDockerPath(val)
|
|
}
|
|
}
|
|
functionsConfigBytes, err := json.Marshal(functionsConfig)
|
|
if err != nil {
|
|
return nil, "", errors.Errorf("failed to marshal config json: %w", err)
|
|
}
|
|
return utils.RemoveDuplicates(binds), string(functionsConfigBytes), nil
|
|
}
|