From 1b2ecc27d5ec860f0d2218415d7846a5a8a9b88c Mon Sep 17 00:00:00 2001 From: David Zhang Date: Wed, 28 Aug 2024 17:56:18 +1000 Subject: [PATCH 1/4] add certificate datasource. Signed-off-by: David Zhang --- docs/data-sources/certificate.md | 58 ++++ .../certificates/certificate_operations.go | 136 +++++++- .../certificates/certificate_subresource.go | 95 ++++-- internal/provider/data_certificate.go | 298 ++++++++++++++++++ internal/provider/data_certificate_test.go | 50 +++ internal/provider/provider.go | 1 + internal/provider/resource_certificate.go | 4 +- .../provider/resource_external_certificate.go | 4 +- 8 files changed, 595 insertions(+), 51 deletions(-) create mode 100644 docs/data-sources/certificate.md create mode 100644 internal/provider/data_certificate.go create mode 100644 internal/provider/data_certificate_test.go diff --git a/docs/data-sources/certificate.md b/docs/data-sources/certificate.md new file mode 100644 index 0000000..19f1493 --- /dev/null +++ b/docs/data-sources/certificate.md @@ -0,0 +1,58 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "vcf_certificate Data Source - terraform-provider-vcf" +subcategory: "" +description: |- + Datasource used to extract certificate details for various resources based on fields like domain, issued_by, issued_to, key_size, and others. +--- + +# vcf_certificate (Data Source) + +Datasource used to extract certificate details for various resources based on fields like domain, issued_by, issued_to, key_size, and others. + + + + +## Schema + +### Required + +- `domain_id` (String) The ID of the domain to fetch certificates for. +- `resource_fqdn` (String) the fqdn of resource certificate. + +### Read-Only + +- `certificate` (List of Object) List of certificates retrieved from the API. (see [below for nested schema](#nestedatt--certificate)) +- `id` (String) The ID of this resource. + + +### Nested Schema for `certificate` + +Read-Only: + +- `certificate_error` (String) +- `domain` (String) +- `expiration_status` (String) +- `is_installed` (Boolean) +- `issued_by` (String) +- `issued_to` (String) +- `key_size` (String) +- `not_after` (String) +- `not_before` (String) +- `number_of_days_to_expire` (Number) +- `pem_encoded` (String) +- `public_key` (String) +- `public_key_algorithm` (String) +- `serial_number` (String) +- `signature_algorithm` (String) +- `subject` (String) +- `subject_alternative_name` (List of String) +- `subject_cn` (String) +- `subject_country` (String) +- `subject_locality` (String) +- `subject_org` (String) +- `subject_ou` (String) +- `subject_st` (String) +- `thumbprint` (String) +- `thumbprint_algorithm` (String) +- `version` (String) diff --git a/internal/certificates/certificate_operations.go b/internal/certificates/certificate_operations.go index c4298e4..e4599ae 100644 --- a/internal/certificates/certificate_operations.go +++ b/internal/certificates/certificate_operations.go @@ -6,6 +6,11 @@ package certificates import ( "context" + md52 "crypto/md5" + "encoding/hex" + "fmt" + "io" + "strings" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -68,26 +73,28 @@ func ValidateResourceCertificates(ctx context.Context, client *vcfclient.VcfClie return nil } +/* func GetCertificateForResourceInDomain(ctx context.Context, client *vcfclient.VcfClient, - domainId, resourceFqdn string) (*models.Certificate, error) { - viewCertificatesParams := certificates.NewGetCertificatesByDomainParamsWithContext(ctx). - WithTimeout(constants.DefaultVcfApiCallTimeout) - viewCertificatesParams.ID = domainId - certificatesResponse, _, err := client.Certificates.GetCertificatesByDomain(viewCertificatesParams) - if err != nil { - return nil, err - } + domainId, resourceFqdn string) (*models.Certificate, error) { + viewCertificatesParams := certificates.NewGetCertificatesByDomainParamsWithContext(ctx). + WithTimeout(constants.DefaultVcfApiCallTimeout) + viewCertificatesParams.ID = domainId - allCertsForDomain := certificatesResponse.Payload.Elements - for _, cert := range allCertsForDomain { - if cert.IssuedTo != nil && *cert.IssuedTo == resourceFqdn { - return cert, nil + certificatesResponse, _, err := client.Certificates.GetCertificatesByDomain(viewCertificatesParams) + if err != nil { + return nil, err } - } - return nil, nil -} + allCertsForDomain := certificatesResponse.Payload.Elements + for _, cert := range allCertsForDomain { + if cert.IssuedTo != nil && *cert.IssuedTo == resourceFqdn { + return cert, nil + } + } + return nil, nil + } +*/ func GenerateCertificateForResource(ctx context.Context, client *api_client.SddcManagerClient, domainId, resourceType, resourceFqdn, caType *string) error { @@ -120,3 +127,102 @@ func GenerateCertificateForResource(ctx context.Context, client *api_client.Sddc } return nil } + +func ReadCertificate(ctx context.Context, client *vcfclient.VcfClient, + domainId, resourceFqdn string) (*models.Certificate, error) { + viewCertificatesParams := certificates.NewGetCertificatesByDomainParamsWithContext(ctx). + WithTimeout(constants.DefaultVcfApiCallTimeout) + viewCertificatesParams.ID = domainId + + certificatesResponse, _, err := client.Certificates.GetCertificatesByDomain(viewCertificatesParams) + if err != nil { + return nil, fmt.Errorf("failed to get certificates by domain: %w", err) + } + + // Check if any certificates are found + if certificatesResponse.Payload == nil || len(certificatesResponse.Payload.Elements) == 0 { + return nil, fmt.Errorf("no certificates found for domain ID %s", domainId) + } + + allCertsForDomain := certificatesResponse.Payload.Elements + for _, cert := range allCertsForDomain { + if cert.IssuedTo != nil && *cert.IssuedTo == resourceFqdn { + return cert, nil + } + } + return nil, nil +} + +func FlattenCertificateWithSubject(cert *models.Certificate) map[string]interface{} { + result := make(map[string]interface{}) + if cert.Domain == nil { + result["domain"] = "nil" + } else { + result["domain"] = *cert.Domain + } + if cert.GetCertificateError == nil { + result["certificate_error"] = "nil" + } else { + result["certificate_error"] = *cert.GetCertificateError + } + + result["expiration_status"] = *cert.ExpirationStatus + result["issued_by"] = *cert.IssuedBy + result["issued_to"] = *cert.IssuedTo + result["key_size"] = *cert.KeySize + result["not_after"] = *cert.NotAfter + result["not_before"] = *cert.NotBefore + result["number_of_days_to_expire"] = *cert.NumberOfDaysToExpire + result["pem_encoded"] = *cert.PemEncoded + result["public_key"] = *cert.PublicKey + result["public_key_algorithm"] = *cert.PublicKeyAlgorithm + result["serial_number"] = *cert.SerialNumber + result["signature_algorithm"] = *cert.SignatureAlgorithm + result["subject"] = *cert.Subject + result["subject_alternative_name"] = cert.SubjectAlternativeName + result["thumbprint"] = *cert.Thumbprint + result["thumbprint_algorithm"] = *cert.ThumbprintAlgorithm + result["version"] = *cert.Version + + // Parse the subject string to extract CN, OU, O, L, ST, C + subjectDetails := parseSubject(*cert.Subject) + + // Add parsed subject components to the result map + result["subject_cn"] = subjectDetails["CN"] + result["subject_ou"] = subjectDetails["OU"] + result["subject_org"] = subjectDetails["O"] + result["subject_locality"] = subjectDetails["L"] + result["subject_st"] = subjectDetails["ST"] + result["subject_country"] = subjectDetails["C"] + + return result +} + +func parseSubject(subject string) map[string]string { + parsedSubject := make(map[string]string) + + // Split the subject string by commas to separate key-value pairs + subjectParts := strings.Split(subject, ",") + + for _, part := range subjectParts { + // Split each part by the equals sign to separate the key and value + keyValue := strings.SplitN(strings.TrimSpace(part), "=", 2) + if len(keyValue) == 2 { + // Store the value in the map with the key as the subject component + parsedSubject[keyValue[0]] = keyValue[1] + } + } + + return parsedSubject +} + +func HashFields(fields []string) (string, error) { + md5 := md52.New() + _, err := io.WriteString(md5, strings.Join(fields, "")) + + if err != nil { + return "", err + } + + return hex.EncodeToString(md5.Sum(nil)), nil +} diff --git a/internal/certificates/certificate_subresource.go b/internal/certificates/certificate_subresource.go index 39aa3b6..3a4f11a 100644 --- a/internal/certificates/certificate_subresource.go +++ b/internal/certificates/certificate_subresource.go @@ -6,7 +6,6 @@ package certificates import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/vmware/vcf-sdk-go/models" ) func CertificateSchema() *schema.Resource { @@ -88,6 +87,36 @@ func CertificateSchema() *schema.Resource { Description: "Complete distinguished name to which the certificate is issued", Computed: true, }, + "subject_cn": { + Type: schema.TypeString, + Computed: true, + Description: "The subject_cn(common name) of the certificate.", + }, + "subject_ou": { + Type: schema.TypeString, + Computed: true, + Description: "The subject_ou(org unit) of the certificate.", + }, + "subject_org": { + Type: schema.TypeString, + Computed: true, + Description: "The subject_org of the certificate.", + }, + "subject_locality": { + Type: schema.TypeString, + Computed: true, + Description: "The subject_locality of the certificate.", + }, + "subject_st": { + Type: schema.TypeString, + Computed: true, + Description: "The subject_st(state) of the certificate.", + }, + "subject_country": { + Type: schema.TypeString, + Computed: true, + Description: "The subject_country of the certificate.", + }, "subject_alternative_name": { Type: schema.TypeList, Description: "The alternative names to which the certificate is issued", @@ -113,35 +142,37 @@ func CertificateSchema() *schema.Resource { } } -func FlattenCertificate(cert *models.Certificate) map[string]interface{} { - result := make(map[string]interface{}) - if cert.Domain == nil { - result["domain"] = "nil" - } else { - result["domain"] = *cert.Domain - } - if cert.GetCertificateError == nil { - result["certificate_error"] = "nil" - } else { - result["certificate_error"] = *cert.GetCertificateError - } +/* + func FlattenCertificate(cert *models.Certificate) map[string]interface{} { + result := make(map[string]interface{}) + if cert.Domain == nil { + result["domain"] = "nil" + } else { + result["domain"] = *cert.Domain + } + if cert.GetCertificateError == nil { + result["certificate_error"] = "nil" + } else { + result["certificate_error"] = *cert.GetCertificateError + } - result["expiration_status"] = *cert.ExpirationStatus - result["issued_by"] = *cert.IssuedBy - result["issued_to"] = *cert.IssuedTo - result["key_size"] = *cert.KeySize - result["not_after"] = *cert.NotAfter - result["not_before"] = *cert.NotBefore - result["number_of_days_to_expire"] = *cert.NumberOfDaysToExpire - result["pem_encoded"] = *cert.PemEncoded - result["public_key"] = *cert.PublicKey - result["public_key_algorithm"] = *cert.PublicKeyAlgorithm - result["serial_number"] = *cert.SerialNumber - result["signature_algorithm"] = *cert.SignatureAlgorithm - result["subject"] = *cert.Subject - result["subject_alternative_name"] = cert.SubjectAlternativeName - result["thumbprint"] = *cert.Thumbprint - result["thumbprint_algorithm"] = *cert.ThumbprintAlgorithm - result["version"] = *cert.Version - return result -} + result["expiration_status"] = *cert.ExpirationStatus + result["issued_by"] = *cert.IssuedBy + result["issued_to"] = *cert.IssuedTo + result["key_size"] = *cert.KeySize + result["not_after"] = *cert.NotAfter + result["not_before"] = *cert.NotBefore + result["number_of_days_to_expire"] = *cert.NumberOfDaysToExpire + result["pem_encoded"] = *cert.PemEncoded + result["public_key"] = *cert.PublicKey + result["public_key_algorithm"] = *cert.PublicKeyAlgorithm + result["serial_number"] = *cert.SerialNumber + result["signature_algorithm"] = *cert.SignatureAlgorithm + result["subject"] = *cert.Subject + result["subject_alternative_name"] = cert.SubjectAlternativeName + result["thumbprint"] = *cert.Thumbprint + result["thumbprint_algorithm"] = *cert.ThumbprintAlgorithm + result["version"] = *cert.Version + return result + } +*/ diff --git a/internal/provider/data_certificate.go b/internal/provider/data_certificate.go new file mode 100644 index 0000000..f8cc82d --- /dev/null +++ b/internal/provider/data_certificate.go @@ -0,0 +1,298 @@ +// © Broadcom. All Rights Reserved. +// The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + "log" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/vmware/terraform-provider-vcf/internal/api_client" + "github.com/vmware/terraform-provider-vcf/internal/certificates" // Ensure this package exists and contains necessary methods +) + +func DataSourceCertificate() *schema.Resource { + return &schema.Resource{ + ReadContext: dataCertificateRead, + Description: "Datasource used to extract certificate details for various resources based on fields like domain, issued_by, issued_to, key_size, and others.", + Schema: map[string]*schema.Schema{ + "domain_id": { + Type: schema.TypeString, + Required: true, + Description: "The ID of the domain to fetch certificates for.", + }, + "resource_fqdn": { + Type: schema.TypeString, + Required: true, + Description: "the fqdn of resource certificate.", + }, + "certificate": { + Type: schema.TypeList, + Computed: true, + Description: "List of certificates retrieved from the API.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "domain": { + Type: schema.TypeString, + Computed: true, + Description: "The domain id of the certificate.", + }, + "certificate_error": { + Type: schema.TypeString, + Computed: true, + Description: "Error related to the certificate if any.", + }, + "expiration_status": { + Type: schema.TypeString, + Computed: true, + Description: "The expiration status of the certificate.", + }, + "is_installed": { + Type: schema.TypeBool, + Computed: true, + Description: "Whether the certificate is installed or not.", + }, + "issued_by": { + Type: schema.TypeString, + Computed: true, + Description: "The entity that issued the certificate.", + }, + "issued_to": { + Type: schema.TypeString, + Computed: true, + Description: "The entity to which the certificate was issued.", + }, + "key_size": { + Type: schema.TypeString, + Computed: true, + Description: "The size of the key in the certificate.", + }, + "not_after": { + Type: schema.TypeString, + Computed: true, + Description: "The date after which the certificate is no longer valid.", + }, + "not_before": { + Type: schema.TypeString, + Computed: true, + Description: "The date before which the certificate is not valid.", + }, + "number_of_days_to_expire": { + Type: schema.TypeInt, + Computed: true, + Description: "The number of days until the certificate expires.", + }, + "pem_encoded": { + Type: schema.TypeString, + Computed: true, + Description: "The PEM-encoded certificate.", + }, + "public_key": { + Type: schema.TypeString, + Computed: true, + Description: "The public key of the certificate.", + }, + "public_key_algorithm": { + Type: schema.TypeString, + Computed: true, + Description: "The algorithm used for the public key.", + }, + "serial_number": { + Type: schema.TypeString, + Computed: true, + Description: "The serial number of the certificate.", + }, + "signature_algorithm": { + Type: schema.TypeString, + Computed: true, + Description: "The algorithm used for the certificate's signature.", + }, + "subject": { + Type: schema.TypeString, + Computed: true, + Description: "The subject of the certificate.", + }, + "subject_cn": { + Type: schema.TypeString, + Computed: true, + Description: "The subject_cn(common name) of the certificate.", + }, + "subject_ou": { + Type: schema.TypeString, + Computed: true, + Description: "The subject_ou(org unit) of the certificate.", + }, + "subject_org": { + Type: schema.TypeString, + Computed: true, + Description: "The subject_org of the certificate.", + }, + "subject_locality": { + Type: schema.TypeString, + Computed: true, + Description: "The subject_locality of the certificate.", + }, + "subject_st": { + Type: schema.TypeString, + Computed: true, + Description: "The subject_st(state) of the certificate.", + }, + "subject_country": { + Type: schema.TypeString, + Computed: true, + Description: "The subject_country of the certificate.", + }, + "subject_alternative_name": { + Type: schema.TypeList, + Computed: true, + Description: "The subject alternative names in the certificate.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "thumbprint": { + Type: schema.TypeString, + Computed: true, + Description: "The thumbprint of the certificate.", + }, + "thumbprint_algorithm": { + Type: schema.TypeString, + Computed: true, + Description: "The algorithm used to generate the thumbprint.", + }, + "version": { + Type: schema.TypeString, + Computed: true, + Description: "The version of certificate.", + }, + }, + }, + }, + }, + } +} + +func dataCertificateRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*api_client.SddcManagerClient).ApiClient + log.Print("[DEBUG] Function dataCertificateRead start") + // Extract the domain_id from ResourceData + domainId, ok := data.Get("domain_id").(string) + if !ok { + log.Print("[DEBUG] Function dataCertificateRead, domainId not found or not a string") + } else { + log.Printf("[DEBUG] Function dataCertificateRead, domainId: %s", domainId) + } + + // Extract the resource_fqdn from ResourceData + resourceFqdn, ok := data.Get("resource_fqdn").(string) + if !ok { + log.Print("[DEBUG] Function dataCertificateRead, resourceFqdn not found or not a string") + return diag.Errorf("resource_fqdn is not set or is not a string") + } + + // Call ReadCertificate with the domainId and resourceFqdn + cert, err := certificates.ReadCertificate(ctx, apiClient, domainId, resourceFqdn) + if err != nil { + log.Printf("[ERROR] Failed to read certificate: %s", err) + return diag.FromErr(err) + } + + if cert == nil { + return diag.Errorf("certificate with FQDN %s not found in domain ID %s", resourceFqdn, domainId) + } + + log.Printf("[DEBUG] Function dataCertificateRead, cert: %+v", cert) + + // Process and flatten the single certificate + flatCertificate := certificates.FlattenCertificateWithSubject(cert) + log.Printf("[DEBUG] flatCertificate Data type: %T", flatCertificate) + log.Printf("[DEBUG] flatCertificate Data value: %+v", flatCertificate) + + // Wrap flatCertificate in a slice + _ = data.Set("certificate", []interface{}{flatCertificate}) + + // create and set certificateID + id, err := createCertificateID(data) + log.Printf("[DEBUG] Function dataCertificateRead, cert-id: %+v", id) + if err != nil { + return diag.Errorf("error during id generation %s", err) + } + + data.SetId(id) + log.Printf("[DEBUG] Function dataCertificateRead, dataset with ID: %+v", data) + return nil +} + +func createCertificateID(data *schema.ResourceData) (string, error) { + // Fetch the single certificate from the data schema + certificatesList := data.Get("certificate").([]interface{}) + if len(certificatesList) == 0 { + return "", fmt.Errorf("no certificates found") + } + certInterface, ok := certificatesList[0].(map[string]interface{}) + if !ok { + return "", fmt.Errorf("certificate data is not a valid map") + } + + // Initialize a params slice to store certificate field values + var params []string + + // Fetch individual certificate fields and append them to params + params = append(params, getString(certInterface, "domain")) + params = append(params, getString(certInterface, "certificate_error")) + params = append(params, getString(certInterface, "expiration_status")) + params = append(params, getBoolAsString(certInterface, "is_installed")) + params = append(params, getString(certInterface, "issued_by")) + params = append(params, getString(certInterface, "issued_to")) + params = append(params, getString(certInterface, "key_size")) + params = append(params, getString(certInterface, "not_after")) + params = append(params, getString(certInterface, "not_before")) + params = append(params, getIntAsString(certInterface, "number_of_days_to_expire")) + params = append(params, getString(certInterface, "pem_encoded")) + params = append(params, getString(certInterface, "public_key")) + params = append(params, getString(certInterface, "public_key_algorithm")) + params = append(params, getString(certInterface, "serial_number")) + params = append(params, getString(certInterface, "signature_algorithm")) + params = append(params, getString(certInterface, "subject")) + params = append(params, getString(certInterface, "thumbprint")) + params = append(params, getString(certInterface, "thumbprint_algorithm")) + params = append(params, getString(certInterface, "version")) + + // Use a hashing function to create a unique ID based on the certificate fields. + id, err := certificates.HashFields(params) + if err != nil { + return "", fmt.Errorf("error creating hash for certificate ID: %v", err) + } + + return id, nil +} + +// Helper function to get a string from a map. +func getString(certMap map[string]interface{}, key string) string { + if val, ok := certMap[key].(string); ok { + return val + } + return "" +} + +// Helper function to get an integer from a map and convert it to string. +func getIntAsString(certMap map[string]interface{}, key string) string { + if val, ok := certMap[key].(int); ok { + return strconv.Itoa(val) + } + return "" +} + +func getBoolAsString(certMap map[string]interface{}, key string) string { + if val, ok := certMap[key].(bool); ok { + if val { + return "true" + } + return "false" + } + return "" +} diff --git a/internal/provider/data_certificate_test.go b/internal/provider/data_certificate_test.go new file mode 100644 index 0000000..23e4014 --- /dev/null +++ b/internal/provider/data_certificate_test.go @@ -0,0 +1,50 @@ +// © Broadcom. All Rights Reserved. +// The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceCertificate(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccSDDCManagerOrCloudBuilderPreCheck(t) }, + ProtoV6ProviderFactories: muxedFactories(), + Steps: []resource.TestStep{ + { + Config: testAccDataSourceCertificateConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.vcf_certificate.cert", "certificate.0.subject_cn", "sfo-w01-vc01.sfo.rainpole.io"), + resource.TestCheckResourceAttr("data.vcf_certificate.cert", "certificate.0.subject_locality", "Palo Alto"), + resource.TestCheckResourceAttr("data.vcf_certificate.cert", "certificate.0.subject_st", "California"), + resource.TestCheckResourceAttr("data.vcf_certificate.cert", "certificate.0.subject_country", "US"), + resource.TestCheckResourceAttr("data.vcf_certificate.cert", "certificate.0.subject_org", "VMware Inc."), + resource.TestCheckResourceAttr("data.vcf_certificate.cert", "certificate.0.subject_ou", "VCF"), + resource.TestCheckResourceAttr("data.vcf_certificate.cert", "certificate.0.public_key_algorithm", "RSA"), + resource.TestCheckResourceAttr("data.vcf_certificate.cert", "certificate.0.key_size", "3072"), + resource.TestCheckResourceAttr("data.vcf_certificate.cert", "certificate.0.signature_algorithm", "SHA256withRSA"), + resource.TestCheckResourceAttr("data.vcf_certificate.cert", "certificate.0.expiration_status", "ACTIVE"), + resource.TestCheckResourceAttr("data.vcf_certificate.cert", "certificate.0.issued_to", "sfo-w01-vc01.sfo.rainpole.io"), + resource.TestCheckResourceAttr("data.vcf_certificate.cert", "certificate.0.issued_by", "CN=rainpole-RPL-AD01-CA, DC=rainpole, DC=io"), + resource.TestCheckResourceAttr("data.vcf_certificate.cert", "certificate.0.version", "V3"), + ), + }, + }, + }) +} + +func testAccDataSourceCertificateConfig() string { + return ` +data "vcf_domain" "w01" { + name = "sfo-w01" +} +data "vcf_certificate" "cert" { + domain_id = data.vcf_domain.w01.id + resource_fqdn = "sfo-w01-vc01.sfo.rainpole.io" +} +` +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 9fd2f21..858fa42 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -79,6 +79,7 @@ func Provider() *schema.Provider { "vcf_domain": DataSourceDomain(), "vcf_credentials": DataSourceCredentials(), "vcf_network_pool": DataSourceNetworkPool(), + "vcf_certificate": DataSourceCertificate(), }, ResourcesMap: map[string]*schema.Resource{ diff --git a/internal/provider/resource_certificate.go b/internal/provider/resource_certificate.go index 647ea60..b82061d 100644 --- a/internal/provider/resource_certificate.go +++ b/internal/provider/resource_certificate.go @@ -121,12 +121,12 @@ func resourceResourceCertificateRead(ctx context.Context, data *schema.ResourceD domainID := csrIdComponents[1] resourceFqdn := csrIdComponents[3] - cert, err := certificates.GetCertificateForResourceInDomain(ctx, apiClient, domainID, resourceFqdn) + cert, err := certificates.ReadCertificate(ctx, apiClient, domainID, resourceFqdn) if err != nil { return diag.FromErr(err) } - flattenedCert := certificates.FlattenCertificate(cert) + flattenedCert := certificates.FlattenCertificateWithSubject(cert) _ = data.Set("certificate", []interface{}{flattenedCert}) return nil diff --git a/internal/provider/resource_external_certificate.go b/internal/provider/resource_external_certificate.go index 88ff09f..fbd0e1d 100644 --- a/internal/provider/resource_external_certificate.go +++ b/internal/provider/resource_external_certificate.go @@ -151,12 +151,12 @@ func resourceResourceExternalCertificateRead(ctx context.Context, data *schema.R domainID := csrIdComponents[1] resourceType := csrIdComponents[2] - cert, err := certificates.GetCertificateForResourceInDomain(ctx, apiClient, domainID, resourceType) + cert, err := certificates.ReadCertificate(ctx, apiClient, domainID, resourceType) if err != nil { return diag.FromErr(err) } - flattenedCert := certificates.FlattenCertificate(cert) + flattenedCert := certificates.FlattenCertificateWithSubject(cert) _ = data.Set("certificate", []interface{}{flattenedCert}) return nil From 95de99caab18946ceb38ec996dc642943040617f Mon Sep 17 00:00:00 2001 From: David Zhang Date: Wed, 28 Aug 2024 22:19:07 +1000 Subject: [PATCH 2/4] remove commented functions Signed-off-by David Zhang Signed-off-by: David Zhang --- .../certificates/certificate_operations.go | 22 ------------ .../certificates/certificate_subresource.go | 35 ------------------- 2 files changed, 57 deletions(-) diff --git a/internal/certificates/certificate_operations.go b/internal/certificates/certificate_operations.go index e4599ae..ab6db73 100644 --- a/internal/certificates/certificate_operations.go +++ b/internal/certificates/certificate_operations.go @@ -73,28 +73,6 @@ func ValidateResourceCertificates(ctx context.Context, client *vcfclient.VcfClie return nil } -/* -func GetCertificateForResourceInDomain(ctx context.Context, client *vcfclient.VcfClient, - - domainId, resourceFqdn string) (*models.Certificate, error) { - viewCertificatesParams := certificates.NewGetCertificatesByDomainParamsWithContext(ctx). - WithTimeout(constants.DefaultVcfApiCallTimeout) - viewCertificatesParams.ID = domainId - - certificatesResponse, _, err := client.Certificates.GetCertificatesByDomain(viewCertificatesParams) - if err != nil { - return nil, err - } - - allCertsForDomain := certificatesResponse.Payload.Elements - for _, cert := range allCertsForDomain { - if cert.IssuedTo != nil && *cert.IssuedTo == resourceFqdn { - return cert, nil - } - } - return nil, nil - } -*/ func GenerateCertificateForResource(ctx context.Context, client *api_client.SddcManagerClient, domainId, resourceType, resourceFqdn, caType *string) error { diff --git a/internal/certificates/certificate_subresource.go b/internal/certificates/certificate_subresource.go index 3a4f11a..de18904 100644 --- a/internal/certificates/certificate_subresource.go +++ b/internal/certificates/certificate_subresource.go @@ -141,38 +141,3 @@ func CertificateSchema() *schema.Resource { }, } } - -/* - func FlattenCertificate(cert *models.Certificate) map[string]interface{} { - result := make(map[string]interface{}) - if cert.Domain == nil { - result["domain"] = "nil" - } else { - result["domain"] = *cert.Domain - } - if cert.GetCertificateError == nil { - result["certificate_error"] = "nil" - } else { - result["certificate_error"] = *cert.GetCertificateError - } - - result["expiration_status"] = *cert.ExpirationStatus - result["issued_by"] = *cert.IssuedBy - result["issued_to"] = *cert.IssuedTo - result["key_size"] = *cert.KeySize - result["not_after"] = *cert.NotAfter - result["not_before"] = *cert.NotBefore - result["number_of_days_to_expire"] = *cert.NumberOfDaysToExpire - result["pem_encoded"] = *cert.PemEncoded - result["public_key"] = *cert.PublicKey - result["public_key_algorithm"] = *cert.PublicKeyAlgorithm - result["serial_number"] = *cert.SerialNumber - result["signature_algorithm"] = *cert.SignatureAlgorithm - result["subject"] = *cert.Subject - result["subject_alternative_name"] = cert.SubjectAlternativeName - result["thumbprint"] = *cert.Thumbprint - result["thumbprint_algorithm"] = *cert.ThumbprintAlgorithm - result["version"] = *cert.Version - return result - } -*/ From ac3be0ee45f67aa72455cd218c89c0e5270b588e Mon Sep 17 00:00:00 2001 From: David Zhang Date: Fri, 30 Aug 2024 10:12:27 +1000 Subject: [PATCH 3/4] update as per PR feedbacks for certificate datasource Signed-off-by: David Zhang --- docs/data-sources/certificate.md | 4 +- .../certificates/certificate_operations.go | 8 ++-- internal/provider/data_certificate.go | 37 ++++++++----------- 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/docs/data-sources/certificate.md b/docs/data-sources/certificate.md index 19f1493..76fcfb9 100644 --- a/docs/data-sources/certificate.md +++ b/docs/data-sources/certificate.md @@ -3,12 +3,12 @@ page_title: "vcf_certificate Data Source - terraform-provider-vcf" subcategory: "" description: |- - Datasource used to extract certificate details for various resources based on fields like domain, issued_by, issued_to, key_size, and others. + Datasource used to extract certificate details based on fields domain_id and resource_fqdn. --- # vcf_certificate (Data Source) -Datasource used to extract certificate details for various resources based on fields like domain, issued_by, issued_to, key_size, and others. +Datasource used to extract certificate details based on fields domain_id and resource_fqdn. diff --git a/internal/certificates/certificate_operations.go b/internal/certificates/certificate_operations.go index ab6db73..cd1f807 100644 --- a/internal/certificates/certificate_operations.go +++ b/internal/certificates/certificate_operations.go @@ -114,7 +114,7 @@ func ReadCertificate(ctx context.Context, client *vcfclient.VcfClient, certificatesResponse, _, err := client.Certificates.GetCertificatesByDomain(viewCertificatesParams) if err != nil { - return nil, fmt.Errorf("failed to get certificates by domain: %w", err) + return nil, fmt.Errorf("failed to get certificate by domain: %w", err) } // Check if any certificates are found @@ -128,18 +128,18 @@ func ReadCertificate(ctx context.Context, client *vcfclient.VcfClient, return cert, nil } } - return nil, nil + return nil, fmt.Errorf("no certificate found for resource FQDN %s in domain ID %s", resourceFqdn, domainId) } func FlattenCertificateWithSubject(cert *models.Certificate) map[string]interface{} { result := make(map[string]interface{}) if cert.Domain == nil { - result["domain"] = "nil" + result["domain"] = nil } else { result["domain"] = *cert.Domain } if cert.GetCertificateError == nil { - result["certificate_error"] = "nil" + result["certificate_error"] = nil } else { result["certificate_error"] = *cert.GetCertificateError } diff --git a/internal/provider/data_certificate.go b/internal/provider/data_certificate.go index f8cc82d..3571951 100644 --- a/internal/provider/data_certificate.go +++ b/internal/provider/data_certificate.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/vmware/terraform-provider-vcf/internal/api_client" "github.com/vmware/terraform-provider-vcf/internal/certificates" // Ensure this package exists and contains necessary methods ) @@ -23,14 +23,16 @@ func DataSourceCertificate() *schema.Resource { Description: "Datasource used to extract certificate details for various resources based on fields like domain, issued_by, issued_to, key_size, and others.", Schema: map[string]*schema.Schema{ "domain_id": { - Type: schema.TypeString, - Required: true, - Description: "The ID of the domain to fetch certificates for.", + Type: schema.TypeString, + Required: true, + Description: "The ID of the domain to fetch certificates for.", + ValidateFunc: validation.StringIsNotEmpty, }, "resource_fqdn": { - Type: schema.TypeString, - Required: true, - Description: "the fqdn of resource certificate.", + Type: schema.TypeString, + Required: true, + Description: "the fqdn of resource certificate.", + ValidateFunc: validation.StringIsNotEmpty, }, "certificate": { Type: schema.TypeList, @@ -179,20 +181,12 @@ func DataSourceCertificate() *schema.Resource { func dataCertificateRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { apiClient := meta.(*api_client.SddcManagerClient).ApiClient log.Print("[DEBUG] Function dataCertificateRead start") - // Extract the domain_id from ResourceData - domainId, ok := data.Get("domain_id").(string) - if !ok { - log.Print("[DEBUG] Function dataCertificateRead, domainId not found or not a string") - } else { - log.Printf("[DEBUG] Function dataCertificateRead, domainId: %s", domainId) - } + // Extract the domain_id and resource_fqdn from ResourceData + domainId := data.Get("domain_id").(string) + log.Printf("[DEBUG] Function dataCertificateRead, domainId: %s", domainId) - // Extract the resource_fqdn from ResourceData - resourceFqdn, ok := data.Get("resource_fqdn").(string) - if !ok { - log.Print("[DEBUG] Function dataCertificateRead, resourceFqdn not found or not a string") - return diag.Errorf("resource_fqdn is not set or is not a string") - } + resourceFqdn := data.Get("resource_fqdn").(string) + log.Printf("[DEBUG] Function dataCertificateRead, resourceFqdn: %s", resourceFqdn) // Call ReadCertificate with the domainId and resourceFqdn cert, err := certificates.ReadCertificate(ctx, apiClient, domainId, resourceFqdn) @@ -219,7 +213,7 @@ func dataCertificateRead(ctx context.Context, data *schema.ResourceData, meta in id, err := createCertificateID(data) log.Printf("[DEBUG] Function dataCertificateRead, cert-id: %+v", id) if err != nil { - return diag.Errorf("error during id generation %s", err) + return diag.Errorf("error during certificate id generation %s", err) } data.SetId(id) @@ -287,6 +281,7 @@ func getIntAsString(certMap map[string]interface{}, key string) string { return "" } +// Helper function to get Boolean as string. func getBoolAsString(certMap map[string]interface{}, key string) string { if val, ok := certMap[key].(bool); ok { if val { From 57369a64cab50fc38e088adf2df39fcca8c9b103 Mon Sep 17 00:00:00 2001 From: David Zhang Date: Fri, 30 Aug 2024 18:15:22 +1000 Subject: [PATCH 4/4] update the certificate.md per PR feedback Signed-off-by: David Zhang --- docs/data-sources/certificate.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/data-sources/certificate.md b/docs/data-sources/certificate.md index 76fcfb9..b28f235 100644 --- a/docs/data-sources/certificate.md +++ b/docs/data-sources/certificate.md @@ -3,12 +3,12 @@ page_title: "vcf_certificate Data Source - terraform-provider-vcf" subcategory: "" description: |- - Datasource used to extract certificate details based on fields domain_id and resource_fqdn. + Datasource used to extract certificate details based on fields `domain_id` and `resource_fqdn`. --- # vcf_certificate (Data Source) -Datasource used to extract certificate details based on fields domain_id and resource_fqdn. +Datasource used to extract certificate details based on fields `domain_id` and `resource_fqdn`.