hashicorp / terraform-provider-aws

The AWS Provider enables Terraform to manage AWS resources.
https://registry.terraform.io/providers/hashicorp/aws
Mozilla Public License 2.0
9.79k stars 9.14k forks source link

[Documentation]: Python examples for `aws_acm_certificate` do not work #33613

Open DanielRepik opened 1 year ago

DanielRepik commented 1 year ago

Terraform Core Version

1.5.5

AWS Provider Version

4.67.0

Affected Resource(s)

I'm attempting to create an ACM certificate along with the domain validation records in Python using cdktf without success. I'm not sure if the problem is with the result of the 'domain_validation_options' for the certificate is correct or with the for_each looping. In either case the example code is incorrect.

Starting with the example code found at (https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation?lang=python) with non-relevant code removed, I have the following code that illustrates the problem:

from constructs import Construct
from cdktf import App, TerraformStack, Token, TerraformIterator, Fn

from imports.aws.acm_certificate import AcmCertificate
from imports.aws.acm_certificate_validation import AcmCertificateValidation
from imports.aws.data_aws_route53_zone import DataAwsRoute53Zone

from imports.aws.provider import AwsProvider

class CertificateExampleStack(TerraformStack):
    def __init__(self, scope, name):
        super().__init__(scope, name)

        domain_name = "<domain name goes here>"
        zone_id = "<zone id goes here>"    # hard coded to simplify example

        AwsProvider(self, "aws-provider", region="eu-west-1")

        example_cert = AcmCertificate(self, "example",
            domain_name=domain_name,
            validation_method="DNS"
        )

        # this line is where the error occurs, the string interpolation
        # in the Token.as_any isn't correct
        example_for_each_iterator = TerraformIterator.from_list(
            Token.as_any(
            "${{ for dvo in ${" 
            + example_cert.domain_validation_options 
            + "} : dvo.domain_name => {\n      name   = dvo.resource_record_name\n      record = dvo.resource_record_value\n      type   = dvo.resource_record_type\n    }}}"))

        aws_route53_record_example = Route53Record(self, "example_2",
            allow_overwrite=True,
            name=Token.as_string(
                Fn.lookup_nested(example_for_each_iterator.value, ["name"])),
            records=[
                Token.as_string(
                    Fn.lookup_nested(example_for_each_iterator.value, ["record"]))
            ],
            ttl=60,
            type=Token.as_string(
                Fn.lookup_nested(example_for_each_iterator.value, ["type"])),
            zone_id=zone_id,
            for_each=example_for_each_iterator
        )

        aws_route53_record_example.override_logical_id("example")
        # this expression also has incorrect string interpolation.
        AcmCertificateValidation(self, "example_3",
            certificate_arn=example_cert.arn,
            validation_record_fqdns=Token.as_list("${[ for record in ${" + aws_route53_record_example.fqn + "} : record.fqdn]}")
        )

app = App()
CertificateExampleStack(app, "tf-test-python")

app.synth()

On running;

cdktf deploy

I get the following results;

    Traceback (most recent call last):
      File "/Users/drepik/workspaces/tf-test-python/main.py", line 56, in <module>
        CertificateExampleStack(app, "tf-test-python")
      File "/Users/drepik/.local/share/virtualenvs/tf-test-python-xvBxJD8u/lib/python3.11/site-packages/jsii/_runtime.py", line 118, in __call__
        inst = super(JSIIMeta, cast(JSIIMeta, cls)).__call__(*args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/Users/drepik/workspaces/tf-test-python/main.py", line 32, in __init__
        Token.as_any("${{ for dvo in ${" + example_cert.domain_validation_options + "} : dvo.domain_name => {\n      name   = dvo.resource_record_name\n      record = dvo.resource_record_value\n      type   = dvo.resource_record_type\n    }}}"))
                     ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    TypeError: can only concatenate str (not "AcmCertificateDomainValidationOptionsList") to str

The original code source of the example posted in the documentation is generated by some tool and doesn't seem to handle converting string interpolations between Typescript and Python.

When I change line 24 from:

        example_for_each_iterator = TerraformIterator.from_list(
            Token.as_any(
            "${{ for dvo in ${" 
            + example_cert.domain_validation_options 
            + "} : dvo.domain_name => {\n      name   = dvo.resource_record_name\n      record = dvo.resource_record_value\n      type   = dvo.resource_record_type\n    }}}"))

To:

        example_for_each_iterator = TerraformIterator.from_list(
            Token.as_any(
                "{{ for dvo in " 
                + "example.domain_validation_options" 
                + " : dvo.domain_name => {\n      name   = dvo.resource_record_name\n      record = dvo.resource_record_value\n      type   = dvo.resource_record_type\n    }}}")
            )

Where the difference is;

Additionally I made similar changes to line 47. Originally it was;

        AcmCertificateValidation(self, "example_3",
            certificate_arn=example_cert.arn,
            validation_record_fqdns=Token.as_list("${[ for record in ${" + aws_route53_record_example.fqn + "} : record.fqdn]}")

To:

        AcmCertificateValidation(self, "example_3",
            certificate_arn=example_cert.arn,
            validation_record_fqdns=Token.as_list("{[ for record in example_2 : record.fqdn]}")
        )

Now the results of running 'cdktf deploy' with these changes results with following error;

                │ Error: Incorrect attribute value type
                │ 
                │   on cdk.tf.json line 40, in resource.aws_acm_certificate_validation.example_3 (example_3):
                │   40:         "validation_record_fqdns": "{[ for record in example.fqdn : record.fqdn]}"
                │ 
                │ Inappropriate value for attribute "validation_record_fqdns": set of string
                │ required.
                ╵
tf-test-python  ╷
                │ Error: Missing key/value separator
                │ 
                │   on cdk.tf.json line 52, in resource.aws_route53_record.example (example_2):
                │   52:         "for_each": "${toset({{ for dvo in {example.domain_validation_options} : dvo.domain_name => {\n      name   = dvo.resource_record_name\n      record = dvo.resource_record_value\n      type   = dvo.resource_record_type\n    }}})}",
                │ 
                │ Expected an equals sign ("=") to mark the beginning of the attribute value.

At this point I'm stuck and not sure if I'm making a progress. Neither have I found a way to effectively debug this issue further.

Expected Behavior

A Route53 domain validation record to be created.

Actual Behavior

Deployment generates the following error;

            │ Error: Incorrect attribute value type
            │ 
            │   on cdk.tf.json line 40, in resource.aws_acm_certificate_validation.example_3 (example_3):
            │   40:         "validation_record_fqdns": "{[ for record in example.fqdn : record.fqdn]}"
            │ 
            │ Inappropriate value for attribute "validation_record_fqdns": set of string
            │ required.
            ╵

tf-test-python ╷ │ Error: Missing key/value separator │ │ on cdk.tf.json line 52, in resource.aws_route53_record.example (example_2): │ 52: "for_each": "${toset({{ for dvo in {example.domain_validation_options} : dvo.domain_name => {\n name = dvo.resource_record_name\n record = dvo.resource_record_value\n type = dvo.resource_record_type\n }}})}", │ │ Expected an equals sign ("=") to mark the beginning of the attribute value.

Relevant Error/Panic Output Snippet

cdktf version: 0.18.0
python version 

Here's the cdktf output of the provider versions;

~/workspaces/tf-test-python > cdktf provider list  
┌───────────────┬──────────────────┬─────────┬────────────┬──────────────────────────┬─────────────────┐
│ Provider Name │ Provider Version │ CDKTF   │ Constraint │ Package Name             │ Package Version │
├───────────────┼──────────────────┼─────────┼────────────┼──────────────────────────┼─────────────────┤
│ aws           │ 4.67.0           │         │ ~>4.0      │                          │                 │
├───────────────┼──────────────────┼─────────┼────────────┼──────────────────────────┼─────────────────┤
│ aws           │ 5.17.0           │ ^0.18.0 │            │ cdktf-cdktf-provider-aws │ 17.0.6          │
└───────────────┴──────────────────┴─────────┴────────────┴──────────────────────────┴─────────────────┘

Terraform Configuration Files

Here's the cdktf.json file;

{
  "language": "python",
  "app": "pipenv run python main.py",
  "projectId": "0a834de4-5397-4b3c-b822-0c7983809785",
  "sendCrashReports": "true",
  "terraformProviders": [
    "hashicorp/aws@~>4.0"
  ],
  "terraformModules": [],
  "codeMakerOutput": "imports",
  "context": {}
}

Steps to Reproduce

init the project

cdktf init --template=typescript --providers=hashicorp/aws --local

paste the following into main.py

from cdktf import App, TerraformStack, Token, TerraformIterator, Fn

from imports.aws.acm_certificate import AcmCertificate
from imports.aws.acm_certificate_validation import AcmCertificateValidation
from imports.aws.route53_record import Route53Record

from imports.aws.provider import AwsProvider

class CertificateExampleStack(TerraformStack):
    def __init__(self, scope, name):
        super().__init__(scope, name)

        domain_name = "<domain name goes here"
        zone_id = "<hosted zone Id for the domain goes here>"    # hard coded to simplify example

        AwsProvider(self, "aws-provider", region="eu-west-1")

        example_cert = AcmCertificate(self, "example",
            domain_name=domain_name,
            validation_method="DNS"
        )

        example_for_each_iterator = TerraformIterator.from_list(
            Token.as_any(
                "{{ for dvo in " 
                + "example.domain_validation_options" 
                + " : dvo.domain_name => {\n      name   = dvo.resource_record_name\n      record = dvo.resource_record_value\n      type   = dvo.resource_record_type\n    }}}")
            )

        aws_route53_record_example = Route53Record(self, "example_2",
            allow_overwrite=True,
            name=Token.as_string(
                Fn.lookup_nested(example_for_each_iterator.value, ["resource_record_name"])),
            records=[
                Token.as_string(
                    Fn.lookup_nested(example_for_each_iterator.value, ["resource_record_value"]))
            ],
            ttl=60,
            type=Token.as_string(
                Fn.lookup_nested(example_for_each_iterator.value, ["resource_record_type"])),
            zone_id=zone_id,
            for_each=example_for_each_iterator
        )
        # This allows the Terraform resource name to match the original name. You can remove the call if you don't need them to match.
        aws_route53_record_example.override_logical_id("example")
        AcmCertificateValidation(self, "example_3",
            certificate_arn=example_cert.arn,
            validation_record_fqdns=Token.as_list("{[ for record in example_2 : record.fqdn]}")
        )

app = App()
CertificateExampleStack(app, "tf-test-python")

app.synth()

set the domain name and hosted zone id to correct values.

run the deployment

cdktf deploy

Debug Output

No response

Panic Output

No response

Important Factoids

Here is the cdk.tf.json file generated;

{
  "//": {
    "metadata": {
      "backend": "local",
      "stackName": "tf-test-python",
      "version": "0.18.0"
    },
    "outputs": {
    }
  },
  "provider": {
    "aws": [
      {
        "region": "eu-west-1"
      }
    ]
  },
  "resource": {
    "aws_acm_certificate": {
      "example": {
        "//": {
          "metadata": {
            "path": "tf-test-python/example",
            "uniqueId": "example"
          }
        },
        "domain_name": "<domain-name-goes-here>",
        "validation_method": "DNS"
      }
    },
    "aws_acm_certificate_validation": {
      "example_3": {
        "//": {
          "metadata": {
            "path": "tf-test-python/example_3",
            "uniqueId": "example_3"
          }
        },
        "certificate_arn": "${aws_acm_certificate.example.arn}",
        "validation_record_fqdns": "{[ for record in example_2 : record.fqdn]}"
      }
    },
    "aws_route53_record": {
      "example": {
        "//": {
          "metadata": {
            "path": "tf-test-python/example_2",
            "uniqueId": "example"
          }
        },
        "allow_overwrite": true,
        "for_each": "${toset({{ for dvo in example.domain_validation_options : dvo.domain_name => dvo}})}",
        "name": "${each.value.resource_record_name}",
        "records": [
          "${each.value.resource_record_value}"
        ],
        "ttl": 60,
        "type": "${each.value.resource_record_type}",
        "zone_id": "<zone_id_goes_here>"
      }
    }
  },
  "terraform": {
    "backend": {
      "local": {
        "path": "/Users/drepik/workspaces/tf-test-python/terraform.tf-test-python.tfstate"
      }
    },
    "required_providers": {
      "aws": {
        "source": "hashicorp/aws",
        "version": "4.67.0"
      }
    }
  }
}

References

No response

Would you like to implement a fix?

None

github-actions[bot] commented 1 year ago

Community Note

Voting for Prioritization

Volunteering to Work on This Issue