Skip to content

Commit

Permalink
feat: add logical replication configuration
Browse files Browse the repository at this point in the history
fix: postgres 16 support

Signed-off-by: Dmitry Kisler <admin@dkisler.com>
  • Loading branch information
kislerdm committed Dec 22, 2023
1 parent 81d201b commit 754a9aa
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 17 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
124 changes: 124 additions & 0 deletions internal/provider/acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
57 changes: 41 additions & 16 deletions internal/provider/resource_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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": {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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})
Expand Down

0 comments on commit 754a9aa

Please sign in to comment.