diff --git a/api/overlay.yaml b/api/overlay.yaml index 58fef786db..b028333bd9 100644 --- a/api/overlay.yaml +++ b/api/overlay.yaml @@ -35,3 +35,7 @@ actions: - target: $.components.schemas.*.properties.private_jwk.discriminator description: Replaces discriminated union with concrete type remove: true +- target: $.paths.*.*.parameters[?(@.name=='branch_id_or_ref')] + update: + schema: + type: string diff --git a/cmd/branches.go b/cmd/branches.go index c949c8858a..4faa54f67a 100644 --- a/cmd/branches.go +++ b/cmd/branches.go @@ -249,7 +249,7 @@ func promptBranchId(ctx context.Context, fsys afero.Fs) error { for i, branch := range branches { items[i] = utils.PromptItem{ Summary: branch.Name, - Details: branch.Id.String(), + Details: branch.ProjectRef, } } title := "Select a branch:" diff --git a/cmd/gen.go b/cmd/gen.go index d4c90d3509..75377505b5 100644 --- a/cmd/gen.go +++ b/cmd/gen.go @@ -7,8 +7,10 @@ import ( env "github.com/Netflix/go-env" "github.com/go-errors/errors" + "github.com/golang-jwt/jwt/v5" "github.com/spf13/afero" "github.com/spf13/cobra" + "github.com/supabase/cli/internal/gen/bearerjwt" "github.com/supabase/cli/internal/gen/keys" "github.com/supabase/cli/internal/gen/signingkeys" "github.com/supabase/cli/internal/gen/types" @@ -118,6 +120,23 @@ Supported algorithms: return signingkeys.Run(cmd.Context(), algorithm.Value, appendKeys, afero.NewOsFs()) }, } + + claims config.CustomClaims + expiry time.Time + validFor time.Duration + + genJWTCmd = &cobra.Command{ + Use: "bearer-jwt", + Short: "Generate a Bearer Auth JWT for accessing Data API", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + if expiry.IsZero() { + expiry = time.Now().Add(validFor) + } + claims.ExpiresAt = jwt.NewNumericDate(expiry) + return bearerjwt.Run(cmd.Context(), claims, os.Stdout, afero.NewOsFs()) + }, + } ) func init() { @@ -145,5 +164,14 @@ func init() { signingKeyFlags.Var(&algorithm, "algorithm", "Algorithm for signing key generation.") signingKeyFlags.BoolVar(&appendKeys, "append", false, "Append new key to existing keys file instead of overwriting.") genCmd.AddCommand(genSigningKeyCmd) + tokenFlags := genJWTCmd.Flags() + tokenFlags.StringVar(&claims.Role, "role", "", "Postgres role to use.") + tokenFlags.StringVar(&claims.Subject, "sub", "", "User ID to impersonate.") + genJWTCmd.Flag("sub").DefValue = "anonymous" + tokenFlags.TimeVar(&expiry, "exp", time.Time{}, []string{time.RFC3339}, "Expiry timestamp for this token.") + tokenFlags.DurationVar(&validFor, "valid-for", time.Minute*30, "Validity duration for this token.") + genJWTCmd.MarkFlagsMutuallyExclusive("exp", "valid-for") + cobra.CheckErr(genJWTCmd.MarkFlagRequired("role")) + genCmd.AddCommand(genJWTCmd) rootCmd.AddCommand(genCmd) } diff --git a/go.mod b/go.mod index c73881e102..b1aae5c68f 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/andybalholm/brotli v1.2.0 github.com/cenkalti/backoff/v4 v4.3.0 github.com/charmbracelet/bubbles v0.21.0 - github.com/charmbracelet/bubbletea v1.3.7 + github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/glamour v0.10.0 github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 github.com/containerd/errdefs v1.0.0 @@ -17,11 +17,12 @@ require ( github.com/docker/docker v28.4.0+incompatible github.com/docker/go-connections v0.6.0 github.com/fsnotify/fsnotify v1.9.0 - github.com/getsentry/sentry-go v0.35.1 + github.com/getsentry/sentry-go v0.35.3 github.com/go-errors/errors v1.5.1 github.com/go-git/go-git/v5 v5.16.2 github.com/go-playground/validator/v10 v10.27.0 github.com/go-xmlfmt/xmlfmt v1.1.3 + github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/go-github/v62 v62.0.0 github.com/google/go-querystring v1.1.0 github.com/google/uuid v1.6.0 @@ -35,10 +36,10 @@ require ( github.com/muesli/reflow v0.3.0 github.com/oapi-codegen/nullable v1.1.0 github.com/slack-go/slack v0.17.3 - github.com/spf13/afero v1.14.0 + github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 - github.com/spf13/viper v1.20.1 + github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 github.com/stripe/pg-schema-diff v1.0.2 github.com/supabase/cli/pkg v1.0.0 @@ -47,10 +48,10 @@ require ( github.com/zalando/go-keyring v0.2.6 go.opentelemetry.io/otel v1.38.0 golang.org/x/mod v0.28.0 - golang.org/x/net v0.43.0 + golang.org/x/net v0.44.0 golang.org/x/oauth2 v0.31.0 golang.org/x/term v0.35.0 - google.golang.org/grpc v1.75.0 + google.golang.org/grpc v1.75.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -156,7 +157,6 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gofrs/flock v0.12.1 // indirect - github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect @@ -274,7 +274,7 @@ require ( github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/ryancurrah/gomodguard v1.4.1 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect @@ -286,10 +286,10 @@ require ( github.com/sivchari/containedctx v1.0.3 // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/sonatard/noctx v0.1.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect - github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/cast v1.10.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -336,11 +336,12 @@ require ( go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.41.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.42.0 // indirect golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect - golang.org/x/sync v0.16.0 // indirect + golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.28.0 // indirect + golang.org/x/text v0.29.0 // indirect golang.org/x/tools v0.36.0 // indirect golang.org/x/tools/go/expect v0.1.1-deprecated // indirect golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect diff --git a/go.sum b/go.sum index 473f705a9c..718c449087 100644 --- a/go.sum +++ b/go.sum @@ -159,8 +159,8 @@ github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iy github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= -github.com/charmbracelet/bubbletea v1.3.7 h1:FNaEEFEenOEPnZsY9MI64thl2c84MI66+1QaQbxGOl4= -github.com/charmbracelet/bubbletea v1.3.7/go.mod h1:PEOcbQCNzJ2BYUd484kHPO5g3kLO28IffOdFeI2EWus= +github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= +github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY= @@ -300,8 +300,8 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE= github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= -github.com/getsentry/sentry-go v0.35.1 h1:iopow6UVLE2aXu46xKVIs8Z9D/YZkJrHkgozrxa+tOQ= -github.com/getsentry/sentry-go v0.35.1/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE= +github.com/getsentry/sentry-go v0.35.3 h1:u5IJaEqZyPdWqe/hKlBKBBnMTSxB/HenCqF3QLabeds= +github.com/getsentry/sentry-go v0.35.3/go.mod h1:mdL49ixwT2yi57k5eh7mpnDyPybixPzlzEJFu0Z76QA= github.com/ghostiam/protogetter v0.3.15 h1:1KF5sXel0HE48zh1/vn0Loiw25A9ApyseLzQuif1mLY= github.com/ghostiam/protogetter v0.3.15/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= @@ -881,8 +881,8 @@ github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= @@ -920,17 +920,17 @@ github.com/slack-go/slack v0.17.3 h1:zV5qO3Q+WJAQ/XwbGfNFrRMaJ5T/naqaonyPV/1TP4g github.com/slack-go/slack v0.17.3/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk= github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/speakeasy-api/openapi-overlay v0.9.0 h1:Wrz6NO02cNlLzx1fB093lBlYxSI54VRhy1aSutx0PQg= github.com/speakeasy-api/openapi-overlay v0.9.0/go.mod h1:f5FloQrHA7MsxYg9djzMD5h6dxrHjVVByWKh7an8TRc= -github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= -github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= @@ -941,8 +941,8 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= -github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= -github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= @@ -1099,6 +1099,8 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -1119,8 +1121,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1215,8 +1217,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1240,8 +1242,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1336,8 +1338,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1487,8 +1489,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= -google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/internal/backups/list/list.go b/internal/backups/list/list.go index bcff285ff8..b4b16dd1c8 100644 --- a/internal/backups/list/list.go +++ b/internal/backups/list/list.go @@ -6,7 +6,6 @@ import ( "os" "github.com/go-errors/errors" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/api" @@ -20,8 +19,7 @@ func Run(ctx context.Context) error { } else if resp.JSON200 == nil { return errors.Errorf("unexpected list backup status %d: %s", resp.StatusCode(), string(resp.Body)) } - switch utils.OutputFormat.Value { - case utils.OutputPretty: + if utils.OutputFormat.Value == utils.OutputPretty { if len(resp.JSON200.Backups) > 0 { return listLogicalBackups(*resp.JSON200) } @@ -36,9 +34,7 @@ func Run(ctx context.Context) error { cast.Val(resp.JSON200.PhysicalBackupData.EarliestPhysicalBackupDateUnix, 0), cast.Val(resp.JSON200.PhysicalBackupData.LatestPhysicalBackupDateUnix, 0), ) - return list.RenderTable(table) - case utils.OutputEnv: - return errors.Errorf("--output env flag is not supported") + return utils.RenderTable(table) } return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, *resp.JSON200) } @@ -65,5 +61,5 @@ func listLogicalBackups(resp api.V1BackupsResponse) error { utils.FormatTimestamp(backup.InsertedAt), ) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/branches/create/create.go b/internal/branches/create/create.go index 6fce6be013..6d5b9c7a15 100644 --- a/internal/branches/create/create.go +++ b/internal/branches/create/create.go @@ -3,9 +3,11 @@ package create import ( "context" "fmt" + "os" "github.com/go-errors/errors" "github.com/spf13/afero" + "github.com/supabase/cli/internal/branches/list" "github.com/supabase/cli/internal/gen/keys" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" @@ -28,12 +30,14 @@ func Run(ctx context.Context, body api.CreateBranchBody, fsys afero.Fs) error { resp, err := utils.GetSupabase().V1CreateABranchWithResponse(ctx, flags.ProjectRef, body) if err != nil { return errors.Errorf("failed to create preview branch: %w", err) + } else if resp.JSON201 == nil { + return errors.Errorf("unexpected create branch status %d: %s", resp.StatusCode(), string(resp.Body)) } - if resp.JSON201 == nil { - return errors.New("Unexpected error creating preview branch: " + string(resp.Body)) + fmt.Println("Created preview branch:") + if utils.OutputFormat.Value == utils.OutputPretty { + table := list.ToMarkdown([]api.BranchResponse{*resp.JSON201}) + return utils.RenderTable(table) } - - fmt.Println("Created preview branch:", resp.JSON201.Id) - return nil + return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, *resp.JSON201) } diff --git a/internal/branches/create/create_test.go b/internal/branches/create/create_test.go index 2fd2a09048..60dbfc5279 100644 --- a/internal/branches/create/create_test.go +++ b/internal/branches/create/create_test.go @@ -75,6 +75,6 @@ func TestCreateCommand(t *testing.T) { Region: cast.Ptr("sin"), }, fsys) // Check error - assert.ErrorContains(t, err, "Unexpected error creating preview branch:") + assert.ErrorContains(t, err, "unexpected create branch status 503:") }) } diff --git a/internal/branches/delete/delete.go b/internal/branches/delete/delete.go index 4eae6d5e50..95c3099818 100644 --- a/internal/branches/delete/delete.go +++ b/internal/branches/delete/delete.go @@ -6,22 +6,22 @@ import ( "net/http" "github.com/go-errors/errors" - "github.com/supabase/cli/internal/branches/get" + "github.com/supabase/cli/internal/branches/pause" "github.com/supabase/cli/internal/utils" ) func Run(ctx context.Context, branchId string) error { - parsed, err := get.GetBranchID(ctx, branchId) + projectRef, err := pause.GetBranchProjectRef(ctx, branchId) if err != nil { return err } - resp, err := utils.GetSupabase().V1DeleteABranchWithResponse(ctx, parsed) + resp, err := utils.GetSupabase().V1DeleteABranchWithResponse(ctx, projectRef) if err != nil { return errors.Errorf("failed to delete preview branch: %w", err) } if resp.StatusCode() != http.StatusOK { return errors.New("Unexpected error deleting preview branch: " + string(resp.Body)) } - fmt.Println("Deleted preview branch:", branchId) + fmt.Println("Deleted preview branch:", projectRef) return nil } diff --git a/internal/branches/get/get.go b/internal/branches/get/get.go index 9d8f4a2ee3..ab2f174b11 100644 --- a/internal/branches/get/get.go +++ b/internal/branches/get/get.go @@ -9,7 +9,6 @@ import ( "github.com/google/uuid" "github.com/jackc/pgconn" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/projects/apiKeys" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" @@ -49,16 +48,21 @@ func Run(ctx context.Context, branchId string, fsys afero.Fs) error { detail.Status, ) - return list.RenderTable(table) + return utils.RenderTable(table) } func getBranchDetail(ctx context.Context, branchId string) (api.BranchDetailResponse, error) { var result api.BranchDetailResponse - parsed, err := GetBranchID(ctx, branchId) - if err != nil { - return result, err + if err := uuid.Validate(branchId); err != nil && !utils.ProjectRefPattern.Match([]byte(branchId)) { + resp, err := utils.GetSupabase().V1GetABranchWithResponse(ctx, flags.ProjectRef, branchId) + if err != nil { + return result, errors.Errorf("failed to find branch: %w", err) + } else if resp.JSON200 == nil { + return result, errors.Errorf("unexpected find branch status %d: %s", resp.StatusCode(), string(resp.Body)) + } + branchId = resp.JSON200.ProjectRef } - resp, err := utils.GetSupabase().V1GetABranchConfigWithResponse(ctx, parsed) + resp, err := utils.GetSupabase().V1GetABranchConfigWithResponse(ctx, branchId) if err != nil { return result, errors.Errorf("failed to get branch: %w", err) } else if resp.JSON200 == nil { @@ -77,20 +81,6 @@ func getBranchDetail(ctx context.Context, branchId string) (api.BranchDetailResp return *resp.JSON200, nil } -func GetBranchID(ctx context.Context, branchId string) (uuid.UUID, error) { - parsed, err := uuid.Parse(branchId) - if err == nil { - return parsed, nil - } - resp, err := utils.GetSupabase().V1GetABranchWithResponse(ctx, flags.ProjectRef, branchId) - if err != nil { - return parsed, errors.Errorf("failed to get branch: %w", err) - } else if resp.JSON200 == nil { - return parsed, errors.Errorf("unexpected get branch status %d: %s", resp.StatusCode(), string(resp.Body)) - } - return resp.JSON200.Id, nil -} - func getPoolerConfig(ctx context.Context, ref string) (api.SupavisorConfigResponse, error) { var result api.SupavisorConfigResponse resp, err := utils.GetSupabase().V1GetPoolerConfigWithResponse(ctx, ref) diff --git a/internal/branches/list/list.go b/internal/branches/list/list.go index 0d2335162d..4c6d3757a9 100644 --- a/internal/branches/list/list.go +++ b/internal/branches/list/list.go @@ -8,7 +8,6 @@ import ( "github.com/go-errors/errors" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/api" @@ -22,27 +21,8 @@ func Run(ctx context.Context, fsys afero.Fs) error { switch utils.OutputFormat.Value { case utils.OutputPretty: - table := `|ID|BRANCH PROJECT ID|NAME|DEFAULT|GIT BRANCH|STATUS|CREATED AT (UTC)|UPDATED AT (UTC)| -|-|-|-|-|-|-|-|-| -` - for _, branch := range branches { - gitBranch := " " - if branch.GitBranch != nil { - gitBranch = *branch.GitBranch - } - table += fmt.Sprintf( - "|`%s`|`%s`|`%s`|`%t`|`%s`|`%s`|`%s`|`%s`|\n", - branch.Id, - branch.ProjectRef, - strings.ReplaceAll(branch.Name, "|", "\\|"), - branch.IsDefault, - strings.ReplaceAll(gitBranch, "|", "\\|"), - branch.Status, - utils.FormatTime(branch.CreatedAt), - utils.FormatTime(branch.UpdatedAt), - ) - } - return list.RenderTable(table) + table := ToMarkdown(branches) + return utils.RenderTable(table) case utils.OutputToml: return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, struct { Branches []api.BranchResponse `toml:"branches"` @@ -56,6 +36,30 @@ func Run(ctx context.Context, fsys afero.Fs) error { return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, branches) } +func ToMarkdown(branches []api.BranchResponse) string { + table := `|ID|NAME|DEFAULT|GIT BRANCH|WITH DATA|STATUS|CREATED AT (UTC)|UPDATED AT (UTC)| +|-|-|-|-|-|-|-|-| +` + for _, branch := range branches { + gitBranch := " " + if branch.GitBranch != nil { + gitBranch = *branch.GitBranch + } + table += fmt.Sprintf( + "|`%s`|`%s`|`%t`|`%s`|`%t`|`%s`|`%s`|`%s`|\n", + branch.ProjectRef, + strings.ReplaceAll(branch.Name, "|", "\\|"), + branch.IsDefault, + strings.ReplaceAll(gitBranch, "|", "\\|"), + branch.WithData, + branch.Status, + utils.FormatTime(branch.CreatedAt), + utils.FormatTime(branch.UpdatedAt), + ) + } + return table +} + type BranchFilter func(api.BranchResponse) bool func ListBranch(ctx context.Context, ref string, filter ...BranchFilter) ([]api.BranchResponse, error) { diff --git a/internal/branches/pause/pause.go b/internal/branches/pause/pause.go index 02d22bf3db..95f98bf0d9 100644 --- a/internal/branches/pause/pause.go +++ b/internal/branches/pause/pause.go @@ -24,8 +24,11 @@ func Run(ctx context.Context, branchId string) error { } func GetBranchProjectRef(ctx context.Context, branchId string) (string, error) { - if parsed, err := uuid.Parse(branchId); err == nil { - resp, err := utils.GetSupabase().V1GetABranchConfigWithResponse(ctx, parsed) + if utils.ProjectRefPattern.Match([]byte(branchId)) { + return branchId, nil + } + if err := uuid.Validate(branchId); err == nil { + resp, err := utils.GetSupabase().V1GetABranchConfigWithResponse(ctx, branchId) if err != nil { return "", errors.Errorf("failed to get branch: %w", err) } else if resp.JSON200 == nil { @@ -35,9 +38,9 @@ func GetBranchProjectRef(ctx context.Context, branchId string) (string, error) { } resp, err := utils.GetSupabase().V1GetABranchWithResponse(ctx, flags.ProjectRef, branchId) if err != nil { - return "", errors.Errorf("failed to get branch: %w", err) + return "", errors.Errorf("failed to find branch: %w", err) } else if resp.JSON200 == nil { - return "", errors.Errorf("unexpected get branch status %d: %s", resp.StatusCode(), string(resp.Body)) + return "", errors.Errorf("unexpected find branch status %d: %s", resp.StatusCode(), string(resp.Body)) } return resp.JSON200.ProjectRef, nil } diff --git a/internal/branches/update/update.go b/internal/branches/update/update.go index 91261b174f..20ec6f82d0 100644 --- a/internal/branches/update/update.go +++ b/internal/branches/update/update.go @@ -3,26 +3,31 @@ package update import ( "context" "fmt" + "os" "github.com/go-errors/errors" "github.com/spf13/afero" - "github.com/supabase/cli/internal/branches/get" + "github.com/supabase/cli/internal/branches/list" + "github.com/supabase/cli/internal/branches/pause" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/api" ) func Run(ctx context.Context, branchId string, body api.UpdateBranchBody, fsys afero.Fs) error { - parsed, err := get.GetBranchID(ctx, branchId) + projectRef, err := pause.GetBranchProjectRef(ctx, branchId) if err != nil { return err } - resp, err := utils.GetSupabase().V1UpdateABranchConfigWithResponse(ctx, parsed, body) + resp, err := utils.GetSupabase().V1UpdateABranchConfigWithResponse(ctx, projectRef, body) if err != nil { return errors.Errorf("failed to update preview branch: %w", err) + } else if resp.JSON200 == nil { + return errors.Errorf("unexpected update branch status %d: %s", resp.StatusCode(), string(resp.Body)) } - if resp.JSON200 == nil { - return errors.New("Unexpected error updating preview branch: " + string(resp.Body)) + fmt.Println("Updated preview branch:") + if utils.OutputFormat.Value == utils.OutputPretty { + table := list.ToMarkdown([]api.BranchResponse{*resp.JSON200}) + return utils.RenderTable(table) } - fmt.Println("Updated preview branch:", resp.JSON200.Id) - return nil + return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, *resp.JSON200) } diff --git a/internal/config/push/push.go b/internal/config/push/push.go index d2dadddc3c..1a23400608 100644 --- a/internal/config/push/push.go +++ b/internal/config/push/push.go @@ -5,6 +5,7 @@ import ( "fmt" "os" + "github.com/go-errors/errors" "github.com/spf13/afero" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" @@ -21,10 +22,17 @@ func Run(ctx context.Context, ref string, fsys afero.Fs) error { // Use base config when no remote is declared remote.ProjectId = ref } + cost, err := getCostMatrix(ctx, ref) + if err != nil { + return err + } fmt.Fprintln(os.Stderr, "Pushing config to project:", remote.ProjectId) console := utils.NewConsole() keep := func(name string) bool { title := fmt.Sprintf("Do you want to push %s config to remote?", name) + if item, exists := cost[name]; exists { + title = fmt.Sprintf("Enabling %s will cost you %s. Keep it enabled?", item.Name, item.Price) + } shouldPush, err := console.PromptYesNo(ctx, title, true) if err != nil { fmt.Fprintln(os.Stderr, err) @@ -33,3 +41,27 @@ func Run(ctx context.Context, ref string, fsys afero.Fs) error { } return client.UpdateRemoteConfig(ctx, remote, keep) } + +type CostItem struct { + Name string + Price string +} + +func getCostMatrix(ctx context.Context, projectRef string) (map[string]CostItem, error) { + resp, err := utils.GetSupabase().V1ListProjectAddonsWithResponse(ctx, projectRef) + if err != nil { + return nil, errors.Errorf("failed to list addons: %w", err) + } else if resp.JSON200 == nil { + return nil, errors.Errorf("unexpected list addons status %d: %s", resp.StatusCode(), string(resp.Body)) + } + costMatrix := make(map[string]CostItem, len(resp.JSON200.AvailableAddons)) + for _, addon := range resp.JSON200.AvailableAddons { + if len(addon.Variants) == 1 { + costMatrix[string(addon.Type)] = CostItem{ + Name: addon.Variants[0].Name, + Price: addon.Variants[0].Price.Description, + } + } + } + return costMatrix, nil +} diff --git a/internal/config/push/push_test.go b/internal/config/push/push_test.go new file mode 100644 index 0000000000..5af8fb273f --- /dev/null +++ b/internal/config/push/push_test.go @@ -0,0 +1,114 @@ +package push + +import ( + "context" + "errors" + "net/http" + "testing" + + "github.com/h2non/gock" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/supabase/cli/internal/testing/apitest" + "github.com/supabase/cli/internal/utils" +) + +func TestPushConfig(t *testing.T) { + project := apitest.RandomProjectRef() + // Setup valid access token + token := apitest.RandomAccessToken(t) + t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) + + t.Run("throws error on malformed config", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + require.NoError(t, utils.WriteFile(utils.ConfigPath, []byte("malformed"), fsys)) + // Run test + err := Run(context.Background(), "", fsys) + // Check error + assert.ErrorContains(t, err, "toml: expected = after a key, but the document ends there") + }) + + t.Run("throws error on service unavailable", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + // Setup mock api + defer gock.OffAll() + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/billing/addons"). + Reply(http.StatusServiceUnavailable) + // Run test + err := Run(context.Background(), project, fsys) + // Check error + assert.ErrorContains(t, err, "unexpected list addons status 503:") + }) +} + +func TestCostMatrix(t *testing.T) { + project := apitest.RandomProjectRef() + // Setup valid access token + token := apitest.RandomAccessToken(t) + t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) + + t.Run("fetches cost matrix", func(t *testing.T) { + // Setup mock api + defer gock.OffAll() + gock.New(utils.DefaultApiHost). + Get("/v1/projects/"+project+"/billing/addons"). + Reply(http.StatusOK). + SetHeader("Content-Type", "application/json"). + BodyString(`{ + "available_addons":[{ + "name": "Advanced MFA - Phone", + "type": "auth_mfa_phone", + "variants": [{ + "id": "auth_mfa_phone_default", + "name": "Advanced MFA - Phone", + "price": { + "amount": 0.1027, + "description": "$75/month, then $10/month", + "interval": "hourly", + "type": "usage" + } + }] + }, { + "name": "Advanced MFA - WebAuthn", + "type": "auth_mfa_web_authn", + "variants": [{ + "id": "auth_mfa_web_authn_default", + "name": "Advanced MFA - WebAuthn", + "price": { + "amount": 0.1027, + "description": "$75/month, then $10/month", + "interval": "hourly", + "type": "usage" + } + }] + }] + }`) + // Run test + cost, err := getCostMatrix(context.Background(), project) + // Check error + assert.NoError(t, err) + require.Len(t, cost, 2) + assert.Equal(t, "Advanced MFA - Phone", cost["auth_mfa_phone"].Name) + assert.Equal(t, "$75/month, then $10/month", cost["auth_mfa_phone"].Price) + assert.Equal(t, "Advanced MFA - WebAuthn", cost["auth_mfa_web_authn"].Name) + assert.Equal(t, "$75/month, then $10/month", cost["auth_mfa_web_authn"].Price) + }) + + t.Run("throws error on network error", func(t *testing.T) { + errNetwork := errors.New("network error") + // Setup mock api + defer gock.OffAll() + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + project + "/billing/addons"). + ReplyError(errNetwork) + // Run test + cost, err := getCostMatrix(context.Background(), project) + // Check error + assert.ErrorIs(t, err, errNetwork) + assert.Nil(t, cost) + }) +} diff --git a/internal/db/diff/diff.go b/internal/db/diff/diff.go index e34dfbcb4f..af19faa5aa 100644 --- a/internal/db/diff/diff.go +++ b/internal/db/diff/diff.go @@ -28,7 +28,7 @@ import ( "github.com/supabase/cli/pkg/parser" ) -type DiffFunc func(context.Context, string, string, []string, ...func(*pgx.ConnConfig)) (string, error) +type DiffFunc func(context.Context, pgconn.Config, pgconn.Config, []string, ...func(*pgx.ConnConfig)) (string, error) func Run(ctx context.Context, schema []string, file string, config pgconn.Config, differ DiffFunc, fsys afero.Fs, options ...func(*pgx.ConnConfig)) (err error) { out, err := DiffDatabase(ctx, schema, config, os.Stderr, fsys, differ, options...) @@ -173,9 +173,7 @@ func DiffDatabase(ctx context.Context, schema []string, config pgconn.Config, w } else { fmt.Fprintln(w, "Diffing schemas...") } - source := utils.ToPostgresURL(shadowConfig) - target := utils.ToPostgresURL(config) - return differ(ctx, source, target, schema, options...) + return differ(ctx, shadowConfig, config, schema, options...) } func migrateBaseDatabase(ctx context.Context, config pgconn.Config, migrations []string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { diff --git a/internal/db/diff/diff_test.go b/internal/db/diff/diff_test.go index 9a708a1c46..213329e754 100644 --- a/internal/db/diff/diff_test.go +++ b/internal/db/diff/diff_test.go @@ -192,6 +192,7 @@ func TestDiffDatabase(t *testing.T) { utils.InitialSchemaPg14Sql = "create schema private" t.Run("throws error on failure to create shadow", func(t *testing.T) { + errNetwork := errors.New("network error") // Setup in-memory fs fsys := afero.NewMemMapFs() // Setup mock docker @@ -199,12 +200,12 @@ func TestDiffDatabase(t *testing.T) { defer gock.OffAll() gock.New(utils.Docker.DaemonHost()). Get("/v" + utils.Docker.ClientVersion() + "/images/" + utils.GetRegistryImageUrl(utils.Config.Db.Image) + "/json"). - ReplyError(errors.New("network error")) + ReplyError(errNetwork) // Run test diff, err := DiffDatabase(context.Background(), []string{"public"}, dbConfig, io.Discard, fsys, DiffSchemaMigra) // Check error assert.Empty(t, diff) - assert.ErrorContains(t, err, "network error") + assert.ErrorIs(t, err, errNetwork) assert.Empty(t, apitest.ListUnmatchedRequests()) }) diff --git a/internal/db/diff/migra.go b/internal/db/diff/migra.go index 34b7d579e7..f79c4ae167 100644 --- a/internal/db/diff/migra.go +++ b/internal/db/diff/migra.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "github.com/go-errors/errors" + "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" "github.com/spf13/viper" "github.com/supabase/cli/internal/gen/types" @@ -57,7 +58,7 @@ var ( ) // Diffs local database schema against shadow, dumps output to stdout. -func DiffSchemaMigraBash(ctx context.Context, source, target string, schema []string, options ...func(*pgx.ConnConfig)) (string, error) { +func DiffSchemaMigraBash(ctx context.Context, source, target pgconn.Config, schema []string, options ...func(*pgx.ConnConfig)) (string, error) { // Load all user defined schemas if len(schema) == 0 { var err error @@ -65,7 +66,10 @@ func DiffSchemaMigraBash(ctx context.Context, source, target string, schema []st return "", err } } - env := []string{"SOURCE=" + source, "TARGET=" + target} + env := []string{ + "SOURCE=" + utils.ToPostgresURL(source), + "TARGET=" + utils.ToPostgresURL(target), + } // Passing in script string means command line args must be set manually, ie. "$@" args := "set -- " + strings.Join(schema, " ") + ";" cmd := []string{"/bin/sh", "-c", args + diffSchemaScript} @@ -90,8 +94,8 @@ func DiffSchemaMigraBash(ctx context.Context, source, target string, schema []st return out.String(), nil } -func loadSchema(ctx context.Context, dbURL string, options ...func(*pgx.ConnConfig)) ([]string, error) { - conn, err := utils.ConnectByUrl(ctx, dbURL, options...) +func loadSchema(ctx context.Context, config pgconn.Config, options ...func(*pgx.ConnConfig)) ([]string, error) { + conn, err := utils.ConnectByConfig(ctx, config, options...) if err != nil { return nil, err } @@ -100,9 +104,12 @@ func loadSchema(ctx context.Context, dbURL string, options ...func(*pgx.ConnConf return migration.ListUserSchemas(ctx, conn) } -func DiffSchemaMigra(ctx context.Context, source, target string, schema []string, options ...func(*pgx.ConnConfig)) (string, error) { - env := []string{"SOURCE=" + source, "TARGET=" + target} - if ca, err := types.GetRootCA(ctx, target, options...); err != nil { +func DiffSchemaMigra(ctx context.Context, source, target pgconn.Config, schema []string, options ...func(*pgx.ConnConfig)) (string, error) { + env := []string{ + "SOURCE=" + utils.ToPostgresURL(source), + "TARGET=" + utils.ToPostgresURL(target), + } + if ca, err := types.GetRootCA(ctx, utils.ToPostgresURL(target), options...); err != nil { return "", err } else if len(ca) > 0 { env = append(env, "SSL_CA="+ca) diff --git a/internal/db/diff/pgschema.go b/internal/db/diff/pgschema.go index 36387990c7..5126ca96ed 100644 --- a/internal/db/diff/pgschema.go +++ b/internal/db/diff/pgschema.go @@ -7,17 +7,19 @@ import ( "strings" "github.com/go-errors/errors" + "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" pgschema "github.com/stripe/pg-schema-diff/pkg/diff" + "github.com/supabase/cli/internal/utils" ) -func DiffPgSchema(ctx context.Context, source, target string, schema []string, _ ...func(*pgx.ConnConfig)) (string, error) { - dbSrc, err := sql.Open("pgx", source) +func DiffPgSchema(ctx context.Context, source, target pgconn.Config, schema []string, _ ...func(*pgx.ConnConfig)) (string, error) { + dbSrc, err := sql.Open("pgx", utils.ToPostgresURL(source)) if err != nil { return "", errors.Errorf("failed to open source database: %w", err) } defer dbSrc.Close() - dbDst, err := sql.Open("pgx", target) + dbDst, err := sql.Open("pgx", utils.ToPostgresURL(target)) if err != nil { return "", errors.Errorf("failed to open target database: %w", err) } diff --git a/internal/db/diff/templates/migra.ts b/internal/db/diff/templates/migra.ts index bf915cded4..788a77d9d0 100644 --- a/internal/db/diff/templates/migra.ts +++ b/internal/db/diff/templates/migra.ts @@ -27,7 +27,14 @@ try { ignore_extension_versions: true, }); m.set_safety(false); - m.add_all_changes(true); + if (managedSchemas.includes(schema)) { + m.add(m.changes.triggers({ drops_only: true })); + m.add(m.changes.rlspolicies({ drops_only: true })); + m.add(m.changes.rlspolicies({ creations_only: true })); + m.add(m.changes.triggers({ creations_only: true })); + } else { + m.add_all_changes(true); + } sql += m.sql; } if (includedSchemas.length === 0) { diff --git a/internal/db/pull/pull.go b/internal/db/pull/pull.go index 3363ce7a63..feffe2ce26 100644 --- a/internal/db/pull/pull.go +++ b/internal/db/pull/pull.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "strconv" + "strings" "github.com/go-errors/errors" "github.com/jackc/pgconn" @@ -15,6 +16,7 @@ import ( "github.com/spf13/afero" "github.com/supabase/cli/internal/db/diff" "github.com/supabase/cli/internal/db/dump" + "github.com/supabase/cli/internal/db/start" "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/migration/new" "github.com/supabase/cli/internal/migration/repair" @@ -23,15 +25,10 @@ import ( ) var ( - errMissing = errors.New("No migrations found") - errInSync = errors.New("No schema changes found") - errConflict = errors.Errorf("The remote database's migration history does not match local files in %s directory.", utils.MigrationsDir) - suggestExtraPull = fmt.Sprintf( - "The %s and %s schemas are excluded. Run %s again to diff them.", - utils.Bold("auth"), - utils.Bold("storage"), - utils.Aqua("supabase db pull --schema auth,storage"), - ) + errMissing = errors.New("No migrations found") + errInSync = errors.New("No schema changes found") + errConflict = errors.Errorf("The remote database's migration history does not match local files in %s directory.", utils.MigrationsDir) + managedSchemas = []string{"auth", "storage", "realtime"} ) func Run(ctx context.Context, schema []string, config pgconn.Config, name string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { @@ -61,28 +58,28 @@ func run(ctx context.Context, schema []string, path string, conn *pgx.Conn, fsys config := conn.Config().Config // 1. Assert `supabase/migrations` and `schema_migrations` are in sync. if err := assertRemoteInSync(ctx, conn, fsys); errors.Is(err, errMissing) { - // Not passing down schemas to avoid pulling in managed schemas - if err = dumpRemoteSchema(ctx, path, config, fsys); err == nil { - utils.CmdSuggestion = suggestExtraPull + // Ignore schemas flag when working on the initial pull + if err = dumpRemoteSchema(ctx, path, config, fsys); err != nil { + return err + } + // Pull changes in managed schemas automatically + if err = diffRemoteSchema(ctx, managedSchemas, path, config, fsys); errors.Is(err, errInSync) { + err = nil } return err } else if err != nil { return err } - // 2. Fetch remote schema changes - defaultSchema := len(schema) == 0 - if defaultSchema { + // 2. Fetch user defined schemas + if len(schema) == 0 { var err error - schema, err = migration.ListUserSchemas(ctx, conn) - if err != nil { + if schema, err = migration.ListUserSchemas(ctx, conn); err != nil { return err } + schema = append(schema, managedSchemas...) } - err := diffRemoteSchema(ctx, schema, path, config, fsys) - if defaultSchema && (err == nil || errors.Is(err, errInSync)) { - utils.CmdSuggestion = suggestExtraPull - } - return err + // 3. Fetch remote schema changes + return diffUserSchemas(ctx, schema, path, config, fsys) } func dumpRemoteSchema(ctx context.Context, path string, config pgconn.Config, fsys afero.Fs) error { @@ -108,6 +105,65 @@ func diffRemoteSchema(ctx context.Context, schema []string, path string, config if len(output) == 0 { return errors.New(errInSync) } + // Append to existing migration file since we run this after dump + f, err := fsys.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + return errors.Errorf("failed to open migration file: %w", err) + } + defer f.Close() + if _, err := f.WriteString(output); err != nil { + return errors.Errorf("failed to write migration file: %w", err) + } + return nil +} + +func diffUserSchemas(ctx context.Context, schema []string, path string, config pgconn.Config, fsys afero.Fs) error { + var managed, user []string + for _, s := range schema { + if utils.SliceContains(managedSchemas, s) { + managed = append(managed, s) + } else { + user = append(user, s) + } + } + fmt.Fprintln(os.Stderr, "Creating shadow database...") + shadow, err := diff.CreateShadowDatabase(ctx, utils.Config.Db.ShadowPort) + if err != nil { + return err + } + defer utils.DockerRemove(shadow) + if err := start.WaitForHealthyService(ctx, start.HealthTimeout, shadow); err != nil { + return err + } + if err := diff.MigrateShadowDatabase(ctx, shadow, fsys); err != nil { + return err + } + shadowConfig := pgconn.Config{ + Host: utils.Config.Hostname, + Port: utils.Config.Db.ShadowPort, + User: "postgres", + Password: utils.Config.Db.Password, + Database: "postgres", + } + // Diff managed and user defined schemas separately + var output string + if len(user) > 0 { + fmt.Fprintln(os.Stderr, "Diffing schemas:", strings.Join(user, ",")) + if output, err = diff.DiffSchemaMigraBash(ctx, shadowConfig, config, user); err != nil { + return err + } + } + if len(managed) > 0 { + fmt.Fprintln(os.Stderr, "Diffing schemas:", strings.Join(managed, ",")) + if result, err := diff.DiffSchemaMigra(ctx, shadowConfig, config, managed); err != nil { + return err + } else { + output += result + } + } + if len(output) == 0 { + return errors.New(errInSync) + } if err := utils.WriteFile(path, []byte(output), fsys); err != nil { return errors.Errorf("failed to write dump file: %w", err) } diff --git a/internal/db/pull/pull_test.go b/internal/db/pull/pull_test.go index 33e1d849f5..8161f51e1b 100644 --- a/internal/db/pull/pull_test.go +++ b/internal/db/pull/pull_test.go @@ -57,6 +57,7 @@ func TestPullCommand(t *testing.T) { func TestPullSchema(t *testing.T) { t.Run("dumps remote schema", func(t *testing.T) { + errNetwork := errors.New("network error") // Setup in-memory fs fsys := afero.NewMemMapFs() // Setup mock docker @@ -64,6 +65,9 @@ func TestPullSchema(t *testing.T) { defer gock.OffAll() apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Db.Image), "test-db") require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-db", "test")) + gock.New(utils.Docker.DaemonHost()). + Get("/v" + utils.Docker.ClientVersion() + "/images/" + utils.GetRegistryImageUrl(utils.Config.Db.Image) + "/json"). + ReplyError(errNetwork) // Setup mock postgres conn := pgtest.NewConn() defer conn.Close(t) @@ -72,7 +76,7 @@ func TestPullSchema(t *testing.T) { // Run test err := run(context.Background(), nil, "0_test.sql", conn.MockClient(t), fsys) // Check error - assert.NoError(t, err) + assert.ErrorIs(t, err, errNetwork) assert.Empty(t, apitest.ListUnmatchedRequests()) contents, err := afero.ReadFile(fsys, "0_test.sql") assert.NoError(t, err) diff --git a/internal/db/start/start.go b/internal/db/start/start.go index 7febc92943..322406557b 100644 --- a/internal/db/start/start.go +++ b/internal/db/start/start.go @@ -317,6 +317,7 @@ func initStorageJob(host string) utils.DockerJob { // TODO: https://github.com/supabase/storage-api/issues/55 "REGION=stub", "GLOBAL_S3_BUCKET=stub", + "SIGNED_UPLOAD_URL_EXPIRATION_TIME=7200", }, Cmd: []string{"node", "dist/scripts/migrate-call.js"}, } diff --git a/internal/functions/list/list.go b/internal/functions/list/list.go index d0d2d9ecc8..b174365a3f 100644 --- a/internal/functions/list/list.go +++ b/internal/functions/list/list.go @@ -3,39 +3,50 @@ package list import ( "context" "fmt" + "os" "time" "github.com/go-errors/errors" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/pkg/api" ) func Run(ctx context.Context, projectRef string, fsys afero.Fs) error { resp, err := utils.GetSupabase().V1ListAllFunctionsWithResponse(ctx, projectRef) if err != nil { return errors.Errorf("failed to list functions: %w", err) + } else if resp.JSON200 == nil { + return errors.Errorf("unexpected list functions status %d: %s", resp.StatusCode(), string(resp.Body)) } - if resp.JSON200 == nil { - return errors.New("Unexpected error retrieving functions: " + string(resp.Body)) - } - - table := `|ID|NAME|SLUG|STATUS|VERSION|UPDATED_AT (UTC)| + switch utils.OutputFormat.Value { + case utils.OutputPretty: + table := `|ID|NAME|SLUG|STATUS|VERSION|UPDATED_AT (UTC)| |-|-|-|-|-|-| ` - for _, function := range *resp.JSON200 { - t := time.UnixMilli(function.UpdatedAt) - table += fmt.Sprintf( - "|`%s`|`%s`|`%s`|`%s`|`%d`|`%s`|\n", - function.Id, - function.Name, - function.Slug, - function.Status, - function.Version, - t.UTC().Format("2006-01-02 15:04:05"), - ) + for _, function := range *resp.JSON200 { + t := time.UnixMilli(function.UpdatedAt) + table += fmt.Sprintf( + "|`%s`|`%s`|`%s`|`%s`|`%d`|`%s`|\n", + function.Id, + function.Name, + function.Slug, + function.Status, + function.Version, + t.UTC().Format("2006-01-02 15:04:05"), + ) + } + return utils.RenderTable(table) + case utils.OutputToml: + return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, struct { + Functions []api.FunctionResponse `toml:"functions"` + }{ + Functions: *resp.JSON200, + }) + case utils.OutputEnv: + return errors.Errorf("--output env flag is not supported") } - return list.RenderTable(table) + return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, *resp.JSON200) } diff --git a/internal/functions/list/list_test.go b/internal/functions/list/list_test.go index bbc704f9d9..7b6197ff51 100644 --- a/internal/functions/list/list_test.go +++ b/internal/functions/list/list_test.go @@ -66,7 +66,7 @@ func TestFunctionsListCommand(t *testing.T) { // Run test err := Run(context.Background(), project, fsys) // Check error - assert.ErrorContains(t, err, "Unexpected error retrieving functions") + assert.ErrorContains(t, err, "unexpected list functions status 503:") }) t.Run("throws error on network error", func(t *testing.T) { diff --git a/internal/gen/bearerjwt/bearerjwt.go b/internal/gen/bearerjwt/bearerjwt.go new file mode 100644 index 0000000000..4c686f9918 --- /dev/null +++ b/internal/gen/bearerjwt/bearerjwt.go @@ -0,0 +1,42 @@ +package bearerjwt + +import ( + "context" + "fmt" + "io" + "os" + "strings" + + "github.com/go-errors/errors" + "github.com/spf13/afero" + "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/internal/utils/flags" + "github.com/supabase/cli/pkg/config" +) + +func Run(ctx context.Context, claims config.CustomClaims, w io.Writer, fsys afero.Fs) error { + if err := flags.LoadConfig(fsys); err != nil { + return err + } + // Set is_anonymous = true for authenticated role without explicit user ID + if strings.EqualFold(claims.Role, "authenticated") && len(claims.Subject) == 0 { + claims.IsAnon = true + } + // Use the first signing key that passes validation + for _, k := range utils.Config.Auth.SigningKeys { + fmt.Fprintln(os.Stderr, "Using signing key ID:", k.KeyID.String()) + if token, err := config.GenerateAsymmetricJWT(k, claims); err != nil { + fmt.Fprintln(os.Stderr, err) + } else { + fmt.Fprintln(w, token) + return nil + } + } + fmt.Fprintln(os.Stderr, "Using legacy JWT secret...") + token, err := claims.NewToken().SignedString([]byte(utils.Config.Auth.JwtSecret.Value)) + if err != nil { + return errors.Errorf("failed to generate auth token: %w", err) + } + fmt.Fprintln(w, token) + return nil +} diff --git a/internal/gen/bearerjwt/bearerjwt_test.go b/internal/gen/bearerjwt/bearerjwt_test.go new file mode 100644 index 0000000000..47b307932e --- /dev/null +++ b/internal/gen/bearerjwt/bearerjwt_test.go @@ -0,0 +1,98 @@ +package bearerjwt + +import ( + "bytes" + "context" + "crypto/ecdsa" + "crypto/elliptic" + _ "embed" + "encoding/json" + "testing" + + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/supabase/cli/internal/gen/signingkeys" + "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/pkg/config" +) + +func TestGenerateToken(t *testing.T) { + t.Run("mints custom JWT", func(t *testing.T) { + claims := config.CustomClaims{ + Role: "authenticated", + } + // Setup private key + privateKey, err := signingkeys.GeneratePrivateKey(config.AlgES256) + require.NoError(t, err) + // Setup public key for validation + publicKey := ecdsa.PublicKey{Curve: elliptic.P256()} + publicKey.X, err = config.NewBigIntFromBase64(privateKey.X) + require.NoError(t, err) + publicKey.Y, err = config.NewBigIntFromBase64(privateKey.Y) + require.NoError(t, err) + // Setup in-memory fs + fsys := afero.NewMemMapFs() + require.NoError(t, utils.WriteFile("supabase/config.toml", []byte(` + [auth] + signing_keys_path = "./keys.json" + `), fsys)) + testKey, err := json.Marshal([]config.JWK{*privateKey}) + require.NoError(t, err) + require.NoError(t, utils.WriteFile("supabase/keys.json", testKey, fsys)) + // Run test + var buf bytes.Buffer + err = Run(context.Background(), claims, &buf, fsys) + // Check error + assert.NoError(t, err) + token, err := jwt.NewParser().Parse(buf.String(), func(t *jwt.Token) (any, error) { + return &publicKey, nil + }) + assert.NoError(t, err) + assert.True(t, token.Valid) + assert.Equal(t, map[string]any{ + "alg": "ES256", + "kid": privateKey.KeyID.String(), + "typ": "JWT", + }, token.Header) + assert.Equal(t, jwt.MapClaims{ + "is_anonymous": true, + "role": "authenticated", + }, token.Claims) + }) + + t.Run("mints legacy JWT", func(t *testing.T) { + utils.Config.Auth.SigningKeysPath = "" + utils.Config.Auth.SigningKeys = nil + claims := config.CustomClaims{ + RegisteredClaims: jwt.RegisteredClaims{ + Subject: uuid.New().String(), + }, + Role: "authenticated", + } + // Setup in-memory fs + fsys := afero.NewMemMapFs() + // Run test + var buf bytes.Buffer + err := Run(context.Background(), claims, &buf, fsys) + // Check error + assert.NoError(t, err) + token, err := jwt.NewParser().Parse(buf.String(), func(t *jwt.Token) (any, error) { + return []byte(utils.Config.Auth.JwtSecret.Value), nil + }) + assert.NoError(t, err) + assert.True(t, token.Valid) + assert.Equal(t, map[string]any{ + "alg": "HS256", + "typ": "JWT", + }, token.Header) + assert.Equal(t, jwt.MapClaims{ + "exp": float64(1983812996), + "iss": "supabase-demo", + "role": "authenticated", + "sub": claims.Subject, + }, token.Claims) + }) +} diff --git a/internal/inspect/bloat/bloat.go b/internal/inspect/bloat/bloat.go index a339cd2053..eeac48ef11 100644 --- a/internal/inspect/bloat/bloat.go +++ b/internal/inspect/bloat/bloat.go @@ -10,7 +10,6 @@ import ( "github.com/jackc/pgx/v4" "github.com/spf13/afero" "github.com/supabase/cli/internal/db/reset" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/pgxv5" ) @@ -44,5 +43,5 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu for _, r := range result { table += fmt.Sprintf("|`%s`|`%s`|`%s`|`%s`|\n", r.Type, r.Name, r.Bloat, r.Waste) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/inspect/blocking/blocking.go b/internal/inspect/blocking/blocking.go index 37baed1f95..7e5c6dee2a 100644 --- a/internal/inspect/blocking/blocking.go +++ b/internal/inspect/blocking/blocking.go @@ -10,7 +10,6 @@ import ( "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/pgxv5" ) @@ -56,5 +55,5 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu blocked_statement = re.ReplaceAllString(blocked_statement, `\|`) table += fmt.Sprintf("|`%d`|`%s`|`%s`|`%d`|%s|`%s`|\n", r.Blocked_pid, blocking_statement, r.Blocking_duration, r.Blocking_pid, blocked_statement, r.Blocked_duration) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/inspect/calls/calls.go b/internal/inspect/calls/calls.go index 2fd0c8df85..325e01837b 100644 --- a/internal/inspect/calls/calls.go +++ b/internal/inspect/calls/calls.go @@ -10,7 +10,6 @@ import ( "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/pgxv5" ) @@ -52,5 +51,5 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu query = re.ReplaceAllString(query, `\|`) table += fmt.Sprintf("|`%s`|`%s`|`%s`|`%s`|`%s`|\n", query, r.Total_exec_time, r.Prop_exec_time, r.Ncalls, r.Sync_io_time) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/inspect/db_stats/db_stats.go b/internal/inspect/db_stats/db_stats.go index e9ca43f7e0..4607951b87 100644 --- a/internal/inspect/db_stats/db_stats.go +++ b/internal/inspect/db_stats/db_stats.go @@ -10,7 +10,6 @@ import ( "github.com/jackc/pgx/v4" "github.com/spf13/afero" "github.com/supabase/cli/internal/db/reset" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/pgxv5" ) @@ -48,5 +47,5 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu for _, r := range result { table += fmt.Sprintf("|`%s`|`%s`|`%s`|`%s`|`%s`|`%s`|`%s`|`%s`|`%s`|\n", config.Database, r.Database_size, r.Total_index_size, r.Total_table_size, r.Total_toast_size, r.Time_since_stats_reset, r.Index_hit_rate, r.Table_hit_rate, r.WAL_size) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/inspect/index_stats/index_stats.go b/internal/inspect/index_stats/index_stats.go index 8f781c688b..6db47c4bfe 100644 --- a/internal/inspect/index_stats/index_stats.go +++ b/internal/inspect/index_stats/index_stats.go @@ -10,7 +10,6 @@ import ( "github.com/jackc/pgx/v4" "github.com/spf13/afero" "github.com/supabase/cli/internal/db/reset" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/pgxv5" ) @@ -46,5 +45,5 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu for _, r := range result { table += fmt.Sprintf("|`%s`|`%s`|`%s`|`%d`|`%d`|`%t`|\n", r.Name, r.Size, r.Percent_used, r.Index_scans, r.Seq_scans, r.Unused) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/inspect/locks/locks.go b/internal/inspect/locks/locks.go index be5c34d3a8..09b8146839 100644 --- a/internal/inspect/locks/locks.go +++ b/internal/inspect/locks/locks.go @@ -10,7 +10,6 @@ import ( "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/pgxv5" ) @@ -53,5 +52,5 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu stmt = re.ReplaceAllString(stmt, `\|`) table += fmt.Sprintf("|`%d`|`%s`|`%s`|`%t`|%s|`%s`|\n", r.Pid, r.Relname, r.Transactionid, r.Granted, stmt, r.Age) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/inspect/long_running_queries/long_running_queries.go b/internal/inspect/long_running_queries/long_running_queries.go index acf4be4563..d8197939a9 100644 --- a/internal/inspect/long_running_queries/long_running_queries.go +++ b/internal/inspect/long_running_queries/long_running_queries.go @@ -9,7 +9,6 @@ import ( "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/pgxv5" ) @@ -42,5 +41,5 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu for _, r := range result { table += fmt.Sprintf("|`%d`|`%s`|`%s`|\n", r.Pid, r.Duration, r.Query) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/inspect/outliers/outliers.go b/internal/inspect/outliers/outliers.go index 06f015c18a..d38d6ce59a 100644 --- a/internal/inspect/outliers/outliers.go +++ b/internal/inspect/outliers/outliers.go @@ -10,7 +10,6 @@ import ( "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/pgxv5" ) @@ -50,5 +49,5 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu query = re.ReplaceAllString(query, `\|`) table += fmt.Sprintf("|`%s`|`%s`|`%s`|`%s`|`%s`|\n", query, r.Total_exec_time, r.Prop_exec_time, r.Ncalls, r.Sync_io_time) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/inspect/replication_slots/replication_slots.go b/internal/inspect/replication_slots/replication_slots.go index 927ad052ab..809503063d 100644 --- a/internal/inspect/replication_slots/replication_slots.go +++ b/internal/inspect/replication_slots/replication_slots.go @@ -9,7 +9,6 @@ import ( "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/pgxv5" ) @@ -44,5 +43,5 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu for _, r := range result { table += fmt.Sprintf("|`%s`|`%t`|`%s`|`%s`|`%s`|\n", r.Slot_name, r.Active, r.State, r.Replication_client_address, r.Replication_lag_gb) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/inspect/report.go b/internal/inspect/report.go index e69e792b14..162d0bb08f 100644 --- a/internal/inspect/report.go +++ b/internal/inspect/report.go @@ -18,7 +18,6 @@ import ( _ "github.com/mithrandie/csvq-driver" "github.com/spf13/afero" "github.com/supabase/cli/internal/db/reset" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" ) @@ -121,5 +120,5 @@ func printSummary(ctx context.Context, outDir string) error { } table += fmt.Sprintf("|`%s`|`%s`|`%s`|\n", r.Name, status, match.String) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/inspect/role_stats/role_stats.go b/internal/inspect/role_stats/role_stats.go index ee5e724f20..2724c9533e 100644 --- a/internal/inspect/role_stats/role_stats.go +++ b/internal/inspect/role_stats/role_stats.go @@ -9,7 +9,6 @@ import ( "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/pgxv5" ) @@ -44,5 +43,5 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu table += fmt.Sprintf("|`%s`|`%d`|`%d`|`%s`|\n", r.Role_name, r.Active_connections, r.Connection_limit, r.Custom_config) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/inspect/table_stats/table_stats.go b/internal/inspect/table_stats/table_stats.go index afc3b193be..539b483dee 100644 --- a/internal/inspect/table_stats/table_stats.go +++ b/internal/inspect/table_stats/table_stats.go @@ -10,7 +10,6 @@ import ( "github.com/jackc/pgx/v4" "github.com/spf13/afero" "github.com/supabase/cli/internal/db/reset" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/pgxv5" ) @@ -46,5 +45,5 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu for _, r := range result { table += fmt.Sprintf("|`%s`|`%s`|`%s`|`%s`|`%d`|`%d`|\n", r.Name, r.Table_size, r.Index_size, r.Total_size, r.Estimated_row_count, r.Seq_scans) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/inspect/unused_indexes/unused_indexes.go b/internal/inspect/unused_indexes/unused_indexes.go index bfbbf523a2..f18422ea4d 100644 --- a/internal/inspect/unused_indexes/unused_indexes.go +++ b/internal/inspect/unused_indexes/unused_indexes.go @@ -10,7 +10,6 @@ import ( "github.com/jackc/pgx/v4" "github.com/spf13/afero" "github.com/supabase/cli/internal/db/reset" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/pgxv5" ) @@ -44,5 +43,5 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu for _, r := range result { table += fmt.Sprintf("|`%s`|`%s`|`%s`|`%d`|\n", r.Name, r.Index, r.Index_size, r.Index_scans) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/inspect/vacuum_stats/vacuum_stats.go b/internal/inspect/vacuum_stats/vacuum_stats.go index 8b36698793..a848f22725 100644 --- a/internal/inspect/vacuum_stats/vacuum_stats.go +++ b/internal/inspect/vacuum_stats/vacuum_stats.go @@ -11,7 +11,6 @@ import ( "github.com/jackc/pgx/v4" "github.com/spf13/afero" "github.com/supabase/cli/internal/db/reset" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/pgxv5" ) @@ -49,5 +48,5 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu rowcount := strings.Replace(r.Rowcount, "-1", "No stats", 1) table += fmt.Sprintf("|`%s`|%s|%s|`%s`|`%s`|`%s`|\n", r.Name, r.Last_vacuum, r.Last_autovacuum, rowcount, r.Dead_rowcount, r.Expect_autovacuum) } - return list.RenderTable(table) + return utils.RenderTable(table) } diff --git a/internal/link/link.go b/internal/link/link.go index cffdf13398..5d1f41aa31 100644 --- a/internal/link/link.go +++ b/internal/link/link.go @@ -215,10 +215,7 @@ func linkDatabase(ctx context.Context, config pgconn.Config, fsys afero.Fs, opti func updatePostgresConfig(conn *pgx.Conn) { serverVersion := conn.PgConn().ParameterStatus("server_version") // Safe to assume that supported Postgres version is 10.0 <= n < 100.0 - majorDigits := len(serverVersion) - if majorDigits > 2 { - majorDigits = 2 - } + majorDigits := min(len(serverVersion), 2) // Treat error as unchanged if dbMajorVersion, err := strconv.ParseUint(serverVersion[:majorDigits], 10, 7); err == nil { utils.Config.Db.MajorVersion = uint(dbMajorVersion) diff --git a/internal/migration/list/list.go b/internal/migration/list/list.go index c10245d56b..ad4a062c06 100644 --- a/internal/migration/list/list.go +++ b/internal/migration/list/list.go @@ -6,9 +6,6 @@ import ( "math" "strconv" - "github.com/charmbracelet/glamour" - "github.com/charmbracelet/glamour/styles" - "github.com/go-errors/errors" "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" "github.com/spf13/afero" @@ -26,7 +23,7 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu return err } table := makeTable(remoteVersions, localVersions) - return RenderTable(table) + return utils.RenderTable(table) } func loadRemoteVersions(ctx context.Context, config pgconn.Config, options ...func(*pgx.ConnConfig)) ([]string, error) { @@ -72,22 +69,6 @@ func makeTable(remoteMigrations, localMigrations []string) string { return table } -func RenderTable(markdown string) error { - r, err := glamour.NewTermRenderer( - glamour.WithStandardStyle(styles.AsciiStyle), - glamour.WithWordWrap(-1), - ) - if err != nil { - return errors.Errorf("failed to initialise terminal renderer: %w", err) - } - out, err := r.Render(markdown) - if err != nil { - return errors.Errorf("failed to render markdown: %w", err) - } - fmt.Print(out) - return nil -} - func LoadLocalVersions(fsys afero.Fs) ([]string, error) { var versions []string filter := func(v string) bool { diff --git a/internal/orgs/create/create.go b/internal/orgs/create/create.go index ed6008ab3d..1d88630c18 100644 --- a/internal/orgs/create/create.go +++ b/internal/orgs/create/create.go @@ -3,8 +3,10 @@ package create import ( "context" "fmt" + "os" "github.com/go-errors/errors" + "github.com/supabase/cli/internal/orgs/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/api" ) @@ -13,12 +15,14 @@ func Run(ctx context.Context, name string) error { resp, err := utils.GetSupabase().V1CreateAnOrganizationWithResponse(ctx, api.V1CreateAnOrganizationJSONRequestBody{Name: name}) if err != nil { return errors.Errorf("failed to create organization: %w", err) - } - - if resp.JSON201 == nil { - return errors.New("Unexpected error creating organization: " + string(resp.Body)) + } else if resp.JSON201 == nil { + return errors.Errorf("unexpected create organization status %d: %s", resp.StatusCode(), string(resp.Body)) } fmt.Println("Created organization:", resp.JSON201.Id) - return nil + if utils.OutputFormat.Value == utils.OutputPretty { + table := list.ToMarkdown([]api.OrganizationResponseV1{*resp.JSON201}) + return utils.RenderTable(table) + } + return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, *resp.JSON201) } diff --git a/internal/orgs/list/list.go b/internal/orgs/list/list.go index abd6c08dfe..aedbb42367 100644 --- a/internal/orgs/list/list.go +++ b/internal/orgs/list/list.go @@ -3,29 +3,45 @@ package list import ( "context" "fmt" + "os" "strings" "github.com/go-errors/errors" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/pkg/api" ) func Run(ctx context.Context) error { resp, err := utils.GetSupabase().V1ListAllOrganizationsWithResponse(ctx) if err != nil { return errors.Errorf("failed to list organizations: %w", err) + } else if resp.JSON200 == nil { + return errors.Errorf("unexpected list organizations status %d: %s", resp.StatusCode(), string(resp.Body)) } - if resp.JSON200 == nil { - return errors.New("Unexpected error retrieving organizations: " + string(resp.Body)) + switch utils.OutputFormat.Value { + case utils.OutputPretty: + table := ToMarkdown(*resp.JSON200) + return utils.RenderTable(table) + case utils.OutputToml: + return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, struct { + Organizations []api.OrganizationResponseV1 `toml:"organizations"` + }{ + Organizations: *resp.JSON200, + }) + case utils.OutputEnv: + return errors.Errorf("--output env flag is not supported") } + return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, *resp.JSON200) +} + +func ToMarkdown(orgs []api.OrganizationResponseV1) string { table := `|ID|NAME| |-|-| ` - for _, org := range *resp.JSON200 { + for _, org := range orgs { table += fmt.Sprintf("|`%s`|`%s`|\n", org.Id, strings.ReplaceAll(org.Name, "|", "\\|")) } - - return list.RenderTable(table) + return table } diff --git a/internal/postgresConfig/get/get.go b/internal/postgresConfig/get/get.go index ad311b3969..01a749ce45 100644 --- a/internal/postgresConfig/get/get.go +++ b/internal/postgresConfig/get/get.go @@ -5,50 +5,49 @@ import ( "encoding/json" "fmt" "io" + "os" "strings" "github.com/go-errors/errors" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" ) func Run(ctx context.Context, projectRef string, fsys afero.Fs) error { // 1. get current config - { - config, err := GetCurrentPostgresConfig(ctx, projectRef) - if err != nil { - return err - } - err = PrintOutPostgresConfigOverrides(config) - if err != nil { - return err - } - return nil + config, err := GetCurrentPostgresConfig(ctx, projectRef) + if err != nil { + return err + } + err = PrintOutPostgresConfigOverrides(config) + if err != nil { + return err } + return nil } -func PrintOutPostgresConfigOverrides(config map[string]interface{}) error { +func PrintOutPostgresConfigOverrides(config map[string]any) error { + if utils.OutputFormat.Value != utils.OutputPretty { + return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, config) + } fmt.Println("- Custom Postgres Config -") markdownTable := []string{ "|Parameter|Value|\n|-|-|\n", } - for k, v := range config { markdownTable = append(markdownTable, fmt.Sprintf( "|`%s`|`%+v`|\n", k, v, )) } - - if err := list.RenderTable(strings.Join(markdownTable, "")); err != nil { + if err := utils.RenderTable(strings.Join(markdownTable, "")); err != nil { return err } fmt.Println("- End of Custom Postgres Config -") return nil } -func GetCurrentPostgresConfig(ctx context.Context, projectRef string) (map[string]interface{}, error) { +func GetCurrentPostgresConfig(ctx context.Context, projectRef string) (map[string]any, error) { resp, err := utils.GetSupabase().V1GetPostgresConfig(ctx, projectRef) if err != nil { return nil, errors.Errorf("failed to retrieve Postgres config overrides: %w", err) @@ -60,8 +59,7 @@ func GetCurrentPostgresConfig(ctx context.Context, projectRef string) (map[strin if err != nil { return nil, errors.Errorf("failed to read response body: %w", err) } - - var config map[string]interface{} + var config map[string]any err = json.Unmarshal(contents, &config) if err != nil { return nil, errors.Errorf("failed to unmarshal response body: %w. Contents were %s", err, contents) diff --git a/internal/projects/apiKeys/api_keys.go b/internal/projects/apiKeys/api_keys.go index 32a6d1cf05..0161273a42 100644 --- a/internal/projects/apiKeys/api_keys.go +++ b/internal/projects/apiKeys/api_keys.go @@ -9,7 +9,6 @@ import ( "github.com/go-errors/errors" "github.com/oapi-codegen/nullable" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/api" ) @@ -31,7 +30,7 @@ func Run(ctx context.Context, projectRef string, fsys afero.Fs) error { table += fmt.Sprintf("|`%s`|`%s`|\n", k, v) } - return list.RenderTable(table) + return utils.RenderTable(table) case utils.OutputToml, utils.OutputEnv: return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, ToEnv(keys)) } diff --git a/internal/projects/create/create.go b/internal/projects/create/create.go index 6d565bfcfe..0463aa610e 100644 --- a/internal/projects/create/create.go +++ b/internal/projects/create/create.go @@ -35,12 +35,21 @@ func Run(ctx context.Context, params api.V1CreateProjectBody, fsys afero.Fs) err } projectUrl := fmt.Sprintf("%s/project/%s", utils.GetSupabaseDashboardURL(), resp.JSON201.Id) - fmt.Fprintf(os.Stderr, "Created a new project %s at %s\n", utils.Aqua(resp.JSON201.Name), utils.Bold(projectUrl)) - switch utils.OutputFormat.Value { - case utils.OutputPretty, utils.OutputEnv: - return nil + fmt.Fprintf(os.Stderr, "Created a new project at %s\n", utils.Bold(projectUrl)) + if utils.OutputFormat.Value == utils.OutputPretty { + table := `|ORG ID|REFERENCE ID|NAME|REGION|CREATED AT (UTC)| +|-|-|-|-|-| +` + table += fmt.Sprintf( + "|`%s`|`%s`|`%s`|`%s`|`%s`|\n", + resp.JSON201.OrganizationId, + resp.JSON201.Id, + strings.ReplaceAll(resp.JSON201.Name, "|", "\\|"), + utils.FormatRegion(resp.JSON201.Region), + utils.FormatTimestamp(resp.JSON201.CreatedAt), + ) + return utils.RenderTable(table) } - return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, resp.JSON201) } diff --git a/internal/projects/list/list.go b/internal/projects/list/list.go index da788de687..ef201f0c58 100644 --- a/internal/projects/list/list.go +++ b/internal/projects/list/list.go @@ -8,7 +8,6 @@ import ( "github.com/go-errors/errors" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/api" @@ -57,7 +56,7 @@ func Run(ctx context.Context, fsys afero.Fs) error { utils.FormatTimestamp(project.CreatedAt), ) } - return list.RenderTable(table) + return utils.RenderTable(table) case utils.OutputToml: return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, struct { Projects []linkedProject `toml:"projects"` diff --git a/internal/secrets/list/list.go b/internal/secrets/list/list.go index e5b4d45388..731ba2daaf 100644 --- a/internal/secrets/list/list.go +++ b/internal/secrets/list/list.go @@ -3,12 +3,12 @@ package list import ( "context" "fmt" + "os" "sort" "strings" "github.com/go-errors/errors" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/api" ) @@ -19,23 +19,34 @@ func Run(ctx context.Context, projectRef string, fsys afero.Fs) error { return err } - table := `|NAME|DIGEST| + switch utils.OutputFormat.Value { + case utils.OutputPretty: + table := `|NAME|DIGEST| |-|-| ` - for _, secret := range secrets { - table += fmt.Sprintf("|`%s`|`%s`|\n", strings.ReplaceAll(secret.Name, "|", "\\|"), secret.Value) + for _, secret := range secrets { + table += fmt.Sprintf("|`%s`|`%s`|\n", strings.ReplaceAll(secret.Name, "|", "\\|"), secret.Value) + } + return utils.RenderTable(table) + case utils.OutputToml: + return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, struct { + Secrets []api.SecretResponse `toml:"secrets"` + }{ + Secrets: secrets, + }) + case utils.OutputEnv: + return errors.Errorf("--output env flag is not supported") } - return list.RenderTable(table) + return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, secrets) } func GetSecretDigests(ctx context.Context, projectRef string) ([]api.SecretResponse, error) { resp, err := utils.GetSupabase().V1ListAllSecretsWithResponse(ctx, projectRef) if err != nil { return nil, errors.Errorf("failed to list secrets: %w", err) - } - if resp.JSON200 == nil { - return nil, errors.New("Unexpected error retrieving project secrets: " + string(resp.Body)) + } else if resp.JSON200 == nil { + return nil, errors.Errorf("unexpected list secrets status %d: %s", resp.StatusCode(), string(resp.Body)) } secrets := *resp.JSON200 sort.Slice(secrets, func(i, j int) bool { diff --git a/internal/secrets/list/list_test.go b/internal/secrets/list/list_test.go index 4e0da98b61..5b0c88ad4d 100644 --- a/internal/secrets/list/list_test.go +++ b/internal/secrets/list/list_test.go @@ -3,6 +3,7 @@ package list import ( "context" "errors" + "net/http" "testing" "github.com/h2non/gock" @@ -40,16 +41,6 @@ func TestSecretListCommand(t *testing.T) { assert.Empty(t, apitest.ListUnmatchedRequests()) }) - t.Run("throws error on missing access token", func(t *testing.T) { - t.Skip() - // Setup in-memory fs - fsys := afero.NewMemMapFs() - // Run test - err := Run(context.Background(), "", fsys) - // Check error - assert.ErrorContains(t, err, "Unexpected error retrieving project secrets") - }) - t.Run("throws error on network error", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() @@ -82,12 +73,12 @@ func TestSecretListCommand(t *testing.T) { defer gock.OffAll() gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/secrets"). - Reply(500). + Reply(http.StatusServiceUnavailable). JSON(map[string]string{"message": "unavailable"}) // Run test err := Run(context.Background(), project, fsys) // Check error - assert.ErrorContains(t, err, `Unexpected error retrieving project secrets: {"message":"unavailable"}`) + assert.ErrorContains(t, err, `unexpected list secrets status 503: {"message":"unavailable"}`) assert.Empty(t, apitest.ListUnmatchedRequests()) }) diff --git a/internal/services/services.go b/internal/services/services.go index a84e3a878b..1feeff3c49 100644 --- a/internal/services/services.go +++ b/internal/services/services.go @@ -2,15 +2,14 @@ package services import ( "context" - "errors" "fmt" "os" "strings" "sync" + "github.com/go-errors/errors" "github.com/spf13/afero" "github.com/spf13/viper" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/internal/utils/tenant" @@ -25,18 +24,30 @@ func Run(ctx context.Context, fsys afero.Fs) error { } serviceImages := CheckVersions(ctx, fsys) - table := `|SERVICE IMAGE|LOCAL|LINKED| + switch utils.OutputFormat.Value { + case utils.OutputPretty: + table := `|SERVICE IMAGE|LOCAL|LINKED| |-|-|-| ` - for _, image := range serviceImages { - remote := image.Remote - if len(remote) == 0 { - remote = "-" + for _, image := range serviceImages { + remote := image.Remote + if len(remote) == 0 { + remote = "-" + } + table += fmt.Sprintf("|`%s`|`%s`|`%s`|\n", image.Name, image.Local, remote) } - table += fmt.Sprintf("|`%s`|`%s`|`%s`|\n", image.Name, image.Local, remote) + return utils.RenderTable(table) + case utils.OutputToml: + return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, struct { + Services []imageVersion `toml:"services"` + }{ + Services: serviceImages, + }) + case utils.OutputEnv: + return errors.Errorf("--output env flag is not supported") } - return list.RenderTable(table) + return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, serviceImages) } type imageVersion struct { diff --git a/internal/snippets/list/list.go b/internal/snippets/list/list.go index f54325eaa9..5d32a04c0e 100644 --- a/internal/snippets/list/list.go +++ b/internal/snippets/list/list.go @@ -3,11 +3,11 @@ package list import ( "context" "fmt" + "os" "strings" "github.com/go-errors/errors" "github.com/spf13/afero" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/api" @@ -18,26 +18,30 @@ func Run(ctx context.Context, fsys afero.Fs) error { resp, err := utils.GetSupabase().V1ListAllSnippetsWithResponse(ctx, &opts) if err != nil { return errors.Errorf("failed to list snippets: %w", err) + } else if resp.JSON200 == nil { + return errors.Errorf("unexpected list snippets status %d: %s", resp.StatusCode(), string(resp.Body)) } - if resp.JSON200 == nil { - return errors.New("Unexpected error listing SQL snippets: " + string(resp.Body)) - } - - table := `|ID|NAME|VISIBILITY|OWNER|CREATED AT (UTC)|UPDATED AT (UTC)| + switch utils.OutputFormat.Value { + case utils.OutputPretty: + table := `|ID|NAME|VISIBILITY|OWNER|CREATED AT (UTC)|UPDATED AT (UTC)| |-|-|-|-|-|-| ` - for _, snippet := range resp.JSON200.Data { - table += fmt.Sprintf( - "|`%s`|`%s`|`%s`|`%s`|`%s`|`%s`|\n", - snippet.Id, - strings.ReplaceAll(snippet.Name, "|", "\\|"), - strings.ReplaceAll(string(snippet.Visibility), "|", "\\|"), - strings.ReplaceAll(snippet.Owner.Username, "|", "\\|"), - utils.FormatTimestamp(snippet.InsertedAt), - utils.FormatTimestamp(snippet.UpdatedAt), - ) + for _, snippet := range resp.JSON200.Data { + table += fmt.Sprintf( + "|`%s`|`%s`|`%s`|`%s`|`%s`|`%s`|\n", + snippet.Id, + strings.ReplaceAll(snippet.Name, "|", "\\|"), + strings.ReplaceAll(string(snippet.Visibility), "|", "\\|"), + strings.ReplaceAll(snippet.Owner.Username, "|", "\\|"), + utils.FormatTimestamp(snippet.InsertedAt), + utils.FormatTimestamp(snippet.UpdatedAt), + ) + } + return utils.RenderTable(table) + case utils.OutputEnv: + return errors.Errorf("--output env flag is not supported") } - return list.RenderTable(table) + return utils.EncodeOutput(utils.OutputFormat.Value, os.Stdout, *resp.JSON200) } diff --git a/internal/sso/internal/render/render.go b/internal/sso/internal/render/render.go index e0af9497a4..64e73a2bf5 100644 --- a/internal/sso/internal/render/render.go +++ b/internal/sso/internal/render/render.go @@ -7,7 +7,6 @@ import ( "github.com/go-errors/errors" "github.com/go-xmlfmt/xmlfmt" - "github.com/supabase/cli/internal/migration/list" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/api" ) @@ -83,7 +82,7 @@ func ListMarkdown(providers api.ListProvidersResponse) error { )) } - return list.RenderTable(strings.Join(markdownTable, "")) + return utils.RenderTable(strings.Join(markdownTable, "")) } func SingleMarkdown(provider api.GetProviderResponse) error { @@ -143,7 +142,7 @@ func SingleMarkdown(provider api.GetProviderResponse) error { markdownTable = append(markdownTable, "", "## SAML 2.0 Metadata XML", "```xml", prettyXML, "```") } - return list.RenderTable(strings.Join(markdownTable, "\n")) + return utils.RenderTable(strings.Join(markdownTable, "\n")) } func InfoMarkdown(ref string) error { @@ -167,5 +166,5 @@ func InfoMarkdown(ref string) error { fmt.Sprintf("https://%s.supabase.co", ref), )) - return list.RenderTable(strings.Join(markdownTable, "\n")) + return utils.RenderTable(strings.Join(markdownTable, "\n")) } diff --git a/internal/start/start.go b/internal/start/start.go index 698be1c0f1..15b3209f39 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -85,6 +85,7 @@ type kongConfig struct { PoolerId string ApiHost string ApiPort uint16 + BearerToken string } var ( @@ -345,6 +346,15 @@ EOF PoolerId: utils.PoolerId, ApiHost: utils.Config.Hostname, ApiPort: utils.Config.Api.Port, + BearerToken: fmt.Sprintf( + // Pass down apikey as Authorization header for backwards compatibility with legacy JWT. + // If Authorization header is already set, Kong simply skips evaluating this Lua script. + `$((function() return (headers.apikey == '%s' and 'Bearer %s') or (headers.apikey == '%s' and 'Bearer %s') or headers.apikey end)())`, + utils.Config.Auth.SecretKey.Value, + utils.Config.Auth.ServiceRoleKey.Value, + utils.Config.Auth.PublishableKey.Value, + utils.Config.Auth.AnonKey.Value, + ), }); err != nil { return errors.Errorf("failed to exec template: %w", err) } @@ -399,9 +409,9 @@ cat <<'EOF' > /home/kong/localhost.key && \ EOF ` + nginxConfigEmbed + ` EOF -` + status.KongCert + ` +` + string(utils.Config.Api.Tls.CertContent) + ` EOF -` + status.KongKey + ` +` + string(utils.Config.Api.Tls.KeyContent) + ` EOF `}, ExposedPorts: nat.PortSet{ diff --git a/internal/start/templates/kong.yml b/internal/start/templates/kong.yml index 71f0453447..fc9525f8fd 100644 --- a/internal/start/templates/kong.yml +++ b/internal/start/templates/kong.yml @@ -10,6 +10,11 @@ services: - /auth/v1/verify plugins: - name: cors + - name: request-transformer + config: + add: + headers: + - "Authorization: {{ .BearerToken }}" - name: auth-v1-open-callback _comment: "GoTrue: /auth/v1/callback* -> http://auth:9999/callback*" url: http://{{ .GotrueId }}:9999/callback @@ -20,6 +25,11 @@ services: - /auth/v1/callback plugins: - name: cors + - name: request-transformer + config: + add: + headers: + - "Authorization: {{ .BearerToken }}" - name: auth-v1-open-authorize _comment: "GoTrue: /auth/v1/authorize* -> http://auth:9999/authorize*" url: http://{{ .GotrueId }}:9999/authorize @@ -30,6 +40,11 @@ services: - /auth/v1/authorize plugins: - name: cors + - name: request-transformer + config: + add: + headers: + - "Authorization: {{ .BearerToken }}" - name: auth-v1 _comment: "GoTrue: /auth/v1/* -> http://auth:9999/*" url: http://{{ .GotrueId }}:9999/ @@ -40,6 +55,11 @@ services: - /auth/v1/ plugins: - name: cors + - name: request-transformer + config: + add: + headers: + - "Authorization: {{ .BearerToken }}" - name: rest-v1 _comment: "PostgREST: /rest/v1/* -> http://rest:3000/*" url: http://{{ .RestId }}:3000/ @@ -50,6 +70,11 @@ services: - /rest/v1/ plugins: - name: cors + - name: request-transformer + config: + add: + headers: + - "Authorization: {{ .BearerToken }}" - name: rest-admin-v1 _comment: "PostgREST: /rest-admin/v1/* -> http://rest:3001/*" url: http://{{ .RestId }}:3001/ @@ -60,6 +85,11 @@ services: - /rest-admin/v1/ plugins: - name: cors + - name: request-transformer + config: + add: + headers: + - "Authorization: {{ .BearerToken }}" - name: graphql-v1 _comment: "PostgREST: /graphql/v1 -> http://rest:3000/rpc/graphql" url: http://{{ .RestId }}:3000/rpc/graphql @@ -75,6 +105,7 @@ services: add: headers: - "Content-Profile: graphql_public" + - "Authorization: {{ .BearerToken }}" - name: realtime-v1-ws _comment: "Realtime: /realtime/v1/* -> ws://realtime:4000/socket/websocket" url: http://{{ .RealtimeId }}:4000/socket @@ -86,6 +117,11 @@ services: - /realtime/v1/ plugins: - name: cors + - name: request-transformer + config: + add: + headers: + - "Authorization: {{ .BearerToken }}" - name: realtime-v1-longpoll _comment: "Realtime: /realtime/v1/* -> ws://realtime:4000/socket/longpoll" url: http://{{ .RealtimeId }}:4000/socket @@ -97,6 +133,11 @@ services: - /realtime/v1/ plugins: - name: cors + - name: request-transformer + config: + add: + headers: + - "Authorization: {{ .BearerToken }}" - name: realtime-v1-rest _comment: "Realtime: /realtime/v1/* -> http://realtime:4000/api/*" url: http://{{ .RealtimeId }}:4000/api @@ -108,7 +149,11 @@ services: - /realtime/v1/api plugins: - name: cors - + - name: request-transformer + config: + add: + headers: + - "Authorization: {{ .BearerToken }}" - name: storage-v1 _comment: "Storage: /storage/v1/* -> http://storage-api:5000/*" url: http://{{ .StorageId }}:5000/ @@ -119,6 +164,11 @@ services: - /storage/v1/ plugins: - name: cors + - name: request-transformer + config: + add: + headers: + - "Authorization: {{ .BearerToken }}" - name: pg-meta _comment: "pg-meta: /pg/* -> http://pg-meta:8080/*" url: http://{{ .PgmetaId }}:8080/ @@ -138,6 +188,13 @@ services: strip_path: true paths: - /functions/v1/ + plugins: + - name: cors + - name: request-transformer + config: + add: + headers: + - "Authorization: {{ .BearerToken }}" - name: analytics-v1 _comment: "Analytics: /analytics/v1/* -> http://logflare:4000/*" url: http://{{ .LogflareId }}:4000/ @@ -146,6 +203,13 @@ services: strip_path: true paths: - /analytics/v1/ + plugins: + - name: cors + - name: request-transformer + config: + add: + headers: + - "Authorization: {{ .BearerToken }}" - name: pooler-v2-ws _comment: "Pooler: /pooler/v2/* -> ws://pooler:4000/v2/*" url: http://{{ .PoolerId }}:4000/v2 @@ -157,3 +221,8 @@ services: - /pooler/v2/ plugins: - name: cors + - name: request-transformer + config: + add: + headers: + - "Authorization: {{ .BearerToken }}" diff --git a/internal/status/status.go b/internal/status/status.go index 7dd64ecd04..cb648b2c31 100644 --- a/internal/status/status.go +++ b/internal/status/status.go @@ -11,6 +11,7 @@ import ( "net/url" "os" "reflect" + "strings" "sync" "time" @@ -29,10 +30,13 @@ type CustomName struct { StorageS3URL string `env:"api.storage_s3_url,default=STORAGE_S3_URL"` DbURL string `env:"db.url,default=DB_URL"` StudioURL string `env:"studio.url,default=STUDIO_URL"` - InbucketURL string `env:"inbucket.url,default=INBUCKET_URL"` - JWTSecret string `env:"auth.jwt_secret,default=JWT_SECRET"` - AnonKey string `env:"auth.anon_key,default=ANON_KEY"` - ServiceRoleKey string `env:"auth.service_role_key,default=SERVICE_ROLE_KEY"` + InbucketURL string `env:"inbucket.url,default=INBUCKET_URL,deprecated"` + MailpitURL string `env:"mailpit.url,default=MAILPIT_URL"` + PublishableKey string `env:"auth.publishable_key,default=PUBLISHABLE_KEY"` + SecretKey string `env:"auth.secret_key,default=SECRET_KEY"` + JWTSecret string `env:"auth.jwt_secret,default=JWT_SECRET,deprecated"` + AnonKey string `env:"auth.anon_key,default=ANON_KEY,deprecated"` + ServiceRoleKey string `env:"auth.service_role_key,default=SERVICE_ROLE_KEY,deprecated"` StorageS3AccessKeyId string `env:"storage.s3_access_key_id,default=S3_PROTOCOL_ACCESS_KEY_ID"` StorageS3SecretAccessKey string `env:"storage.s3_secret_access_key,default=S3_PROTOCOL_ACCESS_KEY_SECRET"` StorageS3Region string `env:"storage.s3_region,default=S3_PROTOCOL_REGION"` @@ -50,11 +54,14 @@ func (c *CustomName) toValues(exclude ...string) map[string]string { values[c.StudioURL] = fmt.Sprintf("http://%s:%d", utils.Config.Hostname, utils.Config.Studio.Port) } if utils.Config.Auth.Enabled && !utils.SliceContains(exclude, utils.GotrueId) && !utils.SliceContains(exclude, utils.ShortContainerImageName(utils.Config.Auth.Image)) { + values[c.PublishableKey] = utils.Config.Auth.PublishableKey.Value + values[c.SecretKey] = utils.Config.Auth.SecretKey.Value values[c.JWTSecret] = utils.Config.Auth.JwtSecret.Value values[c.AnonKey] = utils.Config.Auth.AnonKey.Value values[c.ServiceRoleKey] = utils.Config.Auth.ServiceRoleKey.Value } if utils.Config.Inbucket.Enabled && !utils.SliceContains(exclude, utils.InbucketId) && !utils.SliceContains(exclude, utils.ShortContainerImageName(utils.Config.Inbucket.Image)) { + values[c.MailpitURL] = fmt.Sprintf("http://%s:%d", utils.Config.Hostname, utils.Config.Inbucket.Port) values[c.InbucketURL] = fmt.Sprintf("http://%s:%d", utils.Config.Hostname, utils.Config.Inbucket.Port) } if utils.Config.Storage.Enabled && !utils.SliceContains(exclude, utils.StorageId) && !utils.SliceContains(exclude, utils.ShortContainerImageName(utils.Config.Storage.Image)) { @@ -134,13 +141,6 @@ func IsServiceReady(ctx context.Context, container string) error { return assertContainerHealthy(ctx, container) } -var ( - //go:embed kong.local.crt - KongCert string - //go:embed kong.local.key - KongKey string -) - // To regenerate local certificate pair: // // openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 \ @@ -157,7 +157,7 @@ func NewKongClient() *http.Client { pool = x509.NewCertPool() } // No need to replace TLS config if we fail to append cert - if pool.AppendCertsFromPEM([]byte(KongCert)) { + if pool.AppendCertsFromPEM(utils.Config.Api.Tls.CertContent) { rt := t.Clone() rt.TLSClientConfig = &tls.Config{ MinVersion: tls.VersionTLS12, @@ -178,7 +178,7 @@ func checkHTTPHead(ctx context.Context, path string) error { healthOnce.Do(func() { healthClient = fetcher.NewServiceGateway( utils.Config.Api.ExternalUrl, - utils.Config.Auth.AnonKey.Value, + utils.Config.Auth.SecretKey.Value, fetcher.WithHTTPClient(NewKongClient()), fetcher.WithUserAgent("SupabaseCLI/"+utils.Version), ) @@ -202,9 +202,12 @@ func PrettyPrint(w io.Writer, exclude ...string) { ApiURL: " " + utils.Aqua("API URL"), GraphqlURL: " " + utils.Aqua("GraphQL URL"), StorageS3URL: " " + utils.Aqua("S3 Storage URL"), - DbURL: " " + utils.Aqua("DB URL"), + DbURL: " " + utils.Aqua("Database URL"), StudioURL: " " + utils.Aqua("Studio URL"), InbucketURL: " " + utils.Aqua("Inbucket URL"), + MailpitURL: " " + utils.Aqua("Mailpit URL"), + PublishableKey: " " + utils.Aqua("Publishable key"), + SecretKey: " " + utils.Aqua("Secret key"), JWTSecret: " " + utils.Aqua("JWT secret"), AnonKey: " " + utils.Aqua("anon key"), ServiceRoleKey: "" + utils.Aqua("service_role key"), @@ -214,11 +217,24 @@ func PrettyPrint(w io.Writer, exclude ...string) { } values := names.toValues(exclude...) // Iterate through map in order of declared struct fields + t := reflect.TypeOf(names) val := reflect.ValueOf(names) for i := 0; i < val.NumField(); i++ { k := val.Field(i).String() + if tag := t.Field(i).Tag.Get("env"); isDeprecated(tag) { + continue + } if v, ok := values[k]; ok { fmt.Fprintf(w, "%s: %s\n", k, v) } } } + +func isDeprecated(tag string) bool { + for part := range strings.SplitSeq(tag, ",") { + if strings.EqualFold(part, "deprecated") { + return true + } + } + return false +} diff --git a/internal/utils/flags/db_url.go b/internal/utils/flags/db_url.go index 070d47f223..152900ba2b 100644 --- a/internal/utils/flags/db_url.go +++ b/internal/utils/flags/db_url.go @@ -179,7 +179,7 @@ func initLoginRole(ctx context.Context, projectRef string, config pgconn.Config) func UnbanIP(ctx context.Context, projectRef string, addrs ...string) error { includeSelf := len(addrs) == 0 body := api.RemoveNetworkBanRequest{ - Ipv4Addresses: addrs, + Ipv4Addresses: append([]string{}, addrs...), RequesterIp: &includeSelf, } if resp, err := utils.GetSupabase().V1DeleteNetworkBansWithResponse(ctx, projectRef, body); err != nil { diff --git a/internal/utils/output.go b/internal/utils/output.go index b518567ed1..f0c7e191b5 100644 --- a/internal/utils/output.go +++ b/internal/utils/output.go @@ -4,10 +4,15 @@ import ( "encoding/json" "fmt" "io" + "strings" "github.com/BurntSushi/toml" + "github.com/charmbracelet/glamour" + "github.com/charmbracelet/glamour/styles" "github.com/go-errors/errors" + "github.com/go-viper/mapstructure/v2" "github.com/joho/godotenv" + "github.com/spf13/viper" "gopkg.in/yaml.v3" ) @@ -36,9 +41,9 @@ var OutputFormat = EnumFlag{ func EncodeOutput(format string, w io.Writer, value any) error { switch format { case OutputEnv: - mapvalue, ok := value.(map[string]string) - if !ok { - return errors.Errorf("value is not a map[string]string and can't be encoded as an environment file") + mapvalue, err := ToEnvMap(value) + if err != nil { + return err } out, err := godotenv.Marshal(mapvalue) @@ -75,3 +80,42 @@ func EncodeOutput(format string, w io.Writer, value any) error { } return nil } + +func ToEnvMap(value any) (map[string]string, error) { + var result map[string]any + if dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + TagName: "json", + Result: &result, + }); err != nil { + return nil, errors.Errorf("failed to init decoder: %w", err) + } else if err := dec.Decode(value); err != nil { + return nil, errors.Errorf("failed to decode env: %w", err) + } + v := viper.New() + if err := v.MergeConfigMap(result); err != nil { + return nil, errors.Errorf("failed to merge env: %w", err) + } + keys := v.AllKeys() + mapvalue := make(map[string]string, len(keys)) + for _, k := range keys { + name := strings.ToUpper(strings.ReplaceAll(k, ".", "_")) + mapvalue[name] = v.GetString(k) + } + return mapvalue, nil +} + +func RenderTable(markdown string) error { + r, err := glamour.NewTermRenderer( + glamour.WithStandardStyle(styles.AsciiStyle), + glamour.WithWordWrap(-1), + ) + if err != nil { + return errors.Errorf("failed to initialise terminal renderer: %w", err) + } + out, err := r.Render(markdown) + if err != nil { + return errors.Errorf("failed to render markdown: %w", err) + } + fmt.Print(out) + return nil +} diff --git a/internal/utils/output_test.go b/internal/utils/output_test.go index 088136ebac..daaa947c17 100644 --- a/internal/utils/output_test.go +++ b/internal/utils/output_test.go @@ -9,7 +9,7 @@ import ( ) func TestEncodeOutput(t *testing.T) { - t.Run("encodes env format", func(t *testing.T) { + t.Run("encodes flat env", func(t *testing.T) { input := map[string]string{ "DATABASE_URL": "postgres://user:pass@host:5432/db", "API_KEY": "secret-key", @@ -21,11 +21,16 @@ func TestEncodeOutput(t *testing.T) { assert.Equal(t, expected, buf.String()) }) - t.Run("fails env format with invalid type", func(t *testing.T) { - input := map[string]int{"FOO": 123} + t.Run("encodes nested env", func(t *testing.T) { + input := map[string]any{ + "FOO": map[string]any{ + "BAR": 123, + }, + } var buf bytes.Buffer err := EncodeOutput(OutputEnv, &buf, input) - assert.ErrorContains(t, err, "value is not a map[string]string") + assert.NoError(t, err) + assert.Equal(t, "FOO_BAR=123\n", buf.String()) }) t.Run("encodes json format", func(t *testing.T) { diff --git a/package.json b/package.json index dd3014d225..39f6500c41 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "bin-links": "^5.0.0", "https-proxy-agent": "^7.0.2", "node-fetch": "^3.3.2", - "tar": "7.4.3" + "tar": "7.4.4" }, "release": { "branches": [ diff --git a/pkg/api/client.cfg.yaml b/pkg/api/client.cfg.yaml index 72a5adaac8..f06e6cad13 100755 --- a/pkg/api/client.cfg.yaml +++ b/pkg/api/client.cfg.yaml @@ -1,4 +1,7 @@ package: api generate: client: true +output-options: + overlay: + path: api/overlay.yaml output: pkg/api/client.gen.go diff --git a/pkg/api/client.gen.go b/pkg/api/client.gen.go index a1cd952c22..816164f50d 100644 --- a/pkg/api/client.gen.go +++ b/pkg/api/client.gen.go @@ -91,33 +91,33 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { // V1DeleteABranch request - V1DeleteABranch(ctx context.Context, branchId openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) + V1DeleteABranch(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*http.Response, error) // V1GetABranchConfig request - V1GetABranchConfig(ctx context.Context, branchId openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) + V1GetABranchConfig(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*http.Response, error) // V1UpdateABranchConfigWithBody request with any body - V1UpdateABranchConfigWithBody(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + V1UpdateABranchConfigWithBody(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - V1UpdateABranchConfig(ctx context.Context, branchId openapi_types.UUID, body V1UpdateABranchConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + V1UpdateABranchConfig(ctx context.Context, branchIdOrRef string, body V1UpdateABranchConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // V1DiffABranch request - V1DiffABranch(ctx context.Context, branchId openapi_types.UUID, params *V1DiffABranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) + V1DiffABranch(ctx context.Context, branchIdOrRef string, params *V1DiffABranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) // V1MergeABranchWithBody request with any body - V1MergeABranchWithBody(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + V1MergeABranchWithBody(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - V1MergeABranch(ctx context.Context, branchId openapi_types.UUID, body V1MergeABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + V1MergeABranch(ctx context.Context, branchIdOrRef string, body V1MergeABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // V1PushABranchWithBody request with any body - V1PushABranchWithBody(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + V1PushABranchWithBody(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - V1PushABranch(ctx context.Context, branchId openapi_types.UUID, body V1PushABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + V1PushABranch(ctx context.Context, branchIdOrRef string, body V1PushABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // V1ResetABranchWithBody request with any body - V1ResetABranchWithBody(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + V1ResetABranchWithBody(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - V1ResetABranch(ctx context.Context, branchId openapi_types.UUID, body V1ResetABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + V1ResetABranch(ctx context.Context, branchIdOrRef string, body V1ResetABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // V1AuthorizeUser request V1AuthorizeUser(ctx context.Context, params *V1AuthorizeUserParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -592,8 +592,8 @@ type ClientInterface interface { V1GetASnippet(ctx context.Context, id openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) } -func (c *Client) V1DeleteABranch(ctx context.Context, branchId openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewV1DeleteABranchRequest(c.Server, branchId) +func (c *Client) V1DeleteABranch(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1DeleteABranchRequest(c.Server, branchIdOrRef) if err != nil { return nil, err } @@ -604,8 +604,8 @@ func (c *Client) V1DeleteABranch(ctx context.Context, branchId openapi_types.UUI return c.Client.Do(req) } -func (c *Client) V1GetABranchConfig(ctx context.Context, branchId openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewV1GetABranchConfigRequest(c.Server, branchId) +func (c *Client) V1GetABranchConfig(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1GetABranchConfigRequest(c.Server, branchIdOrRef) if err != nil { return nil, err } @@ -616,8 +616,8 @@ func (c *Client) V1GetABranchConfig(ctx context.Context, branchId openapi_types. return c.Client.Do(req) } -func (c *Client) V1UpdateABranchConfigWithBody(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewV1UpdateABranchConfigRequestWithBody(c.Server, branchId, contentType, body) +func (c *Client) V1UpdateABranchConfigWithBody(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1UpdateABranchConfigRequestWithBody(c.Server, branchIdOrRef, contentType, body) if err != nil { return nil, err } @@ -628,8 +628,8 @@ func (c *Client) V1UpdateABranchConfigWithBody(ctx context.Context, branchId ope return c.Client.Do(req) } -func (c *Client) V1UpdateABranchConfig(ctx context.Context, branchId openapi_types.UUID, body V1UpdateABranchConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewV1UpdateABranchConfigRequest(c.Server, branchId, body) +func (c *Client) V1UpdateABranchConfig(ctx context.Context, branchIdOrRef string, body V1UpdateABranchConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1UpdateABranchConfigRequest(c.Server, branchIdOrRef, body) if err != nil { return nil, err } @@ -640,8 +640,8 @@ func (c *Client) V1UpdateABranchConfig(ctx context.Context, branchId openapi_typ return c.Client.Do(req) } -func (c *Client) V1DiffABranch(ctx context.Context, branchId openapi_types.UUID, params *V1DiffABranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewV1DiffABranchRequest(c.Server, branchId, params) +func (c *Client) V1DiffABranch(ctx context.Context, branchIdOrRef string, params *V1DiffABranchParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1DiffABranchRequest(c.Server, branchIdOrRef, params) if err != nil { return nil, err } @@ -652,8 +652,8 @@ func (c *Client) V1DiffABranch(ctx context.Context, branchId openapi_types.UUID, return c.Client.Do(req) } -func (c *Client) V1MergeABranchWithBody(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewV1MergeABranchRequestWithBody(c.Server, branchId, contentType, body) +func (c *Client) V1MergeABranchWithBody(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1MergeABranchRequestWithBody(c.Server, branchIdOrRef, contentType, body) if err != nil { return nil, err } @@ -664,8 +664,8 @@ func (c *Client) V1MergeABranchWithBody(ctx context.Context, branchId openapi_ty return c.Client.Do(req) } -func (c *Client) V1MergeABranch(ctx context.Context, branchId openapi_types.UUID, body V1MergeABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewV1MergeABranchRequest(c.Server, branchId, body) +func (c *Client) V1MergeABranch(ctx context.Context, branchIdOrRef string, body V1MergeABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1MergeABranchRequest(c.Server, branchIdOrRef, body) if err != nil { return nil, err } @@ -676,8 +676,8 @@ func (c *Client) V1MergeABranch(ctx context.Context, branchId openapi_types.UUID return c.Client.Do(req) } -func (c *Client) V1PushABranchWithBody(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewV1PushABranchRequestWithBody(c.Server, branchId, contentType, body) +func (c *Client) V1PushABranchWithBody(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1PushABranchRequestWithBody(c.Server, branchIdOrRef, contentType, body) if err != nil { return nil, err } @@ -688,8 +688,8 @@ func (c *Client) V1PushABranchWithBody(ctx context.Context, branchId openapi_typ return c.Client.Do(req) } -func (c *Client) V1PushABranch(ctx context.Context, branchId openapi_types.UUID, body V1PushABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewV1PushABranchRequest(c.Server, branchId, body) +func (c *Client) V1PushABranch(ctx context.Context, branchIdOrRef string, body V1PushABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1PushABranchRequest(c.Server, branchIdOrRef, body) if err != nil { return nil, err } @@ -700,8 +700,8 @@ func (c *Client) V1PushABranch(ctx context.Context, branchId openapi_types.UUID, return c.Client.Do(req) } -func (c *Client) V1ResetABranchWithBody(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewV1ResetABranchRequestWithBody(c.Server, branchId, contentType, body) +func (c *Client) V1ResetABranchWithBody(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1ResetABranchRequestWithBody(c.Server, branchIdOrRef, contentType, body) if err != nil { return nil, err } @@ -712,8 +712,8 @@ func (c *Client) V1ResetABranchWithBody(ctx context.Context, branchId openapi_ty return c.Client.Do(req) } -func (c *Client) V1ResetABranch(ctx context.Context, branchId openapi_types.UUID, body V1ResetABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewV1ResetABranchRequest(c.Server, branchId, body) +func (c *Client) V1ResetABranch(ctx context.Context, branchIdOrRef string, body V1ResetABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1ResetABranchRequest(c.Server, branchIdOrRef, body) if err != nil { return nil, err } @@ -2779,12 +2779,12 @@ func (c *Client) V1GetASnippet(ctx context.Context, id openapi_types.UUID, reqEd } // NewV1DeleteABranchRequest generates requests for V1DeleteABranch -func NewV1DeleteABranchRequest(server string, branchId openapi_types.UUID) (*http.Request, error) { +func NewV1DeleteABranchRequest(server string, branchIdOrRef string) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id", runtime.ParamLocationPath, branchId) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id_or_ref", runtime.ParamLocationPath, branchIdOrRef) if err != nil { return nil, err } @@ -2813,12 +2813,12 @@ func NewV1DeleteABranchRequest(server string, branchId openapi_types.UUID) (*htt } // NewV1GetABranchConfigRequest generates requests for V1GetABranchConfig -func NewV1GetABranchConfigRequest(server string, branchId openapi_types.UUID) (*http.Request, error) { +func NewV1GetABranchConfigRequest(server string, branchIdOrRef string) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id", runtime.ParamLocationPath, branchId) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id_or_ref", runtime.ParamLocationPath, branchIdOrRef) if err != nil { return nil, err } @@ -2847,23 +2847,23 @@ func NewV1GetABranchConfigRequest(server string, branchId openapi_types.UUID) (* } // NewV1UpdateABranchConfigRequest calls the generic V1UpdateABranchConfig builder with application/json body -func NewV1UpdateABranchConfigRequest(server string, branchId openapi_types.UUID, body V1UpdateABranchConfigJSONRequestBody) (*http.Request, error) { +func NewV1UpdateABranchConfigRequest(server string, branchIdOrRef string, body V1UpdateABranchConfigJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewV1UpdateABranchConfigRequestWithBody(server, branchId, "application/json", bodyReader) + return NewV1UpdateABranchConfigRequestWithBody(server, branchIdOrRef, "application/json", bodyReader) } // NewV1UpdateABranchConfigRequestWithBody generates requests for V1UpdateABranchConfig with any type of body -func NewV1UpdateABranchConfigRequestWithBody(server string, branchId openapi_types.UUID, contentType string, body io.Reader) (*http.Request, error) { +func NewV1UpdateABranchConfigRequestWithBody(server string, branchIdOrRef string, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id", runtime.ParamLocationPath, branchId) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id_or_ref", runtime.ParamLocationPath, branchIdOrRef) if err != nil { return nil, err } @@ -2894,12 +2894,12 @@ func NewV1UpdateABranchConfigRequestWithBody(server string, branchId openapi_typ } // NewV1DiffABranchRequest generates requests for V1DiffABranch -func NewV1DiffABranchRequest(server string, branchId openapi_types.UUID, params *V1DiffABranchParams) (*http.Request, error) { +func NewV1DiffABranchRequest(server string, branchIdOrRef string, params *V1DiffABranchParams) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id", runtime.ParamLocationPath, branchId) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id_or_ref", runtime.ParamLocationPath, branchIdOrRef) if err != nil { return nil, err } @@ -2950,23 +2950,23 @@ func NewV1DiffABranchRequest(server string, branchId openapi_types.UUID, params } // NewV1MergeABranchRequest calls the generic V1MergeABranch builder with application/json body -func NewV1MergeABranchRequest(server string, branchId openapi_types.UUID, body V1MergeABranchJSONRequestBody) (*http.Request, error) { +func NewV1MergeABranchRequest(server string, branchIdOrRef string, body V1MergeABranchJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewV1MergeABranchRequestWithBody(server, branchId, "application/json", bodyReader) + return NewV1MergeABranchRequestWithBody(server, branchIdOrRef, "application/json", bodyReader) } // NewV1MergeABranchRequestWithBody generates requests for V1MergeABranch with any type of body -func NewV1MergeABranchRequestWithBody(server string, branchId openapi_types.UUID, contentType string, body io.Reader) (*http.Request, error) { +func NewV1MergeABranchRequestWithBody(server string, branchIdOrRef string, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id", runtime.ParamLocationPath, branchId) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id_or_ref", runtime.ParamLocationPath, branchIdOrRef) if err != nil { return nil, err } @@ -2997,23 +2997,23 @@ func NewV1MergeABranchRequestWithBody(server string, branchId openapi_types.UUID } // NewV1PushABranchRequest calls the generic V1PushABranch builder with application/json body -func NewV1PushABranchRequest(server string, branchId openapi_types.UUID, body V1PushABranchJSONRequestBody) (*http.Request, error) { +func NewV1PushABranchRequest(server string, branchIdOrRef string, body V1PushABranchJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewV1PushABranchRequestWithBody(server, branchId, "application/json", bodyReader) + return NewV1PushABranchRequestWithBody(server, branchIdOrRef, "application/json", bodyReader) } // NewV1PushABranchRequestWithBody generates requests for V1PushABranch with any type of body -func NewV1PushABranchRequestWithBody(server string, branchId openapi_types.UUID, contentType string, body io.Reader) (*http.Request, error) { +func NewV1PushABranchRequestWithBody(server string, branchIdOrRef string, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id", runtime.ParamLocationPath, branchId) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id_or_ref", runtime.ParamLocationPath, branchIdOrRef) if err != nil { return nil, err } @@ -3044,23 +3044,23 @@ func NewV1PushABranchRequestWithBody(server string, branchId openapi_types.UUID, } // NewV1ResetABranchRequest calls the generic V1ResetABranch builder with application/json body -func NewV1ResetABranchRequest(server string, branchId openapi_types.UUID, body V1ResetABranchJSONRequestBody) (*http.Request, error) { +func NewV1ResetABranchRequest(server string, branchIdOrRef string, body V1ResetABranchJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewV1ResetABranchRequestWithBody(server, branchId, "application/json", bodyReader) + return NewV1ResetABranchRequestWithBody(server, branchIdOrRef, "application/json", bodyReader) } // NewV1ResetABranchRequestWithBody generates requests for V1ResetABranch with any type of body -func NewV1ResetABranchRequestWithBody(server string, branchId openapi_types.UUID, contentType string, body io.Reader) (*http.Request, error) { +func NewV1ResetABranchRequestWithBody(server string, branchIdOrRef string, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id", runtime.ParamLocationPath, branchId) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id_or_ref", runtime.ParamLocationPath, branchIdOrRef) if err != nil { return nil, err } @@ -9176,33 +9176,33 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { // V1DeleteABranchWithResponse request - V1DeleteABranchWithResponse(ctx context.Context, branchId openapi_types.UUID, reqEditors ...RequestEditorFn) (*V1DeleteABranchResponse, error) + V1DeleteABranchWithResponse(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*V1DeleteABranchResponse, error) // V1GetABranchConfigWithResponse request - V1GetABranchConfigWithResponse(ctx context.Context, branchId openapi_types.UUID, reqEditors ...RequestEditorFn) (*V1GetABranchConfigResponse, error) + V1GetABranchConfigWithResponse(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*V1GetABranchConfigResponse, error) // V1UpdateABranchConfigWithBodyWithResponse request with any body - V1UpdateABranchConfigWithBodyWithResponse(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1UpdateABranchConfigResponse, error) + V1UpdateABranchConfigWithBodyWithResponse(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1UpdateABranchConfigResponse, error) - V1UpdateABranchConfigWithResponse(ctx context.Context, branchId openapi_types.UUID, body V1UpdateABranchConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*V1UpdateABranchConfigResponse, error) + V1UpdateABranchConfigWithResponse(ctx context.Context, branchIdOrRef string, body V1UpdateABranchConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*V1UpdateABranchConfigResponse, error) // V1DiffABranchWithResponse request - V1DiffABranchWithResponse(ctx context.Context, branchId openapi_types.UUID, params *V1DiffABranchParams, reqEditors ...RequestEditorFn) (*V1DiffABranchResponse, error) + V1DiffABranchWithResponse(ctx context.Context, branchIdOrRef string, params *V1DiffABranchParams, reqEditors ...RequestEditorFn) (*V1DiffABranchResponse, error) // V1MergeABranchWithBodyWithResponse request with any body - V1MergeABranchWithBodyWithResponse(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1MergeABranchResponse, error) + V1MergeABranchWithBodyWithResponse(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1MergeABranchResponse, error) - V1MergeABranchWithResponse(ctx context.Context, branchId openapi_types.UUID, body V1MergeABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*V1MergeABranchResponse, error) + V1MergeABranchWithResponse(ctx context.Context, branchIdOrRef string, body V1MergeABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*V1MergeABranchResponse, error) // V1PushABranchWithBodyWithResponse request with any body - V1PushABranchWithBodyWithResponse(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1PushABranchResponse, error) + V1PushABranchWithBodyWithResponse(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1PushABranchResponse, error) - V1PushABranchWithResponse(ctx context.Context, branchId openapi_types.UUID, body V1PushABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*V1PushABranchResponse, error) + V1PushABranchWithResponse(ctx context.Context, branchIdOrRef string, body V1PushABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*V1PushABranchResponse, error) // V1ResetABranchWithBodyWithResponse request with any body - V1ResetABranchWithBodyWithResponse(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1ResetABranchResponse, error) + V1ResetABranchWithBodyWithResponse(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1ResetABranchResponse, error) - V1ResetABranchWithResponse(ctx context.Context, branchId openapi_types.UUID, body V1ResetABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*V1ResetABranchResponse, error) + V1ResetABranchWithResponse(ctx context.Context, branchIdOrRef string, body V1ResetABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*V1ResetABranchResponse, error) // V1AuthorizeUserWithResponse request V1AuthorizeUserWithResponse(ctx context.Context, params *V1AuthorizeUserParams, reqEditors ...RequestEditorFn) (*V1AuthorizeUserResponse, error) @@ -12619,8 +12619,8 @@ func (r V1GetASnippetResponse) StatusCode() int { } // V1DeleteABranchWithResponse request returning *V1DeleteABranchResponse -func (c *ClientWithResponses) V1DeleteABranchWithResponse(ctx context.Context, branchId openapi_types.UUID, reqEditors ...RequestEditorFn) (*V1DeleteABranchResponse, error) { - rsp, err := c.V1DeleteABranch(ctx, branchId, reqEditors...) +func (c *ClientWithResponses) V1DeleteABranchWithResponse(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*V1DeleteABranchResponse, error) { + rsp, err := c.V1DeleteABranch(ctx, branchIdOrRef, reqEditors...) if err != nil { return nil, err } @@ -12628,8 +12628,8 @@ func (c *ClientWithResponses) V1DeleteABranchWithResponse(ctx context.Context, b } // V1GetABranchConfigWithResponse request returning *V1GetABranchConfigResponse -func (c *ClientWithResponses) V1GetABranchConfigWithResponse(ctx context.Context, branchId openapi_types.UUID, reqEditors ...RequestEditorFn) (*V1GetABranchConfigResponse, error) { - rsp, err := c.V1GetABranchConfig(ctx, branchId, reqEditors...) +func (c *ClientWithResponses) V1GetABranchConfigWithResponse(ctx context.Context, branchIdOrRef string, reqEditors ...RequestEditorFn) (*V1GetABranchConfigResponse, error) { + rsp, err := c.V1GetABranchConfig(ctx, branchIdOrRef, reqEditors...) if err != nil { return nil, err } @@ -12637,16 +12637,16 @@ func (c *ClientWithResponses) V1GetABranchConfigWithResponse(ctx context.Context } // V1UpdateABranchConfigWithBodyWithResponse request with arbitrary body returning *V1UpdateABranchConfigResponse -func (c *ClientWithResponses) V1UpdateABranchConfigWithBodyWithResponse(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1UpdateABranchConfigResponse, error) { - rsp, err := c.V1UpdateABranchConfigWithBody(ctx, branchId, contentType, body, reqEditors...) +func (c *ClientWithResponses) V1UpdateABranchConfigWithBodyWithResponse(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1UpdateABranchConfigResponse, error) { + rsp, err := c.V1UpdateABranchConfigWithBody(ctx, branchIdOrRef, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseV1UpdateABranchConfigResponse(rsp) } -func (c *ClientWithResponses) V1UpdateABranchConfigWithResponse(ctx context.Context, branchId openapi_types.UUID, body V1UpdateABranchConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*V1UpdateABranchConfigResponse, error) { - rsp, err := c.V1UpdateABranchConfig(ctx, branchId, body, reqEditors...) +func (c *ClientWithResponses) V1UpdateABranchConfigWithResponse(ctx context.Context, branchIdOrRef string, body V1UpdateABranchConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*V1UpdateABranchConfigResponse, error) { + rsp, err := c.V1UpdateABranchConfig(ctx, branchIdOrRef, body, reqEditors...) if err != nil { return nil, err } @@ -12654,8 +12654,8 @@ func (c *ClientWithResponses) V1UpdateABranchConfigWithResponse(ctx context.Cont } // V1DiffABranchWithResponse request returning *V1DiffABranchResponse -func (c *ClientWithResponses) V1DiffABranchWithResponse(ctx context.Context, branchId openapi_types.UUID, params *V1DiffABranchParams, reqEditors ...RequestEditorFn) (*V1DiffABranchResponse, error) { - rsp, err := c.V1DiffABranch(ctx, branchId, params, reqEditors...) +func (c *ClientWithResponses) V1DiffABranchWithResponse(ctx context.Context, branchIdOrRef string, params *V1DiffABranchParams, reqEditors ...RequestEditorFn) (*V1DiffABranchResponse, error) { + rsp, err := c.V1DiffABranch(ctx, branchIdOrRef, params, reqEditors...) if err != nil { return nil, err } @@ -12663,16 +12663,16 @@ func (c *ClientWithResponses) V1DiffABranchWithResponse(ctx context.Context, bra } // V1MergeABranchWithBodyWithResponse request with arbitrary body returning *V1MergeABranchResponse -func (c *ClientWithResponses) V1MergeABranchWithBodyWithResponse(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1MergeABranchResponse, error) { - rsp, err := c.V1MergeABranchWithBody(ctx, branchId, contentType, body, reqEditors...) +func (c *ClientWithResponses) V1MergeABranchWithBodyWithResponse(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1MergeABranchResponse, error) { + rsp, err := c.V1MergeABranchWithBody(ctx, branchIdOrRef, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseV1MergeABranchResponse(rsp) } -func (c *ClientWithResponses) V1MergeABranchWithResponse(ctx context.Context, branchId openapi_types.UUID, body V1MergeABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*V1MergeABranchResponse, error) { - rsp, err := c.V1MergeABranch(ctx, branchId, body, reqEditors...) +func (c *ClientWithResponses) V1MergeABranchWithResponse(ctx context.Context, branchIdOrRef string, body V1MergeABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*V1MergeABranchResponse, error) { + rsp, err := c.V1MergeABranch(ctx, branchIdOrRef, body, reqEditors...) if err != nil { return nil, err } @@ -12680,16 +12680,16 @@ func (c *ClientWithResponses) V1MergeABranchWithResponse(ctx context.Context, br } // V1PushABranchWithBodyWithResponse request with arbitrary body returning *V1PushABranchResponse -func (c *ClientWithResponses) V1PushABranchWithBodyWithResponse(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1PushABranchResponse, error) { - rsp, err := c.V1PushABranchWithBody(ctx, branchId, contentType, body, reqEditors...) +func (c *ClientWithResponses) V1PushABranchWithBodyWithResponse(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1PushABranchResponse, error) { + rsp, err := c.V1PushABranchWithBody(ctx, branchIdOrRef, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseV1PushABranchResponse(rsp) } -func (c *ClientWithResponses) V1PushABranchWithResponse(ctx context.Context, branchId openapi_types.UUID, body V1PushABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*V1PushABranchResponse, error) { - rsp, err := c.V1PushABranch(ctx, branchId, body, reqEditors...) +func (c *ClientWithResponses) V1PushABranchWithResponse(ctx context.Context, branchIdOrRef string, body V1PushABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*V1PushABranchResponse, error) { + rsp, err := c.V1PushABranch(ctx, branchIdOrRef, body, reqEditors...) if err != nil { return nil, err } @@ -12697,16 +12697,16 @@ func (c *ClientWithResponses) V1PushABranchWithResponse(ctx context.Context, bra } // V1ResetABranchWithBodyWithResponse request with arbitrary body returning *V1ResetABranchResponse -func (c *ClientWithResponses) V1ResetABranchWithBodyWithResponse(ctx context.Context, branchId openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1ResetABranchResponse, error) { - rsp, err := c.V1ResetABranchWithBody(ctx, branchId, contentType, body, reqEditors...) +func (c *ClientWithResponses) V1ResetABranchWithBodyWithResponse(ctx context.Context, branchIdOrRef string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1ResetABranchResponse, error) { + rsp, err := c.V1ResetABranchWithBody(ctx, branchIdOrRef, contentType, body, reqEditors...) if err != nil { return nil, err } return ParseV1ResetABranchResponse(rsp) } -func (c *ClientWithResponses) V1ResetABranchWithResponse(ctx context.Context, branchId openapi_types.UUID, body V1ResetABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*V1ResetABranchResponse, error) { - rsp, err := c.V1ResetABranch(ctx, branchId, body, reqEditors...) +func (c *ClientWithResponses) V1ResetABranchWithResponse(ctx context.Context, branchIdOrRef string, body V1ResetABranchJSONRequestBody, reqEditors ...RequestEditorFn) (*V1ResetABranchResponse, error) { + rsp, err := c.V1ResetABranch(ctx, branchIdOrRef, body, reqEditors...) if err != nil { return nil, err } diff --git a/pkg/api/types.gen.go b/pkg/api/types.gen.go index 4abb004f81..2b018cec1c 100644 --- a/pkg/api/types.gen.go +++ b/pkg/api/types.gen.go @@ -219,41 +219,125 @@ const ( CreateSigningKeyBodyAlgorithmRS256 CreateSigningKeyBodyAlgorithm = "RS256" ) +// Defines values for CreateSigningKeyBodyPrivateJwk0Alg. +const ( + CreateSigningKeyBodyPrivateJwk0AlgRS256 CreateSigningKeyBodyPrivateJwk0Alg = "RS256" +) + // Defines values for CreateSigningKeyBodyPrivateJwk0E. const ( AQAB CreateSigningKeyBodyPrivateJwk0E = "AQAB" ) +// Defines values for CreateSigningKeyBodyPrivateJwk0Ext. +const ( + CreateSigningKeyBodyPrivateJwk0ExtTrue CreateSigningKeyBodyPrivateJwk0Ext = true +) + +// Defines values for CreateSigningKeyBodyPrivateJwk0KeyOps. +const ( + CreateSigningKeyBodyPrivateJwk0KeyOpsSign CreateSigningKeyBodyPrivateJwk0KeyOps = "sign" + CreateSigningKeyBodyPrivateJwk0KeyOpsVerify CreateSigningKeyBodyPrivateJwk0KeyOps = "verify" +) + // Defines values for CreateSigningKeyBodyPrivateJwk0Kty. const ( RSA CreateSigningKeyBodyPrivateJwk0Kty = "RSA" ) +// Defines values for CreateSigningKeyBodyPrivateJwk0Use. +const ( + CreateSigningKeyBodyPrivateJwk0UseSig CreateSigningKeyBodyPrivateJwk0Use = "sig" +) + +// Defines values for CreateSigningKeyBodyPrivateJwk1Alg. +const ( + CreateSigningKeyBodyPrivateJwk1AlgES256 CreateSigningKeyBodyPrivateJwk1Alg = "ES256" +) + // Defines values for CreateSigningKeyBodyPrivateJwk1Crv. const ( P256 CreateSigningKeyBodyPrivateJwk1Crv = "P-256" ) +// Defines values for CreateSigningKeyBodyPrivateJwk1Ext. +const ( + CreateSigningKeyBodyPrivateJwk1ExtTrue CreateSigningKeyBodyPrivateJwk1Ext = true +) + +// Defines values for CreateSigningKeyBodyPrivateJwk1KeyOps. +const ( + CreateSigningKeyBodyPrivateJwk1KeyOpsSign CreateSigningKeyBodyPrivateJwk1KeyOps = "sign" + CreateSigningKeyBodyPrivateJwk1KeyOpsVerify CreateSigningKeyBodyPrivateJwk1KeyOps = "verify" +) + // Defines values for CreateSigningKeyBodyPrivateJwk1Kty. const ( EC CreateSigningKeyBodyPrivateJwk1Kty = "EC" ) +// Defines values for CreateSigningKeyBodyPrivateJwk1Use. +const ( + CreateSigningKeyBodyPrivateJwk1UseSig CreateSigningKeyBodyPrivateJwk1Use = "sig" +) + +// Defines values for CreateSigningKeyBodyPrivateJwk2Alg. +const ( + CreateSigningKeyBodyPrivateJwk2AlgEdDSA CreateSigningKeyBodyPrivateJwk2Alg = "EdDSA" +) + // Defines values for CreateSigningKeyBodyPrivateJwk2Crv. const ( Ed25519 CreateSigningKeyBodyPrivateJwk2Crv = "Ed25519" ) +// Defines values for CreateSigningKeyBodyPrivateJwk2Ext. +const ( + CreateSigningKeyBodyPrivateJwk2ExtTrue CreateSigningKeyBodyPrivateJwk2Ext = true +) + +// Defines values for CreateSigningKeyBodyPrivateJwk2KeyOps. +const ( + CreateSigningKeyBodyPrivateJwk2KeyOpsSign CreateSigningKeyBodyPrivateJwk2KeyOps = "sign" + CreateSigningKeyBodyPrivateJwk2KeyOpsVerify CreateSigningKeyBodyPrivateJwk2KeyOps = "verify" +) + // Defines values for CreateSigningKeyBodyPrivateJwk2Kty. const ( OKP CreateSigningKeyBodyPrivateJwk2Kty = "OKP" ) +// Defines values for CreateSigningKeyBodyPrivateJwk2Use. +const ( + CreateSigningKeyBodyPrivateJwk2UseSig CreateSigningKeyBodyPrivateJwk2Use = "sig" +) + +// Defines values for CreateSigningKeyBodyPrivateJwk3Alg. +const ( + HS256 CreateSigningKeyBodyPrivateJwk3Alg = "HS256" +) + +// Defines values for CreateSigningKeyBodyPrivateJwk3Ext. +const ( + CreateSigningKeyBodyPrivateJwk3ExtTrue CreateSigningKeyBodyPrivateJwk3Ext = true +) + +// Defines values for CreateSigningKeyBodyPrivateJwk3KeyOps. +const ( + CreateSigningKeyBodyPrivateJwk3KeyOpsSign CreateSigningKeyBodyPrivateJwk3KeyOps = "sign" + CreateSigningKeyBodyPrivateJwk3KeyOpsVerify CreateSigningKeyBodyPrivateJwk3KeyOps = "verify" +) + // Defines values for CreateSigningKeyBodyPrivateJwk3Kty. const ( Oct CreateSigningKeyBodyPrivateJwk3Kty = "oct" ) +// Defines values for CreateSigningKeyBodyPrivateJwk3Use. +const ( + CreateSigningKeyBodyPrivateJwk3UseSig CreateSigningKeyBodyPrivateJwk3Use = "sig" +) + // Defines values for CreateSigningKeyBodyStatus. const ( CreateSigningKeyBodyStatusInUse CreateSigningKeyBodyStatus = "in_use" @@ -532,6 +616,7 @@ const ( // Defines values for OAuthTokenBodyResource. const ( OAuthTokenBodyResourceHttpsapiSupabaseGreenmcp OAuthTokenBodyResource = "https://api.supabase.green/mcp" + OAuthTokenBodyResourceHttpsmcpSupabaseGreenmcp OAuthTokenBodyResource = "https://mcp.supabase.green/mcp" ) // Defines values for OAuthTokenResponseTokenType. @@ -631,10 +716,10 @@ const ( // Defines values for SigningKeysResponseKeysAlgorithm. const ( - ES256 SigningKeysResponseKeysAlgorithm = "ES256" - EdDSA SigningKeysResponseKeysAlgorithm = "EdDSA" - HS256 SigningKeysResponseKeysAlgorithm = "HS256" - RS256 SigningKeysResponseKeysAlgorithm = "RS256" + SigningKeysResponseKeysAlgorithmES256 SigningKeysResponseKeysAlgorithm = "ES256" + SigningKeysResponseKeysAlgorithmEdDSA SigningKeysResponseKeysAlgorithm = "EdDSA" + SigningKeysResponseKeysAlgorithmHS256 SigningKeysResponseKeysAlgorithm = "HS256" + SigningKeysResponseKeysAlgorithmRS256 SigningKeysResponseKeysAlgorithm = "RS256" ) // Defines values for SigningKeysResponseKeysStatus. @@ -928,6 +1013,7 @@ const ( UnindexedForeignKeys V1ProjectAdvisorsResponseLintsName = "unindexed_foreign_keys" UnsupportedRegTypes V1ProjectAdvisorsResponseLintsName = "unsupported_reg_types" UnusedIndex V1ProjectAdvisorsResponseLintsName = "unused_index" + VulnerablePostgresVersion V1ProjectAdvisorsResponseLintsName = "vulnerable_postgres_version" ) // Defines values for V1ProjectResponseStatus. @@ -1023,6 +1109,7 @@ const ( // Defines values for V1AuthorizeUserParamsResource. const ( V1AuthorizeUserParamsResourceHttpsapiSupabaseGreenmcp V1AuthorizeUserParamsResource = "https://api.supabase.green/mcp" + V1AuthorizeUserParamsResourceHttpsmcpSupabaseGreenmcp V1AuthorizeUserParamsResource = "https://mcp.supabase.green/mcp" ) // Defines values for V1OauthAuthorizeProjectClaimParamsResponseType. @@ -1637,61 +1724,129 @@ type CreateSigningKeyBodyAlgorithm string // CreateSigningKeyBodyPrivateJwk0 defines model for . type CreateSigningKeyBodyPrivateJwk0 struct { - D string `json:"d"` - Dp string `json:"dp"` - Dq string `json:"dq"` - E CreateSigningKeyBodyPrivateJwk0E `json:"e"` - Kty CreateSigningKeyBodyPrivateJwk0Kty `json:"kty"` - N string `json:"n"` - P string `json:"p"` - Q string `json:"q"` - Qi string `json:"qi"` -} + Alg *CreateSigningKeyBodyPrivateJwk0Alg `json:"alg,omitempty"` + D string `json:"d"` + Dp string `json:"dp"` + Dq string `json:"dq"` + E CreateSigningKeyBodyPrivateJwk0E `json:"e"` + Ext *CreateSigningKeyBodyPrivateJwk0Ext `json:"ext,omitempty"` + KeyOps *[]CreateSigningKeyBodyPrivateJwk0KeyOps `json:"key_ops,omitempty"` + Kid *openapi_types.UUID `json:"kid,omitempty"` + Kty CreateSigningKeyBodyPrivateJwk0Kty `json:"kty"` + N string `json:"n"` + P string `json:"p"` + Q string `json:"q"` + Qi string `json:"qi"` + Use *CreateSigningKeyBodyPrivateJwk0Use `json:"use,omitempty"` +} + +// CreateSigningKeyBodyPrivateJwk0Alg defines model for CreateSigningKeyBody.PrivateJwk.0.Alg. +type CreateSigningKeyBodyPrivateJwk0Alg string // CreateSigningKeyBodyPrivateJwk0E defines model for CreateSigningKeyBody.PrivateJwk.0.E. type CreateSigningKeyBodyPrivateJwk0E string +// CreateSigningKeyBodyPrivateJwk0Ext defines model for CreateSigningKeyBody.PrivateJwk.0.Ext. +type CreateSigningKeyBodyPrivateJwk0Ext bool + +// CreateSigningKeyBodyPrivateJwk0KeyOps defines model for CreateSigningKeyBody.PrivateJwk.0.KeyOps. +type CreateSigningKeyBodyPrivateJwk0KeyOps string + // CreateSigningKeyBodyPrivateJwk0Kty defines model for CreateSigningKeyBody.PrivateJwk.0.Kty. type CreateSigningKeyBodyPrivateJwk0Kty string +// CreateSigningKeyBodyPrivateJwk0Use defines model for CreateSigningKeyBody.PrivateJwk.0.Use. +type CreateSigningKeyBodyPrivateJwk0Use string + // CreateSigningKeyBodyPrivateJwk1 defines model for . type CreateSigningKeyBodyPrivateJwk1 struct { - Crv CreateSigningKeyBodyPrivateJwk1Crv `json:"crv"` - D string `json:"d"` - Kty CreateSigningKeyBodyPrivateJwk1Kty `json:"kty"` - X string `json:"x"` - Y string `json:"y"` + Alg *CreateSigningKeyBodyPrivateJwk1Alg `json:"alg,omitempty"` + Crv CreateSigningKeyBodyPrivateJwk1Crv `json:"crv"` + D string `json:"d"` + Ext *CreateSigningKeyBodyPrivateJwk1Ext `json:"ext,omitempty"` + KeyOps *[]CreateSigningKeyBodyPrivateJwk1KeyOps `json:"key_ops,omitempty"` + Kid *openapi_types.UUID `json:"kid,omitempty"` + Kty CreateSigningKeyBodyPrivateJwk1Kty `json:"kty"` + Use *CreateSigningKeyBodyPrivateJwk1Use `json:"use,omitempty"` + X string `json:"x"` + Y string `json:"y"` } +// CreateSigningKeyBodyPrivateJwk1Alg defines model for CreateSigningKeyBody.PrivateJwk.1.Alg. +type CreateSigningKeyBodyPrivateJwk1Alg string + // CreateSigningKeyBodyPrivateJwk1Crv defines model for CreateSigningKeyBody.PrivateJwk.1.Crv. type CreateSigningKeyBodyPrivateJwk1Crv string +// CreateSigningKeyBodyPrivateJwk1Ext defines model for CreateSigningKeyBody.PrivateJwk.1.Ext. +type CreateSigningKeyBodyPrivateJwk1Ext bool + +// CreateSigningKeyBodyPrivateJwk1KeyOps defines model for CreateSigningKeyBody.PrivateJwk.1.KeyOps. +type CreateSigningKeyBodyPrivateJwk1KeyOps string + // CreateSigningKeyBodyPrivateJwk1Kty defines model for CreateSigningKeyBody.PrivateJwk.1.Kty. type CreateSigningKeyBodyPrivateJwk1Kty string +// CreateSigningKeyBodyPrivateJwk1Use defines model for CreateSigningKeyBody.PrivateJwk.1.Use. +type CreateSigningKeyBodyPrivateJwk1Use string + // CreateSigningKeyBodyPrivateJwk2 defines model for . type CreateSigningKeyBodyPrivateJwk2 struct { - Crv CreateSigningKeyBodyPrivateJwk2Crv `json:"crv"` - D string `json:"d"` - Kty CreateSigningKeyBodyPrivateJwk2Kty `json:"kty"` - X string `json:"x"` + Alg *CreateSigningKeyBodyPrivateJwk2Alg `json:"alg,omitempty"` + Crv CreateSigningKeyBodyPrivateJwk2Crv `json:"crv"` + D string `json:"d"` + Ext *CreateSigningKeyBodyPrivateJwk2Ext `json:"ext,omitempty"` + KeyOps *[]CreateSigningKeyBodyPrivateJwk2KeyOps `json:"key_ops,omitempty"` + Kid *openapi_types.UUID `json:"kid,omitempty"` + Kty CreateSigningKeyBodyPrivateJwk2Kty `json:"kty"` + Use *CreateSigningKeyBodyPrivateJwk2Use `json:"use,omitempty"` + X string `json:"x"` } +// CreateSigningKeyBodyPrivateJwk2Alg defines model for CreateSigningKeyBody.PrivateJwk.2.Alg. +type CreateSigningKeyBodyPrivateJwk2Alg string + // CreateSigningKeyBodyPrivateJwk2Crv defines model for CreateSigningKeyBody.PrivateJwk.2.Crv. type CreateSigningKeyBodyPrivateJwk2Crv string +// CreateSigningKeyBodyPrivateJwk2Ext defines model for CreateSigningKeyBody.PrivateJwk.2.Ext. +type CreateSigningKeyBodyPrivateJwk2Ext bool + +// CreateSigningKeyBodyPrivateJwk2KeyOps defines model for CreateSigningKeyBody.PrivateJwk.2.KeyOps. +type CreateSigningKeyBodyPrivateJwk2KeyOps string + // CreateSigningKeyBodyPrivateJwk2Kty defines model for CreateSigningKeyBody.PrivateJwk.2.Kty. type CreateSigningKeyBodyPrivateJwk2Kty string +// CreateSigningKeyBodyPrivateJwk2Use defines model for CreateSigningKeyBody.PrivateJwk.2.Use. +type CreateSigningKeyBodyPrivateJwk2Use string + // CreateSigningKeyBodyPrivateJwk3 defines model for . type CreateSigningKeyBodyPrivateJwk3 struct { - K string `json:"k"` - Kty CreateSigningKeyBodyPrivateJwk3Kty `json:"kty"` + Alg *CreateSigningKeyBodyPrivateJwk3Alg `json:"alg,omitempty"` + Ext *CreateSigningKeyBodyPrivateJwk3Ext `json:"ext,omitempty"` + K string `json:"k"` + KeyOps *[]CreateSigningKeyBodyPrivateJwk3KeyOps `json:"key_ops,omitempty"` + Kid *openapi_types.UUID `json:"kid,omitempty"` + Kty CreateSigningKeyBodyPrivateJwk3Kty `json:"kty"` + Use *CreateSigningKeyBodyPrivateJwk3Use `json:"use,omitempty"` } +// CreateSigningKeyBodyPrivateJwk3Alg defines model for CreateSigningKeyBody.PrivateJwk.3.Alg. +type CreateSigningKeyBodyPrivateJwk3Alg string + +// CreateSigningKeyBodyPrivateJwk3Ext defines model for CreateSigningKeyBody.PrivateJwk.3.Ext. +type CreateSigningKeyBodyPrivateJwk3Ext bool + +// CreateSigningKeyBodyPrivateJwk3KeyOps defines model for CreateSigningKeyBody.PrivateJwk.3.KeyOps. +type CreateSigningKeyBodyPrivateJwk3KeyOps string + // CreateSigningKeyBodyPrivateJwk3Kty defines model for CreateSigningKeyBody.PrivateJwk.3.Kty. type CreateSigningKeyBodyPrivateJwk3Kty string +// CreateSigningKeyBodyPrivateJwk3Use defines model for CreateSigningKeyBody.PrivateJwk.3.Use. +type CreateSigningKeyBodyPrivateJwk3Use string + // CreateSigningKeyBody_PrivateJwk defines model for CreateSigningKeyBody.PrivateJwk. type CreateSigningKeyBody_PrivateJwk struct { union json.RawMessage @@ -2428,6 +2583,7 @@ type SnippetList struct { Cursor *string `json:"cursor,omitempty"` Data []struct { Description nullable.Nullable[string] `json:"description"` + Favorite bool `json:"favorite"` Id string `json:"id"` InsertedAt string `json:"inserted_at"` Name string `json:"name"` @@ -2458,11 +2614,14 @@ type SnippetListDataVisibility string // SnippetResponse defines model for SnippetResponse. type SnippetResponse struct { Content struct { - Favorite bool `json:"favorite"` + // Favorite Deprecated: Rely on root-level favorite property instead. + // Deprecated: + Favorite *bool `json:"favorite,omitempty"` SchemaVersion string `json:"schema_version"` Sql string `json:"sql"` } `json:"content"` Description nullable.Nullable[string] `json:"description"` + Favorite bool `json:"favorite"` Id string `json:"id"` InsertedAt string `json:"inserted_at"` Name string `json:"name"` diff --git a/pkg/config/api.go b/pkg/config/api.go index c29b71a9fc..e294a38c68 100644 --- a/pkg/config/api.go +++ b/pkg/config/api.go @@ -24,7 +24,11 @@ type ( } tlsKong struct { - Enabled bool `toml:"enabled"` + Enabled bool `toml:"enabled"` + CertPath string `toml:"cert_path"` + CertContent []byte `toml:"-"` + KeyPath string `toml:"key_path"` + KeyContent []byte `toml:"-"` } ) diff --git a/pkg/config/apikeys.go b/pkg/config/apikeys.go index 35ed815d12..879ef9fea8 100644 --- a/pkg/config/apikeys.go +++ b/pkg/config/apikeys.go @@ -14,8 +14,39 @@ import ( "github.com/google/uuid" ) +const ( + defaultJwtSecret = "super-secret-jwt-token-with-at-least-32-characters-long" + defaultJwtExpiry = 1983812996 + defaultPublishableKey = "sb_publishable_ACJWlzQHlZjBrEguHvfOxg_3BJgxAaH" + defaultSecretKey = "sb_secret_N7UND0UgjKTVK-Uodkm0Hg_xSvEMPvz" +) + +type CustomClaims struct { + // Overrides Issuer to maintain json order when marshalling + Issuer string `json:"iss,omitempty"` + Ref string `json:"ref,omitempty"` + Role string `json:"role"` + IsAnon bool `json:"is_anonymous,omitempty"` + jwt.RegisteredClaims +} + +func (c CustomClaims) NewToken() *jwt.Token { + if c.ExpiresAt == nil { + c.ExpiresAt = jwt.NewNumericDate(time.Unix(defaultJwtExpiry, 0)) + } + if len(c.Issuer) == 0 { + c.Issuer = "supabase-demo" + } + return jwt.NewWithClaims(jwt.SigningMethodHS256, c) +} + // generateAPIKeys generates JWT tokens using the appropriate signing method func (a *auth) generateAPIKeys() error { + if len(a.JwtSecret.Value) == 0 { + a.JwtSecret.Value = defaultJwtSecret + } else if len(a.JwtSecret.Value) < 16 { + return errors.Errorf("Invalid config for auth.jwt_secret. Must be at least 16 characters") + } // Generate anon key if not provided if len(a.AnonKey.Value) == 0 { signed, err := a.generateJWT("anon") @@ -32,6 +63,13 @@ func (a *auth) generateAPIKeys() error { } a.ServiceRoleKey.Value = signed } + // Set hardcoded opaque keys + if len(a.PublishableKey.Value) == 0 { + a.PublishableKey.Value = defaultPublishableKey + } + if len(a.SecretKey.Value) == 0 { + a.SecretKey.Value = defaultSecretKey + } return nil } @@ -39,12 +77,9 @@ func (a auth) generateJWT(role string) (string, error) { claims := CustomClaims{Issuer: "supabase-demo", Role: role} if len(a.SigningKeys) > 0 { claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * 365 * 10)) // 10 years - return generateAsymmetricJWT(a.SigningKeys[0], claims) + return GenerateAsymmetricJWT(a.SigningKeys[0], claims) } // Fallback to generating symmetric keys - if len(a.JwtSecret.Value) < 16 { - return "", errors.Errorf("Invalid config for auth.jwt_secret. Must be at least 16 characters") - } signed, err := claims.NewToken().SignedString([]byte(a.JwtSecret.Value)) if err != nil { return "", errors.Errorf("failed to generate JWT: %w", err) @@ -52,8 +87,8 @@ func (a auth) generateJWT(role string) (string, error) { return signed, nil } -// generateAsymmetricJWT generates a JWT token signed with the provided JWK private key -func generateAsymmetricJWT(jwk JWK, claims CustomClaims) (string, error) { +// GenerateAsymmetricJWT generates a JWT token signed with the provided JWK private key +func GenerateAsymmetricJWT(jwk JWK, claims CustomClaims) (string, error) { privateKey, err := jwkToPrivateKey(jwk) if err != nil { return "", errors.Errorf("failed to convert JWK to private key: %w", err) @@ -167,3 +202,11 @@ func jwkToECDSAPrivateKey(jwk JWK) (*ecdsa.PrivateKey, error) { D: d, }, nil } + +func NewBigIntFromBase64(n string) (*big.Int, error) { + nBytes, err := base64.RawURLEncoding.DecodeString(n) + if err != nil { + return nil, errors.Errorf("failed to decode base64: %w", err) + } + return new(big.Int).SetBytes(nBytes), nil +} diff --git a/pkg/config/auth.go b/pkg/config/auth.go index 9dc0d30284..fbc918a240 100644 --- a/pkg/config/auth.go +++ b/pkg/config/auth.go @@ -173,6 +173,8 @@ type ( Web3 web3 `toml:"web3"` // Custom secrets can be injected from .env file + PublishableKey Secret `toml:"publishable_key"` + SecretKey Secret `toml:"secret_key"` JwtSecret Secret `toml:"jwt_secret"` AnonKey Secret `toml:"anon_key"` ServiceRoleKey Secret `toml:"service_role_key"` @@ -358,8 +360,13 @@ type ( Enabled bool `toml:"enabled"` } + ethereum struct { + Enabled bool `toml:"enabled"` + } + web3 struct { - Solana solana `toml:"solana"` + Solana solana `toml:"solana"` + Ethereum ethereum `toml:"ethereum"` } ) @@ -1318,22 +1325,44 @@ func (e external) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { func (w web3) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) { body.ExternalWeb3SolanaEnabled = nullable.NewNullableWithValue(w.Solana.Enabled) + body.ExternalWeb3EthereumEnabled = nullable.NewNullableWithValue(w.Ethereum.Enabled) } func (w *web3) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) { if value, err := remoteConfig.ExternalWeb3SolanaEnabled.Get(); err == nil { w.Solana.Enabled = value } + + if value, err := remoteConfig.ExternalWeb3EthereumEnabled.Get(); err == nil { + w.Ethereum.Enabled = value + } } -func (a *auth) DiffWithRemote(remoteConfig v1API.AuthConfigResponse) ([]byte, error) { +func (a *auth) DiffWithRemote(remoteConfig v1API.AuthConfigResponse, filter ...func(string) bool) ([]byte, error) { copy := a.Clone() + copy.FromRemoteAuthConfig(remoteConfig) + // Confirm cost before enabling addons + for _, keep := range filter { + if a.MFA.Phone.VerifyEnabled && !copy.MFA.Phone.VerifyEnabled { + if !keep(string(v1API.ListProjectAddonsResponseAvailableAddonsTypeAuthMfaPhone)) { + a.MFA.Phone.VerifyEnabled = false + // Enroll cannot be enabled on its own + a.MFA.Phone.EnrollEnabled = false + } + } + if a.MFA.WebAuthn.VerifyEnabled && !copy.MFA.WebAuthn.VerifyEnabled { + if !keep(string(v1API.ListProjectAddonsResponseAvailableAddonsTypeAuthMfaWebAuthn)) { + a.MFA.WebAuthn.VerifyEnabled = false + // Enroll cannot be enabled on its own + a.MFA.WebAuthn.EnrollEnabled = false + } + } + } // Convert the config values into easily comparable remoteConfig values - currentValue, err := ToTomlBytes(copy) + currentValue, err := ToTomlBytes(a) if err != nil { return nil, err } - copy.FromRemoteAuthConfig(remoteConfig) remoteCompare, err := ToTomlBytes(copy) if err != nil { return nil, err diff --git a/pkg/config/config.go b/pkg/config/config.go index 3703acc3b5..d21f1847c6 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -27,7 +27,6 @@ import ( "github.com/docker/go-units" "github.com/go-errors/errors" "github.com/go-viper/mapstructure/v2" - "github.com/golang-jwt/jwt/v5" "github.com/joho/godotenv" "github.com/spf13/afero" "github.com/spf13/viper" @@ -125,29 +124,6 @@ func (g Glob) Files(fsys fs.FS) ([]string, error) { return result, errors.Join(allErrors...) } -type CustomClaims struct { - // Overrides Issuer to maintain json order when marshalling - Issuer string `json:"iss,omitempty"` - Ref string `json:"ref,omitempty"` - Role string `json:"role"` - jwt.RegisteredClaims -} - -const ( - defaultJwtSecret = "super-secret-jwt-token-with-at-least-32-characters-long" - defaultJwtExpiry = 1983812996 -) - -func (c CustomClaims) NewToken() *jwt.Token { - if c.ExpiresAt == nil { - c.ExpiresAt = jwt.NewNumericDate(time.Unix(defaultJwtExpiry, 0)) - } - if len(c.Issuer) == 0 { - c.Issuer = "supabase-demo" - } - return jwt.NewWithClaims(jwt.SigningMethodHS256, c) -} - // We follow these rules when adding new config: // 1. Update init_config.toml (and init_config.test.toml) with the new key, default value, and comments to explain usage. // 2. Update config struct with new field and toml tag (spelled in snake_case). @@ -351,6 +327,10 @@ func NewConfig(editors ...ConfigEditor) config { Api: api{ Image: Images.Postgrest, KongImage: Images.Kong, + Tls: tlsKong{ + CertContent: kongCert, + KeyContent: kongKey, + }, }, Db: db{ Image: Images.Pg, @@ -398,9 +378,6 @@ func NewConfig(editors ...ConfigEditor) config { TestOTP: map[string]string{}, }, External: map[string]provider{}, - JwtSecret: Secret{ - Value: defaultJwtSecret, - }, }, Inbucket: inbucket{ Image: Images.Inbucket, @@ -429,6 +406,11 @@ func NewConfig(editors ...ConfigEditor) config { } var ( + //go:embed templates/certs/kong.local.crt + kongCert []byte + //go:embed templates/certs/kong.local.key + kongKey []byte + //go:embed templates/config.toml initConfigEmbed string initConfigTemplate = template.Must(template.New("initConfig").Parse(initConfigEmbed)) @@ -720,6 +702,16 @@ func (c *baseConfig) resolve(builder pathBuilder, fsys fs.FS) error { } c.Functions[slug] = function } + // Resolve TLS config + if c.Api.Enabled && c.Api.Tls.Enabled { + if len(c.Api.Tls.CertPath) > 0 { + c.Api.Tls.CertPath = path.Join(builder.SupabaseDirPath, c.Api.Tls.CertPath) + } + if len(c.Api.Tls.KeyPath) > 0 { + c.Api.Tls.KeyPath = path.Join(builder.SupabaseDirPath, c.Api.Tls.KeyPath) + } + } + // Resolve database config if c.Db.Seed.Enabled { for i, pattern := range c.Db.Seed.SqlPaths { if len(pattern) > 0 && !filepath.IsAbs(pattern) { @@ -753,6 +745,25 @@ func (c *config) Validate(fsys fs.FS) error { if c.Api.Port == 0 { return errors.New("Missing required field in config: api.port") } + if c.Api.Tls.Enabled { + var err error + if len(c.Api.Tls.CertPath) > 0 { + if len(c.Api.Tls.KeyPath) == 0 { + return errors.New("Missing required field in config: api.tls.key_path") + } + if c.Api.Tls.CertContent, err = fs.ReadFile(fsys, c.Api.Tls.CertPath); err != nil { + return errors.Errorf("failed to read TLS cert: %w", err) + } + } + if len(c.Api.Tls.KeyPath) > 0 { + if len(c.Api.Tls.CertPath) == 0 { + return errors.New("Missing required field in config: api.tls.cert_path") + } + if c.Api.Tls.KeyContent, err = fs.ReadFile(fsys, c.Api.Tls.KeyPath); err != nil { + return errors.Errorf("failed to read TLS key: %w", err) + } + } + } } // Validate db config if c.Db.Port == 0 { @@ -843,9 +854,6 @@ func (c *config) Validate(fsys fs.FS) error { return errors.Errorf("failed to decode signing keys: %w", err) } } - if err := c.Auth.generateAPIKeys(); err != nil { - return err - } if err := c.Auth.Hook.validate(); err != nil { return err } @@ -865,6 +873,9 @@ func (c *config) Validate(fsys fs.FS) error { return err } } + if err := c.Auth.generateAPIKeys(); err != nil { + return err + } // Validate functions config for name := range c.Functions { if err := ValidateFunctionSlug(name); err != nil { @@ -876,7 +887,6 @@ func (c *config) Validate(fsys fs.FS) error { return errors.New("Missing required field in config: edge_runtime.deno_version") case 1: c.EdgeRuntime.Image = deno1 - break case 2: break default: diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index fca80ad2df..43a292fc3b 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -45,6 +45,8 @@ func TestConfigParsing(t *testing.T) { fsys := fs.MapFS{ "supabase/config.toml": &fs.MapFile{Data: testInitConfigEmbed}, "supabase/templates/invite.html": &fs.MapFile{}, + "certs/my-cert.pem": &fs.MapFile{}, + "certs/my-key.pem": &fs.MapFile{}, } // Run test t.Setenv("TWILIO_AUTH_TOKEN", "token") @@ -509,7 +511,7 @@ func TestLoadFunctionErrorMessageParsing(t *testing.T) { // Run test err := config.Load("", fsys) // Check error contains both decode errors - assert.ErrorContains(t, err, `cannot parse 'functions[hello].verify_jwt' as bool: strconv.ParseBool: parsing "not-a-bool"`) + assert.ErrorContains(t, err, `'functions[hello].verify_jwt' cannot parse value as 'bool': strconv.ParseBool: invalid syntax`) }) t.Run("returns error for unknown function fields", func(t *testing.T) { @@ -524,8 +526,8 @@ func TestLoadFunctionErrorMessageParsing(t *testing.T) { } // Run test err := config.Load("", fsys) - assert.ErrorContains(t, err, `'functions[name]' expected a map, got 'string'`) - assert.ErrorContains(t, err, `'functions[verify_jwt]' expected a map, got 'bool'`) + assert.ErrorContains(t, err, `'functions[name]' expected a map or struct, got "string"`) + assert.ErrorContains(t, err, `'functions[verify_jwt]' expected a map or struct, got "bool"`) }) } diff --git a/pkg/config/templates/Dockerfile b/pkg/config/templates/Dockerfile index ca817f6dfc..5ab422cca8 100644 --- a/pkg/config/templates/Dockerfile +++ b/pkg/config/templates/Dockerfile @@ -1,19 +1,19 @@ # Exposed for updates by .github/dependabot.yml -FROM supabase/postgres:17.6.1.002 AS pg +FROM supabase/postgres:17.6.1.005 AS pg # Append to ServiceImages when adding new dependencies below FROM library/kong:2.8.1 AS kong FROM axllent/mailpit:v1.22.3 AS mailpit -FROM postgrest/postgrest:v13.0.6 AS postgrest +FROM postgrest/postgrest:v13.0.7 AS postgrest FROM supabase/postgres-meta:v0.91.6 AS pgmeta -FROM supabase/studio:2025.09.08-sha-67c1421 AS studio +FROM supabase/studio:2025.09.22-sha-7b3007d AS studio FROM darthsim/imgproxy:v3.8.0 AS imgproxy -FROM supabase/edge-runtime:v1.69.8 AS edgeruntime +FROM supabase/edge-runtime:v1.69.12 AS edgeruntime FROM timberio/vector:0.28.1-alpine AS vector -FROM supabase/supavisor:2.6.2 AS supavisor +FROM supabase/supavisor:2.7.0 AS supavisor FROM supabase/gotrue:v2.179.0 AS gotrue -FROM supabase/realtime:v2.47.0 AS realtime -FROM supabase/storage-api:v1.26.5 AS storage -FROM supabase/logflare:1.18.4 AS logflare +FROM supabase/realtime:v2.51.3 AS realtime +FROM supabase/storage-api:v1.27.4 AS storage +FROM supabase/logflare:1.22.3 AS logflare # Append to JobImages when adding new dependencies below FROM supabase/pgadmin-schema-diff:cli-0.0.5 AS differ FROM supabase/migra:3.0.1663481299 AS migra diff --git a/internal/status/kong.local.crt b/pkg/config/templates/certs/kong.local.crt similarity index 100% rename from internal/status/kong.local.crt rename to pkg/config/templates/certs/kong.local.crt diff --git a/internal/status/kong.local.key b/pkg/config/templates/certs/kong.local.key similarity index 100% rename from internal/status/kong.local.key rename to pkg/config/templates/certs/kong.local.key diff --git a/pkg/config/templates/config.toml b/pkg/config/templates/config.toml index 382217deef..334ef1ce7a 100644 --- a/pkg/config/templates/config.toml +++ b/pkg/config/templates/config.toml @@ -20,6 +20,9 @@ max_rows = 1000 [api.tls] # Enable HTTPS endpoints locally using a self-signed certificate. enabled = false +# Paths to self-signed certificate pair. +# cert_path = "../certs/my-cert.pem" +# key_path = "../certs/my-key.pem" [db] # Port to use for the local database URL. diff --git a/pkg/config/testdata/config.toml b/pkg/config/testdata/config.toml index 37b5158655..11c328d213 100644 --- a/pkg/config/testdata/config.toml +++ b/pkg/config/testdata/config.toml @@ -20,6 +20,9 @@ max_rows = 1000 [api.tls] # Enable HTTPS endpoints locally using a self-signed certificate. enabled = true +# Paths to self-signed certificate pair. +cert_path = "../certs/my-cert.pem" +key_path = "../certs/my-key.pem" [db] # Port to use for the local database URL. diff --git a/pkg/config/updater.go b/pkg/config/updater.go index 4e21594889..d53b43fa80 100644 --- a/pkg/config/updater.go +++ b/pkg/config/updater.go @@ -143,7 +143,7 @@ func (u *ConfigUpdater) UpdateAuthConfig(ctx context.Context, projectRef string, } else if authConfig.JSON200 == nil { return errors.Errorf("unexpected status %d: %s", authConfig.StatusCode(), string(authConfig.Body)) } - authDiff, err := c.DiffWithRemote(*authConfig.JSON200) + authDiff, err := c.DiffWithRemote(*authConfig.JSON200, filter...) if err != nil { return err } else if len(authDiff) == 0 { diff --git a/pkg/go.mod b/pkg/go.mod index c9df92d371..78d0b999cd 100644 --- a/pkg/go.mod +++ b/pkg/go.mod @@ -22,12 +22,12 @@ require ( github.com/joho/godotenv v1.5.1 github.com/oapi-codegen/nullable v1.1.0 github.com/oapi-codegen/runtime v1.1.2 - github.com/spf13/afero v1.14.0 - github.com/spf13/viper v1.20.1 + github.com/spf13/afero v1.15.0 + github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 github.com/tidwall/jsonc v0.3.2 golang.org/x/mod v0.28.0 - google.golang.org/grpc v1.75.0 + google.golang.org/grpc v1.75.1 ) require ( @@ -45,16 +45,15 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/cast v1.7.1 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.39.0 // indirect golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/text v0.28.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/go.sum b/pkg/go.sum index 81fddafe71..58135e5595 100644 --- a/pkg/go.sum +++ b/pkg/go.sum @@ -148,24 +148,24 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= -github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= -github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -196,17 +196,15 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -274,8 +272,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -292,8 +290,8 @@ golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= -google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/pkg/migration/scripts/dump_data.sh b/pkg/migration/scripts/dump_data.sh index c3c5d478bd..fed47b54e1 100755 --- a/pkg/migration/scripts/dump_data.sh +++ b/pkg/migration/scripts/dump_data.sh @@ -18,6 +18,10 @@ echo "SET session_replication_role = replica; # --column-inserts only column insert syntax is supported, ie. no copy from stdin # --schema '*' include all other schemas by default # +# Explanation of sed substitutions: +# +# - do not emit psql meta commands +# # Never delete SQL comments because multiline records may begin with them. pg_dump \ --data-only \ @@ -28,7 +32,8 @@ pg_dump \ --exclude-table "storage.migrations" \ --exclude-table "supabase_functions.migrations" \ --schema "$INCLUDED_SCHEMAS" \ - ${EXTRA_FLAGS:-} + ${EXTRA_FLAGS:-} \ +| sed -E 's/^\\(un)?restrict .*$/-- &/' # Reset session config generated by pg_dump echo "RESET ALL;" diff --git a/pkg/migration/scripts/dump_role.sh b/pkg/migration/scripts/dump_role.sh index cadc8c542c..96fe6a3068 100755 --- a/pkg/migration/scripts/dump_role.sh +++ b/pkg/migration/scripts/dump_role.sh @@ -13,6 +13,7 @@ export PGDATABASE="$PGDATABASE" # # Explanation of sed substitutions: # +# - do not emit psql meta commands # - do not create or alter reserved roles as they are blocked by supautils # - explicitly allow altering safe attributes, ie. statement_timeout, pgrst.* # - discard role attributes that require superuser, ie. nosuperuser, noreplication @@ -23,6 +24,7 @@ pg_dumpall \ --quote-all-identifier \ --no-role-passwords \ --no-comments \ +| sed -E 's/^\\(un)?restrict .*$/-- &/' \ | sed -E "s/^CREATE ROLE \"($RESERVED_ROLES)\"/-- &/" \ | sed -E "s/^ALTER ROLE \"($RESERVED_ROLES)\"/-- &/" \ | sed -E "s/ (NOSUPERUSER|NOREPLICATION)//g" \ diff --git a/pkg/migration/scripts/dump_schema.sh b/pkg/migration/scripts/dump_schema.sh index ee8407dad0..7f61c58665 100755 --- a/pkg/migration/scripts/dump_schema.sh +++ b/pkg/migration/scripts/dump_schema.sh @@ -14,6 +14,7 @@ export PGDATABASE="$PGDATABASE" # # Explanation of sed substitutions: # +# - do not emit psql meta commands # - do not alter superuser role "supabase_admin" # - do not alter foreign data wrappers owner # - do not include ACL changes on internal schemas @@ -28,6 +29,7 @@ pg_dump \ --role "postgres" \ --exclude-schema "${EXCLUDED_SCHEMAS:-}" \ ${EXTRA_FLAGS:-} \ +| sed -E 's/^\\(un)?restrict .*$/-- &/' \ | sed -E 's/^CREATE SCHEMA "/CREATE SCHEMA IF NOT EXISTS "/' \ | sed -E 's/^CREATE TABLE "/CREATE TABLE IF NOT EXISTS "/' \ | sed -E 's/^CREATE SEQUENCE "/CREATE SEQUENCE IF NOT EXISTS "/' \