From 8463e36db26a4ae7921bb8de4f0170aa19f62585 Mon Sep 17 00:00:00 2001 From: Jonathan Remy Date: Fri, 29 Aug 2025 15:31:42 +0200 Subject: [PATCH] fix(key_manager): add next_rotation_at in key manager resource --- internal/services/keymanager/helpers.go | 20 +- internal/services/keymanager/key_resource.go | 5 +- .../services/keymanager/key_resource_test.go | 35 +++ ...ger-key-with-rotation-policy.cassette.yaml | 248 ++++++++++++++++++ 4 files changed, 303 insertions(+), 5 deletions(-) create mode 100644 internal/services/keymanager/testdata/key-manager-key-with-rotation-policy.cassette.yaml diff --git a/internal/services/keymanager/helpers.go b/internal/services/keymanager/helpers.go index b0da6e7f3d..b1d8e3e2f2 100644 --- a/internal/services/keymanager/helpers.go +++ b/internal/services/keymanager/helpers.go @@ -1,6 +1,8 @@ package keymanager import ( + "errors" + "fmt" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -85,7 +87,7 @@ func ExpandKeyRotationPolicy(v any) (*key_manager.KeyRotationPolicy, error) { periodStr, ok := m["rotation_period"].(string) if !ok || periodStr == "" { - return nil, nil + return nil, errors.New("rotation_period is required when rotation_policy block is specified") } period, err := time.ParseDuration(periodStr) @@ -93,9 +95,21 @@ func ExpandKeyRotationPolicy(v any) (*key_manager.KeyRotationPolicy, error) { return nil, err } - return &key_manager.KeyRotationPolicy{ + policy := &key_manager.KeyRotationPolicy{ RotationPeriod: scw.NewDurationFromTimeDuration(period), - }, nil + } + + // Handle next_rotation_at if provided + if nextRotationStr, ok := m["next_rotation_at"].(string); ok && nextRotationStr != "" { + nextRotation, err := time.Parse(time.RFC3339, nextRotationStr) + if err != nil { + return nil, fmt.Errorf("invalid next_rotation_at format: %w", err) + } + + policy.NextRotationAt = &nextRotation + } + + return policy, nil } func FlattenKeyRotationPolicy(rp *key_manager.KeyRotationPolicy) []map[string]any { diff --git a/internal/services/keymanager/key_resource.go b/internal/services/keymanager/key_resource.go index 355eb0d192..086dde96c3 100644 --- a/internal/services/keymanager/key_resource.go +++ b/internal/services/keymanager/key_resource.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" key_manager "github.com/scaleway/scaleway-sdk-go/api/key_manager/v1alpha1" + "github.com/scaleway/terraform-provider-scaleway/v2/internal/dsf" "github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional" "github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account" "github.com/scaleway/terraform-provider-scaleway/v2/internal/types" @@ -52,8 +53,8 @@ func ResourceKeyManagerKey() *schema.Resource { Description: "Key rotation policy.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "rotation_period": {Type: schema.TypeString, Optional: true, Description: "Time interval between two key rotations. The minimum duration is 24 hours and the maximum duration is 1 year (876000 hours)."}, - "next_rotation_at": {Type: schema.TypeString, Computed: true, Description: "Timestamp indicating the next scheduled rotation."}, + "rotation_period": {Type: schema.TypeString, Required: true, DiffSuppressFunc: dsf.Duration, Description: "Time interval between two key rotations. The minimum duration is 24 hours and the maximum duration is 1 year (876000 hours)."}, + "next_rotation_at": {Type: schema.TypeString, Optional: true, Description: "Timestamp indicating the next scheduled rotation."}, }, }, }, diff --git a/internal/services/keymanager/key_resource_test.go b/internal/services/keymanager/key_resource_test.go index 0072053f69..f61504b369 100644 --- a/internal/services/keymanager/key_resource_test.go +++ b/internal/services/keymanager/key_resource_test.go @@ -123,3 +123,38 @@ func IsKeyManagerKeyDestroyed(tt *acctest.TestTools) resource.TestCheckFunc { return nil } } + +func TestAccKeyManagerKey_WithRotationPolicy(t *testing.T) { + tt := acctest.NewTestTools(t) + defer tt.Cleanup() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProviderFactories: tt.ProviderFactories, + CheckDestroy: IsKeyManagerKeyDestroyed(tt), + Steps: []resource.TestStep{ + { + Config: ` + resource "scaleway_key_manager_key" "main" { + name = "tf-test-kms-key-rotation" + region = "fr-par" + usage = "symmetric_encryption" + description = "Test key with rotation policy" + unprotected = true + + rotation_policy { + rotation_period = "720h" + next_rotation_at = "2026-01-01T00:00:00Z" + } + } + `, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "name", "tf-test-kms-key-rotation"), + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "usage", "symmetric_encryption"), + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "description", "Test key with rotation policy"), + resource.TestCheckResourceAttr("scaleway_key_manager_key.main", "rotation_policy.0.rotation_period", "720h0m0s"), + ), + }, + }, + }) +} diff --git a/internal/services/keymanager/testdata/key-manager-key-with-rotation-policy.cassette.yaml b/internal/services/keymanager/testdata/key-manager-key-with-rotation-policy.cassette.yaml new file mode 100644 index 0000000000..ab4f235ca5 --- /dev/null +++ b/internal/services/keymanager/testdata/key-manager-key-with-rotation-policy.cassette.yaml @@ -0,0 +1,248 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 338 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: '{"project_id":"105bdce1-64c0-48ab-899d-868455867ecf","name":"tf-test-kms-key-rotation","usage":{"symmetric_encryption":"aes_256_gcm"},"description":"Test key with rotation policy","tags":null,"rotation_policy":{"rotation_period":"2592000.000000000s","next_rotation_at":"2026-01-01T00:00:00Z"},"unprotected":true,"origin":"unknown_origin"}' + form: {} + headers: + Content-Type: + - application/json + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 595 + uncompressed: false + body: '{"created_at":"2025-09-01T14:08:23.132185Z","deletion_requested_at":null,"description":"Test key with rotation policy","id":"449ede52-42cd-4692-8ad6-8d3d60b31b1f","locked":false,"name":"tf-test-kms-key-rotation","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-09-01T14:08:23.140726Z","rotation_count":1,"rotation_policy":{"next_rotation_at":"2026-01-01T00:00:00Z","rotation_period":"2592000s"},"state":"enabled","tags":[],"updated_at":"2025-09-01T14:08:23.140726Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "595" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Mon, 01 Sep 2025 14:08:23 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge01) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 26922321-731c-4928-8fec-69e3fb748202 + status: 200 OK + code: 200 + duration: 441.486459ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/449ede52-42cd-4692-8ad6-8d3d60b31b1f + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 595 + uncompressed: false + body: '{"created_at":"2025-09-01T14:08:23.132185Z","deletion_requested_at":null,"description":"Test key with rotation policy","id":"449ede52-42cd-4692-8ad6-8d3d60b31b1f","locked":false,"name":"tf-test-kms-key-rotation","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-09-01T14:08:23.140726Z","rotation_count":1,"rotation_policy":{"next_rotation_at":"2026-01-01T00:00:00Z","rotation_period":"2592000s"},"state":"enabled","tags":[],"updated_at":"2025-09-01T14:08:23.140726Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "595" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Mon, 01 Sep 2025 14:08:23 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge01) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - a38c873d-0db5-4b18-be3d-6702c5221289 + status: 200 OK + code: 200 + duration: 116.597417ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/449ede52-42cd-4692-8ad6-8d3d60b31b1f + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 595 + uncompressed: false + body: '{"created_at":"2025-09-01T14:08:23.132185Z","deletion_requested_at":null,"description":"Test key with rotation policy","id":"449ede52-42cd-4692-8ad6-8d3d60b31b1f","locked":false,"name":"tf-test-kms-key-rotation","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-09-01T14:08:23.140726Z","rotation_count":1,"rotation_policy":{"next_rotation_at":"2026-01-01T00:00:00Z","rotation_period":"2592000s"},"state":"enabled","tags":[],"updated_at":"2025-09-01T14:08:23.140726Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "595" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Mon, 01 Sep 2025 14:08:24 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge01) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - c856061a-4f5d-4f30-b213-7a18e9ab9206 + status: 200 OK + code: 200 + duration: 123.934916ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/449ede52-42cd-4692-8ad6-8d3d60b31b1f + method: DELETE + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 0 + uncompressed: false + body: "" + headers: + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Mon, 01 Sep 2025 14:08:25 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge01) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - dbc7b22c-f2d3-477f-b5ba-9c620a566ed4 + status: 204 No Content + code: 204 + duration: 139.486584ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/key-manager/v1alpha1/regions/fr-par/keys/449ede52-42cd-4692-8ad6-8d3d60b31b1f + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 635 + uncompressed: false + body: '{"created_at":"2025-09-01T14:08:23.132185Z","deletion_requested_at":"2025-09-01T14:08:25.319098Z","description":"Test key with rotation policy","id":"449ede52-42cd-4692-8ad6-8d3d60b31b1f","locked":false,"name":"tf-test-kms-key-rotation","origin":"scaleway_kms","project_id":"105bdce1-64c0-48ab-899d-868455867ecf","protected":false,"region":"fr-par","rotated_at":"2025-09-01T14:08:23.140726Z","rotation_count":1,"rotation_policy":{"next_rotation_at":"2026-01-01T00:00:00Z","rotation_period":"2592000s"},"state":"scheduled_for_deletion","tags":[],"updated_at":"2025-09-01T14:08:23.140726Z","usage":{"symmetric_encryption":"aes_256_gcm"}}' + headers: + Content-Length: + - "635" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Mon, 01 Sep 2025 14:08:25 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge01) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 9e3d94eb-96df-4b75-83aa-48814dba6768 + status: 200 OK + code: 200 + duration: 108.039625ms