Cloudformation to terraform conversion

Hi,

I have the following piece of code to be converted from cloudformation to terraform:

Cloudformation

   lbdServicesBInfoDurationAlarm:
    Type: 'AWS::CloudWatch::Alarm'
    Properties:
      AlarmName: lbdServicesBInfoDurationAlarm
      AlarmDescription: Alarm if elapsed wall clock time is too high
      AlarmActions:
        - !ImportValue 
          'Fn::Sub': '${EnvName}CWNotificationTopicARN'
      Dimensions:
        - Name: FunctionName
          Value: !Sub '${lbdServicesBInfoFunctionName}-${EnvName}'**
      Namespace: AWS/Lambda
      MetricName: Duration
      ComparisonOperator: GreaterThanThreshold
      EvaluationPeriods: '1'
      Period: '300'
      Statistic: Sum
      Threshold: '30000'
      TreatMissingData: missing

Terraform:

resource "aws_cloudwatch_metric_alarm" "lbdServicesBInfoDurationAlarm" {
  alarm_name                = "lbdServicesBInfoDurationAlarm"
  comparison_operator       = "GreaterThanThreshold"
  evaluation_periods        = "1"
  metric_name               = "Duration"
  namespace                 = "AWS/Lambda"
  period                    = "300"
  statistic                 = "Sum"
  threshold                 = "30000"
  alarm_description         = "Alarm if elapsed wall clock time is too high"
  treat_missing_data        = "missing"
  insufficient_data_actions = []
}

I am not sure how to convert AlarmActions and Dimensions from CF to TF. Any help will be appreciated.

Hi @Tyroform,

I think the closest equivalent to !ImportValue in Terraform would be to use a data source to read the data you need. If you are doing a gradual migration where this notification topic ARN will remain in CloudFormation for now then you can directly translate !ImportValue into a use of the aws_cloudformation_export data source, which knows how to read an exported value from CloudFormation in the same way that !ImportValue does:

variable "environment_name" {
  type = string
}

data "aws_cloudformation_export" "topic_arn" {
  name = "${var.environment_name}CWNotificationTopicARN"
}

resource "aws_cloudwatch_metric_alarm" "lbdServicesBInfoDurationAlarm" {
  alarm_name                = "lbdServicesBInfoDurationAlarm"
  alarm_actions             = [data.aws_cloudformation_export.topic_arn.value]
  comparison_operator       = "GreaterThanThreshold"
  evaluation_periods        = "1"
  metric_name               = "Duration"
  namespace                 = "AWS/Lambda"
  period                    = "300"
  statistic                 = "Sum"
  threshold                 = "30000"
  alarm_description         = "Alarm if elapsed wall clock time is too high"
  treat_missing_data        = "missing"
  insufficient_data_actions = []
}

For Fn::Sub you don’t really need to do anything particularly special in Terraform, because Terraform interprets all quoted strings as templates supporting interpolation by default. You can see one example of that above in the name argument in data "aws_cloudformation_export" "topic_arn". A similar principle would apply to your dimensions value, using ${var.environment_name} just as I did above along with some other referenc equivalent to lbdServicesBInfoFunctionName (but I don’t know what that is in order to make a specific suggestion.)


Another note I would add, since we’re talking about converting from CloudFormation to Terraform anyway, is the more subjective concern of naming conventions. The names you’ve translated directly from CloudFormation are syntactically valid in Terraform, but don’t suit the typical Terraform style conventions. In particular:

  • Names within Terraform conventionally use lowercase letters with underscores separating words, like lbd_services_b_info_duration_alarm rather than lbdServicesBInfoDurationAlarm.

    This style matches the style that Terraform providers use for the names they declare, and that Terraform itself uses for its built-in language features.

  • Because we always refer to a resource by stating both its type and its name, the name portion typically shouldn’t duplicate nouns from the type.

    For example, the “alarm” at the end of lbd_services_b_info_duration_alarm is redundant because we would only ever use that name as part of the address aws_cloudwatch_metric_alarm.lbd_services_b_info_duration_alarm. Instead, I’d suggest declaring it as just lbd_services_b_info_duration, giving the full address aws_cloudwatch_metric_alarm.lbd_services_b_info_duration_alarm. Note that (unlike in CloudFormation) Terraform resources are uniquely identified by their type and name together, so it’s common and expected to have several resources of different types but the same name within a single module.

Terraform itself (the software) doesn’t enforce either of these, but following these conventions will make your configuration feel more familiar to folks who already have some Terraform experience.

1 Like

Thank you for your valuable feedback @apparentlymart!!!

Today I released a tool that does most of the work for you GitHub - DontShaveTheYak/cf2tf: Convert Cloudformation templates to Terraform.

@shadycuz I got the following error when I try to convert the cloud formation template to terraform script.

% cf2tf lambda_cdk_template.yml 
// Converting lambda_cdk_template.yml to Terraform!
 existing repo found.
Traceback (most recent call last):
  File "/opt/homebrew/bin/cf2tf", line 8, in <module>
    sys.exit(cli())
             ^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/click/core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/click/core.py", line 1078, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/cf2tf/app.py", line 44, in cli
    config = TemplateConverter(tmpl_path.stem, cf_template, search_manger).convert()
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/cf2tf/convert.py", line 92, in convert
    tf_resources = self.convert_to_tf(self.manifest)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/cf2tf/convert.py", line 139, in convert_to_tf
    tf_resources.extend(converter(resources))
                        ^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/cf2tf/convert.py", line 297, in convert_resources
    valid_arguments, valid_attributes = doc_file.parse_attributes(docs_path)
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/cf2tf/terraform/doc_file.py", line 15, in parse_attributes
    attributes = parse_section("Attributes Reference", file)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/cf2tf/terraform/doc_file.py", line 31, in parse_section
    section_location = find_section(name, file)
                       ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/cf2tf/terraform/doc_file.py", line 111, in find_section
    raise Exception()
Exception