terraform-provider: Add support for STACKIT / OpenStack

This commit is contained in:
Malte Poll 2024-03-06 20:48:40 +01:00
parent d1a22a725e
commit d6d9ef437c
24 changed files with 511 additions and 36 deletions

View file

@ -33,6 +33,7 @@ data "constellation_attestation" "test" {
* `azure-sev-snp`
* `azure-tdx`
* `gcp-sev-es`
* `qemu-vtpm`
- `csp` (String) CSP (Cloud Service Provider) to use. (e.g. `azure`)
See the [full list of CSPs](https://docs.edgeless.systems/constellation/overview/clouds) that Constellation supports.
- `image` (Attributes) Constellation OS Image to use on the nodes. (see [below for nested schema](#nestedatt--image))
@ -82,6 +83,7 @@ Read-Only:
* `azure-sev-snp`
* `azure-tdx`
* `gcp-sev-es`
* `qemu-vtpm`
<a id="nestedatt--attestation--azure_firmware_signer_config"></a>
### Nested Schema for `attestation.azure_firmware_signer_config`

View file

@ -32,6 +32,7 @@ data "constellation_image" "example" {
* `azure-sev-snp`
* `azure-tdx`
* `gcp-sev-es`
* `qemu-vtpm`
- `csp` (String) CSP (Cloud Service Provider) to use. (e.g. `azure`)
See the [full list of CSPs](https://docs.edgeless.systems/constellation/overview/clouds) that Constellation supports.

View file

@ -86,6 +86,7 @@ See the [full list of CSPs](https://docs.edgeless.systems/constellation/overview
- `gcp` (Attributes) GCP-specific configuration. (see [below for nested schema](#nestedatt--gcp))
- `in_cluster_endpoint` (String) The endpoint of the cluster. When not set, the out-of-cluster endpoint is used.
- `license_id` (String) Constellation license ID. When not set, the community license is used.
- `openstack` (Attributes) OpenStack-specific configuration. (see [below for nested schema](#nestedatt--openstack))
### Read-Only
@ -110,6 +111,7 @@ Required:
* `azure-sev-snp`
* `azure-tdx`
* `gcp-sev-es`
* `qemu-vtpm`
Optional:
@ -211,6 +213,24 @@ Required:
- `project_id` (String) ID of the GCP project the cluster resides in.
- `service_account_key` (String) Base64-encoded private key JSON object of the service account used within the cluster.
<a id="nestedatt--openstack"></a>
### Nested Schema for `openstack`
Required:
- `cloud` (String) Name of the cloud in the clouds.yaml file.
- `floating_ip_pool_id` (String) Floating IP pool to use for the VMs.
- `network_id` (String) OpenStack network ID to use for the VMs.
- `subnet_id` (String) OpenStack subnet ID to use for the VMs.
Optional:
- `clouds_yaml_path` (String) Path to the clouds.yaml file.
- `deploy_yawol_load_balancer` (Boolean) Whether to deploy a YAWOL load balancer.
- `yawol_flavor_id` (String) OpenStack flavor used by the yawollet.
- `yawol_image_id` (String) OpenStack OS image used by the yawollet.
## Import
Import is supported using the following syntax:

View file

@ -0,0 +1,128 @@
terraform {
required_providers {
constellation = {
source = "edgelesssys/constellation"
version = "0.0.0" // replace with the version you want to use
}
random = {
source = "hashicorp/random"
version = "3.6.0"
}
}
}
locals {
name = "constell"
image_version = "vX.Y.Z"
kubernetes_version = "vX.Y.Z"
microservice_version = "vX.Y.Z"
csp = "stackit"
attestation_variant = "qemu-vtpm"
zone = "eu01-1"
cloud = "stackit"
clouds_yaml_path = "~/.config/openstack/clouds.yaml"
floating_ip_pool_id = "970ace5c-458f-484a-a660-0903bcfd91ad"
stackit_project_id = "" // replace with the STACKIT project id
control_plane_count = 3
worker_count = 2
instance_type = "m1a.8cd"
deploy_yawol_load_balancer = true
yawol_image_id = "bcd6c13e-75d1-4c3f-bf0f-8f83580cc1be"
yawol_flavor_id = "3b11b27e-6c73-470d-b595-1d85b95a8cdf"
master_secret = random_bytes.master_secret.hex
master_secret_salt = random_bytes.master_secret_salt.hex
measurement_salt = random_bytes.measurement_salt.hex
}
resource "random_bytes" "master_secret" {
length = 32
}
resource "random_bytes" "master_secret_salt" {
length = 32
}
resource "random_bytes" "measurement_salt" {
length = 32
}
module "stackit_infrastructure" {
// replace $VERSION with the Constellation version you want to use, e.g., v2.14.0
source = "https://github.com/edgelesssys/constellation/releases/download/$VERSION/terraform-module.zip//terraform-module/openstack"
name = local.name
node_groups = {
control_plane_default = {
role = "control-plane"
flavor_id = local.instance_type
state_disk_size = 30
state_disk_type = "storage_premium_perf6"
initial_count = local.control_plane_count
zone = local.zone
},
worker_default = {
role = "worker"
flavor_id = local.instance_type
state_disk_size = 30
state_disk_type = "storage_premium_perf6"
initial_count = local.worker_count
zone = local.zone
}
}
image_id = data.constellation_image.bar.image.reference
debug = false
cloud = local.cloud
openstack_clouds_yaml_path = local.clouds_yaml_path
floating_ip_pool_id = local.floating_ip_pool_id
stackit_project_id = local.stackit_project_id
}
data "constellation_attestation" "foo" {
csp = local.csp
attestation_variant = local.attestation_variant
image = data.constellation_image.bar.image
}
data "constellation_image" "bar" {
csp = local.csp
attestation_variant = local.attestation_variant
version = local.image_version
marketplace_image = true
}
resource "constellation_cluster" "stackit_example" {
csp = local.csp
name = module.stackit_infrastructure.name
uid = module.stackit_infrastructure.uid
image = data.constellation_image.bar.image
attestation = data.constellation_attestation.foo.attestation
kubernetes_version = local.kubernetes_version
constellation_microservice_version = local.microservice_version
init_secret = module.stackit_infrastructure.init_secret
master_secret = local.master_secret
master_secret_salt = local.master_secret_salt
measurement_salt = local.measurement_salt
out_of_cluster_endpoint = module.stackit_infrastructure.out_of_cluster_endpoint
in_cluster_endpoint = module.stackit_infrastructure.in_cluster_endpoint
api_server_cert_sans = module.stackit_infrastructure.api_server_cert_sans
openstack = {
cloud = local.cloud
clouds_yaml_path = local.clouds_yaml_path
floating_ip_pool_id = local.floating_ip_pool_id
deploy_yawol_load_balancer = local.deploy_yawol_load_balancer
yawol_image_id = local.yawol_image_id
yawol_flavor_id = local.yawol_flavor_id
network_id = module.stackit_infrastructure.network_id
subnet_id = module.stackit_infrastructure.lb_subnetwork_id
}
network_config = {
ip_cidr_node = module.stackit_infrastructure.ip_cidr_node
ip_cidr_service = "10.96.0.0/12"
}
}
output "kubeconfig" {
value = constellation_cluster.stackit_example.kubeconfig
sensitive = true
description = "KubeConfig for the Constellation cluster."
}

View file

@ -23,6 +23,8 @@ go_library(
"//internal/attestation/variant",
"//internal/cloud/azureshared",
"//internal/cloud/cloudprovider",
"//internal/cloud/openstack",
"//internal/cloud/openstack/clouds",
"//internal/compatibility",
"//internal/config",
"//internal/constants",
@ -30,6 +32,7 @@ go_library(
"//internal/constellation/helm",
"//internal/constellation/kubecmd",
"//internal/constellation/state",
"//internal/file",
"//internal/grpc/dialer",
"//internal/imagefetcher",
"//internal/kms/uri",
@ -53,6 +56,7 @@ go_library(
"@com_github_hashicorp_terraform_plugin_framework//types/basetypes",
"@com_github_hashicorp_terraform_plugin_framework_validators//stringvalidator",
"@com_github_hashicorp_terraform_plugin_log//tflog",
"@com_github_spf13_afero//:afero",
],
)

View file

@ -110,6 +110,58 @@ func TestAccAttestationSource(t *testing.T) {
},
},
},
"STACKIT qemu-vtpm success": {
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
PreCheck: bazelPreCheck,
Steps: []resource.TestStep{
{
Config: testingConfig + `
data "constellation_attestation" "test" {
csp = "stackit"
attestation_variant = "qemu-vtpm"
image = {
version = "v2.13.0"
reference = "v2.13.0"
short_path = "v2.13.0"
}
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.variant", "qemu-vtpm"),
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.bootloader_version", "0"), // since this is not supported on STACKIT, we expect 0
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.measurements.15.expected", "0000000000000000000000000000000000000000000000000000000000000000"),
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.measurements.15.warn_only", "false"),
),
},
},
},
"openstack qemu-vtpm success": {
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
PreCheck: bazelPreCheck,
Steps: []resource.TestStep{
{
Config: testingConfig + `
data "constellation_attestation" "test" {
csp = "openstack"
attestation_variant = "qemu-vtpm"
image = {
version = "v2.13.0"
reference = "v2.13.0"
short_path = "v2.13.0"
}
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.variant", "qemu-vtpm"),
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.bootloader_version", "0"), // since this is not supported on OpenStack, we expect 0
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.measurements.15.expected", "0000000000000000000000000000000000000000000000000000000000000000"),
resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.measurements.15.warn_only", "false"),
),
},
},
},
}
for name, tc := range testCases {

View file

@ -26,6 +26,8 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/cloud/azureshared"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
openstackshared "github.com/edgelesssys/constellation/v2/internal/cloud/openstack"
"github.com/edgelesssys/constellation/v2/internal/cloud/openstack/clouds"
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
@ -33,6 +35,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/constellation/helm"
"github.com/edgelesssys/constellation/v2/internal/constellation/kubecmd"
"github.com/edgelesssys/constellation/v2/internal/constellation/state"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/grpc/dialer"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/edgelesssys/constellation/v2/internal/license"
@ -50,6 +53,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/spf13/afero"
)
var (
@ -96,6 +100,7 @@ type ClusterResourceModel struct {
Attestation types.Object `tfsdk:"attestation"`
GCP types.Object `tfsdk:"gcp"`
Azure types.Object `tfsdk:"azure"`
OpenStack types.Object `tfsdk:"openstack"`
OwnerID types.String `tfsdk:"owner_id"`
ClusterID types.String `tfsdk:"cluster_id"`
@ -129,6 +134,17 @@ type azureAttribute struct {
LoadBalancerName string `tfsdk:"load_balancer_name"`
}
type openStackAttribute struct {
Cloud string `tfsdk:"cloud"`
CloudsYAMLPath string `tfsdk:"clouds_yaml_path"`
FloatingIPPoolID string `tfsdk:"floating_ip_pool_id"`
DeployYawolLoadBalancer bool `tfsdk:"deploy_yawol_load_balancer"`
YawolImageID string `tfsdk:"yawol_image_id"`
YawolFlavorID string `tfsdk:"yawol_flavor_id"`
NetworkID string `tfsdk:"network_id"`
SubnetID string `tfsdk:"subnet_id"`
}
// extraMicroservicesAttribute is the extra microservices attribute's data model.
type extraMicroservicesAttribute struct {
CSIDriver bool `tfsdk:"csi_driver"`
@ -333,6 +349,53 @@ func (r *ClusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
},
},
},
"openstack": schema.SingleNestedAttribute{
MarkdownDescription: "OpenStack-specific configuration.",
Description: "OpenStack-specific configuration.",
Optional: true,
Attributes: map[string]schema.Attribute{
"cloud": schema.StringAttribute{
MarkdownDescription: "Name of the cloud in the clouds.yaml file.",
Description: "Name of the cloud in the clouds.yaml file.",
Required: true,
},
"clouds_yaml_path": schema.StringAttribute{
MarkdownDescription: "Path to the clouds.yaml file.",
Description: "Path to the clouds.yaml file.",
Optional: true,
},
"floating_ip_pool_id": schema.StringAttribute{
MarkdownDescription: "Floating IP pool to use for the VMs.",
Description: "Floating IP pool to use for the VMs.",
Required: true,
},
"deploy_yawol_load_balancer": schema.BoolAttribute{
MarkdownDescription: "Whether to deploy a YAWOL load balancer.",
Description: "Whether to deploy a YAWOL load balancer.",
Optional: true,
},
"yawol_image_id": schema.StringAttribute{
MarkdownDescription: "OpenStack OS image used by the yawollet.",
Description: "OpenStack OS image used by the yawollet.",
Optional: true,
},
"yawol_flavor_id": schema.StringAttribute{
MarkdownDescription: "OpenStack flavor used by the yawollet.",
Description: "OpenStack flavor used by the yawollet.",
Optional: true,
},
"network_id": schema.StringAttribute{
MarkdownDescription: "OpenStack network ID to use for the VMs.",
Description: "OpenStack network ID to use for the VMs.",
Required: true,
},
"subnet_id": schema.StringAttribute{
MarkdownDescription: "OpenStack subnet ID to use for the VMs.",
Description: "OpenStack subnet ID to use for the VMs.",
Required: true,
},
},
},
// Computed (output) attributes
"owner_id": schema.StringAttribute{
@ -406,6 +469,26 @@ func (r *ClusterResource) ValidateConfig(ctx context.Context, req resource.Valid
"GCP configuration not allowed", "When csp is not set to 'gcp', setting the 'gcp' configuration has no effect.",
)
}
// OpenStack Config is required for OpenStack
if (strings.EqualFold(data.CSP.ValueString(), cloudprovider.OpenStack.String()) ||
strings.EqualFold(data.CSP.ValueString(), "stackit")) &&
data.OpenStack.IsNull() {
resp.Diagnostics.AddAttributeError(
path.Root("openstack"),
"OpenStack configuration missing", "When csp is set to 'openstack' or 'stackit', the 'openstack' configuration must be set.",
)
}
// OpenStack Config should not be set for other CSPs
if !strings.EqualFold(data.CSP.ValueString(), cloudprovider.OpenStack.String()) &&
!strings.EqualFold(data.CSP.ValueString(), "stackit") &&
!data.OpenStack.IsNull() {
resp.Diagnostics.AddAttributeWarning(
path.Root("openstack"),
"OpenStack configuration not allowed", "When csp is not set to 'openstack' or 'stackit', setting the 'openstack' configuration has no effect.",
)
}
}
// Configure configures the resource.
@ -779,6 +862,7 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel,
serviceAccPayload := constellation.ServiceAccountPayload{}
var gcpConfig gcpAttribute
var azureConfig azureAttribute
var openStackConfig openStackAttribute
switch csp {
case cloudprovider.GCP:
convertDiags = data.GCP.As(ctx, &gcpConfig, basetypes.ObjectAsOptions{})
@ -815,6 +899,33 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel,
PreferredAuthMethod: azureshared.AuthMethodUserAssignedIdentity,
UamiResourceID: azureConfig.UamiResourceID,
}
case cloudprovider.OpenStack:
convertDiags = data.OpenStack.As(ctx, &openStackConfig, basetypes.ObjectAsOptions{})
diags.Append(convertDiags...)
if diags.HasError() {
return diags
}
cloudsYAML, err := clouds.ReadCloudsYAML(file.NewHandler(afero.NewOsFs()), openStackConfig.CloudsYAMLPath)
if err != nil {
diags.AddError("Reading clouds.yaml", err.Error())
return diags
}
cloud, ok := cloudsYAML.Clouds[openStackConfig.Cloud]
if !ok {
diags.AddError("Reading clouds.yaml", fmt.Sprintf("Cloud %s not found in clouds.yaml", openStackConfig.Cloud))
return diags
}
serviceAccPayload.OpenStack = openstackshared.AccountKey{
AuthURL: cloud.AuthInfo.AuthURL,
Username: cloud.AuthInfo.Username,
Password: cloud.AuthInfo.Password,
ProjectID: cloud.AuthInfo.ProjectID,
ProjectName: cloud.AuthInfo.ProjectName,
UserDomainName: cloud.AuthInfo.UserDomainName,
ProjectDomainName: cloud.AuthInfo.ProjectDomainName,
RegionName: cloud.RegionName,
}
}
serviceAccURI, err := constellation.MarshalServiceAccountURI(csp, serviceAccPayload)
if err != nil {
@ -861,6 +972,11 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel,
ProjectID: gcpConfig.ProjectID,
IPCidrPod: networkCfg.IPCidrPod.ValueString(),
}
case cloudprovider.OpenStack:
stateFile.Infrastructure.OpenStack = &state.OpenStack{
NetworkID: openStackConfig.NetworkID,
SubnetID: openStackConfig.SubnetID,
}
}
// Check license
@ -937,6 +1053,14 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel,
masterSecret: secrets.masterSecret,
serviceAccURI: serviceAccURI,
}
if csp == cloudprovider.OpenStack {
payload.openStackHelmValues = &helm.OpenStackValues{
DeployYawolLoadBalancer: openStackConfig.DeployYawolLoadBalancer,
FloatingIPPoolID: openStackConfig.FloatingIPPoolID,
YawolImageID: openStackConfig.YawolImageID,
YawolFlavorID: openStackConfig.YawolFlavorID,
}
}
helmDiags := r.applyHelmCharts(ctx, applier, payload, stateFile)
diags.Append(helmDiags...)
if diags.HasError() {
@ -1063,6 +1187,7 @@ type applyHelmChartsPayload struct {
DeployCSIDriver bool // Whether to deploy the CSI driver.
masterSecret uri.MasterSecret // master secret of the cluster.
serviceAccURI string // URI of the service account used within the cluster.
openStackHelmValues *helm.OpenStackValues // OpenStack-specific Helm values.
}
// applyHelmCharts applies the Helm charts to the cluster.
@ -1083,10 +1208,11 @@ func (r *ClusterResource) applyHelmCharts(ctx context.Context, applier *constell
// Allow destructive changes to the cluster.
// The user has previously been warned about this when planning a microservice version change.
AllowDestructive: helm.AllowDestructive,
OpenStackValues: payload.openStackHelmValues,
}
executor, _, err := applier.PrepareHelmCharts(options, state,
payload.serviceAccURI, payload.masterSecret, nil)
payload.serviceAccURI, payload.masterSecret)
var upgradeErr *compatibility.InvalidUpgradeError
if err != nil {
if !errors.As(err, &upgradeErr) {

View file

@ -489,6 +489,68 @@ func TestAccClusterResource(t *testing.T) {
},
},
},
"stackit config missing": {
ProtoV6ProviderFactories: testAccProtoV6ProviderFactoriesWithVersion(providerVersion),
PreCheck: bazelPreCheck,
Steps: []resource.TestStep{
{
Config: fullClusterTestingConfig(t, "openstack") + fmt.Sprintf(`
resource "constellation_cluster" "test" {
csp = "stackit"
name = "constell"
uid = "test"
image = data.constellation_image.bar.image
attestation = data.constellation_attestation.foo.attestation
init_secret = "deadbeef"
master_secret = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
master_secret_salt = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
measurement_salt = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
out_of_cluster_endpoint = "192.0.2.1"
in_cluster_endpoint = "192.0.2.1"
network_config = {
ip_cidr_node = "0.0.0.0/24"
ip_cidr_service = "0.0.0.0/24"
ip_cidr_pod = "0.0.0.0/24"
}
kubernetes_version = "%s"
constellation_microservice_version = "%s"
}
`, versions.Default, providerVersion),
ExpectError: regexp.MustCompile(".*When csp is set to 'openstack' or 'stackit', the 'openstack' configuration\nmust be set.*"),
},
},
},
"openstack config missing": {
ProtoV6ProviderFactories: testAccProtoV6ProviderFactoriesWithVersion(providerVersion),
PreCheck: bazelPreCheck,
Steps: []resource.TestStep{
{
Config: fullClusterTestingConfig(t, "openstack") + fmt.Sprintf(`
resource "constellation_cluster" "test" {
csp = "openstack"
name = "constell"
uid = "test"
image = data.constellation_image.bar.image
attestation = data.constellation_attestation.foo.attestation
init_secret = "deadbeef"
master_secret = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
master_secret_salt = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
measurement_salt = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
out_of_cluster_endpoint = "192.0.2.1"
in_cluster_endpoint = "192.0.2.1"
network_config = {
ip_cidr_node = "0.0.0.0/24"
ip_cidr_service = "0.0.0.0/24"
ip_cidr_pod = "0.0.0.0/24"
}
kubernetes_version = "%s"
constellation_microservice_version = "%s"
}
`, versions.Default, providerVersion),
ExpectError: regexp.MustCompile(".*When csp is set to 'openstack' or 'stackit', the 'openstack' configuration\nmust be set.*"),
},
},
},
}
for name, tc := range testCases {
@ -547,6 +609,19 @@ func fullClusterTestingConfig(t *testing.T, csp string) string {
attestation_variant = "gcp-sev-es"
image = data.constellation_image.bar.image
}`, image)
case "openstack":
return providerConfig + fmt.Sprintf(`
data "constellation_image" "bar" {
version = "%s"
attestation_variant = "qemu-vtpm"
csp = "openstack"
}
data "constellation_attestation" "foo" {
csp = "openstack"
attestation_variant = "qemu-vtpm"
image = data.constellation_image.bar.image
}`, image)
default:
t.Fatal("unknown csp")
return ""

View file

@ -122,6 +122,10 @@ func convertFromTfAttestationCfg(tfAttestation attestationAttribute, attestation
attestationConfig = &config.GCPSEVES{
Measurements: c11nMeasurements,
}
case variant.QEMUVTPM{}:
attestationConfig = &config.QEMUVTPM{
Measurements: c11nMeasurements,
}
default:
return nil, fmt.Errorf("unknown attestation variant: %s", attestationVariant)
}
@ -177,7 +181,7 @@ func convertToTfAttestation(attVar variant.Variant, snpVersions attestationconfi
XFAM: hex.EncodeToString(tdxCfg.XFAM),
}
tfAttestation.TDX = tfTdxCfg
case variant.GCPSEVES{}:
case variant.GCPSEVES{}, variant.QEMUVTPM{}:
// no additional fields
default:
return tfAttestation, fmt.Errorf("unknown attestation variant: %s", attVar)

View file

@ -252,9 +252,10 @@ func (d *ImageDataSource) Read(ctx context.Context, req datasource.ReadRequest,
// Save data into Terraform state
diags := resp.State.SetAttribute(ctx, path.Root("image"), imageAttribute{
Reference: imageRef,
Version: imageSemver,
ShortPath: apiCompatibleVer.ShortPath(),
Reference: imageRef,
Version: imageSemver,
ShortPath: apiCompatibleVer.ShortPath(),
MarketplaceImage: data.MarketplaceImage.ValueBoolPointer(),
})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {

View file

@ -141,6 +141,38 @@ func TestAccImageDataSource(t *testing.T) {
},
},
},
"stackit success": {
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
PreCheck: bazelPreCheck,
Steps: []resource.TestStep{
{
Config: testingConfig + `
data "constellation_image" "test" {
version = "v2.16.0"
attestation_variant = "qemu-vtpm"
csp = "stackit"
}
`,
Check: resource.TestCheckResourceAttr("data.constellation_image.test", "image.reference", "8ffc1740-1e41-4281-b872-f8088ffd7692"), // should be immutable,
},
},
},
"openstack success": {
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
PreCheck: bazelPreCheck,
Steps: []resource.TestStep{
{
Config: testingConfig + `
data "constellation_image" "test" {
version = "v2.16.0"
attestation_variant = "qemu-vtpm"
csp = "openstack"
}
`,
Check: resource.TestCheckResourceAttr("data.constellation_image.test", "image.reference", "8ffc1740-1e41-4281-b872-f8088ffd7692"), // should be immutable,
},
},
},
"unknown attestation variant": {
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
PreCheck: bazelPreCheck,

View file

@ -31,11 +31,12 @@ func newAttestationVariantAttributeSchema(t attributeType) schema.Attribute {
" * `aws-nitro-tpm`\n" +
" * `azure-sev-snp`\n" +
" * `azure-tdx`\n" +
" * `gcp-sev-es`\n",
" * `gcp-sev-es`\n" +
" * `qemu-vtpm`\n",
Required: isInput,
Computed: !isInput,
Validators: []validator.String{
stringvalidator.OneOf("aws-sev-snp", "aws-nitro-tpm", "azure-sev-snp", "azure-tdx", "gcp-sev-es"),
stringvalidator.OneOf("aws-sev-snp", "aws-nitro-tpm", "azure-sev-snp", "azure-tdx", "gcp-sev-es", "qemu-vtpm"),
},
}
}
@ -47,7 +48,7 @@ func newCSPAttributeSchema() schema.Attribute {
"See the [full list of CSPs](https://docs.edgeless.systems/constellation/overview/clouds) that Constellation supports.",
Required: true,
Validators: []validator.String{
stringvalidator.OneOf("aws", "azure", "gcp"),
stringvalidator.OneOf("aws", "azure", "gcp", "openstack", "stackit"),
},
}
}