terraform-provider: add usage examples (#2713)

* terraform-provider: add usage example for Azure

* terraform-provider: add usage example for AWS

* terraform-provider: add usage example for GCP

* terraform-provider: update usage example for Azure

* terraform-provider: update generated documentation

* docs: adjust creation on Azure and link to examples

* terraform-provider: unify image in-/output (#2725)

* terraform-provider: check for returned error when converting microservices

* terraform-provider: use state values for outputs after creation

* terraform-provider: ignore invalid upgrades (#2728)

---------

Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com>
Co-authored-by: Thomas Tendyck <51411342+thomasten@users.noreply.github.com>
This commit is contained in:
Moritz Sanft 2023-12-18 10:15:54 +01:00 committed by GitHub
parent 88d626d302
commit af791bd221
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 797 additions and 204 deletions

View file

@ -24,10 +24,12 @@ 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"
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/constellation"
"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/grpc/dialer"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
@ -37,6 +39,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
@ -63,8 +67,7 @@ type ClusterResourceModel struct {
Name types.String `tfsdk:"name"`
CSP types.String `tfsdk:"csp"`
UID types.String `tfsdk:"uid"`
ImageVersion types.String `tfsdk:"image_version"`
ImageReference types.String `tfsdk:"image_reference"`
Image types.Object `tfsdk:"image"`
KubernetesVersion types.String `tfsdk:"kubernetes_version"`
MicroserviceVersion types.String `tfsdk:"constellation_microservice_version"`
OutOfClusterEndpoint types.String `tfsdk:"out_of_cluster_endpoint"`
@ -85,19 +88,22 @@ type ClusterResourceModel struct {
KubeConfig types.String `tfsdk:"kubeconfig"`
}
type networkConfig struct {
// networkConfigAttribute is the network config attribute's data model.
type networkConfigAttribute struct {
IPCidrNode string `tfsdk:"ip_cidr_node"`
IPCidrPod string `tfsdk:"ip_cidr_pod"`
IPCidrService string `tfsdk:"ip_cidr_service"`
}
type gcp struct {
// gcpAttribute is the gcp attribute's data model.
type gcpAttribute struct {
// ServiceAccountKey is the private key of the service account used within the cluster.
ServiceAccountKey string `tfsdk:"service_account_key"`
ProjectID string `tfsdk:"project_id"`
}
type azure struct {
// azureAttribute is the azure attribute's data model.
type azureAttribute struct {
TenantID string `tfsdk:"tenant_id"`
Location string `tfsdk:"location"`
UamiClientID string `tfsdk:"uami_client_id"`
@ -108,6 +114,11 @@ type azure struct {
LoadBalancerName string `tfsdk:"load_balancer_name"`
}
// extraMicroservicesAttribute is the extra microservices attribute's data model.
type extraMicroservicesAttribute struct {
CSIDriver bool `tfsdk:"csi_driver"`
}
// Metadata returns the metadata of the resource.
func (r *ClusterResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_cluster"
@ -136,16 +147,7 @@ func (r *ClusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
Description: "The UID of the cluster.",
Required: true,
},
"image_version": schema.StringAttribute{
MarkdownDescription: "Constellation OS image version to use in the CSP specific reference format. Use the [`constellation_image`](../data-sources/image.md) data source to find the correct image version for your CSP.",
Description: "Constellation OS image version to use in the CSP specific reference format. Use the `constellation_image` data source to find the correct image version for your CSP.",
Required: true,
},
"image_reference": schema.StringAttribute{
MarkdownDescription: "Constellation OS image reference to use in the CSP specific reference format. Use the [`constellation_image`](../data-sources/image.md) data source to find the correct image reference for your CSP.",
Description: "Constellation OS image reference to use in the CSP specific reference format. Use the `constellation_image` data source to find the correct image reference for your CSP.",
Required: true,
},
"image": newImageAttributeSchema(attributeInput),
"kubernetes_version": schema.StringAttribute{
MarkdownDescription: fmt.Sprintf("The Kubernetes version to use for the cluster. When not set, version %s is used. The supported versions are %s.", versions.Default, versions.SupportedK8sVersions()),
Description: fmt.Sprintf("The Kubernetes version to use for the cluster. When not set, version %s is used. The supported versions are %s.", versions.Default, versions.SupportedK8sVersions()),
@ -227,7 +229,7 @@ func (r *ClusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
Description: "Secret used for initialization of the cluster.",
Required: true,
},
"attestation": newAttestationConfigAttribute(attributeInput),
"attestation": newAttestationConfigAttributeSchema(attributeInput),
"gcp": schema.SingleNestedAttribute{
MarkdownDescription: "GCP-specific configuration.",
Description: "GCP-specific configuration.",
@ -298,16 +300,29 @@ func (r *ClusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
MarkdownDescription: "The owner ID of the cluster.",
Description: "The owner ID of the cluster.",
Computed: true,
PlanModifiers: []planmodifier.String{
// We know that this value will never change after creation, so we can use the state value for upgrades.
stringplanmodifier.UseStateForUnknown(),
},
},
"cluster_id": schema.StringAttribute{
MarkdownDescription: "The cluster ID of the cluster.",
Description: "The cluster ID of the cluster.",
Computed: true,
PlanModifiers: []planmodifier.String{
// We know that this value will never change after creation, so we can use the state value for upgrades.
stringplanmodifier.UseStateForUnknown(),
},
},
"kubeconfig": schema.StringAttribute{
MarkdownDescription: "The kubeconfig of the cluster.",
Description: "The kubeconfig of the cluster.",
Computed: true,
Sensitive: true,
PlanModifiers: []planmodifier.String{
// We know that this value will never change after creation, so we can use the state value for upgrades.
stringplanmodifier.UseStateForUnknown(),
},
},
},
}
@ -510,7 +525,7 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel,
}
// parse network config
var networkCfg networkConfig
var networkCfg networkConfigAttribute
convertDiags = data.NetworkConfig.As(ctx, &networkCfg, basetypes.ObjectAsOptions{
UnhandledNullAsEmpty: true, // we want to allow null values, as some of the field's subfields are optional.
})
@ -520,7 +535,7 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel,
}
// parse Constellation microservice config
var microserviceCfg extraMicroservices
var microserviceCfg extraMicroservicesAttribute
convertDiags = data.ExtraMicroservices.As(ctx, &microserviceCfg, basetypes.ObjectAsOptions{
UnhandledNullAsEmpty: true, // we want to allow null values, as the CSIDriver field is optional
})
@ -547,19 +562,25 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel,
}
// parse OS image version
imageVersion, err := semver.New(data.ImageVersion.ValueString())
var image imageAttribute
convertDiags = data.Image.As(ctx, &image, basetypes.ObjectAsOptions{})
diags.Append(convertDiags...)
if diags.HasError() {
return diags
}
imageSemver, err := semver.New(image.Version)
if err != nil {
diags.AddAttributeError(
path.Root("image_version"),
path.Root("image").AtName("version"),
"Invalid image version",
fmt.Sprintf("Parsing image version: %s", err))
fmt.Sprintf("Parsing image version (%s): %s", image.Version, err))
return diags
}
// Parse in-cluster service account info.
serviceAccPayload := constellation.ServiceAccountPayload{}
var gcpConfig gcp
var azureConfig azure
var gcpConfig gcpAttribute
var azureConfig azureAttribute
switch csp {
case cloudprovider.GCP:
convertDiags = data.GCP.As(ctx, &gcpConfig, basetypes.ObjectAsOptions{})
@ -717,17 +738,28 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel,
if !skipNodeUpgrade {
// Upgrade node image
err = applier.UpgradeNodeImage(ctx,
imageVersion,
data.ImageReference.ValueString(),
imageSemver,
image.Reference,
false)
if err != nil {
diags.AddError("Upgrading node OS image", err.Error())
var upgradeImageErr *compatibility.InvalidUpgradeError
switch {
case errors.Is(err, kubecmd.ErrInProgress):
diags.AddWarning("Skipping OS image upgrade", "Another upgrade is already in progress.")
case errors.As(err, &upgradeImageErr):
diags.AddWarning("Ignoring invalid OS image upgrade", err.Error())
case err != nil:
diags.AddError("Upgrading OS image", err.Error())
return diags
}
// Upgrade Kubernetes version
if err := applier.UpgradeKubernetesVersion(ctx, k8sVersion, false); err != nil {
diags.AddError("Upgrading Kubernetes version", err.Error())
// Upgrade Kubernetes components
err = applier.UpgradeKubernetesVersion(ctx, k8sVersion, false)
var upgradeK8sErr *compatibility.InvalidUpgradeError
switch {
case errors.As(err, &upgradeK8sErr):
diags.AddWarning("Ignoring invalid Kubernetes components upgrade", err.Error())
case err != nil:
diags.AddError("Upgrading Kubernetes components", err.Error())
return diags
}
}
@ -741,9 +773,9 @@ type initRPCPayload struct {
masterSecret uri.MasterSecret // master secret of the cluster.
measurementSalt []byte // measurement salt of the cluster.
apiServerCertSANs []string // additional SANs to add to the API server certificate.
azureCfg azure // Azure-specific configuration.
gcpCfg gcp // GCP-specific configuration.
networkCfg networkConfig // network configuration of the cluster.
azureCfg azureAttribute // Azure-specific configuration.
gcpCfg gcpAttribute // GCP-specific configuration.
networkCfg networkConfigAttribute // network configuration of the cluster.
maaURL string // URL of the MAA service. Only used for Azure clusters.
k8sVersion versions.ValidK8sVersion // Kubernetes version of the cluster.
// Internal Endpoint of the cluster.
@ -822,9 +854,13 @@ func (r *ClusterResource) applyHelmCharts(ctx context.Context, applier *constell
executor, _, err := applier.PrepareHelmCharts(options, state,
payload.serviceAccURI, payload.masterSecret, nil)
var upgradeErr *compatibility.InvalidUpgradeError
if err != nil {
diags.AddError("Preparing Helm charts", err.Error())
return diags
if !errors.As(err, &upgradeErr) {
diags.AddError("Upgrading microservices", err.Error())
return diags
}
diags.AddWarning("Ignoring invalid microservice upgrade(s)", err.Error())
}
if err := executor.Apply(ctx); err != nil {
@ -845,7 +881,7 @@ type attestationInput struct {
// used by the Constellation library.
func (r *ClusterResource) convertAttestationConfig(ctx context.Context, data ClusterResourceModel) (attestationInput, diag.Diagnostics) {
diags := diag.Diagnostics{}
var tfAttestation attestation
var tfAttestation attestationAttribute
castDiags := data.Attestation.As(ctx, &tfAttestation, basetypes.ObjectAsOptions{})
diags.Append(castDiags...)
if diags.HasError() {