diff --git a/docs/data-sources/volume.md b/docs/data-sources/volume.md
index 1b1e4064f..7dfed8d49 100644
--- a/docs/data-sources/volume.md
+++ b/docs/data-sources/volume.md
@@ -35,6 +35,7 @@ data "stackit_volume" "example" {
- `availability_zone` (String) The availability zone of the volume.
- `description` (String) The description of the volume.
+- `encrypted` (Boolean) Indicates if the volume is encrypted.
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`volume_id`".
- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container
- `name` (String) The name of the volume.
diff --git a/docs/resources/volume.md b/docs/resources/volume.md
index 0e61bb138..51f4c7248 100644
--- a/docs/resources/volume.md
+++ b/docs/resources/volume.md
@@ -41,6 +41,7 @@ import {
### Optional
- `description` (String) The description of the volume.
+- `encryption_parameters` (Attributes) Parameter to connect to a key-encryption-key within the STACKIT-KMS to create encrypted volumes. These parameters never leave the backend again. So these parameters are not present on imports or in the datasource. They live only in your Terraform state after creation of the resource. (see [below for nested schema](#nestedatt--encryption_parameters))
- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container
- `name` (String) The name of the volume.
- `performance_class` (String) The performance class of the volume. Possible values are documented in [Service plans BlockStorage](https://docs.stackit.cloud/products/storage/block-storage/basics/service-plans/#currently-available-service-plans-performance-classes)
@@ -50,10 +51,26 @@ import {
### Read-Only
+- `encrypted` (Boolean) Indicates if the volume is encrypted.
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`volume_id`".
- `server_id` (String) The server ID of the server to which the volume is attached to.
- `volume_id` (String) The volume ID.
+
+### Nested Schema for `encryption_parameters`
+
+Required:
+
+- `kek_key_id` (String) UUID of the key within the STACKIT-KMS to use for the encryption.
+- `kek_key_version` (Number) Version of the key within the STACKIT-KMS to use for the encryption.
+- `kek_keyring_id` (String) UUID of the keyring where the key is located within the STACKTI-KMS.
+- `service_account` (String) Service-Account linked to the Key within the STACKIT-KMS.
+
+Optional:
+
+- `key_payload_base64` (String, Sensitive) Optional predefined secret, which will be encrypted against the key-encryption-key within the STACKIT-KMS. If not defined, a random secret will be generated by the API and encrypted against the STACKIT-KMS. If a key-payload is provided here, it must be base64 encoded.
+
+
### Nested Schema for `source`
diff --git a/stackit/internal/services/iaas/iaas_acc_test.go b/stackit/internal/services/iaas/iaas_acc_test.go
index a44aa75a3..7c34f2f2c 100644
--- a/stackit/internal/services/iaas/iaas_acc_test.go
+++ b/stackit/internal/services/iaas/iaas_acc_test.go
@@ -225,13 +225,15 @@ var testConfigVolumeVarsMinUpdated = func() config.Variables {
// VOLUME - MAX
var testConfigVolumeVarsMax = config.Variables{
- "project_id": config.StringVariable(testutil.ProjectId),
- "availability_zone": config.StringVariable("eu01-1"),
- "name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
- "size": config.IntegerVariable(16),
- "description": config.StringVariable("description"),
- "performance_class": config.StringVariable("storage_premium_perf0"),
- "label": config.StringVariable("label"),
+ "project_id": config.StringVariable(testutil.ProjectId),
+ "availability_zone": config.StringVariable("eu01-1"),
+ "name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
+ "size": config.IntegerVariable(16),
+ "description": config.StringVariable("description"),
+ "performance_class": config.StringVariable("storage_premium_perf0"),
+ "label": config.StringVariable("label"),
+ "service_account_mail": config.StringVariable(testutil.TestProjectServiceAccountEmail),
+ "key_payload_base64": config.StringVariable("Y2hhbmdpbmdwbGFuc29mdGJhcmtmaXJzdGNoYW5nZXJlZGh1bmdkb29uY2VoaXN0b3I="),
}
var testConfigVolumeVarsMaxUpdated = func() config.Variables {
@@ -1492,6 +1494,8 @@ func TestAccVolumeMin(t *testing.T) {
resource.TestCheckResourceAttr("stackit_volume.volume_size", "size", testutil.ConvertConfigVariable(testConfigVolumeVarsMin["size"])),
resource.TestCheckResourceAttrSet("stackit_volume.volume_size", "performance_class"),
resource.TestCheckNoResourceAttr("stackit_volume.volume_size", "server_id"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_size", "encrypted", "false"),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_size", "encryption_parameters"),
// Volume source
resource.TestCheckResourceAttr("stackit_volume.volume_source", "project_id", testutil.ConvertConfigVariable(testConfigVolumeVarsMin["project_id"])),
@@ -1506,6 +1510,8 @@ func TestAccVolumeMin(t *testing.T) {
),
resource.TestCheckResourceAttr("stackit_volume.volume_source", "source.type", "volume"),
resource.TestCheckNoResourceAttr("stackit_volume.volume_source", "server_id"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_source", "encrypted", "false"),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_source", "encryption_parameters"),
),
},
// Data source
@@ -1539,6 +1545,7 @@ func TestAccVolumeMin(t *testing.T) {
resource.TestCheckResourceAttrSet("data.stackit_volume.volume_size", "performance_class"),
resource.TestCheckNoResourceAttr("data.stackit_volume.volume_size", "server_id"),
resource.TestCheckResourceAttr("data.stackit_volume.volume_size", "size", testutil.ConvertConfigVariable(testConfigVolumeVarsMin["size"])),
+ resource.TestCheckResourceAttr("data.stackit_volume.volume_size", "encrypted", "false"),
// Volume source
resource.TestCheckResourceAttr("data.stackit_volume.volume_source", "project_id", testutil.ConvertConfigVariable(testConfigVolumeVarsMin["project_id"])),
@@ -1555,6 +1562,7 @@ func TestAccVolumeMin(t *testing.T) {
"data.stackit_volume.volume_size", "volume_id",
),
resource.TestCheckResourceAttr("data.stackit_volume.volume_source", "source.type", "volume"),
+ resource.TestCheckResourceAttr("data.stackit_volume.volume_source", "encrypted", "false"),
),
},
// Import
@@ -1605,6 +1613,8 @@ func TestAccVolumeMin(t *testing.T) {
resource.TestCheckResourceAttr("stackit_volume.volume_size", "size", testutil.ConvertConfigVariable(testConfigVolumeVarsMinUpdated["size"])),
resource.TestCheckResourceAttrSet("stackit_volume.volume_size", "performance_class"),
resource.TestCheckNoResourceAttr("stackit_volume.volume_size", "server_id"),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_size", "encryption_parameters"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_size", "encrypted", "false"),
// Volume source
resource.TestCheckResourceAttr("stackit_volume.volume_source", "project_id", testutil.ConvertConfigVariable(testConfigVolumeVarsMinUpdated["project_id"])),
@@ -1620,6 +1630,8 @@ func TestAccVolumeMin(t *testing.T) {
),
resource.TestCheckResourceAttr("stackit_volume.volume_source", "source.type", "volume"),
resource.TestCheckNoResourceAttr("stackit_volume.volume_source", "server_id"),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_source", "encryption_parameters"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_source", "encrypted", "false"),
),
},
// Deletion is done by the framework implicitly
@@ -1650,6 +1662,8 @@ func TestAccVolumeMax(t *testing.T) {
resource.TestCheckResourceAttr("stackit_volume.volume_size", "labels.%", "1"),
resource.TestCheckResourceAttr("stackit_volume.volume_size", "labels.acc-test", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["label"])),
resource.TestCheckNoResourceAttr("stackit_volume.volume_size", "server_id"),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_size", "encryption_parameters"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_size", "encrypted", "false"),
// Volume source
resource.TestCheckResourceAttr("stackit_volume.volume_source", "project_id", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["project_id"])),
@@ -1668,6 +1682,50 @@ func TestAccVolumeMax(t *testing.T) {
),
resource.TestCheckResourceAttr("stackit_volume.volume_source", "source.type", "volume"),
resource.TestCheckNoResourceAttr("stackit_volume.volume_source", "server_id"),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_source", "encryption_parameters"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_source", "encrypted", "false"),
+
+ // Volume encrypted - no key payload
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "project_id", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["project_id"])),
+ resource.TestCheckResourceAttrSet("stackit_volume.volume_encrypted_no_key_payload", "volume_id"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "region", testutil.Region),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "availability_zone", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["availability_zone"])),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "size", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["size"])),
+ resource.TestCheckResourceAttrSet("stackit_volume.volume_encrypted_no_key_payload", "performance_class"),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "server_id"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encrypted", "true"),
+ resource.TestCheckResourceAttrPair(
+ "stackit_kms_key.key", "key_id",
+ "stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.kek_key_id",
+ ),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.kek_key_version", "1"),
+ resource.TestCheckResourceAttrPair(
+ "stackit_kms_key.key", "keyring_id",
+ "stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.kek_keyring_id",
+ ),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.key_payload_base64"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.service_account", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["service_account_mail"])),
+
+ // Volume encrypted - with key payload
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "project_id", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["project_id"])),
+ resource.TestCheckResourceAttrSet("stackit_volume.volume_encrypted_no_key_payload", "volume_id"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "region", testutil.Region),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "availability_zone", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["availability_zone"])),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "size", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["size"])),
+ resource.TestCheckResourceAttrSet("stackit_volume.volume_encrypted_no_key_payload", "performance_class"),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "server_id"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encrypted", "true"),
+ resource.TestCheckResourceAttrPair(
+ "stackit_kms_key.key", "key_id",
+ "stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.kek_key_id",
+ ),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.kek_key_version", "1"),
+ resource.TestCheckResourceAttrPair(
+ "stackit_kms_key.key", "keyring_id",
+ "stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.kek_keyring_id",
+ ),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.key_payload_base64"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.service_account", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["service_account_mail"])),
),
},
// Data source
@@ -1686,6 +1744,16 @@ func TestAccVolumeMax(t *testing.T) {
project_id = stackit_volume.volume_source.project_id
volume_id = stackit_volume.volume_source.volume_id
}
+
+ data "stackit_volume" "volume_encrypted_no_key_payload" {
+ project_id = stackit_volume.volume_encrypted_no_key_payload.project_id
+ volume_id = stackit_volume.volume_encrypted_no_key_payload.volume_id
+ }
+
+ data "stackit_volume" "volume_encrypted_with_key_payload" {
+ project_id = stackit_volume.volume_encrypted_with_key_payload.project_id
+ volume_id = stackit_volume.volume_encrypted_with_key_payload.volume_id
+ }
`,
testutil.IaaSProviderConfig(), resourceVolumeMaxConfig,
),
@@ -1705,6 +1773,7 @@ func TestAccVolumeMax(t *testing.T) {
resource.TestCheckResourceAttr("data.stackit_volume.volume_size", "name", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["name"])),
resource.TestCheckResourceAttr("data.stackit_volume.volume_size", "labels.%", "1"),
resource.TestCheckResourceAttr("data.stackit_volume.volume_size", "labels.acc-test", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["label"])),
+ resource.TestCheckResourceAttr("data.stackit_volume.volume_size", "encrypted", "false"),
// Volume source
resource.TestCheckResourceAttr("data.stackit_volume.volume_source", "project_id", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["project_id"])),
@@ -1726,6 +1795,27 @@ func TestAccVolumeMax(t *testing.T) {
),
resource.TestCheckResourceAttr("data.stackit_volume.volume_source", "source.type", "volume"),
resource.TestCheckNoResourceAttr("data.stackit_volume.volume_source", "server_id"),
+ resource.TestCheckResourceAttr("data.stackit_volume.volume_source", "encrypted", "false"),
+
+ // Volume encrypted - no key payload
+ resource.TestCheckResourceAttr("data.stackit_volume.volume_encrypted_no_key_payload", "project_id", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["project_id"])),
+ resource.TestCheckResourceAttrSet("data.stackit_volume.volume_encrypted_no_key_payload", "volume_id"),
+ resource.TestCheckResourceAttr("data.stackit_volume.volume_encrypted_no_key_payload", "region", testutil.Region),
+ resource.TestCheckResourceAttr("data.stackit_volume.volume_encrypted_no_key_payload", "availability_zone", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["availability_zone"])),
+ resource.TestCheckResourceAttr("data.stackit_volume.volume_encrypted_no_key_payload", "size", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["size"])),
+ resource.TestCheckResourceAttrSet("data.stackit_volume.volume_encrypted_no_key_payload", "performance_class"),
+ resource.TestCheckNoResourceAttr("data.stackit_volume.volume_encrypted_no_key_payload", "server_id"),
+ resource.TestCheckResourceAttr("data.stackit_volume.volume_encrypted_no_key_payload", "encrypted", "true"),
+
+ // Volume encrypted - with key payload
+ resource.TestCheckResourceAttr("data.stackit_volume.volume_encrypted_no_key_payload", "project_id", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["project_id"])),
+ resource.TestCheckResourceAttrSet("data.stackit_volume.volume_encrypted_no_key_payload", "volume_id"),
+ resource.TestCheckResourceAttr("data.stackit_volume.volume_encrypted_no_key_payload", "region", testutil.Region),
+ resource.TestCheckResourceAttr("data.stackit_volume.volume_encrypted_no_key_payload", "availability_zone", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["availability_zone"])),
+ resource.TestCheckResourceAttr("data.stackit_volume.volume_encrypted_no_key_payload", "size", testutil.ConvertConfigVariable(testConfigVolumeVarsMax["size"])),
+ resource.TestCheckResourceAttrSet("data.stackit_volume.volume_encrypted_no_key_payload", "performance_class"),
+ resource.TestCheckNoResourceAttr("data.stackit_volume.volume_encrypted_no_key_payload", "server_id"),
+ resource.TestCheckResourceAttr("data.stackit_volume.volume_encrypted_no_key_payload", "encrypted", "true"),
),
},
// Import
@@ -1763,6 +1853,58 @@ func TestAccVolumeMax(t *testing.T) {
ImportState: true,
ImportStateVerify: true,
},
+ {
+ ConfigVariables: testConfigVolumeVarsMax,
+ ResourceName: "stackit_volume.volume_encrypted_no_key_payload",
+ ImportStateIdFunc: func(s *terraform.State) (string, error) {
+ r, ok := s.RootModule().Resources["stackit_volume.volume_encrypted_no_key_payload"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find resource stackit_volume.volume_encrypted_no_key_payload")
+ }
+ volumeId, ok := r.Primary.Attributes["volume_id"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute volume_id")
+ }
+ return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, volumeId), nil
+ },
+ ImportState: true,
+ ImportStateVerify: true,
+ // the values below won't be imported, as they can be only **sent** to the API, but will be **never returned**
+ ImportStateVerifyIgnore: []string{
+ "encryption_parameters",
+ "encryption_parameters.kek_key_id",
+ "encryption_parameters.kek_key_version",
+ "encryption_parameters.kek_keyring_id",
+ "encryption_parameters.key_payload_base64",
+ "encryption_parameters.service_account",
+ },
+ },
+ {
+ ConfigVariables: testConfigVolumeVarsMax,
+ ResourceName: "stackit_volume.volume_encrypted_with_key_payload",
+ ImportStateIdFunc: func(s *terraform.State) (string, error) {
+ r, ok := s.RootModule().Resources["stackit_volume.volume_encrypted_with_key_payload"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find resource stackit_volume.volume_encrypted_with_key_payload")
+ }
+ volumeId, ok := r.Primary.Attributes["volume_id"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute volume_id")
+ }
+ return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, volumeId), nil
+ },
+ ImportState: true,
+ ImportStateVerify: true,
+ // the values below won't be imported, as they can be only **sent** to the API, but will be **never returned**
+ ImportStateVerifyIgnore: []string{
+ "encryption_parameters",
+ "encryption_parameters.kek_key_id",
+ "encryption_parameters.kek_key_version",
+ "encryption_parameters.kek_keyring_id",
+ "encryption_parameters.key_payload_base64",
+ "encryption_parameters.service_account",
+ },
+ },
// Update
{
ConfigVariables: testConfigVolumeVarsMaxUpdated,
@@ -1779,6 +1921,8 @@ func TestAccVolumeMax(t *testing.T) {
resource.TestCheckNoResourceAttr("stackit_volume.volume_size", "server_id"),
resource.TestCheckResourceAttr("stackit_volume.volume_size", "labels.%", "1"),
resource.TestCheckResourceAttr("stackit_volume.volume_size", "labels.acc-test", testutil.ConvertConfigVariable(testConfigVolumeVarsMaxUpdated["label"])),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_size", "encryption_parameters"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_size", "encrypted", "false"),
// Volume source
resource.TestCheckResourceAttr("stackit_volume.volume_source", "project_id", testutil.ConvertConfigVariable(testConfigVolumeVarsMaxUpdated["project_id"])),
@@ -1796,6 +1940,50 @@ func TestAccVolumeMax(t *testing.T) {
),
resource.TestCheckResourceAttr("stackit_volume.volume_source", "source.type", "volume"),
resource.TestCheckNoResourceAttr("stackit_volume.volume_source", "server_id"),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_source", "encryption_parameters"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_source", "encrypted", "false"),
+
+ // Volume encrypted - no key payload
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "project_id", testutil.ConvertConfigVariable(testConfigVolumeVarsMaxUpdated["project_id"])),
+ resource.TestCheckResourceAttrSet("stackit_volume.volume_encrypted_no_key_payload", "volume_id"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "region", testutil.Region),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "availability_zone", testutil.ConvertConfigVariable(testConfigVolumeVarsMaxUpdated["availability_zone"])),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "size", testutil.ConvertConfigVariable(testConfigVolumeVarsMaxUpdated["size"])),
+ resource.TestCheckResourceAttrSet("stackit_volume.volume_encrypted_no_key_payload", "performance_class"),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "server_id"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encrypted", "true"),
+ resource.TestCheckResourceAttrPair(
+ "stackit_kms_key.key", "key_id",
+ "stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.kek_key_id",
+ ),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.kek_key_version", "1"),
+ resource.TestCheckResourceAttrPair(
+ "stackit_kms_key.key", "keyring_id",
+ "stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.kek_keyring_id",
+ ),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.key_payload_base64"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.service_account", testutil.ConvertConfigVariable(testConfigVolumeVarsMaxUpdated["service_account_mail"])),
+
+ // Volume encrypted - with key payload
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "project_id", testutil.ConvertConfigVariable(testConfigVolumeVarsMaxUpdated["project_id"])),
+ resource.TestCheckResourceAttrSet("stackit_volume.volume_encrypted_no_key_payload", "volume_id"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "region", testutil.Region),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "availability_zone", testutil.ConvertConfigVariable(testConfigVolumeVarsMaxUpdated["availability_zone"])),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "size", testutil.ConvertConfigVariable(testConfigVolumeVarsMaxUpdated["size"])),
+ resource.TestCheckResourceAttrSet("stackit_volume.volume_encrypted_no_key_payload", "performance_class"),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "server_id"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encrypted", "true"),
+ resource.TestCheckResourceAttrPair(
+ "stackit_kms_key.key", "key_id",
+ "stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.kek_key_id",
+ ),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.kek_key_version", "1"),
+ resource.TestCheckResourceAttrPair(
+ "stackit_kms_key.key", "keyring_id",
+ "stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.kek_keyring_id",
+ ),
+ resource.TestCheckNoResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.key_payload_base64"),
+ resource.TestCheckResourceAttr("stackit_volume.volume_encrypted_no_key_payload", "encryption_parameters.service_account", testutil.ConvertConfigVariable(testConfigVolumeVarsMaxUpdated["service_account_mail"])),
),
},
// Deletion is done by the framework implicitly
diff --git a/stackit/internal/services/iaas/testdata/resource-volume-max.tf b/stackit/internal/services/iaas/testdata/resource-volume-max.tf
index 54c590f63..fd47f5259 100644
--- a/stackit/internal/services/iaas/testdata/resource-volume-max.tf
+++ b/stackit/internal/services/iaas/testdata/resource-volume-max.tf
@@ -5,6 +5,8 @@ variable "size" {}
variable "description" {}
variable "performance_class" {}
variable "label" {}
+variable "key_payload_base64" {}
+variable "service_account_mail" {}
resource "stackit_volume" "volume_size" {
project_id = var.project_id
@@ -33,4 +35,59 @@ resource "stackit_volume" "volume_source" {
labels = {
"acc-test" : var.label
}
-}
\ No newline at end of file
+}
+
+# just needed for the test setup for encrypted volumes
+resource "stackit_kms_keyring" "keyring" {
+ project_id = var.project_id
+ display_name = var.name
+}
+
+# just needed for the test setup for encrypted volumes
+resource "stackit_kms_key" "key" {
+ project_id = var.project_id
+ keyring_id = stackit_kms_keyring.keyring.keyring_id
+ display_name = var.name
+ protection = "software"
+ algorithm = "aes_256_gcm"
+ purpose = "symmetric_encrypt_decrypt"
+}
+
+resource "stackit_volume" "volume_encrypted_no_key_payload" {
+ project_id = var.project_id
+ availability_zone = var.availability_zone
+ name = var.name
+ size = var.size
+ description = var.description
+ performance_class = var.performance_class
+ labels = {
+ "acc-test" : var.label
+ }
+
+ encryption_parameters = {
+ kek_key_id = stackit_kms_key.key.key_id
+ kek_key_version = 1
+ kek_keyring_id = stackit_kms_keyring.keyring.keyring_id
+ service_account = var.service_account_mail
+ }
+}
+
+resource "stackit_volume" "volume_encrypted_with_key_payload" {
+ project_id = var.project_id
+ availability_zone = var.availability_zone
+ name = var.name
+ size = var.size
+ description = var.description
+ performance_class = var.performance_class
+ labels = {
+ "acc-test" : var.label
+ }
+
+ encryption_parameters = {
+ kek_key_id = stackit_kms_key.key.key_id
+ kek_key_version = 1
+ kek_keyring_id = stackit_kms_keyring.keyring.keyring_id
+ key_payload_base64 = var.key_payload_base64
+ service_account = var.service_account_mail
+ }
+}
diff --git a/stackit/internal/services/iaas/volume/datasource.go b/stackit/internal/services/iaas/volume/datasource.go
index 5e36a3950..d3553037c 100644
--- a/stackit/internal/services/iaas/volume/datasource.go
+++ b/stackit/internal/services/iaas/volume/datasource.go
@@ -5,6 +5,10 @@ import (
"fmt"
"net/http"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
@@ -24,6 +28,23 @@ var (
_ datasource.DataSource = &volumeDataSource{}
)
+type DatasourceModel struct {
+ // basically the same as the resource model, just without encryption parameters as they are only **sent** to the API, but **never returned**
+ Id types.String `tfsdk:"id"` // needed by TF
+ ProjectId types.String `tfsdk:"project_id"`
+ Region types.String `tfsdk:"region"`
+ VolumeId types.String `tfsdk:"volume_id"`
+ Name types.String `tfsdk:"name"`
+ AvailabilityZone types.String `tfsdk:"availability_zone"`
+ Labels types.Map `tfsdk:"labels"`
+ Description types.String `tfsdk:"description"`
+ PerformanceClass types.String `tfsdk:"performance_class"`
+ Size types.Int64 `tfsdk:"size"`
+ ServerId types.String `tfsdk:"server_id"`
+ Source types.Object `tfsdk:"source"`
+ Encrypted types.Bool `tfsdk:"encrypted"`
+}
+
// NewVolumeDataSource is a helper function to simplify the provider implementation.
func NewVolumeDataSource() datasource.DataSource {
return &volumeDataSource{}
@@ -134,13 +155,17 @@ func (d *volumeDataSource) Schema(_ context.Context, _ datasource.SchemaRequest,
},
},
},
+ "encrypted": schema.BoolAttribute{
+ Description: "Indicates if the volume is encrypted.",
+ Computed: true,
+ },
},
}
}
// Read refreshes the Terraform state with the latest data.
func (d *volumeDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
- var model Model
+ var model DatasourceModel
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
@@ -174,7 +199,7 @@ func (d *volumeDataSource) Read(ctx context.Context, req datasource.ReadRequest,
ctx = core.LogResponse(ctx)
- err = mapFields(ctx, volumeResp, &model, region)
+ err = mapDatasourceFields(ctx, volumeResp, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading volume", fmt.Sprintf("Processing API payload: %v", err))
return
@@ -186,3 +211,62 @@ func (d *volumeDataSource) Read(ctx context.Context, req datasource.ReadRequest,
}
tflog.Info(ctx, "volume read")
}
+
+func mapDatasourceFields(ctx context.Context, volumeResp *iaas.Volume, model *DatasourceModel, region string) error {
+ if volumeResp == nil {
+ return fmt.Errorf("response input is nil")
+ }
+ if model == nil {
+ return fmt.Errorf("model input is nil")
+ }
+
+ var volumeId string
+ if model.VolumeId.ValueString() != "" {
+ volumeId = model.VolumeId.ValueString()
+ } else if volumeResp.Id != nil {
+ volumeId = *volumeResp.Id
+ } else {
+ return fmt.Errorf("Volume id not present")
+ }
+
+ model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, volumeId)
+ model.Region = types.StringValue(region)
+
+ labels, err := iaasUtils.MapLabels(ctx, volumeResp.Labels, model.Labels)
+ if err != nil {
+ return err
+ }
+
+ var sourceValues map[string]attr.Value
+ var sourceObject basetypes.ObjectValue
+ if volumeResp.Source == nil {
+ sourceObject = types.ObjectNull(sourceTypes)
+ } else {
+ sourceValues = map[string]attr.Value{
+ "type": types.StringPointerValue(volumeResp.Source.Type),
+ "id": types.StringPointerValue(volumeResp.Source.Id),
+ }
+ var diags diag.Diagnostics
+ sourceObject, diags = types.ObjectValue(sourceTypes, sourceValues)
+ if diags.HasError() {
+ return fmt.Errorf("creating source: %w", core.DiagsToError(diags))
+ }
+ }
+
+ model.VolumeId = types.StringValue(volumeId)
+ model.AvailabilityZone = types.StringPointerValue(volumeResp.AvailabilityZone)
+ model.Description = types.StringPointerValue(volumeResp.Description)
+ model.Name = types.StringPointerValue(volumeResp.Name)
+ // Workaround for volumes with no names which return an empty string instead of nil
+ if name := volumeResp.Name; name != nil && *name == "" {
+ model.Name = types.StringNull()
+ }
+ model.Labels = labels
+ model.PerformanceClass = types.StringPointerValue(volumeResp.PerformanceClass)
+ model.ServerId = types.StringPointerValue(volumeResp.ServerId)
+ model.Size = types.Int64PointerValue(volumeResp.Size)
+ model.Source = sourceObject
+ model.Encrypted = types.BoolPointerValue(volumeResp.Encrypted)
+
+ return nil
+}
diff --git a/stackit/internal/services/iaas/volume/datasource_test.go b/stackit/internal/services/iaas/volume/datasource_test.go
new file mode 100644
index 000000000..e5b505576
--- /dev/null
+++ b/stackit/internal/services/iaas/volume/datasource_test.go
@@ -0,0 +1,172 @@
+package volume
+
+import (
+ "context"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/stackitcloud/stackit-sdk-go/core/utils"
+ "github.com/stackitcloud/stackit-sdk-go/services/iaas"
+)
+
+func TestMapDatasourceFields(t *testing.T) {
+ type args struct {
+ state DatasourceModel
+ input *iaas.Volume
+ region string
+ }
+ tests := []struct {
+ description string
+ args args
+ expected DatasourceModel
+ isValid bool
+ }{
+ {
+ description: "default_values",
+ args: args{
+ state: DatasourceModel{
+ ProjectId: types.StringValue("pid"),
+ VolumeId: types.StringValue("nid"),
+ },
+ input: &iaas.Volume{
+ Id: utils.Ptr("nid"),
+ EncryptionParameters: nil,
+ },
+ region: "eu01",
+ },
+ expected: DatasourceModel{
+ Id: types.StringValue("pid,eu01,nid"),
+ ProjectId: types.StringValue("pid"),
+ VolumeId: types.StringValue("nid"),
+ Name: types.StringNull(),
+ AvailabilityZone: types.StringNull(),
+ Labels: types.MapNull(types.StringType),
+ Description: types.StringNull(),
+ PerformanceClass: types.StringNull(),
+ ServerId: types.StringNull(),
+ Size: types.Int64Null(),
+ Source: types.ObjectNull(sourceTypes),
+ Region: types.StringValue("eu01"),
+ },
+ isValid: true,
+ },
+ {
+ description: "simple_values",
+ args: args{
+ state: DatasourceModel{
+ ProjectId: types.StringValue("pid"),
+ VolumeId: types.StringValue("nid"),
+ Region: types.StringValue("eu01"),
+ },
+ input: &iaas.Volume{
+ Id: utils.Ptr("nid"),
+ Name: utils.Ptr("name"),
+ AvailabilityZone: utils.Ptr("zone"),
+ Labels: &map[string]interface{}{
+ "key": "value",
+ },
+ Description: utils.Ptr("desc"),
+ PerformanceClass: utils.Ptr("class"),
+ ServerId: utils.Ptr("sid"),
+ Size: utils.Ptr(int64(1)),
+ Source: &iaas.VolumeSource{},
+ Encrypted: utils.Ptr(true),
+ EncryptionParameters: &iaas.VolumeEncryptionParameter{
+ KekKeyId: utils.Ptr("kek-key-id"),
+ KekKeyVersion: utils.Ptr(int64(1)),
+ KekKeyringId: utils.Ptr("kek-keyring-id"),
+ KekProjectId: utils.Ptr("kek-project-id"),
+ KeyPayload: nil,
+ ServiceAccount: utils.Ptr("test-sa@sa.stackit.cloud"),
+ },
+ },
+ region: "eu02",
+ },
+ expected: DatasourceModel{
+ Id: types.StringValue("pid,eu02,nid"),
+ ProjectId: types.StringValue("pid"),
+ VolumeId: types.StringValue("nid"),
+ Name: types.StringValue("name"),
+ AvailabilityZone: types.StringValue("zone"),
+ Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
+ "key": types.StringValue("value"),
+ }),
+ Description: types.StringValue("desc"),
+ PerformanceClass: types.StringValue("class"),
+ ServerId: types.StringValue("sid"),
+ Size: types.Int64Value(1),
+ Source: types.ObjectValueMust(sourceTypes, map[string]attr.Value{
+ "type": types.StringNull(),
+ "id": types.StringNull(),
+ }),
+ Region: types.StringValue("eu02"),
+ Encrypted: types.BoolValue(true),
+ },
+ isValid: true,
+ },
+ {
+ description: "empty labels and encryption parameters",
+ args: args{
+ state: DatasourceModel{
+ ProjectId: types.StringValue("pid"),
+ VolumeId: types.StringValue("nid"),
+ Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
+ },
+ input: &iaas.Volume{
+ Id: utils.Ptr("nid"),
+ EncryptionParameters: &iaas.VolumeEncryptionParameter{},
+ },
+ region: "eu01",
+ },
+ expected: DatasourceModel{
+ Id: types.StringValue("pid,eu01,nid"),
+ ProjectId: types.StringValue("pid"),
+ VolumeId: types.StringValue("nid"),
+ Name: types.StringNull(),
+ AvailabilityZone: types.StringNull(),
+ Labels: types.MapValueMust(types.StringType, map[string]attr.Value{}),
+ Description: types.StringNull(),
+ PerformanceClass: types.StringNull(),
+ ServerId: types.StringNull(),
+ Size: types.Int64Null(),
+ Source: types.ObjectNull(sourceTypes),
+ Region: types.StringValue("eu01"),
+ },
+ isValid: true,
+ },
+ {
+ description: "response_nil_fail",
+ },
+ {
+ description: "no_resource_id",
+ args: args{
+ state: DatasourceModel{
+ ProjectId: types.StringValue("pid"),
+ },
+ input: &iaas.Volume{},
+ },
+ expected: DatasourceModel{},
+ isValid: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ err := mapDatasourceFields(context.Background(), tt.args.input, &tt.args.state, tt.args.region)
+ if !tt.isValid && err == nil {
+ t.Fatalf("Should have failed")
+ }
+ if tt.isValid && err != nil {
+ t.Fatalf("Should not have failed: %v", err)
+ }
+ if tt.isValid {
+ diff := cmp.Diff(tt.args.state, tt.expected)
+ if diff != "" {
+ t.Fatalf("Data does not match: %s", diff)
+ }
+ }
+ })
+ }
+}
diff --git a/stackit/internal/services/iaas/volume/resource.go b/stackit/internal/services/iaas/volume/resource.go
index 1ce0d5d73..4ccf21d3c 100644
--- a/stackit/internal/services/iaas/volume/resource.go
+++ b/stackit/internal/services/iaas/volume/resource.go
@@ -7,6 +7,10 @@ import (
"regexp"
"strings"
+ sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils"
+
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
+
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
"github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator"
@@ -43,18 +47,28 @@ var (
)
type Model struct {
- Id types.String `tfsdk:"id"` // needed by TF
- ProjectId types.String `tfsdk:"project_id"`
- Region types.String `tfsdk:"region"`
- VolumeId types.String `tfsdk:"volume_id"`
- Name types.String `tfsdk:"name"`
- AvailabilityZone types.String `tfsdk:"availability_zone"`
- Labels types.Map `tfsdk:"labels"`
- Description types.String `tfsdk:"description"`
- PerformanceClass types.String `tfsdk:"performance_class"`
- Size types.Int64 `tfsdk:"size"`
- ServerId types.String `tfsdk:"server_id"`
- Source types.Object `tfsdk:"source"`
+ Id types.String `tfsdk:"id"` // needed by TF
+ ProjectId types.String `tfsdk:"project_id"`
+ Region types.String `tfsdk:"region"`
+ VolumeId types.String `tfsdk:"volume_id"`
+ Name types.String `tfsdk:"name"`
+ AvailabilityZone types.String `tfsdk:"availability_zone"`
+ Labels types.Map `tfsdk:"labels"`
+ Description types.String `tfsdk:"description"`
+ PerformanceClass types.String `tfsdk:"performance_class"`
+ Size types.Int64 `tfsdk:"size"`
+ ServerId types.String `tfsdk:"server_id"`
+ Source types.Object `tfsdk:"source"`
+ EncryptionParameters *encryptionParametersModel `tfsdk:"encryption_parameters"`
+ Encrypted types.Bool `tfsdk:"encrypted"`
+}
+
+type encryptionParametersModel struct {
+ KekKeyId types.String `tfsdk:"kek_key_id"`
+ KekKeyVersion types.Int64 `tfsdk:"kek_key_version"`
+ KekKeyringId types.String `tfsdk:"kek_keyring_id"`
+ KeyPayloadBase64 types.String `tfsdk:"key_payload_base64"`
+ ServiceAccount types.String `tfsdk:"service_account"`
}
// Struct corresponding to Model.Source
@@ -283,6 +297,63 @@ func (r *volumeResource) Schema(_ context.Context, _ resource.SchemaRequest, res
},
},
},
+ "encryption_parameters": schema.SingleNestedAttribute{
+ Description: "Parameter to connect to a key-encryption-key within the STACKIT-KMS to create encrypted volumes. These parameters never leave the backend again. So these parameters are not present on imports or in the datasource. They live only in your Terraform state after creation of the resource.",
+ Optional: true,
+ PlanModifiers: []planmodifier.Object{
+ objectplanmodifier.RequiresReplace(),
+ },
+ Attributes: map[string]schema.Attribute{
+ "kek_key_id": schema.StringAttribute{
+ Description: "UUID of the key within the STACKIT-KMS to use for the encryption.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "kek_key_version": schema.Int64Attribute{
+ Description: "Version of the key within the STACKIT-KMS to use for the encryption.",
+ Required: true,
+ PlanModifiers: []planmodifier.Int64{
+ int64planmodifier.RequiresReplace(),
+ },
+ },
+ "kek_keyring_id": schema.StringAttribute{
+ Description: "UUID of the keyring where the key is located within the STACKTI-KMS.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "key_payload_base64": schema.StringAttribute{
+ Description: "Optional predefined secret, which will be encrypted against the key-encryption-key within the STACKIT-KMS. If not defined, a random secret will be generated by the API and encrypted against the STACKIT-KMS. If a key-payload is provided here, it must be base64 encoded.",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Sensitive: true,
+ },
+ "service_account": schema.StringAttribute{
+ Description: "Service-Account linked to the Key within the STACKIT-KMS.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ },
+ },
+ "encrypted": schema.BoolAttribute{
+ Description: "Indicates if the volume is encrypted.",
+ Computed: true,
+ },
},
}
}
@@ -621,6 +692,10 @@ func mapFields(ctx context.Context, volumeResp *iaas.Volume, model *Model, regio
model.ServerId = types.StringPointerValue(volumeResp.ServerId)
model.Size = types.Int64PointerValue(volumeResp.Size)
model.Source = sourceObject
+ model.Encrypted = types.BoolPointerValue(volumeResp.Encrypted)
+
+ // no need to map encryption parameters as they are only **sent** to the API but **never returned**
+
return nil
}
@@ -643,7 +718,7 @@ func toCreatePayload(ctx context.Context, model *Model, source *sourceModel) (*i
}
}
- return &iaas.CreateVolumePayload{
+ payload := iaas.CreateVolumePayload{
AvailabilityZone: conversion.StringValueToPointer(model.AvailabilityZone),
Description: conversion.StringValueToPointer(model.Description),
Labels: &labels,
@@ -651,7 +726,25 @@ func toCreatePayload(ctx context.Context, model *Model, source *sourceModel) (*i
PerformanceClass: conversion.StringValueToPointer(model.PerformanceClass),
Size: conversion.Int64ValueToPointer(model.Size),
Source: sourcePayload,
- }, nil
+ }
+
+ if model.EncryptionParameters != nil {
+ var keyPayload *[]byte
+ if !model.EncryptionParameters.KeyPayloadBase64.IsNull() && !model.EncryptionParameters.KeyPayloadBase64.IsUnknown() {
+ keyPayload = sdkUtils.Ptr([]byte(model.EncryptionParameters.KeyPayloadBase64.ValueString()))
+ }
+
+ payload.EncryptionParameters = &iaas.VolumeEncryptionParameter{
+ KekKeyId: conversion.StringValueToPointer(model.EncryptionParameters.KekKeyId),
+ KekKeyVersion: conversion.Int64ValueToPointer(model.EncryptionParameters.KekKeyVersion),
+ KekKeyringId: conversion.StringValueToPointer(model.EncryptionParameters.KekKeyringId),
+ KekProjectId: nil,
+ KeyPayload: keyPayload,
+ ServiceAccount: conversion.StringValueToPointer(model.EncryptionParameters.ServiceAccount),
+ }
+ }
+
+ return &payload, nil
}
func toUpdatePayload(ctx context.Context, model *Model, currentLabels types.Map) (*iaas.UpdateVolumePayload, error) {
diff --git a/stackit/internal/services/iaas/volume/resource_test.go b/stackit/internal/services/iaas/volume/resource_test.go
index 14f456a73..b97b1dc6e 100644
--- a/stackit/internal/services/iaas/volume/resource_test.go
+++ b/stackit/internal/services/iaas/volume/resource_test.go
@@ -31,23 +31,25 @@ func TestMapFields(t *testing.T) {
VolumeId: types.StringValue("nid"),
},
input: &iaas.Volume{
- Id: utils.Ptr("nid"),
+ Id: utils.Ptr("nid"),
+ EncryptionParameters: nil,
},
region: "eu01",
},
expected: Model{
- Id: types.StringValue("pid,eu01,nid"),
- ProjectId: types.StringValue("pid"),
- VolumeId: types.StringValue("nid"),
- Name: types.StringNull(),
- AvailabilityZone: types.StringNull(),
- Labels: types.MapNull(types.StringType),
- Description: types.StringNull(),
- PerformanceClass: types.StringNull(),
- ServerId: types.StringNull(),
- Size: types.Int64Null(),
- Source: types.ObjectNull(sourceTypes),
- Region: types.StringValue("eu01"),
+ Id: types.StringValue("pid,eu01,nid"),
+ ProjectId: types.StringValue("pid"),
+ VolumeId: types.StringValue("nid"),
+ Name: types.StringNull(),
+ AvailabilityZone: types.StringNull(),
+ Labels: types.MapNull(types.StringType),
+ Description: types.StringNull(),
+ PerformanceClass: types.StringNull(),
+ ServerId: types.StringNull(),
+ Size: types.Int64Null(),
+ Source: types.ObjectNull(sourceTypes),
+ Region: types.StringValue("eu01"),
+ EncryptionParameters: nil,
},
isValid: true,
},
@@ -58,6 +60,13 @@ func TestMapFields(t *testing.T) {
ProjectId: types.StringValue("pid"),
VolumeId: types.StringValue("nid"),
Region: types.StringValue("eu01"),
+ EncryptionParameters: &encryptionParametersModel{
+ KekKeyId: types.StringValue("kek-key-id"),
+ KekKeyVersion: types.Int64Value(int64(1)),
+ KekKeyringId: types.StringValue("kek-keyring-id"),
+ KeyPayloadBase64: types.StringValue("cm91dGVkb3VidGV2ZXJvdmVyY2xhc3Nkcml2aW5ndGhpbmdmbGFtZWNyb3dkcXVpY2s="),
+ ServiceAccount: types.StringValue("test-sa@sa.stackit.cloud"),
+ },
},
input: &iaas.Volume{
Id: utils.Ptr("nid"),
@@ -71,6 +80,15 @@ func TestMapFields(t *testing.T) {
ServerId: utils.Ptr("sid"),
Size: utils.Ptr(int64(1)),
Source: &iaas.VolumeSource{},
+ Encrypted: utils.Ptr(true),
+ EncryptionParameters: &iaas.VolumeEncryptionParameter{
+ KekKeyId: utils.Ptr("kek-key-id"),
+ KekKeyVersion: utils.Ptr(int64(1)),
+ KekKeyringId: utils.Ptr("kek-keyring-id"),
+ KekProjectId: utils.Ptr("kek-project-id"),
+ KeyPayload: nil,
+ ServiceAccount: utils.Ptr("test-sa@sa.stackit.cloud"),
+ },
},
region: "eu02",
},
@@ -91,12 +109,20 @@ func TestMapFields(t *testing.T) {
"type": types.StringNull(),
"id": types.StringNull(),
}),
- Region: types.StringValue("eu02"),
+ Region: types.StringValue("eu02"),
+ Encrypted: types.BoolValue(true),
+ EncryptionParameters: &encryptionParametersModel{
+ KekKeyId: types.StringValue("kek-key-id"),
+ KekKeyVersion: types.Int64Value(int64(1)),
+ KekKeyringId: types.StringValue("kek-keyring-id"),
+ KeyPayloadBase64: types.StringValue("cm91dGVkb3VidGV2ZXJvdmVyY2xhc3Nkcml2aW5ndGhpbmdmbGFtZWNyb3dkcXVpY2s="),
+ ServiceAccount: types.StringValue("test-sa@sa.stackit.cloud"),
+ },
},
isValid: true,
},
{
- description: "empty_labels",
+ description: "empty labels",
args: args{
state: Model{
ProjectId: types.StringValue("pid"),
@@ -167,8 +193,8 @@ func TestToCreatePayload(t *testing.T) {
isValid bool
}{
{
- "default_ok",
- &Model{
+ description: "no volume encryption",
+ input: &Model{
Name: types.StringValue("name"),
AvailabilityZone: types.StringValue("zone"),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
@@ -182,11 +208,11 @@ func TestToCreatePayload(t *testing.T) {
"id": types.StringNull(),
}),
},
- &sourceModel{
+ source: &sourceModel{
Type: types.StringValue("volume"),
Id: types.StringValue("id"),
},
- &iaas.CreateVolumePayload{
+ expected: &iaas.CreateVolumePayload{
Name: utils.Ptr("name"),
AvailabilityZone: utils.Ptr("zone"),
Labels: &map[string]interface{}{
@@ -200,7 +226,81 @@ func TestToCreatePayload(t *testing.T) {
Id: utils.Ptr("id"),
},
},
- true,
+ isValid: true,
+ },
+ {
+ description: "with volume encryption without key payload",
+ input: &Model{
+ Labels: types.MapNull(types.StringType),
+ EncryptionParameters: &encryptionParametersModel{
+ KekKeyId: types.StringValue("kek-key-id"),
+ KekKeyVersion: types.Int64Value(int64(1)),
+ KekKeyringId: types.StringValue("kek-keyring-id"),
+ KeyPayloadBase64: types.StringNull(),
+ ServiceAccount: types.StringValue("test-sa@sa.stackit.cloud"),
+ },
+ },
+ source: &sourceModel{
+ Type: types.StringValue("volume"),
+ Id: types.StringValue("id"),
+ },
+ expected: &iaas.CreateVolumePayload{
+ Source: &iaas.VolumeSource{
+ Type: utils.Ptr("volume"),
+ Id: utils.Ptr("id"),
+ },
+ Labels: &map[string]interface{}{},
+ EncryptionParameters: &iaas.VolumeEncryptionParameter{
+ KekKeyId: utils.Ptr("kek-key-id"),
+ KekKeyVersion: utils.Ptr(int64(1)),
+ KekKeyringId: utils.Ptr("kek-keyring-id"),
+ KekProjectId: nil,
+ KeyPayload: nil,
+ ServiceAccount: utils.Ptr("test-sa@sa.stackit.cloud"),
+ },
+ },
+ isValid: true,
+ },
+ {
+ description: "with volume encryption including key payload",
+ input: &Model{
+ Labels: types.MapNull(types.StringType),
+ EncryptionParameters: &encryptionParametersModel{
+ KekKeyId: types.StringValue("kek-key-id"),
+ KekKeyVersion: types.Int64Value(int64(1)),
+ KekKeyringId: types.StringValue("kek-keyring-id"),
+ KeyPayloadBase64: types.StringValue("VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIDEzIGxhenkgZG9ncy4="), // The quick brown fox jumps over 13 lazy dogs.
+ ServiceAccount: types.StringValue("test-sa@sa.stackit.cloud"),
+ },
+ },
+ source: &sourceModel{
+ Type: types.StringValue("volume"),
+ Id: types.StringValue("id"),
+ },
+ expected: &iaas.CreateVolumePayload{
+ Source: &iaas.VolumeSource{
+ Type: utils.Ptr("volume"),
+ Id: utils.Ptr("id"),
+ },
+ Labels: &map[string]interface{}{},
+ EncryptionParameters: &iaas.VolumeEncryptionParameter{
+ KekKeyId: utils.Ptr("kek-key-id"),
+ KekKeyVersion: utils.Ptr(int64(1)),
+ KekKeyringId: utils.Ptr("kek-keyring-id"),
+ KekProjectId: nil,
+ KeyPayload: func() *[]byte {
+ keyPayload := []byte{
+ 0x56, 0x47, 0x68, 0x6c, 0x49, 0x48, 0x46, 0x31, 0x61, 0x57, 0x4e, 0x72, 0x49, 0x47, 0x4a,
+ 0x79, 0x62, 0x33, 0x64, 0x75, 0x49, 0x47, 0x5a, 0x76, 0x65, 0x43, 0x42, 0x71, 0x64, 0x57,
+ 0x31, 0x77, 0x63, 0x79, 0x42, 0x76, 0x64, 0x6d, 0x56, 0x79, 0x49, 0x44, 0x45, 0x7a, 0x49,
+ 0x47, 0x78, 0x68, 0x65, 0x6e, 0x6b, 0x67, 0x5a, 0x47, 0x39, 0x6e, 0x63, 0x79, 0x34, 0x3d,
+ }
+ return &keyPayload
+ }(),
+ ServiceAccount: utils.Ptr("test-sa@sa.stackit.cloud"),
+ },
+ },
+ isValid: true,
},
}
for _, tt := range tests {