Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/data-sources/volume.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
17 changes: 17 additions & 0 deletions docs/resources/volume.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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.

<a id="nestedatt--encryption_parameters"></a>
### 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.


<a id="nestedatt--source"></a>
### Nested Schema for `source`

Expand Down
202 changes: 195 additions & 7 deletions stackit/internal/services/iaas/iaas_acc_test.go

Large diffs are not rendered by default.

59 changes: 58 additions & 1 deletion stackit/internal/services/iaas/testdata/resource-volume-max.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -33,4 +35,59 @@ resource "stackit_volume" "volume_source" {
labels = {
"acc-test" : var.label
}
}
}

# 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
}
}
88 changes: 86 additions & 2 deletions stackit/internal/services/iaas/volume/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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{}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Loading
Loading