From acbcd77a53bdd131b085455ee65706d9d92361a7 Mon Sep 17 00:00:00 2001 From: Dmitry Kisler Date: Fri, 4 Oct 2024 22:50:38 +0200 Subject: [PATCH] add branch protection configuration's attribute; enhance test suit by excluding unit tests when running acceptance tests; enhance the skip message for allow IPs test. Signed-off-by: Dmitry Kisler --- CHANGELOG.md | 16 ++++ GNUmakefile | 2 +- docs/resources/branch.md | 2 + internal/provider/acc-import_test.go | 2 +- internal/provider/acc_test.go | 105 ++++++++++++++++++++++++++- internal/provider/resource_branch.go | 53 ++++++++++++-- internal/types/tristatebool.go | 15 ++-- internal/types/tristatebool_test.go | 3 + 8 files changed, 182 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f6a1c2..ea90c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ 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.6.2] - Unreleased + +### Added + +- Added the attribute `protected` to the resource `neon_branch` to provision protected branches. + +### Fixed + +- [[#108](https://github.com/kislerdm/terraform-provider-neon/issues/108)] Fixed import of the resource `neon_role`. +- Fixed mutability for the `brach` attribute of the `neon_project` resource. + +### Changed + +- Updated dependencies: + - Neon Go SDK: [v0.6.1](https://github.com/kislerdm/neon-sdk-go/compare/v0.5.0...v0.6.1) + ## [v0.6.1] - 2024-09-28 ### Added diff --git a/GNUmakefile b/GNUmakefile index c1808a4..dfe9c04 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -26,7 +26,7 @@ test: ## Runs unit tests. @ echo $(TEST) | xargs -t -n4 go test $(TESTARGS) -timeout=30s -parallel=4 testacc: ## Runs acceptance tests. - @ TF_ACC=1 go test -v -timeout 120m ./... + @ TF_ACC=1 go test -tags=acceptance -v -timeout 120m ./... docu: ## Generates docu. @ go generate diff --git a/docs/resources/branch.md b/docs/resources/branch.md index 862b157..8a4fba6 100644 --- a/docs/resources/branch.md +++ b/docs/resources/branch.md @@ -38,6 +38,8 @@ resource "neon_branch" "example" { See details: https://neon.tech/docs/reference/glossary/#lsn - `parent_timestamp` (Number) Timestamp horizon for the data to be present in the new branch. **Note**: it's defined as Unix epoch.' +- `protected` (String) Set to 'yes' to activate, 'no' to deactivate explicitly, and omit to keep the default value. +Set whether the branch is protected. ### Read-Only diff --git a/internal/provider/acc-import_test.go b/internal/provider/acc-import_test.go index f94934d..d12e62d 100644 --- a/internal/provider/acc-import_test.go +++ b/internal/provider/acc-import_test.go @@ -12,7 +12,7 @@ import ( neon "github.com/kislerdm/neon-sdk-go" ) -func TestResourcesImport(t *testing.T) { +func TestAccResourcesImport(t *testing.T) { if os.Getenv("TF_ACC") != "1" { t.Skip("TF_ACC must be set to 1") } diff --git a/internal/provider/acc_test.go b/internal/provider/acc_test.go index 9f92686..bb260ed 100644 --- a/internal/provider/acc_test.go +++ b/internal/provider/acc_test.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" neon "github.com/kislerdm/neon-sdk-go" + "github.com/kislerdm/terraform-provider-neon/internal/types" "github.com/stretchr/testify/assert" ) @@ -518,7 +519,7 @@ resource "neon_database" "this" { } func projectAllowedIPs(t *testing.T, client *neon.Client) { - t.Skip("skipped because of temp switch to the Free plan and back to Scale on 2024-09-23") + t.Skip("skipped because the Business plan is required for the feature") wantAllowedIPs := []string{"192.168.1.0", "192.168.2.0/24"} ips := `["` + strings.Join(wantAllowedIPs, `", "`) + `"]` @@ -1068,3 +1069,105 @@ func testPlanAfterRoleImport(t *testing.T, client *neon.Client) { }, }) } + +func TestAccBranch(t *testing.T) { + if os.Getenv("TF_ACC") != "1" { + t.Skip("TF_ACC must be set to 1") + } + + client, err := neon.NewClient(neon.Config{Key: os.Getenv("NEON_API_KEY")}) + if err != nil { + t.Fatal(err) + } + + prefix := "branch-" + + t.Cleanup(func() { + resp, _ := client.ListProjects(nil, nil, &prefix, nil) + for _, project := range resp.Projects { + br, _ := client.ListProjectBranches(project.ID) + for _, b := range br.BranchesResponse.Branches { + _, _ = client.UpdateProjectBranch(project.ID, b.ID, neon.BranchUpdateRequest{ + Branch: neon.BranchUpdateRequestBranch{ + Protected: pointer(false), + }, + }) + } + _, _ = client.DeleteProject(project.ID) + } + }) + + t.Run(`shall create the project with the custom protected branch +and update its state afterwards to be unprotected`, func(t *testing.T) { + projectName := prefix + newProjectName() + + const branchName = "foo" + + newDefinition := func(protected *bool) string { + var cfg string + switch { + case protected == nil: + case *protected: + cfg = `protected = "yes"` + case !*protected: + cfg = `protected = "no"` + } + return fmt.Sprintf(`resource "neon_project" "this" {name = "%s"} + +resource "neon_branch" "this" { + name = "%s" + project_id = neon_project.this.id + %s +}`, projectName, branchName, cfg) + } + + resource.Test(t, resource.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "neon": func() (*schema.Provider, error) { + return New("0.6.2"), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: newDefinition(pointer(true)), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("neon_branch.this", "protected", types.ValTrue), + func(state *terraform.State) error { + var e error + respProjects, e := client.ListProjects(nil, nil, &projectName, nil) + if e != nil { + return e + } + projectID := respProjects.Projects[0].ID + respBranches, e := client.ListProjectBranches(projectID) + if e != nil { + return e + } + var got bool + for _, branch := range respBranches.BranchesResponse.Branches { + if branch.Name == branchName { + got = branch.Protected + } + } + if !got { + e = fmt.Errorf("branch protect must be set to true") + } + return e + }), + }, + { + Config: newDefinition(pointer(false)), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("neon_branch.this", "protected", types.ValFalse), + ), + }, + { + Config: newDefinition(nil), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("neon_branch.this", "protected", types.ValNull), + ), + }, + }, + }) + }) +} diff --git a/internal/provider/resource_branch.go b/internal/provider/resource_branch.go index 1620b80..9c75a08 100644 --- a/internal/provider/resource_branch.go +++ b/internal/provider/resource_branch.go @@ -10,12 +10,13 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" neon "github.com/kislerdm/neon-sdk-go" + "github.com/kislerdm/terraform-provider-neon/internal/types" ) func resourceBranch() *schema.Resource { return &schema.Resource{ Description: "Project Branch. See details: https://neon.tech/docs/introduction/branching/", - SchemaVersion: 7, + SchemaVersion: 8, Importer: &schema.ResourceImporter{ StateContext: resourceBranchImport, }, @@ -70,6 +71,9 @@ See details: https://neon.tech/docs/reference/glossary/#lsn`, Computed: true, Description: "Branch logical size in MB.", }, + "protected": types.NewOptionalTristateBool( + `Set whether the branch is protected.`, false, + ), }, } } @@ -94,6 +98,11 @@ func updateStateBranch(d *schema.ResourceData, v neon.Branch) error { return err } } + if _, ok := d.GetOk("protected"); ok || v.Protected { + if err := types.SetTristateBool(d, "protected", &v.Protected); err != nil { + return err + } + } return nil } @@ -123,6 +132,7 @@ func resourceBranchCreate(ctx context.Context, d *schema.ResourceData, meta inte Name: pointer(d.Get("name").(string)), ParentID: pointer(d.Get("parent_id").(string)), ParentLsn: pointer(d.Get("parent_lsn").(string)), + Protected: types.GetTristateBool(d, "protected"), }, }, } @@ -156,15 +166,42 @@ func resourceBranchUpdate(ctx context.Context, d *schema.ResourceData, meta inte return nil } - cfg := neon.BranchUpdateRequest{ - Branch: neon.BranchUpdateRequestBranch{ - Name: pointer(v.(string)), - }, + if !d.HasChange("name") && !d.HasChange("protected") { + return nil } - resp, err := meta.(*neon.Client).UpdateProjectBranch(d.Get("project_id").(string), d.Id(), cfg) - if err != nil { - return err + var ( + resp neon.BranchOperations + err error + ) + if d.HasChange("name") { + resp, err = meta.(*neon.Client).UpdateProjectBranch(d.Get("project_id").(string), d.Id(), + neon.BranchUpdateRequest{ + Branch: neon.BranchUpdateRequestBranch{ + Name: pointer(d.Get("name").(string)), + }, + }, + ) + if err != nil { + return err + } + } + + if d.HasChange("protected") { + status := types.GetTristateBool(d, "protected") + if status == nil { + status = pointer(false) + } + resp, err = meta.(*neon.Client).UpdateProjectBranch(d.Get("project_id").(string), d.Id(), + neon.BranchUpdateRequest{ + Branch: neon.BranchUpdateRequestBranch{ + Protected: status, + }, + }, + ) + if err != nil { + return err + } } return updateStateBranch(d, resp.Branch) diff --git a/internal/types/tristatebool.go b/internal/types/tristatebool.go index 55fd581..af7db30 100644 --- a/internal/types/tristatebool.go +++ b/internal/types/tristatebool.go @@ -9,17 +9,17 @@ import ( const ( ValTrue = "yes" ValFalse = "no" - valNull = "" + ValNull = "" ) func validateFuncNewOptionalTristateBool(v interface{}, s string) (warns []string, errs []error) { const supportedVals = "Supported values: '" + ValTrue + "', '" + - ValFalse + "', '" + valNull + "'." + ValFalse + "', '" + ValNull + "'." vv, ok := v.(string) if ok { switch vv { - case ValTrue, ValFalse, valNull: + case ValTrue, ValFalse, ValNull: default: ok = false } @@ -54,7 +54,7 @@ func SetTristateBool(d *schema.ResourceData, name string, v *bool) error { var err error switch { case v == nil: - err = d.Set(name, valNull) + err = d.Set(name, ValNull) case *v: err = d.Set(name, ValTrue) default: @@ -68,7 +68,7 @@ func SetTristateBool(d *schema.ResourceData, name string, v *bool) error { func GetTristateBool(d *schema.ResourceData, name string) *bool { var o *bool = nil switch d.Get(name) { - case valNull: + case ValNull: case ValFalse: var tmp bool o = &tmp @@ -78,3 +78,8 @@ func GetTristateBool(d *schema.ResourceData, name string) *bool { } return o } + +// IsNull checks if the attribute's value is null. +func IsNull(d *schema.ResourceData, name string) bool { + return d.Get(name) == ValNull +} diff --git a/internal/types/tristatebool_test.go b/internal/types/tristatebool_test.go index 1355db5..98cc0a0 100644 --- a/internal/types/tristatebool_test.go +++ b/internal/types/tristatebool_test.go @@ -1,3 +1,6 @@ +//go:build !acceptance +// +build !acceptance + package types import (