From 09dceba7bb6d1e4c03a02cf8c0651d1af4ceca6e Mon Sep 17 00:00:00 2001 From: Max Metcalfe Date: Fri, 7 Nov 2025 13:03:34 -0300 Subject: [PATCH 01/10] remove confusing log from restrictions get --- internal/restrictions/get/get.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/restrictions/get/get.go b/internal/restrictions/get/get.go index f8e27342d..a608f6bbf 100644 --- a/internal/restrictions/get/get.go +++ b/internal/restrictions/get/get.go @@ -19,6 +19,5 @@ func Run(ctx context.Context, projectRef string) error { fmt.Printf("DB Allowed IPv4 CIDRs: %+v\n", resp.JSON200.Config.DbAllowedCidrs) fmt.Printf("DB Allowed IPv6 CIDRs: %+v\n", resp.JSON200.Config.DbAllowedCidrsV6) - fmt.Printf("Restrictions applied successfully: %+v\n", resp.JSON200.Status == "applied") return nil } From 2a30c9cfd5c4fa607deee1a0d5b5c6e8bac12d4c Mon Sep 17 00:00:00 2001 From: Max Metcalfe Date: Fri, 7 Nov 2025 13:03:53 -0300 Subject: [PATCH 02/10] improve update logic --- internal/restrictions/update/update.go | 88 +++++++++++++++++++++----- 1 file changed, 73 insertions(+), 15 deletions(-) diff --git a/internal/restrictions/update/update.go b/internal/restrictions/update/update.go index 98a70da98..15ca6125b 100644 --- a/internal/restrictions/update/update.go +++ b/internal/restrictions/update/update.go @@ -4,14 +4,76 @@ import ( "context" "fmt" "net" + "slices" "github.com/go-errors/errors" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/api" ) +// Run updates the network restriction lists using the provided CIDRs. func Run(ctx context.Context, projectRef string, dbCidrsToAllow []string, bypassCidrChecks bool) error { - // 1. separate CIDR to v4 and v6 + newCidrsBody, err := buildRequestBody(dbCidrsToAllow, bypassCidrChecks) + if err != nil { + return err + } + + resp, err := utils.GetSupabase().V1GetNetworkRestrictionsWithResponse(ctx, projectRef) + if err != nil { + return errors.Errorf("failed to retrieve network restrictions: %w", err) + } + if resp.JSON200 == nil { + return errors.New("failed to retrieve network restrictions: " + string(resp.Body)) + } + + currentV4 := []string{} + if resp.JSON200.Config.DbAllowedCidrs != nil { + currentV4 = append(currentV4, *resp.JSON200.Config.DbAllowedCidrs...) + } + currentV6 := []string{} + if resp.JSON200.Config.DbAllowedCidrsV6 != nil { + currentV6 = append(currentV6, *resp.JSON200.Config.DbAllowedCidrsV6...) + } + + if newCidrsBody.DbAllowedCidrs != nil { + currentV4 = appendUnique(currentV4, *newCidrsBody.DbAllowedCidrs...) + } + if newCidrsBody.DbAllowedCidrsV6 != nil { + currentV6 = appendUnique(currentV6, *newCidrsBody.DbAllowedCidrsV6...) + } + + body := api.V1UpdateNetworkRestrictionsJSONRequestBody{ + DbAllowedCidrs: ¤tV4, + DbAllowedCidrsV6: ¤tV6, + } + return Apply(ctx, projectRef, body) +} + +// Apply submits a pre-built network restriction payload to the Supabase API. +func Apply(ctx context.Context, projectRef string, body api.V1UpdateNetworkRestrictionsJSONRequestBody) error { + resp, err := utils.GetSupabase().V1UpdateNetworkRestrictionsWithResponse(ctx, projectRef, body) + if err != nil { + return errors.Errorf("failed to apply network restrictions: %w", err) + } + if resp.JSON201 == nil { + return errors.New("failed to apply network restrictions: " + string(resp.Body)) + } + + if resp.JSON201.Config.DbAllowedCidrs != nil { + fmt.Printf("DB Allowed IPv4 CIDRs: %+v\n", *resp.JSON201.Config.DbAllowedCidrs) + } else { + fmt.Println("DB Allowed IPv4 CIDRs: []") + } + if resp.JSON201.Config.DbAllowedCidrsV6 != nil { + fmt.Printf("DB Allowed IPv6 CIDRs: %+v\n", *resp.JSON201.Config.DbAllowedCidrsV6) + } else { + fmt.Println("DB Allowed IPv6 CIDRs: []") + } + fmt.Printf("Restrictions applied successfully") + return nil +} + +func buildRequestBody(dbCidrsToAllow []string, bypassCidrChecks bool) (api.V1UpdateNetworkRestrictionsJSONRequestBody, error) { body := api.V1UpdateNetworkRestrictionsJSONRequestBody{ DbAllowedCidrs: &[]string{}, DbAllowedCidrsV6: &[]string{}, @@ -19,10 +81,10 @@ func Run(ctx context.Context, projectRef string, dbCidrsToAllow []string, bypass for _, cidr := range dbCidrsToAllow { ip, _, err := net.ParseCIDR(cidr) if err != nil { - return errors.Errorf("failed to parse IP: %s", cidr) + return api.V1UpdateNetworkRestrictionsJSONRequestBody{}, errors.Errorf("failed to parse IP: %s", cidr) } if ip.IsPrivate() && !bypassCidrChecks { - return errors.Errorf("private IP provided: %s", cidr) + return api.V1UpdateNetworkRestrictionsJSONRequestBody{}, errors.Errorf("private IP provided: %s", cidr) } if ip.To4() != nil { *body.DbAllowedCidrs = append(*body.DbAllowedCidrs, cidr) @@ -30,18 +92,14 @@ func Run(ctx context.Context, projectRef string, dbCidrsToAllow []string, bypass *body.DbAllowedCidrsV6 = append(*body.DbAllowedCidrsV6, cidr) } } + return body, nil +} - // 2. update restrictions - resp, err := utils.GetSupabase().V1UpdateNetworkRestrictionsWithResponse(ctx, projectRef, body) - if err != nil { - return errors.Errorf("failed to apply network restrictions: %w", err) - } - if resp.JSON201 == nil { - return errors.New("failed to apply network restrictions: " + string(resp.Body)) +func appendUnique(existing []string, additions ...string) []string { + for _, cidr := range additions { + if !slices.Contains(existing, cidr) { + existing = append(existing, cidr) + } } - - fmt.Printf("DB Allowed IPv4 CIDRs: %+v\n", resp.JSON201.Config.DbAllowedCidrs) - fmt.Printf("DB Allowed IPv6 CIDRs: %+v\n", resp.JSON201.Config.DbAllowedCidrsV6) - fmt.Printf("Restrictions applied successfully: %+v\n", resp.JSON201.Status == "applied") - return nil + return existing } From acc34e7f74baa21d2c694f1ed270ba139248e5a7 Mon Sep 17 00:00:00 2001 From: Max Metcalfe Date: Fri, 7 Nov 2025 13:03:57 -0300 Subject: [PATCH 03/10] update test --- internal/restrictions/update/update_test.go | 42 +++++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/internal/restrictions/update/update_test.go b/internal/restrictions/update/update_test.go index 5a6583ea8..b58837d97 100644 --- a/internal/restrictions/update/update_test.go +++ b/internal/restrictions/update/update_test.go @@ -22,19 +22,33 @@ func TestUpdateRestrictionsCommand(t *testing.T) { t.Run("updates v4 and v6 CIDR", func(t *testing.T) { // Setup mock api defer gock.OffAll() + currentV4 := []string{"2.2.2.2/32"} + currentV6 := []string{"2001:db8:ffff::0/64"} + respBody := api.NetworkRestrictionsResponse{} + respBody.Config.DbAllowedCidrs = ¤tV4 + respBody.Config.DbAllowedCidrsV6 = ¤tV6 + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + projectRef + "/network-restrictions"). + Reply(http.StatusOK). + JSON(respBody) + expectedV4 := append([]string{}, currentV4...) + expectedV4 = append(expectedV4, "12.3.4.5/32", "1.2.3.1/24") + expectedV6 := append([]string{}, currentV6...) + expectedV6 = append(expectedV6, "2001:db8:abcd:0012::0/64") gock.New(utils.DefaultApiHost). Post("/v1/projects/" + projectRef + "/network-restrictions/apply"). MatchType("json"). JSON(api.NetworkRestrictionsRequest{ - DbAllowedCidrs: &[]string{"12.3.4.5/32", "1.2.3.1/24"}, - DbAllowedCidrsV6: &[]string{"2001:db8:abcd:0012::0/64"}, + DbAllowedCidrs: &expectedV4, + DbAllowedCidrsV6: &expectedV6, }). Reply(http.StatusCreated). JSON(api.NetworkRestrictionsResponse{ Status: api.NetworkRestrictionsResponseStatus("applied"), }) // Run test - err := Run(context.Background(), projectRef, []string{"12.3.4.5/32", "2001:db8:abcd:0012::0/64", "1.2.3.1/24"}, false) + // Include duplicates and an existing CIDR to verify deduplication + err := Run(context.Background(), projectRef, []string{"12.3.4.5/32", "12.3.4.5/32", "1.2.3.1/24", "2.2.2.2/32", "2001:db8:abcd:0012::0/64", "2001:db8:abcd:0012::0/64"}, false) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -44,6 +58,13 @@ func TestUpdateRestrictionsCommand(t *testing.T) { errNetwork := errors.New("network error") // Setup mock api defer gock.OffAll() + respBody := api.NetworkRestrictionsResponse{} + respBody.Config.DbAllowedCidrs = &[]string{} + respBody.Config.DbAllowedCidrsV6 = &[]string{} + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + projectRef + "/network-restrictions"). + Reply(http.StatusOK). + JSON(respBody) gock.New(utils.DefaultApiHost). Post("/v1/projects/" + projectRef + "/network-restrictions/apply"). MatchType("json"). @@ -62,6 +83,13 @@ func TestUpdateRestrictionsCommand(t *testing.T) { t.Run("throws error on server unavailable", func(t *testing.T) { // Setup mock api defer gock.OffAll() + respBody := api.NetworkRestrictionsResponse{} + respBody.Config.DbAllowedCidrs = &[]string{} + respBody.Config.DbAllowedCidrsV6 = &[]string{} + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + projectRef + "/network-restrictions"). + Reply(http.StatusOK). + JSON(respBody) gock.New(utils.DefaultApiHost). Post("/v1/projects/" + projectRef + "/network-restrictions/apply"). MatchType("json"). @@ -76,6 +104,7 @@ func TestUpdateRestrictionsCommand(t *testing.T) { assert.ErrorContains(t, err, "failed to apply network restrictions:") assert.Empty(t, apitest.ListUnmatchedRequests()) }) + } func TestValidateCIDR(t *testing.T) { @@ -87,6 +116,13 @@ func TestValidateCIDR(t *testing.T) { t.Run("bypasses private subnet checks", func(t *testing.T) { // Setup mock api defer gock.OffAll() + respBody := api.NetworkRestrictionsResponse{} + respBody.Config.DbAllowedCidrs = &[]string{} + respBody.Config.DbAllowedCidrsV6 = &[]string{} + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + projectRef + "/network-restrictions"). + Reply(http.StatusOK). + JSON(respBody) gock.New(utils.DefaultApiHost). Post("/v1/projects/" + projectRef + "/network-restrictions/apply"). MatchType("json"). From 36a79185880e188404e2d8409862c508589212b9 Mon Sep 17 00:00:00 2001 From: Max Metcalfe Date: Tue, 11 Nov 2025 09:14:20 -0300 Subject: [PATCH 04/10] move append logic behind flag --- cmd/restrictions.go | 4 +- internal/restrictions/update/update.go | 59 ++++++++++++--------- internal/restrictions/update/update_test.go | 33 +++--------- 3 files changed, 42 insertions(+), 54 deletions(-) diff --git a/cmd/restrictions.go b/cmd/restrictions.go index 4358d6132..6bc1973d0 100644 --- a/cmd/restrictions.go +++ b/cmd/restrictions.go @@ -16,12 +16,13 @@ var ( dbCidrsToAllow []string bypassCidrChecks bool + appendMode bool restrictionsUpdateCmd = &cobra.Command{ Use: "update", Short: "Update network restrictions", RunE: func(cmd *cobra.Command, args []string) error { - return update.Run(cmd.Context(), flags.ProjectRef, dbCidrsToAllow, bypassCidrChecks) + return update.Run(cmd.Context(), flags.ProjectRef, dbCidrsToAllow, bypassCidrChecks, appendMode) }, } @@ -38,6 +39,7 @@ func init() { restrictionsCmd.PersistentFlags().StringVar(&flags.ProjectRef, "project-ref", "", "Project ref of the Supabase project.") restrictionsUpdateCmd.Flags().StringSliceVar(&dbCidrsToAllow, "db-allow-cidr", []string{}, "CIDR to allow DB connections from.") restrictionsUpdateCmd.Flags().BoolVar(&bypassCidrChecks, "bypass-cidr-checks", false, "Bypass some of the CIDR validation checks.") + restrictionsUpdateCmd.Flags().BoolVar(&appendMode, "append", false, "Append to existing restrictions instead of replacing them.") restrictionsCmd.AddCommand(restrictionsGetCmd) restrictionsCmd.AddCommand(restrictionsUpdateCmd) rootCmd.AddCommand(restrictionsCmd) diff --git a/internal/restrictions/update/update.go b/internal/restrictions/update/update.go index 15ca6125b..892cfe149 100644 --- a/internal/restrictions/update/update.go +++ b/internal/restrictions/update/update.go @@ -12,40 +12,47 @@ import ( ) // Run updates the network restriction lists using the provided CIDRs. -func Run(ctx context.Context, projectRef string, dbCidrsToAllow []string, bypassCidrChecks bool) error { +func Run(ctx context.Context, projectRef string, dbCidrsToAllow []string, bypassCidrChecks bool, appendMode bool) error { newCidrsBody, err := buildRequestBody(dbCidrsToAllow, bypassCidrChecks) if err != nil { return err } - resp, err := utils.GetSupabase().V1GetNetworkRestrictionsWithResponse(ctx, projectRef) - if err != nil { - return errors.Errorf("failed to retrieve network restrictions: %w", err) - } - if resp.JSON200 == nil { - return errors.New("failed to retrieve network restrictions: " + string(resp.Body)) - } + var body api.V1UpdateNetworkRestrictionsJSONRequestBody - currentV4 := []string{} - if resp.JSON200.Config.DbAllowedCidrs != nil { - currentV4 = append(currentV4, *resp.JSON200.Config.DbAllowedCidrs...) - } - currentV6 := []string{} - if resp.JSON200.Config.DbAllowedCidrsV6 != nil { - currentV6 = append(currentV6, *resp.JSON200.Config.DbAllowedCidrsV6...) - } + if appendMode { + resp, err := utils.GetSupabase().V1GetNetworkRestrictionsWithResponse(ctx, projectRef) + if err != nil { + return errors.Errorf("failed to retrieve network restrictions: %w", err) + } + if resp.JSON200 == nil { + return errors.New("failed to retrieve network restrictions: " + string(resp.Body)) + } - if newCidrsBody.DbAllowedCidrs != nil { - currentV4 = appendUnique(currentV4, *newCidrsBody.DbAllowedCidrs...) - } - if newCidrsBody.DbAllowedCidrsV6 != nil { - currentV6 = appendUnique(currentV6, *newCidrsBody.DbAllowedCidrsV6...) - } + currentV4 := []string{} + if resp.JSON200.Config.DbAllowedCidrs != nil { + currentV4 = append(currentV4, *resp.JSON200.Config.DbAllowedCidrs...) + } + currentV6 := []string{} + if resp.JSON200.Config.DbAllowedCidrsV6 != nil { + currentV6 = append(currentV6, *resp.JSON200.Config.DbAllowedCidrsV6...) + } - body := api.V1UpdateNetworkRestrictionsJSONRequestBody{ - DbAllowedCidrs: ¤tV4, - DbAllowedCidrsV6: ¤tV6, + if newCidrsBody.DbAllowedCidrs != nil { + currentV4 = appendUnique(currentV4, *newCidrsBody.DbAllowedCidrs...) + } + if newCidrsBody.DbAllowedCidrsV6 != nil { + currentV6 = appendUnique(currentV6, *newCidrsBody.DbAllowedCidrsV6...) + } + + body = api.V1UpdateNetworkRestrictionsJSONRequestBody{ + DbAllowedCidrs: ¤tV4, + DbAllowedCidrsV6: ¤tV6, + } + } else { + body = newCidrsBody } + return Apply(ctx, projectRef, body) } @@ -69,7 +76,7 @@ func Apply(ctx context.Context, projectRef string, body api.V1UpdateNetworkRestr } else { fmt.Println("DB Allowed IPv6 CIDRs: []") } - fmt.Printf("Restrictions applied successfully") + fmt.Printf("Restrictions applied successfully \n") return nil } diff --git a/internal/restrictions/update/update_test.go b/internal/restrictions/update/update_test.go index b58837d97..c73f0f471 100644 --- a/internal/restrictions/update/update_test.go +++ b/internal/restrictions/update/update_test.go @@ -48,7 +48,7 @@ func TestUpdateRestrictionsCommand(t *testing.T) { }) // Run test // Include duplicates and an existing CIDR to verify deduplication - err := Run(context.Background(), projectRef, []string{"12.3.4.5/32", "12.3.4.5/32", "1.2.3.1/24", "2.2.2.2/32", "2001:db8:abcd:0012::0/64", "2001:db8:abcd:0012::0/64"}, false) + err := Run(context.Background(), projectRef, []string{"12.3.4.5/32", "12.3.4.5/32", "1.2.3.1/24", "2.2.2.2/32", "2001:db8:abcd:0012::0/64", "2001:db8:abcd:0012::0/64"}, false, true) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -58,13 +58,6 @@ func TestUpdateRestrictionsCommand(t *testing.T) { errNetwork := errors.New("network error") // Setup mock api defer gock.OffAll() - respBody := api.NetworkRestrictionsResponse{} - respBody.Config.DbAllowedCidrs = &[]string{} - respBody.Config.DbAllowedCidrsV6 = &[]string{} - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/network-restrictions"). - Reply(http.StatusOK). - JSON(respBody) gock.New(utils.DefaultApiHost). Post("/v1/projects/" + projectRef + "/network-restrictions/apply"). MatchType("json"). @@ -74,7 +67,7 @@ func TestUpdateRestrictionsCommand(t *testing.T) { }). ReplyError(errNetwork) // Run test - err := Run(context.Background(), projectRef, []string{}, true) + err := Run(context.Background(), projectRef, []string{}, true, false) // Check error assert.ErrorIs(t, err, errNetwork) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -83,13 +76,6 @@ func TestUpdateRestrictionsCommand(t *testing.T) { t.Run("throws error on server unavailable", func(t *testing.T) { // Setup mock api defer gock.OffAll() - respBody := api.NetworkRestrictionsResponse{} - respBody.Config.DbAllowedCidrs = &[]string{} - respBody.Config.DbAllowedCidrsV6 = &[]string{} - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/network-restrictions"). - Reply(http.StatusOK). - JSON(respBody) gock.New(utils.DefaultApiHost). Post("/v1/projects/" + projectRef + "/network-restrictions/apply"). MatchType("json"). @@ -99,7 +85,7 @@ func TestUpdateRestrictionsCommand(t *testing.T) { }). Reply(http.StatusServiceUnavailable) // Run test - err := Run(context.Background(), projectRef, []string{}, true) + err := Run(context.Background(), projectRef, []string{}, true, false) // Check error assert.ErrorContains(t, err, "failed to apply network restrictions:") assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -116,13 +102,6 @@ func TestValidateCIDR(t *testing.T) { t.Run("bypasses private subnet checks", func(t *testing.T) { // Setup mock api defer gock.OffAll() - respBody := api.NetworkRestrictionsResponse{} - respBody.Config.DbAllowedCidrs = &[]string{} - respBody.Config.DbAllowedCidrsV6 = &[]string{} - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/network-restrictions"). - Reply(http.StatusOK). - JSON(respBody) gock.New(utils.DefaultApiHost). Post("/v1/projects/" + projectRef + "/network-restrictions/apply"). MatchType("json"). @@ -135,7 +114,7 @@ func TestValidateCIDR(t *testing.T) { Status: api.NetworkRestrictionsResponseStatus("applied"), }) // Run test - err := Run(context.Background(), projectRef, []string{"10.0.0.0/8"}, true) + err := Run(context.Background(), projectRef, []string{"10.0.0.0/8"}, true, false) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -143,14 +122,14 @@ func TestValidateCIDR(t *testing.T) { t.Run("throws error on private subnet", func(t *testing.T) { // Run test - err := Run(context.Background(), projectRef, []string{"12.3.4.5/32", "10.0.0.0/8", "1.2.3.1/24"}, false) + err := Run(context.Background(), projectRef, []string{"12.3.4.5/32", "10.0.0.0/8", "1.2.3.1/24"}, false, false) // Check error assert.ErrorContains(t, err, "private IP provided: 10.0.0.0/8") }) t.Run("throws error on invalid subnet", func(t *testing.T) { // Run test - err := Run(context.Background(), projectRef, []string{"12.3.4.5", "10.0.0.0/8", "1.2.3.1/24"}, false) + err := Run(context.Background(), projectRef, []string{"12.3.4.5", "10.0.0.0/8", "1.2.3.1/24"}, false, false) // Check error assert.ErrorContains(t, err, "failed to parse IP: 12.3.4.5") }) From c7516b593e0b49ab49a82bb24665021fc56cf86c Mon Sep 17 00:00:00 2001 From: Max Metcalfe Date: Tue, 11 Nov 2025 21:12:54 -0300 Subject: [PATCH 05/10] remove trailing whitespace --- internal/restrictions/update/update_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/restrictions/update/update_test.go b/internal/restrictions/update/update_test.go index c73f0f471..ceabbb0f8 100644 --- a/internal/restrictions/update/update_test.go +++ b/internal/restrictions/update/update_test.go @@ -133,4 +133,4 @@ func TestValidateCIDR(t *testing.T) { // Check error assert.ErrorContains(t, err, "failed to parse IP: 12.3.4.5") }) -} +} \ No newline at end of file From a829888df62ccc63f654468296d92830d1b11b49 Mon Sep 17 00:00:00 2001 From: Max Metcalfe Date: Tue, 11 Nov 2025 21:19:51 -0300 Subject: [PATCH 06/10] fix lint --- internal/restrictions/update/update_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/restrictions/update/update_test.go b/internal/restrictions/update/update_test.go index ceabbb0f8..ac4675b56 100644 --- a/internal/restrictions/update/update_test.go +++ b/internal/restrictions/update/update_test.go @@ -90,7 +90,6 @@ func TestUpdateRestrictionsCommand(t *testing.T) { assert.ErrorContains(t, err, "failed to apply network restrictions:") assert.Empty(t, apitest.ListUnmatchedRequests()) }) - } func TestValidateCIDR(t *testing.T) { @@ -133,4 +132,5 @@ func TestValidateCIDR(t *testing.T) { // Check error assert.ErrorContains(t, err, "failed to parse IP: 12.3.4.5") }) -} \ No newline at end of file +} + From 616edca600f7f5602829be16fea0cfbec378a313 Mon Sep 17 00:00:00 2001 From: Max Metcalfe Date: Tue, 11 Nov 2025 21:22:21 -0300 Subject: [PATCH 07/10] try another lint fix --- internal/restrictions/update/update_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/restrictions/update/update_test.go b/internal/restrictions/update/update_test.go index ac4675b56..751ed015f 100644 --- a/internal/restrictions/update/update_test.go +++ b/internal/restrictions/update/update_test.go @@ -133,4 +133,3 @@ func TestValidateCIDR(t *testing.T) { assert.ErrorContains(t, err, "failed to parse IP: 12.3.4.5") }) } - From 8fdfb997c6d475d581f905dd907b259daf60cde8 Mon Sep 17 00:00:00 2001 From: Max Metcalfe Date: Wed, 12 Nov 2025 10:46:40 -0300 Subject: [PATCH 08/10] update to use PATCH endpoint --- internal/restrictions/update/update.go | 92 +++++++++++---------- internal/restrictions/update/update_test.go | 48 +++++++---- 2 files changed, 80 insertions(+), 60 deletions(-) diff --git a/internal/restrictions/update/update.go b/internal/restrictions/update/update.go index 892cfe149..51025e494 100644 --- a/internal/restrictions/update/update.go +++ b/internal/restrictions/update/update.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net" - "slices" "github.com/go-errors/errors" "github.com/supabase/cli/internal/utils" @@ -13,50 +12,40 @@ import ( // Run updates the network restriction lists using the provided CIDRs. func Run(ctx context.Context, projectRef string, dbCidrsToAllow []string, bypassCidrChecks bool, appendMode bool) error { + if appendMode { + // Use PATCH endpoint for append mode + return runPatch(ctx, projectRef, dbCidrsToAllow, bypassCidrChecks) + } + + // Use POST endpoint for replace mode newCidrsBody, err := buildRequestBody(dbCidrsToAllow, bypassCidrChecks) if err != nil { return err } - var body api.V1UpdateNetworkRestrictionsJSONRequestBody - - if appendMode { - resp, err := utils.GetSupabase().V1GetNetworkRestrictionsWithResponse(ctx, projectRef) - if err != nil { - return errors.Errorf("failed to retrieve network restrictions: %w", err) - } - if resp.JSON200 == nil { - return errors.New("failed to retrieve network restrictions: " + string(resp.Body)) - } - - currentV4 := []string{} - if resp.JSON200.Config.DbAllowedCidrs != nil { - currentV4 = append(currentV4, *resp.JSON200.Config.DbAllowedCidrs...) - } - currentV6 := []string{} - if resp.JSON200.Config.DbAllowedCidrsV6 != nil { - currentV6 = append(currentV6, *resp.JSON200.Config.DbAllowedCidrsV6...) - } + return Apply(ctx, projectRef, newCidrsBody) +} - if newCidrsBody.DbAllowedCidrs != nil { - currentV4 = appendUnique(currentV4, *newCidrsBody.DbAllowedCidrs...) - } - if newCidrsBody.DbAllowedCidrsV6 != nil { - currentV6 = appendUnique(currentV6, *newCidrsBody.DbAllowedCidrsV6...) - } +func runPatch(ctx context.Context, projectRef string, dbCidrsToAllow []string, bypassCidrChecks bool) error { + addBody, err := buildRequestBody(dbCidrsToAllow, bypassCidrChecks) + if err != nil { + return err + } - body = api.V1UpdateNetworkRestrictionsJSONRequestBody{ - DbAllowedCidrs: ¤tV4, - DbAllowedCidrsV6: ¤tV6, - } - } else { - body = newCidrsBody + patchBody := api.V1PatchNetworkRestrictionsJSONRequestBody{ + Add: &struct { + DbAllowedCidrs *[]string `json:"dbAllowedCidrs,omitempty"` + DbAllowedCidrsV6 *[]string `json:"dbAllowedCidrsV6,omitempty"` + }{ + DbAllowedCidrs: addBody.DbAllowedCidrs, + DbAllowedCidrsV6: addBody.DbAllowedCidrsV6, + }, } - return Apply(ctx, projectRef, body) + return ApplyPatch(ctx, projectRef, patchBody) } -// Apply submits a pre-built network restriction payload to the Supabase API. +// Apply submits a pre-built network restriction payload to the Supabase API using POST (replace mode). func Apply(ctx context.Context, projectRef string, body api.V1UpdateNetworkRestrictionsJSONRequestBody) error { resp, err := utils.GetSupabase().V1UpdateNetworkRestrictionsWithResponse(ctx, projectRef, body) if err != nil { @@ -76,10 +65,34 @@ func Apply(ctx context.Context, projectRef string, body api.V1UpdateNetworkRestr } else { fmt.Println("DB Allowed IPv6 CIDRs: []") } - fmt.Printf("Restrictions applied successfully \n") + fmt.Printf("Restrictions applied successfully\n") return nil } +// ApplyPatch submits a network restriction payload using PATCH (add/remove mode). +func ApplyPatch(ctx context.Context, projectRef string, body api.V1PatchNetworkRestrictionsJSONRequestBody) error { + resp, err := utils.GetSupabase().V1PatchNetworkRestrictionsWithResponse(ctx, projectRef, body) + if err != nil { + return errors.Errorf("failed to apply network restrictions: %w", err) + } + if resp.JSON200 == nil { + return errors.New("failed to apply network restrictions: " + string(resp.Body)) + } + + if resp.JSON200.Config.DbAllowedCidrs != nil { + fmt.Println("DB Allowed CIDRs:") + for _, cidr := range *resp.JSON200.Config.DbAllowedCidrs { + fmt.Printf(" - %s (%s)\n", cidr.Address, cidr.Type) + } + } else { + fmt.Println("DB Allowed CIDRs: []") + } + fmt.Printf("Restrictions applied successfully\n") + return nil +} + + + func buildRequestBody(dbCidrsToAllow []string, bypassCidrChecks bool) (api.V1UpdateNetworkRestrictionsJSONRequestBody, error) { body := api.V1UpdateNetworkRestrictionsJSONRequestBody{ DbAllowedCidrs: &[]string{}, @@ -101,12 +114,3 @@ func buildRequestBody(dbCidrsToAllow []string, bypassCidrChecks bool) (api.V1Upd } return body, nil } - -func appendUnique(existing []string, additions ...string) []string { - for _, cidr := range additions { - if !slices.Contains(existing, cidr) { - existing = append(existing, cidr) - } - } - return existing -} diff --git a/internal/restrictions/update/update_test.go b/internal/restrictions/update/update_test.go index 751ed015f..087f3d15d 100644 --- a/internal/restrictions/update/update_test.go +++ b/internal/restrictions/update/update_test.go @@ -19,22 +19,11 @@ func TestUpdateRestrictionsCommand(t *testing.T) { token := apitest.RandomAccessToken(t) t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) - t.Run("updates v4 and v6 CIDR", func(t *testing.T) { + t.Run("replaces v4 and v6 CIDRs when append mode is false", func(t *testing.T) { // Setup mock api defer gock.OffAll() - currentV4 := []string{"2.2.2.2/32"} - currentV6 := []string{"2001:db8:ffff::0/64"} - respBody := api.NetworkRestrictionsResponse{} - respBody.Config.DbAllowedCidrs = ¤tV4 - respBody.Config.DbAllowedCidrsV6 = ¤tV6 - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/network-restrictions"). - Reply(http.StatusOK). - JSON(respBody) - expectedV4 := append([]string{}, currentV4...) - expectedV4 = append(expectedV4, "12.3.4.5/32", "1.2.3.1/24") - expectedV6 := append([]string{}, currentV6...) - expectedV6 = append(expectedV6, "2001:db8:abcd:0012::0/64") + expectedV4 := []string{"12.3.4.5/32", "1.2.3.1/24"} + expectedV6 := []string{"2001:db8:abcd:0012::0/64"} gock.New(utils.DefaultApiHost). Post("/v1/projects/" + projectRef + "/network-restrictions/apply"). MatchType("json"). @@ -47,8 +36,35 @@ func TestUpdateRestrictionsCommand(t *testing.T) { Status: api.NetworkRestrictionsResponseStatus("applied"), }) // Run test - // Include duplicates and an existing CIDR to verify deduplication - err := Run(context.Background(), projectRef, []string{"12.3.4.5/32", "12.3.4.5/32", "1.2.3.1/24", "2.2.2.2/32", "2001:db8:abcd:0012::0/64", "2001:db8:abcd:0012::0/64"}, false, true) + err := Run(context.Background(), projectRef, []string{"12.3.4.5/32", "1.2.3.1/24", "2001:db8:abcd:0012::0/64"}, false, false) + // Check error + assert.NoError(t, err) + assert.Empty(t, apitest.ListUnmatchedRequests()) + }) + + t.Run("appends v4 and v6 CIDRs using PATCH when append mode is true", func(t *testing.T) { + // Setup mock api + defer gock.OffAll() + addV4 := []string{"12.3.4.5/32", "1.2.3.1/24"} + addV6 := []string{"2001:db8:abcd:0012::0/64"} + gock.New(utils.DefaultApiHost). + Patch("/v1/projects/" + projectRef + "/network-restrictions"). + MatchType("json"). + JSON(api.NetworkRestrictionsPatchRequest{ + Add: &struct { + DbAllowedCidrs *[]string `json:"dbAllowedCidrs,omitempty"` + DbAllowedCidrsV6 *[]string `json:"dbAllowedCidrsV6,omitempty"` + }{ + DbAllowedCidrs: &addV4, + DbAllowedCidrsV6: &addV6, + }, + }). + Reply(http.StatusOK). + JSON(api.NetworkRestrictionsV2Response{ + Status: api.NetworkRestrictionsV2ResponseStatus("applied"), + }) + // Run test + err := Run(context.Background(), projectRef, []string{"12.3.4.5/32", "1.2.3.1/24", "2001:db8:abcd:0012::0/64"}, false, true) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) From 27dae926bee23a2c3e21e1e6ed9e345254c4648d Mon Sep 17 00:00:00 2001 From: Max Metcalfe Date: Wed, 12 Nov 2025 10:57:08 -0300 Subject: [PATCH 09/10] fix lint --- internal/restrictions/update/update.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/restrictions/update/update.go b/internal/restrictions/update/update.go index 51025e494..671eabd16 100644 --- a/internal/restrictions/update/update.go +++ b/internal/restrictions/update/update.go @@ -91,8 +91,6 @@ func ApplyPatch(ctx context.Context, projectRef string, body api.V1PatchNetworkR return nil } - - func buildRequestBody(dbCidrsToAllow []string, bypassCidrChecks bool) (api.V1UpdateNetworkRestrictionsJSONRequestBody, error) { body := api.V1UpdateNetworkRestrictionsJSONRequestBody{ DbAllowedCidrs: &[]string{}, From 7439d428c816813fb767821e46e0a41f7af1c13a Mon Sep 17 00:00:00 2001 From: Qiao Han Date: Wed, 19 Nov 2025 18:06:59 +0800 Subject: [PATCH 10/10] chore: minor refactor --- internal/restrictions/get/get.go | 2 + internal/restrictions/update/update.go | 120 ++++++++------------ internal/restrictions/update/update_test.go | 6 +- 3 files changed, 53 insertions(+), 75 deletions(-) diff --git a/internal/restrictions/get/get.go b/internal/restrictions/get/get.go index a608f6bbf..fa5e54d95 100644 --- a/internal/restrictions/get/get.go +++ b/internal/restrictions/get/get.go @@ -6,6 +6,7 @@ import ( "github.com/go-errors/errors" "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/pkg/api" ) func Run(ctx context.Context, projectRef string) error { @@ -19,5 +20,6 @@ func Run(ctx context.Context, projectRef string) error { fmt.Printf("DB Allowed IPv4 CIDRs: %+v\n", resp.JSON200.Config.DbAllowedCidrs) fmt.Printf("DB Allowed IPv6 CIDRs: %+v\n", resp.JSON200.Config.DbAllowedCidrsV6) + fmt.Printf("Restrictions applied successfully: %+v\n", resp.JSON200.Status == api.NetworkRestrictionsResponseStatusApplied) return nil } diff --git a/internal/restrictions/update/update.go b/internal/restrictions/update/update.go index 671eabd16..eb9e2f555 100644 --- a/internal/restrictions/update/update.go +++ b/internal/restrictions/update/update.go @@ -12,41 +12,31 @@ import ( // Run updates the network restriction lists using the provided CIDRs. func Run(ctx context.Context, projectRef string, dbCidrsToAllow []string, bypassCidrChecks bool, appendMode bool) error { - if appendMode { - // Use PATCH endpoint for append mode - return runPatch(ctx, projectRef, dbCidrsToAllow, bypassCidrChecks) - } - - // Use POST endpoint for replace mode - newCidrsBody, err := buildRequestBody(dbCidrsToAllow, bypassCidrChecks) - if err != nil { - return err + // 1. separate CIDR to v4 and v6 + body := api.V1UpdateNetworkRestrictionsJSONRequestBody{ + DbAllowedCidrs: &[]string{}, + DbAllowedCidrsV6: &[]string{}, } - - return Apply(ctx, projectRef, newCidrsBody) -} - -func runPatch(ctx context.Context, projectRef string, dbCidrsToAllow []string, bypassCidrChecks bool) error { - addBody, err := buildRequestBody(dbCidrsToAllow, bypassCidrChecks) - if err != nil { - return err + for _, cidr := range dbCidrsToAllow { + ip, _, err := net.ParseCIDR(cidr) + if err != nil { + return errors.Errorf("failed to parse IP: %s", cidr) + } + if ip.IsPrivate() && !bypassCidrChecks { + return errors.Errorf("private IP provided: %s", cidr) + } + if ip.To4() != nil { + *body.DbAllowedCidrs = append(*body.DbAllowedCidrs, cidr) + } else { + *body.DbAllowedCidrsV6 = append(*body.DbAllowedCidrsV6, cidr) + } } - patchBody := api.V1PatchNetworkRestrictionsJSONRequestBody{ - Add: &struct { - DbAllowedCidrs *[]string `json:"dbAllowedCidrs,omitempty"` - DbAllowedCidrsV6 *[]string `json:"dbAllowedCidrsV6,omitempty"` - }{ - DbAllowedCidrs: addBody.DbAllowedCidrs, - DbAllowedCidrsV6: addBody.DbAllowedCidrsV6, - }, + if appendMode { + return ApplyPatch(ctx, projectRef, body) } - return ApplyPatch(ctx, projectRef, patchBody) -} - -// Apply submits a pre-built network restriction payload to the Supabase API using POST (replace mode). -func Apply(ctx context.Context, projectRef string, body api.V1UpdateNetworkRestrictionsJSONRequestBody) error { + // 2. update restrictions resp, err := utils.GetSupabase().V1UpdateNetworkRestrictionsWithResponse(ctx, projectRef, body) if err != nil { return errors.Errorf("failed to apply network restrictions: %w", err) @@ -55,23 +45,25 @@ func Apply(ctx context.Context, projectRef string, body api.V1UpdateNetworkRestr return errors.New("failed to apply network restrictions: " + string(resp.Body)) } - if resp.JSON201.Config.DbAllowedCidrs != nil { - fmt.Printf("DB Allowed IPv4 CIDRs: %+v\n", *resp.JSON201.Config.DbAllowedCidrs) - } else { - fmt.Println("DB Allowed IPv4 CIDRs: []") - } - if resp.JSON201.Config.DbAllowedCidrsV6 != nil { - fmt.Printf("DB Allowed IPv6 CIDRs: %+v\n", *resp.JSON201.Config.DbAllowedCidrsV6) - } else { - fmt.Println("DB Allowed IPv6 CIDRs: []") - } - fmt.Printf("Restrictions applied successfully\n") + fmt.Printf("DB Allowed IPv4 CIDRs: %+v\n", resp.JSON201.Config.DbAllowedCidrs) + fmt.Printf("DB Allowed IPv6 CIDRs: %+v\n", resp.JSON201.Config.DbAllowedCidrsV6) + fmt.Printf("Restrictions applied successfully: %+v\n", resp.JSON201.Status == api.NetworkRestrictionsResponseStatusApplied) return nil } // ApplyPatch submits a network restriction payload using PATCH (add/remove mode). -func ApplyPatch(ctx context.Context, projectRef string, body api.V1PatchNetworkRestrictionsJSONRequestBody) error { - resp, err := utils.GetSupabase().V1PatchNetworkRestrictionsWithResponse(ctx, projectRef, body) +func ApplyPatch(ctx context.Context, projectRef string, body api.V1UpdateNetworkRestrictionsJSONRequestBody) error { + patchBody := api.V1PatchNetworkRestrictionsJSONRequestBody{ + Add: &struct { + DbAllowedCidrs *[]string `json:"dbAllowedCidrs,omitempty"` + DbAllowedCidrsV6 *[]string `json:"dbAllowedCidrsV6,omitempty"` + }{ + DbAllowedCidrs: body.DbAllowedCidrs, + DbAllowedCidrsV6: body.DbAllowedCidrsV6, + }, + } + + resp, err := utils.GetSupabase().V1PatchNetworkRestrictionsWithResponse(ctx, projectRef, patchBody) if err != nil { return errors.Errorf("failed to apply network restrictions: %w", err) } @@ -79,36 +71,20 @@ func ApplyPatch(ctx context.Context, projectRef string, body api.V1PatchNetworkR return errors.New("failed to apply network restrictions: " + string(resp.Body)) } - if resp.JSON200.Config.DbAllowedCidrs != nil { - fmt.Println("DB Allowed CIDRs:") - for _, cidr := range *resp.JSON200.Config.DbAllowedCidrs { - fmt.Printf(" - %s (%s)\n", cidr.Address, cidr.Type) + var allowedIPv4, allowedIPv6 []string + if allowed := resp.JSON200.Config.DbAllowedCidrs; allowed != nil { + for _, cidr := range *allowed { + switch cidr.Type { + case api.NetworkRestrictionsV2ResponseConfigDbAllowedCidrsTypeV4: + allowedIPv4 = append(allowedIPv4, cidr.Address) + case api.NetworkRestrictionsV2ResponseConfigDbAllowedCidrsTypeV6: + allowedIPv6 = append(allowedIPv6, cidr.Address) + } } - } else { - fmt.Println("DB Allowed CIDRs: []") } - fmt.Printf("Restrictions applied successfully\n") - return nil -} -func buildRequestBody(dbCidrsToAllow []string, bypassCidrChecks bool) (api.V1UpdateNetworkRestrictionsJSONRequestBody, error) { - body := api.V1UpdateNetworkRestrictionsJSONRequestBody{ - DbAllowedCidrs: &[]string{}, - DbAllowedCidrsV6: &[]string{}, - } - for _, cidr := range dbCidrsToAllow { - ip, _, err := net.ParseCIDR(cidr) - if err != nil { - return api.V1UpdateNetworkRestrictionsJSONRequestBody{}, errors.Errorf("failed to parse IP: %s", cidr) - } - if ip.IsPrivate() && !bypassCidrChecks { - return api.V1UpdateNetworkRestrictionsJSONRequestBody{}, errors.Errorf("private IP provided: %s", cidr) - } - if ip.To4() != nil { - *body.DbAllowedCidrs = append(*body.DbAllowedCidrs, cidr) - } else { - *body.DbAllowedCidrsV6 = append(*body.DbAllowedCidrsV6, cidr) - } - } - return body, nil + fmt.Printf("DB Allowed IPv4 CIDRs: %+v\n", &allowedIPv4) + fmt.Printf("DB Allowed IPv6 CIDRs: %+v\n", &allowedIPv6) + fmt.Printf("Restrictions applied successfully: %+v\n", resp.JSON200.Status == api.NetworkRestrictionsV2ResponseStatusApplied) + return nil } diff --git a/internal/restrictions/update/update_test.go b/internal/restrictions/update/update_test.go index 087f3d15d..a927e2711 100644 --- a/internal/restrictions/update/update_test.go +++ b/internal/restrictions/update/update_test.go @@ -19,7 +19,7 @@ func TestUpdateRestrictionsCommand(t *testing.T) { token := apitest.RandomAccessToken(t) t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) - t.Run("replaces v4 and v6 CIDRs when append mode is false", func(t *testing.T) { + t.Run("replaces v4 and v6 CIDR", func(t *testing.T) { // Setup mock api defer gock.OffAll() expectedV4 := []string{"12.3.4.5/32", "1.2.3.1/24"} @@ -36,13 +36,13 @@ func TestUpdateRestrictionsCommand(t *testing.T) { Status: api.NetworkRestrictionsResponseStatus("applied"), }) // Run test - err := Run(context.Background(), projectRef, []string{"12.3.4.5/32", "1.2.3.1/24", "2001:db8:abcd:0012::0/64"}, false, false) + err := Run(context.Background(), projectRef, []string{"12.3.4.5/32", "2001:db8:abcd:0012::0/64", "1.2.3.1/24"}, false, false) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) }) - t.Run("appends v4 and v6 CIDRs using PATCH when append mode is true", func(t *testing.T) { + t.Run("appends v4 and v6 CIDR", func(t *testing.T) { // Setup mock api defer gock.OffAll() addV4 := []string{"12.3.4.5/32", "1.2.3.1/24"}