From 648f27e3c627b96672779dca549b35c1949be9ae Mon Sep 17 00:00:00 2001 From: Ivan De Marino Date: Mon, 25 Jul 2022 16:42:10 +0100 Subject: [PATCH] BUGFIX: use `ListType` of `ObjectType` for `Computed` attributes instead of `Blocks` (#246) * Define `certificates` in `tls_certificate` data source, as a `List` of `Object`, instead of a blocks' list This is necessary. so that we can express to Terraform that the attribute is indeed `Computed` and it can't be expected to be populated, until the data source is read. This was creating an issue (see #244), as Terraform protocol doesn't support expressing that a Block is Computed: only attributes can be. This approach avoids the use of `NestedAttributes`, and as such is compatible with Protocol v5 (i.e. TF >= 0.12). * Preparing CHANGELOG for v4.0.1 * Adding acceptance test for `tls_certificate` data source, exercising a scenario where "computed certificates are unknown until applied" * Apply suggestions from code review Co-authored-by: Brian Flad --- CHANGELOG.md | 6 ++ internal/provider/data_source_certificate.go | 79 +------------------ .../provider/data_source_certificate_test.go | 47 +++++++++++ 3 files changed, 57 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c5c82cc..01ea04ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 4.0.1 (July 25, 2022) + +BUG FIXES: + +* data-source/tls_certificate: Prevented `empty list of object` error with `certificates` attribute ([#244](https://github.com/hashicorp/terraform-provider-tls/issues/244)). + ## 4.0.0 (July 21, 2022) NOTES: diff --git a/internal/provider/data_source_certificate.go b/internal/provider/data_source_certificate.go index 446318bb..89a088cf 100644 --- a/internal/provider/data_source_certificate.go +++ b/internal/provider/data_source_certificate.go @@ -12,7 +12,6 @@ import ( "strings" "time" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/schemavalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -84,83 +83,13 @@ func (dst *certificateDataSourceType) GetSchema(_ context.Context) (tfsdk.Schema Computed: true, MarkdownDescription: "Unique identifier of this data source: hashing of the certificates in the chain.", }, - }, - Blocks: map[string]tfsdk.Block{ "certificates": { - NestingMode: tfsdk.BlockNestingModeList, - MinItems: 0, - // TODO Remove the validators below, once a fix for https://github.com/hashicorp/terraform-plugin-framework/issues/421 ships - Validators: []tfsdk.AttributeValidator{ - listvalidator.SizeAtLeast(0), - }, - Attributes: map[string]tfsdk.Attribute{ - "signature_algorithm": { - Type: types.StringType, - Computed: true, - MarkdownDescription: "The algorithm used to sign the certificate.", - }, - "public_key_algorithm": { - Type: types.StringType, - Computed: true, - MarkdownDescription: "The key algorithm used to create the certificate.", - }, - "serial_number": { - Type: types.StringType, - Computed: true, - MarkdownDescription: "Number that uniquely identifies the certificate with the CA's system. " + - "The `format` function can be used to convert this _base 10_ number " + - "into other bases, such as hex.", - }, - "is_ca": { - Type: types.BoolType, - Computed: true, - MarkdownDescription: "`true` if the certificate is of a CA (Certificate Authority).", - }, - "version": { - Type: types.Int64Type, - Computed: true, - MarkdownDescription: "The version the certificate is in.", - }, - "issuer": { - Type: types.StringType, - Computed: true, - MarkdownDescription: "Who verified and signed the certificate, roughly following " + - "[RFC2253](https://tools.ietf.org/html/rfc2253).", - }, - "subject": { - Type: types.StringType, - Computed: true, - MarkdownDescription: "The entity the certificate belongs to, roughly following " + - "[RFC2253](https://tools.ietf.org/html/rfc2253).", - }, - "not_before": { - Type: types.StringType, - Computed: true, - MarkdownDescription: "The time after which the certificate is valid, as an " + - "[RFC3339](https://tools.ietf.org/html/rfc3339) timestamp.", - }, - "not_after": { - Type: types.StringType, - Computed: true, - MarkdownDescription: "The time until which the certificate is invalid, as an " + - "[RFC3339](https://tools.ietf.org/html/rfc3339) timestamp.", - }, - "sha1_fingerprint": { - Type: types.StringType, - Computed: true, - MarkdownDescription: "The SHA1 fingerprint of the public key of the certificate.", - }, - "cert_pem": { - Type: types.StringType, - Computed: true, - MarkdownDescription: "Certificate data in [PEM (RFC 1421)](https://datatracker.ietf.org/doc/html/rfc1421) format. " + - "**NOTE**: the [underlying](https://pkg.go.dev/encoding/pem#Encode) " + - "[libraries](https://pkg.go.dev/golang.org/x/crypto/ssh#MarshalAuthorizedKey) that generate this " + - "value append a `\\n` at the end of the PEM. " + - "In case this disrupts your use case, we recommend using " + - "[`trimspace()`](https://www.terraform.io/language/functions/trimspace).", + Type: types.ListType{ + ElemType: types.ObjectType{ + AttrTypes: x509CertObjectAttrTypes(), }, }, + Computed: true, MarkdownDescription: "The certificates protecting the site, with the root of the chain first.", }, }, diff --git a/internal/provider/data_source_certificate_test.go b/internal/provider/data_source_certificate_test.go index aad86f04..05684ec0 100644 --- a/internal/provider/data_source_certificate_test.go +++ b/internal/provider/data_source_certificate_test.go @@ -639,3 +639,50 @@ func TestDataSourceCertificate_MalformedURL(t *testing.T) { }, }) } + +func TestDataSourceCertificate_UnknownComputedCertificatesUntilApplied(t *testing.T) { + r.Test(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + + Steps: []r.TestStep{ + { + + Config: ` + resource "tls_private_key" "test" { + algorithm = "ED25519" + } + + data "tls_certificate" "test" { + # This attribute value must be unknown to trigger + # the behavior, therefore this replaces the unknown + # value with a known value on apply, so the URL is valid. + url = replace(tls_private_key.test.id, "/^.*$/","https://terraform.io") + } + + output "test" { + # This test must reference an underlying value of the + # certificates attribute to trigger the behavior. + value = data.tls_certificate.test.certificates[0].sha1_fingerprint + } + `, + Check: r.ComposeAggregateTestCheckFunc( + r.TestCheckResourceAttr("data.tls_certificate.test", "certificates.#", "3"), + + // ISRG Root X1 + r.TestCheckResourceAttr("data.tls_certificate.test", "certificates.0.issuer", "CN=DST Root CA X3,O=Digital Signature Trust Co."), + r.TestCheckResourceAttr("data.tls_certificate.test", "certificates.0.subject", "CN=ISRG Root X1,O=Internet Security Research Group,C=US"), + + // R3 + r.TestCheckResourceAttr("data.tls_certificate.test", "certificates.1.issuer", "CN=ISRG Root X1,O=Internet Security Research Group,C=US"), + r.TestCheckResourceAttrPair("data.tls_certificate.test", "certificates.1.issuer", "data.tls_certificate.test", "certificates.0.subject"), + r.TestCheckResourceAttr("data.tls_certificate.test", "certificates.1.subject", "CN=R3,O=Let's Encrypt,C=US"), + + // terraform.io + r.TestCheckResourceAttr("data.tls_certificate.test", "certificates.2.issuer", "CN=R3,O=Let's Encrypt,C=US"), + r.TestCheckResourceAttrPair("data.tls_certificate.test", "certificates.2.issuer", "data.tls_certificate.test", "certificates.1.subject"), + r.TestCheckResourceAttr("data.tls_certificate.test", "certificates.2.subject", "CN=www.terraform.io"), + ), + }, + }, + }) +}