From 754a9aaf14a09f1ae82606243e071886bb9ad816 Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Fri, 22 Dec 2023 12:32:52 +0100 Subject: [PATCH] feat: add logical replication configuration fix: postgres 16 support Signed-off-by: Dmitry Kisler --- CHANGELOG.md | 10 +++ go.mod | 2 +- go.sum | 2 + internal/provider/acc_test.go | 124 ++++++++++++++++++++++++++ internal/provider/resource_project.go | 57 ++++++++---- 5 files changed, 178 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5b256b..6463199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v0.3.1] - 2023-12-22 + +### Added + +- `resource_project` includes the attribute `enable_logical_replication` to configure the [logical replication](https://neon.tech/docs/introduction/logical-replication). + +### Fixed + +- PostgreSQL 16 is now supported. + ## [v0.3.0] - 2023-12-21 ### Added diff --git a/go.mod b/go.mod index 70ae465..ac5f24b 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/hashicorp/terraform-plugin-docs v0.13.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.31.0 - github.com/kislerdm/neon-sdk-go v0.4.0 + github.com/kislerdm/neon-sdk-go v0.4.1 ) require ( diff --git a/go.sum b/go.sum index 3dbec4e..51ad2fc 100644 --- a/go.sum +++ b/go.sum @@ -121,6 +121,8 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kislerdm/neon-sdk-go v0.4.0 h1:9HhG++QO1NCTOdz9fVIjK0a8i/JkrElJzXCf9lEj0yo= github.com/kislerdm/neon-sdk-go v0.4.0/go.mod h1:WSwEZ7oeR5KfQoCuDh/04LZxnSKDcvfsZyfG/QicDb8= +github.com/kislerdm/neon-sdk-go v0.4.1 h1:vhgtcPffKq7eUdAunFU+tuYVUkRIfEvqYDS2mXsHeqA= +github.com/kislerdm/neon-sdk-go v0.4.1/go.mod h1:WSwEZ7oeR5KfQoCuDh/04LZxnSKDcvfsZyfG/QicDb8= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= diff --git a/internal/provider/acc_test.go b/internal/provider/acc_test.go index fb4442a..0e58324 100644 --- a/internal/provider/acc_test.go +++ b/internal/provider/acc_test.go @@ -28,6 +28,8 @@ func TestAcc(t *testing.T) { end2end(t, client) projectAllowedIPs(t, client) + + projectLogicalReplication(t, client) } func end2end(t *testing.T, client *neon.Client) { @@ -726,6 +728,128 @@ func projectAllowedIPs(t *testing.T, client *neon.Client) { }) } +func projectLogicalReplication(t *testing.T, client *neon.Client) { + t.Run("shall create project without logical replication", func(t *testing.T) { + projectName := strconv.FormatInt(time.Now().UnixMilli(), 10) + resourceDefinition := fmt.Sprintf(`resource "neon_project" "this" { + name = "%s" + region_id = "aws-us-west-2" + pg_version = 16 + }`, projectName) + const resourceNameProject = "neon_project.this" + resource.UnitTest( + t, resource.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "neon": func() (*schema.Provider, error) { + return New("0.3.0"), nil + }, + }, + Steps: []resource.TestStep{ + { + ResourceName: "project", + Config: resourceDefinition, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + resourceNameProject, + "name", projectName, + ), + resource.TestCheckResourceAttr( + resourceNameProject, + "enable_logical_replication", "false", + ), + func(state *terraform.State) error { + resp, err := client.ListProjects(nil, nil) + if err != nil { + return errors.New("listing error: " + err.Error()) + } + + var ref neon.ProjectListItem + for _, project := range resp.ProjectsResponse.Projects { + if project.Name == projectName { + ref = project + } + break + } + + if ref.ID == "" { + return errors.New("project " + projectName + " shall be created") + } + + if ref.Settings.EnableLogicalReplication == nil || *ref.Settings.EnableLogicalReplication { + return errors.New("unexpected enable_logical_replication value, shall be 'false'") + } + + return nil + }, + ), + }, + }, + }) + }) + + t.Run("shall create project with logical replication", func(t *testing.T) { + projectName := strconv.FormatInt(time.Now().UnixMilli(), 10) + + resourceDefinition := fmt.Sprintf(`resource "neon_project" "this" { + name = "%s" + region_id = "aws-us-west-2" + pg_version = 16 + enable_logical_replication = true + }`, projectName) + + const resourceNameProject = "neon_project.this" + resource.UnitTest( + t, resource.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "neon": func() (*schema.Provider, error) { + return New("0.3.0"), nil + }, + }, + Steps: []resource.TestStep{ + { + ResourceName: "project", + Config: resourceDefinition, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + resourceNameProject, + "name", projectName, + ), + resource.TestCheckResourceAttr( + resourceNameProject, + "enable_logical_replication", "true", + ), + func(state *terraform.State) error { + resp, err := client.ListProjects(nil, nil) + if err != nil { + return errors.New("listing error: " + err.Error()) + } + + var ref neon.ProjectListItem + for _, project := range resp.ProjectsResponse.Projects { + if project.Name == projectName { + ref = project + } + break + } + + if ref.ID == "" { + return errors.New("project " + projectName + " shall be created") + } + + if ref.Settings.EnableLogicalReplication == nil || !*ref.Settings.EnableLogicalReplication { + return errors.New("unexpected enable_logical_replication value, shall be 'true'") + } + + return nil + }, + ), + }, + }, + }, + ) + }) +} + func mustParseFloat64(s string) float64 { v, err := strconv.ParseFloat(s, 64) if err != nil { diff --git a/internal/provider/resource_project.go b/internal/provider/resource_project.go index fe1d773..81f586c 100644 --- a/internal/provider/resource_project.go +++ b/internal/provider/resource_project.go @@ -50,7 +50,7 @@ API: https://api-docs.neon.tech/reference/createproject`, Description: "Postgres version", ValidateFunc: func(i interface{}, _ string) (warns []string, errs []error) { switch v := i.(int); v { - case 14, 15: + case 14, 15, 16: return default: errs = append( @@ -113,6 +113,15 @@ Note that the feature is available to the Neon Pro Plan only. Details: https://n Default: false, Description: `Apply the allow-list to the primary branch only. Note that the feature is available to the Neon Pro Plan only.`, + }, + "enable_logical_replication": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Description: `Sets wal_level=logical for all compute endpoints in this project. +All active endpoints will be suspended. Once enabled, logical replication cannot be disabled. +See details: https://neon.tech/docs/introduction/logical-replication +`, }, // computed fields "default_branch_id": { @@ -466,6 +475,12 @@ func updateStateProject( return err } } + + if r.Settings.EnableLogicalReplication != nil { + if err := d.Set("enable_logical_replication", *r.Settings.EnableLogicalReplication); err != nil { + return err + } + } } if err := d.Set("default_branch_id", defaultBranchID); err != nil { @@ -541,19 +556,22 @@ func resourceProjectCreate(ctx context.Context, d *schema.ResourceData, meta int ips[i] = fmt.Sprintf("%v", vv) } - var q *neon.ProjectQuota - if projectDef.Settings != nil && projectDef.Settings.Quota != nil { - q = projectDef.Settings.Quota + if projectDef.Settings == nil { + projectDef.Settings = &neon.ProjectSettingsData{} } - projectDef.Settings = &neon.ProjectSettingsData{ - AllowedIps: &neon.AllowedIps{ - Ips: ips, - PrimaryBranchOnly: d.Get("allowed_ips_primary_branch_only").(bool), - }, - Quota: q, + projectDef.Settings.AllowedIps = &neon.AllowedIps{ + Ips: ips, + PrimaryBranchOnly: d.Get("allowed_ips_primary_branch_only").(bool), } } + if v, ok := d.GetOk("enable_logical_replication"); ok { + if projectDef.Settings == nil { + projectDef.Settings = &neon.ProjectSettingsData{} + } + projectDef.Settings.EnableLogicalReplication = pointer(v.(bool)) + } + if v, ok := d.GetOk("branch"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { if v, ok := v.([]interface{})[0].(map[string]interface{}); ok && len(v) > 0 { projectDef.Branch = mapToBranchSettings(v) @@ -620,13 +638,20 @@ func resourceProjectUpdate(ctx context.Context, d *schema.ResourceData, meta int for i, vv := range v.([]interface{}) { ips[i] = fmt.Sprintf("%v", vv) } - req.Settings = &neon.ProjectSettingsData{ - Quota: req.Settings.Quota, - AllowedIps: &neon.AllowedIps{ - Ips: ips, - PrimaryBranchOnly: d.Get("allowed_ips_primary_branch_only").(bool), - }, + if req.Settings == nil { + req.Settings = &neon.ProjectSettingsData{} + } + req.Settings.AllowedIps = &neon.AllowedIps{ + Ips: ips, + PrimaryBranchOnly: d.Get("allowed_ips_primary_branch_only").(bool), + } + } + + if v, ok := d.GetOk("enable_logical_replication"); ok { + if req.Settings == nil { + req.Settings = &neon.ProjectSettingsData{} } + req.Settings.EnableLogicalReplication = pointer(v.(bool)) } _, err := meta.(sdkProject).UpdateProject(d.Id(), neon.ProjectUpdateRequest{Project: req})