terraform-provider: cleanup and improve docs (#2685)

Co-authored-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>
This commit is contained in:
Adrian Stobbe 2023-12-14 15:47:55 +01:00 committed by GitHub
parent 9a4e96905f
commit 37580009fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 262 additions and 592 deletions

View file

@ -31,6 +31,9 @@ go_library(
srcs = ["main.go"],
importpath = "github.com/edgelesssys/constellation/v2/terraform-provider-constellation",
visibility = ["//visibility:private"],
x_defs = {
"version": "{STABLE_STAMP_VERSION}",
},
deps = [
"//terraform-provider-constellation/internal/provider",
"@com_github_hashicorp_terraform_plugin_framework//providerserver",

View file

@ -32,10 +32,10 @@ data "constellation_attestation" "test" {
* `gcp-sev-es`
- `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_version` (String) The image version to use
### Optional
- `image_version` (String) The image version to use. If not set, the provider version value is used.
- `maa_url` (String) For Azure only, the URL of the Microsoft Azure Attestation service
### Read-Only

View file

@ -3,12 +3,12 @@
page_title: "constellation_image Data Source - constellation"
subcategory: ""
description: |-
Data source to retrieve the Constellation OS image reference for a given CSP and Attestation Variant.
Data source to resolve the CSP-specific OS image reference for a given version and attestation variant.
---
# constellation_image (Data Source)
Data source to retrieve the Constellation OS image reference for a given CSP and Attestation Variant.
Data source to resolve the CSP-specific OS image reference for a given version and attestation variant.
## Example Usage
@ -33,10 +33,10 @@ data "constellation_image" "example" {
* `gcp-sev-es`
- `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_version` (String) Version of the Constellation OS image to use. (e.g. `v2.13.0`)
### Optional
- `image_version` (String) Version of the Constellation OS image to use. (e.g. `v2.13.0`). If not set, the provider version value is used.
- `marketplace_image` (Boolean) Whether a marketplace image should be used. Currently only supported for Azure.
- `region` (String) Region to retrieve the image for. Only required for AWS.
The Constellation OS image must be [replicated to the region](https://docs.edgeless.systems/constellation/workflows/config),and the region must [support AMD SEV-SNP](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/snp-requirements.html), if it is used for Attestation.

View file

@ -4,12 +4,19 @@ page_title: "constellation Provider"
subcategory: ""
description: |-
The Constellation provider manages Constellation clusters.
Given user-defined infrastructure in Terraform, the provider with its main 'constellation_cluster' resource manages the entire lifecycle of a cluster.
The provider allows easy usage of custom infrastructure setups and GitOps workflows.
It is released as part of Constellation releases, such that each provider version is compatible with the corresponding Constellation version.
---
# constellation Provider
The Constellation provider manages Constellation clusters.
Given user-defined infrastructure in Terraform, the provider with its main 'constellation_cluster' resource manages the entire lifecycle of a cluster.
The provider allows easy usage of custom infrastructure setups and GitOps workflows.
It is released as part of Constellation releases, such that each provider version is compatible with the corresponding Constellation version.
## Example Usage
```terraform

View file

@ -42,7 +42,7 @@ resource "constellation_cluster" "aws_example" {
### Required
- `attestation` (Attributes) Attestation comprises the measurements and SEV-SNP specific parameters. (see [below for nested schema](#nestedatt--attestation))
- `attestation` (Attributes) Attestation comprises the measurements and SEV-SNP specific parameters. The output of the [constellation_attestation](../data-sources/attestation.md) data source provides sensible defaults. (see [below for nested schema](#nestedatt--attestation))
- `csp` (String) The Cloud Service Provider (CSP) the cluster should run on.
- `image_reference` (String) 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.
- `image_version` (String) 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.

View file

@ -8,4 +8,6 @@ package data
// ProviderData is the data that get's passed down from the provider
// configuration to the resources and data sources.
type ProviderData struct{}
type ProviderData struct {
Version string
}

View file

@ -78,12 +78,15 @@ go_test(
"requires-network",
],
# keep
x_defs = {"runsUnder": "bazel"},
x_defs = {
"runsUnder": "bazel",
},
deps = [
"//internal/attestation/idkeydigest",
"//internal/attestation/measurements",
"//internal/attestation/variant",
"//internal/config",
"//internal/constants",
"@com_github_hashicorp_terraform_plugin_framework//providerserver",
"@com_github_hashicorp_terraform_plugin_go//tfprotov6",
"@com_github_hashicorp_terraform_plugin_testing//helper/resource",

View file

@ -17,6 +17,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/sigstore"
"github.com/edgelesssys/constellation/v2/terraform-provider-constellation/internal/data"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/path"
@ -37,6 +38,7 @@ type AttestationDataSource struct {
client *http.Client
fetcher attestationconfigapi.Fetcher
rekor *sigstore.Rekor
version string
}
// AttestationDataSourceModel describes the data source data model.
@ -49,7 +51,21 @@ type AttestationDataSourceModel struct {
}
// Configure configures the data source.
func (d *AttestationDataSource) Configure(_ context.Context, _ datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
func (d *AttestationDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
// Prevent panic if the provider has not been configured. is necessary!
if req.ProviderData == nil {
return
}
providerData, ok := req.ProviderData.(data.ProviderData)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected data.ProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)
return
}
d.version = providerData.Version
d.client = http.DefaultClient
d.fetcher = attestationconfigapi.NewFetcher()
rekor, err := sigstore.NewRekor()
@ -70,13 +86,12 @@ func (d *AttestationDataSource) Schema(_ context.Context, _ datasource.SchemaReq
resp.Schema = schema.Schema{
// This description is used by the documentation generator and the language server.
MarkdownDescription: "The data source to fetch measurements from a configured cloud provider and image.",
Attributes: map[string]schema.Attribute{
"csp": newCSPAttribute(),
"attestation_variant": newAttestationVariantAttribute(attributeInput),
"image_version": schema.StringAttribute{
MarkdownDescription: "The image version to use",
Required: true,
MarkdownDescription: "The image version to use. If not set, the provider version value is used.",
Optional: true,
},
"maa_url": schema.StringAttribute{
MarkdownDescription: "For Azure only, the URL of the Microsoft Azure Attestation service",
@ -87,6 +102,28 @@ func (d *AttestationDataSource) Schema(_ context.Context, _ datasource.SchemaReq
}
}
// ValidateConfig validates the configuration for the image data source.
func (d *AttestationDataSource) ValidateConfig(ctx context.Context, req datasource.ValidateConfigRequest, resp *datasource.ValidateConfigResponse) {
var data AttestationDataSourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
if !data.AttestationVariant.Equal(types.StringValue("azure-sev-snp")) && !data.MaaURL.IsNull() {
resp.Diagnostics.AddAttributeWarning(
path.Root("maa_url"),
"MAA URL should only be set for Azure SEV-SNP", "Only when attestation_variant is set to 'azure-sev-snp', 'maa_url' should be specified.",
)
return
}
if data.AttestationVariant.Equal(types.StringValue("azure-sev-snp")) && data.MaaURL.IsNull() {
tflog.Info(ctx, "MAA URL not set, MAA fallback will be unavaiable")
}
}
// Read reads from the data source.
func (d *AttestationDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data AttestationDataSourceModel
@ -130,7 +167,13 @@ func (d *AttestationDataSource) Read(ctx context.Context, req datasource.ReadReq
resp.Diagnostics.AddError("Converting SNP attestation", err.Error())
}
verifyFetcher := measurements.NewVerifyFetcher(sigstore.NewCosignVerifier, d.rekor, d.client)
fetchedMeasurements, err := verifyFetcher.FetchAndVerifyMeasurements(ctx, data.ImageVersion.ValueString(),
imageVersion := data.ImageVersion.ValueString()
if imageVersion == "" {
tflog.Info(ctx, fmt.Sprintf("No image version specified, using provider version %s", d.version))
imageVersion = d.version // Use provider version as default.
}
fetchedMeasurements, err := verifyFetcher.FetchAndVerifyMeasurements(ctx, imageVersion,
csp, attestationVariant, false)
if err != nil {
var rekErr *measurements.RekorError

View file

@ -17,8 +17,8 @@ func TestAccAttestationSource(t *testing.T) {
bazelPreCheck := func() { bazelSetTerraformBinaryPath(t) }
testCases := map[string]resource.TestCase{
"aws sev-snp succcess": {
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
"aws sev-snp succcess without explicit image_version": {
ProtoV6ProviderFactories: testAccProtoV6ProviderFactoriesWithVersion("v2.13.0"), // do this to test if a valid default version is set
PreCheck: bazelPreCheck,
Steps: []resource.TestStep{
{
@ -26,7 +26,6 @@ func TestAccAttestationSource(t *testing.T) {
data "constellation_attestation" "test" {
csp = "aws"
attestation_variant = "aws-sev-snp"
image_version = "v2.13.0"
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
@ -54,6 +53,7 @@ func TestAccAttestationSource(t *testing.T) {
csp = "azure"
attestation_variant = "azure-sev-snp"
image_version = "v2.13.0"
maa_url = "https://www.example.com"
}
`,
Check: resource.ComposeAggregateTestCheckFunc(

View file

@ -14,10 +14,12 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/imagefetcher"
"github.com/edgelesssys/constellation/v2/terraform-provider-constellation/internal/data"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
var (
@ -38,6 +40,7 @@ func NewImageDataSource() datasource.DataSource {
// It is used to retrieve the Constellation OS image reference for a given CSP and Attestation Variant.
type ImageDataSource struct {
imageFetcher imageFetcher
version string
}
// imageFetcher gets an image reference from the versionsapi.
@ -66,14 +69,14 @@ func (d *ImageDataSource) Metadata(_ context.Context, req datasource.MetadataReq
// Schema returns the schema for the image data source.
func (d *ImageDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Data source to retrieve the Constellation OS image reference for a given CSP and Attestation Variant.",
MarkdownDescription: "Data source to retrieve the Constellation OS image reference for a given CSP and Attestation Variant.",
Description: "The data source to resolve the CSP-specific OS image reference for a given version and attestation variant.",
MarkdownDescription: "Data source to resolve the CSP-specific OS image reference for a given version and attestation variant.",
Attributes: map[string]schema.Attribute{
"attestation_variant": newAttestationVariantAttribute(attributeInput),
"image_version": schema.StringAttribute{
Description: "Version of the Constellation OS image to use. (e.g. `v2.13.0`)",
MarkdownDescription: "Version of the Constellation OS image to use. (e.g. `v2.13.0`)",
Required: true, // TODO(msanft): Make this optional to support "lockstep" mode.
Description: "Version of the Constellation OS image to use. (e.g. `v2.13.0`). If not set, the provider version is used.",
MarkdownDescription: "Version of the Constellation OS image to use. (e.g. `v2.13.0`). If not set, the provider version value is used.",
Optional: true,
},
"csp": newCSPAttribute(),
"marketplace_image": schema.BoolAttribute{
@ -97,13 +100,43 @@ func (d *ImageDataSource) Schema(_ context.Context, _ datasource.SchemaRequest,
}
}
// TODO(msanft): Possibly implement more complex validation for inter-dependencies between attributes.
// E.g., region should be required if, and only if, AWS is used.
// ValidateConfig validates the configuration for the image data source.
func (d *ImageDataSource) ValidateConfig(ctx context.Context, req datasource.ValidateConfigRequest, resp *datasource.ValidateConfigResponse) {
var data ImageDataSourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
if data.CSP.Equal(types.StringValue("aws")) && data.Region.IsNull() {
resp.Diagnostics.AddAttributeError(
path.Root("region"),
"Region must be set for AWS", "When csp is set to 'aws', 'region' must be specified.",
)
return
}
}
// Configure configures the data source.
func (d *ImageDataSource) Configure(_ context.Context, _ datasource.ConfigureRequest, _ *datasource.ConfigureResponse) {
// Create the image-fetcher client.
func (d *ImageDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
d.imageFetcher = imagefetcher.New()
// Prevent panic if the provider has not been configured. is necessary!
if req.ProviderData == nil {
return
}
providerData, ok := req.ProviderData.(data.ProviderData)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected data.ProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)
return
}
d.version = providerData.Version
}
// Read reads from the data source.
@ -111,7 +144,6 @@ func (d *ImageDataSource) Read(ctx context.Context, req datasource.ReadRequest,
// Retrieve the configuration values for this data source instance.
var data ImageDataSourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
// Check configuration for errors.
csp := cloudprovider.FromString(data.CSP.ValueString())
if csp == cloudprovider.Unknown {
@ -135,9 +167,15 @@ func (d *ImageDataSource) Read(ctx context.Context, req datasource.ReadRequest,
return
}
imageVersion := data.ImageVersion.ValueString()
if imageVersion == "" {
tflog.Info(ctx, fmt.Sprintf("No image version specified, using provider version %s", d.version))
imageVersion = d.version // Use provider version as default.
}
// Retrieve Image Reference
imageRef, err := d.imageFetcher.FetchReference(ctx, csp, attestationVariant,
data.ImageVersion.ValueString(), data.Region.ValueString(), data.MarketplaceImage.ValueBool())
imageVersion, data.Region.ValueString(), data.MarketplaceImage.ValueBool())
if err != nil {
resp.Diagnostics.AddError(
"Error fetching Image Reference",

View file

@ -18,6 +18,22 @@ func TestAccImageDataSource(t *testing.T) {
bazelPreCheck := func() { bazelSetTerraformBinaryPath(t) }
testCases := map[string]resource.TestCase{
"no image_version succeeds": {
ProtoV6ProviderFactories: testAccProtoV6ProviderFactoriesWithVersion("v2.13.0"),
PreCheck: bazelPreCheck,
Steps: []resource.TestStep{
{
Config: testingConfig + `
data "constellation_image" "test" {
attestation_variant = "aws-sev-snp"
csp = "aws"
region = "eu-west-1"
}
`,
Check: resource.TestCheckResourceAttrSet("data.constellation_image.test", "reference"),
},
},
},
"aws succcess": {
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
PreCheck: bazelPreCheck,
@ -37,6 +53,23 @@ func TestAccImageDataSource(t *testing.T) {
},
},
},
"aws without region fails": {
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
PreCheck: bazelPreCheck,
Steps: []resource.TestStep{
// Read testing
{
Config: testingConfig + `
data "constellation_image" "test" {
image_version = "v2.13.0"
attestation_variant = "aws-sev-snp"
csp = "aws"
}
`,
ExpectError: regexp.MustCompile(".*Region must be set for AWS.*"),
},
},
},
"azure success": {
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
PreCheck: bazelPreCheck,

View file

@ -27,9 +27,7 @@ type ConstellationProviderModel struct{}
// ConstellationProvider is the provider implementation.
type ConstellationProvider struct {
// version is set to the provider version on release, "dev" when the
// provider is built and ran locally, and "test" when running acceptance
// testing.
// version is set to the provider version on release, and the pseudo version on local builds. The pseudo version is not a valid default for the image_version attribute.
version string
}
@ -51,8 +49,12 @@ func (p *ConstellationProvider) Metadata(_ context.Context, _ provider.MetadataR
// Schema defines the HCL schema of the provider, i.e. what attributes it has and what they are used for.
func (p *ConstellationProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "The Constellation provider manages Constellation clusters.",
MarkdownDescription: `The Constellation provider manages Constellation clusters.`, // TODO(msanft): Provide a more sophisticated description.
Description: "The Constellation provider manages Constellation clusters.",
MarkdownDescription: `The Constellation provider manages Constellation clusters.
Given user-defined infrastructure in Terraform, the provider with its main 'constellation_cluster' resource manages the entire lifecycle of a cluster.
The provider allows easy usage of custom infrastructure setups and GitOps workflows.
It is released as part of Constellation releases, such that each provider version is compatible with the corresponding Constellation version.`,
}
}
@ -67,8 +69,9 @@ func (p *ConstellationProvider) Configure(ctx context.Context, req provider.Conf
return
}
// TODO(msanft): Initialize persistent clients here.
config := datastruct.ProviderData{}
config := datastruct.ProviderData{
Version: p.version,
}
// Make the clients available during data source and resource "Configure" methods.
resp.DataSourceData = config

View file

@ -12,6 +12,7 @@ import (
"testing"
"github.com/bazelbuild/rules_go/go/runfiles"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
)
@ -26,9 +27,17 @@ const (
// testAccProtoV6ProviderFactories are used to instantiate a provider during
// acceptance testing. The factory function will be invoked for every Terraform
// CLI command executed to create a provider server to which the CLI can
// reattach. It sets a pseudo version for the provider version.
var testAccProtoV6ProviderFactories = testAccProtoV6ProviderFactoriesWithVersion(constants.BinaryVersion().String())
// testAccProtoV6ProviderFactoriesWithVersion are used to instantiate a provider during
// acceptance testing. The factory function will be invoked for every Terraform
// CLI command executed to create a provider server to which the CLI can
// reattach.
var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){
"constellation": providerserver.NewProtocol6WithError(New("test")()),
var testAccProtoV6ProviderFactoriesWithVersion = func(version string) map[string]func() (tfprotov6.ProviderServer, error) {
return map[string]func() (tfprotov6.ProviderServer, error){
"constellation": providerserver.NewProtocol6WithError(New(version)()),
}
}
// bazelSetTerraformBinaryPath sets the path to the Terraform binary for

View file

@ -73,11 +73,15 @@ func newMeasurementsAttribute(t attributeType) schema.Attribute {
func newAttestationConfigAttribute(t attributeType) schema.Attribute {
isInput := bool(t)
var additionalDescription string
if isInput {
additionalDescription = " The output of the [constellation_attestation](../data-sources/attestation.md) data source provides sensible defaults. "
}
return schema.SingleNestedAttribute{
Computed: !isInput,
Required: isInput,
MarkdownDescription: "Attestation comprises the measurements and SEV-SNP specific parameters.",
Description: "The values provide sensible defaults. See the docs for advanced usage.", // TODO(elchead): AB#3568
MarkdownDescription: "Attestation comprises the measurements and SEV-SNP specific parameters." + additionalDescription,
Description: "Attestation comprises the measurements and SEV-SNP specific parameters." + additionalDescription,
Attributes: map[string]schema.Attribute{
"variant": newAttestationVariantAttribute(t), // duplicated for convenience in cluster resource
"bootloader_version": schema.Int64Attribute{

View file

@ -15,7 +15,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/providerserver"
)
// TODO(msanft): Set this accordingly in the release CI.
// version is the version of Constellation to use. Left as a separate variable to allow override during build.
var version = "dev"
func main() {