Repository: crossplane/provider-aws (go)

Issue:
Provide Certificate dns info in status
<!--
Thank you for helping to improve Crossplane!

Please be sure to search for open issues before raising a new one. We use issues
for bug reports and feature requests. Please find us at https://slack.crossplane.io
for questions, support, and discussion.
-->

### What problem are you facing?
<!--
Please tell us a little about your use case - it's okay if it's hypothetical!
Leading with this context helps frame the feature request so we can ensure we
implement it sensibly.
--->
It's impossible to fully automate ACM certs using DNS validation when using Crossplane resources for AWS. I am trying to automate the certs through a Composition but there is still the manual step of adding the needed CNAME record to Route53 for the DNS validation of a Certificate to be complete. 

To create a DNS validated cert use the following template: 
```yaml
apiVersion: acm.aws.crossplane.io/v1alpha1
kind: Certificate
metadata:
  name: foo.example.com
spec:
  forProvider:
    domainName: foo.example.com
    region: us-east-1
    validationMethod: DNS
    tags:
    - key: Name
      value: foo.example.com
```
Now to fully validate the certificate:
You must manually log into the AWS console and navigate to the created resource.
There is an option to create a CNAME record in Route53 and you click to create it. 
Only now will the Crossplane resource say the Certificate is ready. 

Or you can get the information for needed CNAME like so with cli:
```
AWS_REGION=us-east-1 aws acm describe-certificate \
  --certificate-arn <arn from certificate> \
  --query 'Certificate.DomainValidationOptions'
```

My Issue is I want this to be automated in a Crossplane `Composition`, but I can't because the `Certificate` resource within kubernetes does not have information about the needed CNAME record for DNS validation. 

### How could Crossplane help solve your problem?
<!--
Let us know how you think Crossplane could help with your use case. 
-->
In the status of the `Certificate`, show information about the needed CNAME record to properly finish DNS validation. 

It could look something like this:
```yaml
status:
  atProvider:
    dnsValidation:
      recordName: _dsj24j034j80f34j0adpsw.foo.example.com
      recordValue: _dfijodfsope9023r.dfsijqwiu.acm-validations.aws
      type: CNAME
      policy: simple
```

Now to fully automate ACM certs with some patch magic in the `Composition`, create a `ResourceRecordSet` using the values from the certs status so you would end up with:
```yaml
apiVersion: route53.aws.crossplane.io/v1alpha1
kind: ResourceRecordSet
metadata:
  name: _dsj24j034j80f34j0adpsw.foo.example.com
spec:
  forProvider:
    type: CNAME
    resourceRecords:
    - value: _dfijodfsope9023r.dfsijqwiu.acm-validations.aws
    zoneIdRef:
      name: example.com
```

Automation!

Pull request:
Implements ACM ResourceRecord for dns validation in status field
Signed-off-by: Christopher Haar <chhaar30@googlemail.com>

### Description of your changes
Implements ACM ResourceRecord for dns validation in status field

Fixes #804

```
  At Provider:
    Certificate ARN:      arn:aws:acm:eu-central-1:255932642927:certificate/3eb5bed4-6539-4e3d-8bd4-425810f89d60
    Renewal Eligibility:  INELIGIBLE
    Resource Record:
      Name:   _ff74bcdb41a2f939c93d449daad4565a.example.com.
      Type:   CNAME
      Value:  _03494a5eb651455affe9cb5fa3bc0e75.bwlshdtstt.acm-validations.aws.
    Status:   PENDING_VALIDATION
    Type:     AMAZON_ISSUED
```

I have:

- [x] Read and followed Crossplane's [contribution process].
- [x] Run `make reviewable test` to ensure this PR is ready for review.

[contribution process]: https://git.io/fj2m9


Patch:
diff --git a/apis/acm/v1alpha1/certificate_types.go b/apis/acm/v1alpha1/certificate_types.go
index 0b25ec1a1..c2ce3a8a1 100644
--- a/apis/acm/v1alpha1/certificate_types.go
+++ b/apis/acm/v1alpha1/certificate_types.go
@@ -65,6 +65,16 @@ type CertificateExternalStatus struct {
 	// Type of the certificate
 	// +kubebuilder:validation:Enum=IMPORTED;AMAZON_ISSUED;PRIVATE
 	Type acm.CertificateType `json:"type,omitempty"`
+
+	// Contains the CNAME record that you add to your DNS database for domain
+	// validation. For more information, see Use DNS to Validate Domain Ownership
+	// (https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate-dns.html).
+	// Note: The CNAME information that you need does not include the name of your
+	// domain. If you include your domain name in the DNS database CNAME record,
+	// validation fails. For example, if the name is
+	// "_a79865eb4cd1a6ab990a45779b4e0b96.yourdomain.com", only
+	// "_a79865eb4cd1a6ab990a45779b4e0b96" must be used.
+	ResourceRecord *ResourceRecord `json:"resourceRecord,omitempty"`
 }
 
 // An CertificateStatus represents the observed state of an Certificate manager.
@@ -73,6 +83,19 @@ type CertificateStatus struct {
 	AtProvider          CertificateExternalStatus `json:"atProvider,omitempty"`
 }
 
+// ResourceRecord Contains a DNS record value that you can use to validate ownership or control of a domain.
+type ResourceRecord struct {
+	// The name of the DNS record to create in your domain. This is supplied by ACM.
+	Name *string `json:"name,omitempty"`
+
+	// The type of DNS record. Currently this can be CNAME.
+	// +kubebuilder:validation:Enum=CNAME
+	Type *string `json:"type,omitempty"`
+
+	// The value of the CNAME record to add to your DNS database.
+	Value *string `json:"value,omitempty"`
+}
+
 // CertificateParameters defines the desired state of an AWS Certificate.
 type CertificateParameters struct {
 
diff --git a/apis/acm/v1alpha1/zz_generated.deepcopy.go b/apis/acm/v1alpha1/zz_generated.deepcopy.go
index 365359119..5dde674a0 100644
--- a/apis/acm/v1alpha1/zz_generated.deepcopy.go
+++ b/apis/acm/v1alpha1/zz_generated.deepcopy.go
@@ -56,6 +56,11 @@ func (in *Certificate) DeepCopyObject() runtime.Object {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *CertificateExternalStatus) DeepCopyInto(out *CertificateExternalStatus) {
 	*out = *in
+	if in.ResourceRecord != nil {
+		in, out := &in.ResourceRecord, &out.ResourceRecord
+		*out = new(ResourceRecord)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateExternalStatus.
@@ -193,7 +198,7 @@ func (in *CertificateSpec) DeepCopy() *CertificateSpec {
 func (in *CertificateStatus) DeepCopyInto(out *CertificateStatus) {
 	*out = *in
 	in.ResourceStatus.DeepCopyInto(&out.ResourceStatus)
-	out.AtProvider = in.AtProvider
+	in.AtProvider.DeepCopyInto(&out.AtProvider)
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateStatus.
@@ -221,6 +226,36 @@ func (in *DomainValidationOption) DeepCopy() *DomainValidationOption {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ResourceRecord) DeepCopyInto(out *ResourceRecord) {
+	*out = *in
+	if in.Name != nil {
+		in, out := &in.Name, &out.Name
+		*out = new(string)
+		**out = **in
+	}
+	if in.Type != nil {
+		in, out := &in.Type, &out.Type
+		*out = new(string)
+		**out = **in
+	}
+	if in.Value != nil {
+		in, out := &in.Value, &out.Value
+		*out = new(string)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceRecord.
+func (in *ResourceRecord) DeepCopy() *ResourceRecord {
+	if in == nil {
+		return nil
+	}
+	out := new(ResourceRecord)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Tag) DeepCopyInto(out *Tag) {
 	*out = *in
diff --git a/examples/acm/certificate_dns.yaml b/examples/acm/certificate_dns.yaml
new file mode 100644
index 000000000..065b7d559
--- /dev/null
+++ b/examples/acm/certificate_dns.yaml
@@ -0,0 +1,14 @@
+apiVersion: acm.aws.crossplane.io/v1alpha1
+kind: Certificate
+metadata:
+  name: dev.crossplane.io
+spec:
+  forProvider:
+    domainName: dev.crossplane.io
+    region: us-east-1
+    validationMethod: DNS
+    tags:
+    - key: Name
+      value: example
+  providerConfigRef:
+    name: example
diff --git a/package/crds/acm.aws.crossplane.io_certificates.yaml b/package/crds/acm.aws.crossplane.io_certificates.yaml
index 3e453842b..01d40b850 100644
--- a/package/crds/acm.aws.crossplane.io_certificates.yaml
+++ b/package/crds/acm.aws.crossplane.io_certificates.yaml
@@ -229,6 +229,31 @@ spec:
                     - ELIGIBLE
                     - INELIGIBLE
                     type: string
+                  resourceRecord:
+                    description: 'Contains the CNAME record that you add to your DNS
+                      database for domain validation. For more information, see Use
+                      DNS to Validate Domain Ownership (https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate-dns.html).
+                      Note: The CNAME information that you need does not include the
+                      name of your domain. If you include your domain name in the
+                      DNS database CNAME record, validation fails. For example, if
+                      the name is "_a79865eb4cd1a6ab990a45779b4e0b96.yourdomain.com",
+                      only "_a79865eb4cd1a6ab990a45779b4e0b96" must be used.'
+                    properties:
+                      name:
+                        description: The name of the DNS record to create in your
+                          domain. This is supplied by ACM.
+                        type: string
+                      type:
+                        description: The type of DNS record. Currently this can be
+                          CNAME.
+                        enum:
+                        - CNAME
+                        type: string
+                      value:
+                        description: The value of the CNAME record to add to your
+                          DNS database.
+                        type: string
+                    type: object
                   status:
                     description: Status of the certificate
                     enum:
diff --git a/pkg/clients/acm/certificate.go b/pkg/clients/acm/certificate.go
index dc55f047d..73083debf 100644
--- a/pkg/clients/acm/certificate.go
+++ b/pkg/clients/acm/certificate.go
@@ -74,6 +74,23 @@ func GenerateCreateCertificateInput(name string, p *v1alpha1.CertificateParamete
 
 // GenerateCertificateStatus is used to produce CertificateExternalStatus from acm.certificateStatus
 func GenerateCertificateStatus(certificate acm.CertificateDetail) v1alpha1.CertificateExternalStatus {
+
+	if certificate.Type == acm.CertificateTypeAmazonIssued && len(certificate.DomainValidationOptions) > 0 {
+		if certificate.DomainValidationOptions[0].ResourceRecord != nil {
+			return v1alpha1.CertificateExternalStatus{
+				CertificateARN:     aws.StringValue(certificate.CertificateArn),
+				RenewalEligibility: certificate.RenewalEligibility,
+				Status:             certificate.Status,
+				Type:               certificate.Type,
+				ResourceRecord: &v1alpha1.ResourceRecord{
+					Name:  certificate.DomainValidationOptions[0].ResourceRecord.Name,
+					Value: certificate.DomainValidationOptions[0].ResourceRecord.Value,
+					Type:  (*string)(&certificate.DomainValidationOptions[0].ResourceRecord.Type),
+				},
+			}
+		}
+	}
+
 	return v1alpha1.CertificateExternalStatus{
 		CertificateARN:     aws.StringValue(certificate.CertificateArn),
 		RenewalEligibility: certificate.RenewalEligibility,
@@ -119,7 +136,6 @@ func LateInitializeCertificate(in *v1alpha1.CertificateParameters, certificate *
 			}
 		}
 	}
-
 }
 
 // IsCertificateUpToDate checks whether there is a change in any of the modifiable fields.


Test patch:
diff --git a/pkg/clients/acm/certificate_test.go b/pkg/clients/acm/certificate_test.go
index ccfa338ec..3d9f009d9 100644
--- a/pkg/clients/acm/certificate_test.go
+++ b/pkg/clients/acm/certificate_test.go
@@ -137,6 +137,10 @@ func TestLateInitializeCertificate(t *testing.T) {
 }
 
 func TestGenerateCertificateStatus(t *testing.T) {
+	sName := "_xyz.crossplane.io."
+	sType := "CNAME"
+	sValue := "_xxx.zzz.acm-validations.aws."
+
 	cases := map[string]struct {
 		in  acm.CertificateDetail
 		out v1alpha1.CertificateExternalStatus
@@ -161,6 +165,35 @@ func TestGenerateCertificateStatus(t *testing.T) {
 				RenewalEligibility: acm.RenewalEligibilityEligible,
 			},
 		},
+		"DomainValidationOptionsResourceRecord": {
+			in: acm.CertificateDetail{
+				CertificateArn:     aws.String(certificateArn),
+				RenewalEligibility: acm.RenewalEligibilityIneligible,
+				Type:               acm.CertificateTypeAmazonIssued,
+				Status:             acm.CertificateStatusPendingValidation,
+				DomainValidationOptions: []acm.DomainValidation{
+					{
+						DomainName: &sName,
+						ResourceRecord: &acm.ResourceRecord{
+							Name:  &sName,
+							Value: &sValue,
+							Type:  acm.RecordType(sType),
+						},
+					},
+				},
+			},
+			out: v1alpha1.CertificateExternalStatus{
+				CertificateARN:     certificateArn,
+				RenewalEligibility: acm.RenewalEligibilityIneligible,
+				Type:               acm.CertificateTypeAmazonIssued,
+				Status:             acm.CertificateStatusPendingValidation,
+				ResourceRecord: &v1alpha1.ResourceRecord{
+					Name:  &sName,
+					Value: &sValue,
+					Type:  &sType,
+				},
+			},
+		},
 	}
 
 	for name, tc := range cases {