Skip to content

Commit bddd49f

Browse files
authored
feat(secret): add protected field (#2693)
* feat(secret): add protected field * lint * fix cassette validation
1 parent 1079fc3 commit bddd49f

File tree

6 files changed

+796
-0
lines changed

6 files changed

+796
-0
lines changed

docs/resources/secret.md

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ The following arguments are supported:
2626

2727
- `name` - (Required) Name of the secret (e.g. `my-secret`).
2828
- `path` - (Optional) Path of the secret, defaults to `/`.
29+
- `protected` - (Optional) True if secret protection is enabled on the secret. A protected secret cannot be deleted, terraform will fail to destroy unless this is set to false.
2930
- `description` - (Optional) Description of the secret (e.g. `my-new-description`).
3031
- `tags` - (Optional) Tags of the secret (e.g. `["tag", "secret"]`).
3132
- `region` - (Defaults to [provider](../index.md#region) `region`) The [region](../guides/regions_and_zones.md#regions)

internal/acctest/validate_cassettes_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func exceptionsCassettesCases() map[string]struct{} {
2626
"../services/rdb/testdata/data-source-privilege-basic.cassette.yaml": {},
2727
"../services/rdb/testdata/privilege-basic.cassette.yaml": {},
2828
"../services/object/testdata/object-bucket-destroy-force.cassette.yaml": {},
29+
"../services/secret/testdata/secret-protected.cassette.yaml": {},
2930
}
3031
}
3132

internal/services/secret/helpers.go

+39
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package secret
22

33
import (
44
"encoding/base64"
5+
"errors"
6+
"fmt"
57
"time"
68

79
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -16,6 +18,8 @@ const (
1618
defaultSecretTimeout = 5 * time.Minute
1719
)
1820

21+
var ErrCannotDeleteProtectedSecret = errors.New("cannot delete a protected secret")
22+
1923
// newAPIWithRegion returns a new Secret API and the region for a Create request
2024
func newAPIWithRegion(d *schema.ResourceData, m interface{}) (*secret.API, scw.Region, error) {
2125
api := secret.NewAPI(meta.ExtractScwClient(m))
@@ -87,3 +91,38 @@ func Base64Encoded(data []byte) string {
8791
}
8892
return base64.StdEncoding.EncodeToString(data)
8993
}
94+
95+
// updateSecretProtection sets the protected value of a secret to requested one.
96+
func updateSecretProtection(api *secret.API, region scw.Region, secretID string, protected bool) error {
97+
s, err := api.GetSecret(&secret.GetSecretRequest{
98+
Region: region,
99+
SecretID: secretID,
100+
})
101+
if err != nil {
102+
return err
103+
}
104+
105+
if s.Protected == protected {
106+
return nil
107+
}
108+
109+
if protected {
110+
_, err = api.ProtectSecret(&secret.ProtectSecretRequest{
111+
Region: region,
112+
SecretID: secretID,
113+
})
114+
if err != nil {
115+
return fmt.Errorf("failed to protect secret %s: %w", secretID, err)
116+
}
117+
} else {
118+
_, err = api.UnprotectSecret(&secret.UnprotectSecretRequest{
119+
Region: region,
120+
SecretID: secretID,
121+
})
122+
if err != nil {
123+
return fmt.Errorf("failed to unprotect secret %s: %w", secretID, err)
124+
}
125+
}
126+
127+
return nil
128+
}

internal/services/secret/secret.go

+10
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ func ResourceSecret() *schema.Resource {
7575
return filepath.Clean(oldValue) == filepath.Clean(newValue)
7676
},
7777
},
78+
"protected": {
79+
Type: schema.TypeBool,
80+
Optional: true,
81+
Description: "True if secret protection is enabled on a given secret. A protected secret cannot be deleted.",
82+
},
7883
"region": regional.Schema(),
7984
"project_id": account.ProjectIDSchema(),
8085
},
@@ -91,6 +96,7 @@ func ResourceSecretCreate(ctx context.Context, d *schema.ResourceData, m interfa
9196
Region: region,
9297
ProjectID: d.Get("project_id").(string),
9398
Name: d.Get("name").(string),
99+
Protected: d.Get("protected").(bool),
94100
}
95101

96102
rawTag, tagExist := d.GetOk("tags")
@@ -149,6 +155,7 @@ func ResourceSecretRead(ctx context.Context, d *schema.ResourceData, m interface
149155
_ = d.Set("region", string(region))
150156
_ = d.Set("project_id", secretResponse.ProjectID)
151157
_ = d.Set("path", secretResponse.Path)
158+
_ = d.Set("protected", secretResponse.Protected)
152159

153160
return nil
154161
}
@@ -191,7 +198,10 @@ func ResourceSecretUpdate(ctx context.Context, d *schema.ResourceData, m interfa
191198
if err != nil {
192199
return diag.FromErr(err)
193200
}
201+
}
194202

203+
if d.HasChange("protected") {
204+
err = updateSecretProtection(api, region, id, d.Get("protected").(bool))
195205
if err != nil {
196206
return diag.FromErr(err)
197207
}

internal/services/secret/secret_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package secret_test
22

33
import (
44
"fmt"
5+
"regexp"
56
"testing"
67

78
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
@@ -157,6 +158,59 @@ func TestAccSecret_Path(t *testing.T) {
157158
})
158159
}
159160

161+
func TestAccSecret_Protected(t *testing.T) {
162+
tt := acctest.NewTestTools(t)
163+
defer tt.Cleanup()
164+
165+
resource.ParallelTest(t, resource.TestCase{
166+
PreCheck: func() { acctest.PreCheck(t) },
167+
ProviderFactories: tt.ProviderFactories,
168+
CheckDestroy: testAccCheckSecretDestroy(tt),
169+
Steps: []resource.TestStep{
170+
{
171+
Config: `
172+
resource "scaleway_secret" "main" {
173+
name = "test-secret-protected-secret"
174+
protected = true
175+
}
176+
`,
177+
Check: resource.ComposeTestCheckFunc(
178+
testAccCheckSecretExists(tt, "scaleway_secret.main"),
179+
resource.TestCheckResourceAttr("scaleway_secret.main", "name", "test-secret-protected-secret"),
180+
resource.TestCheckResourceAttr("scaleway_secret.main", "path", "/"),
181+
resource.TestCheckResourceAttr("scaleway_secret.main", "protected", "true"),
182+
acctest.CheckResourceAttrUUID("scaleway_secret.main", "id"),
183+
),
184+
},
185+
{
186+
Destroy: true,
187+
Config: `
188+
resource "scaleway_secret" "main" {
189+
name = "test-secret-protected-secret"
190+
protected = true
191+
}
192+
`,
193+
ExpectError: regexp.MustCompile(secret.ErrCannotDeleteProtectedSecret.Error()),
194+
},
195+
{
196+
Config: `
197+
resource "scaleway_secret" "main" {
198+
name = "test-secret-protected-secret"
199+
protected = false
200+
}
201+
`,
202+
Check: resource.ComposeTestCheckFunc(
203+
testAccCheckSecretExists(tt, "scaleway_secret.main"),
204+
resource.TestCheckResourceAttr("scaleway_secret.main", "name", "test-secret-protected-secret"),
205+
resource.TestCheckResourceAttr("scaleway_secret.main", "path", "/"),
206+
resource.TestCheckResourceAttr("scaleway_secret.main", "protected", "false"),
207+
acctest.CheckResourceAttrUUID("scaleway_secret.main", "id"),
208+
),
209+
},
210+
},
211+
})
212+
}
213+
160214
func testAccCheckSecretExists(tt *acctest.TestTools, n string) resource.TestCheckFunc {
161215
return func(state *terraform.State) error {
162216
rs, ok := state.RootModule().Resources[n]

0 commit comments

Comments
 (0)