DontShaveTheYak / cf2tf

Convert Cloudformation templates to Terraform.
GNU General Public License v3.0
500 stars 81 forks source link

Error with crawler-cfn.yml #175

Closed Almenon closed 1 year ago

Almenon commented 1 year ago

In https://docs.aws.amazon.com/cur/latest/userguide/use-athena-cf.html amazon provides you a cloudformation template to apply to sync cost and usage reports to AWS Athena. Howecer, cf2tf gets an error when running against the template.

The template:

AWSTemplateFormatVersion: 2010-09-09
Resources:

  AWSCURDatabase:
    Type: 'AWS::Glue::Database'
    Properties:
      DatabaseInput:
        Name: 'athenacurcfn_report_name'
      CatalogId: !Ref AWS::AccountId

  AWSCURCrawlerComponentFunction:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - glue.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      ManagedPolicyArns:
        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSGlueServiceRole'
      Policies:
        - PolicyName: AWSCURCrawlerComponentFunction
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: !Sub 'arn:${AWS::Partition}:logs:*:*:*'
              - Effect: Allow
                Action:
                  - 'glue:UpdateDatabase'
                  - 'glue:UpdatePartition'
                  - 'glue:CreateTable'
                  - 'glue:UpdateTable'
                  - 'glue:ImportCatalogToGlue'
                Resource: '*'
              - Effect: Allow
                Action:
                  - 's3:GetObject'
                  - 's3:PutObject'
                Resource: !Sub 'arn:${AWS::Partition}:s3:::report-bucket/cur/report-name/report-name*'
        - PolicyName: AWSCURKMSDecryption
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'kms:Decrypt'
                Resource: '*'

  AWSCURCrawlerLambdaExecutor:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: AWSCURCrawlerLambdaExecutor
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: !Sub 'arn:${AWS::Partition}:logs:*:*:*'
              - Effect: Allow
                Action:
                  - 'glue:StartCrawler'
                Resource: '*'

  AWSCURCrawler:
    Type: 'AWS::Glue::Crawler'
    DependsOn:
      - AWSCURDatabase
      - AWSCURCrawlerComponentFunction
    Properties:
      Name: AWSCURCrawler-report-name
      Description: A recurring crawler that keeps your CUR table in Athena up-to-date.
      Role: !GetAtt AWSCURCrawlerComponentFunction.Arn
      DatabaseName: !Ref AWSCURDatabase
      Targets:
        S3Targets:
          - Path: 's3://report-bucket/cur/report-name/report-name'
            Exclusions:
              - '**.json'
              - '**.yml'
              - '**.sql'
              - '**.csv'
              - '**.gz'
              - '**.zip'
      SchemaChangePolicy:
        UpdateBehavior: UPDATE_IN_DATABASE
        DeleteBehavior: DELETE_FROM_DATABASE

  AWSCURInitializer:
    Type: 'AWS::Lambda::Function'
    DependsOn: AWSCURCrawler
    Properties:
      Code:
        ZipFile: >
          const AWS = require('aws-sdk');
          const response = require('./cfn-response');
          exports.handler = function(event, context, callback) {
            if (event.RequestType === 'Delete') {
              response.send(event, context, response.SUCCESS);
            } else {
              const glue = new AWS.Glue();
              glue.startCrawler({ Name: 'AWSCURCrawler-report-name' }, function(err, data) {
                if (err) {
                  const responseData = JSON.parse(this.httpResponse.body);
                  if (responseData['__type'] == 'CrawlerRunningException') {
                    callback(null, responseData.Message);
                  } else {
                    const responseString = JSON.stringify(responseData);
                    if (event.ResponseURL) {
                      response.send(event, context, response.FAILED,{ msg: responseString });
                    } else {
                      callback(responseString);
                    }
                  }
                }
                else {
                  if (event.ResponseURL) {
                    response.send(event, context, response.SUCCESS);
                  } else {
                    callback(null, response.SUCCESS);
                  }
                }
              });
            }
          };
      Handler: 'index.handler'
      Timeout: 30
      Runtime: nodejs16.x
      ReservedConcurrentExecutions: 1
      Role: !GetAtt AWSCURCrawlerLambdaExecutor.Arn

  AWSStartCURCrawler:
    Type: 'Custom::AWSStartCURCrawler'
    Properties:
      ServiceToken: !GetAtt AWSCURInitializer.Arn

  AWSS3CUREventLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: 'lambda:InvokeFunction'
      FunctionName: !GetAtt AWSCURInitializer.Arn
      Principal: 's3.amazonaws.com'
      SourceAccount: !Ref AWS::AccountId
      SourceArn: !Sub 'arn:${AWS::Partition}:s3:::report-bucket'

  AWSS3CURLambdaExecutor:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: AWSS3CURLambdaExecutor
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: !Sub 'arn:${AWS::Partition}:logs:*:*:*'
              - Effect: Allow
                Action:
                  - 's3:PutBucketNotification'
                Resource: !Sub 'arn:${AWS::Partition}:s3:::report-bucket'

  AWSS3CURNotification:
    Type: 'AWS::Lambda::Function'
    DependsOn:
    - AWSCURInitializer
    - AWSS3CUREventLambdaPermission
    - AWSS3CURLambdaExecutor
    Properties:
      Code:
        ZipFile: >
          const AWS = require('aws-sdk');
          const response = require('./cfn-response');
          exports.handler = function(event, context, callback) {
            const s3 = new AWS.S3();
            const putConfigRequest = function(notificationConfiguration) {
              return new Promise(function(resolve, reject) {
                s3.putBucketNotificationConfiguration({
                  Bucket: event.ResourceProperties.BucketName,
                  NotificationConfiguration: notificationConfiguration
                }, function(err, data) {
                  if (err) reject({ msg: this.httpResponse.body.toString(), error: err, data: data });
                  else resolve(data);
                });
              });
            };
            const newNotificationConfig = {};
            if (event.RequestType !== 'Delete') {
              newNotificationConfig.LambdaFunctionConfigurations = [{
                Events: [ 's3:ObjectCreated:*' ],
                LambdaFunctionArn: event.ResourceProperties.TargetLambdaArn || 'missing arn',
                Filter: { Key: { FilterRules: [ { Name: 'prefix', Value: event.ResourceProperties.ReportKey } ] } }
              }];
            }
            putConfigRequest(newNotificationConfig).then(function(result) {
              response.send(event, context, response.SUCCESS, result);
              callback(null, result);
            }).catch(function(error) {
              response.send(event, context, response.FAILED, error);
              console.log(error);
              callback(error);
            });
          };
      Handler: 'index.handler'
      Timeout: 30
      Runtime: nodejs16.x
      ReservedConcurrentExecutions: 1
      Role: !GetAtt AWSS3CURLambdaExecutor.Arn

  AWSPutS3CURNotification:
    Type: 'Custom::AWSPutS3CURNotification'
    Properties:
      ServiceToken: !GetAtt AWSS3CURNotification.Arn
      TargetLambdaArn: !GetAtt AWSCURInitializer.Arn
      BucketName: 'report-bucket'
      ReportKey: 'cur/report-name/report-name'

  AWSCURReportStatusTable:
    Type: 'AWS::Glue::Table'
    DependsOn: AWSCURDatabase
    Properties:
      DatabaseName: athenacurcfn_report_name
      CatalogId: !Ref AWS::AccountId
      TableInput:
        Name: 'cost_and_usage_data_status'
        TableType: 'EXTERNAL_TABLE'
        StorageDescriptor:
          Columns:
            - Name: status
              Type: 'string'
          InputFormat: 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
          OutputFormat: 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
          SerdeInfo:
            SerializationLibrary: 'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
          Location: 's3://report-bucket/cur/report-name/cost_and_usage_data_status/'

The error:

$ cf2tf -v DEBUG cfn.yml
// Converting cfn.yml to Terraform!
debug: // Template location is cfn.yml
 existing repo found.
debug: Ignoring section Parameters not found in ['Parameters', 'Mappings', 'Conditions', 'Resources', 'Outputs']
debug: Ignoring section Mappings not found in ['Parameters', 'Mappings', 'Conditions', 'Resources', 'Outputs']
debug: Ignoring section Conditions not found in ['Parameters', 'Mappings', 'Conditions', 'Resources', 'Outputs']
debug: Ignoring section Outputs not found in ['Parameters', 'Mappings', 'Conditions', 'Resources', 'Outputs']
debug: Parsed the following resources for processing:
debug: {"Resources": [["AWSCURDatabase", {"Type": "AWS::Glue::Database", "Properties": {"DatabaseInput": {"Name": "athenacurcfn_report_name"}, "CatalogId": {"Ref": "AWS::AccountId"}}}], ["AWSCURCrawlerComponentFunction", {"Type": "AWS::IAM::Role", "Properties": {"AssumeRolePolicyDocument": {"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Principal": {"Service": ["glue.amazonaws.com"]}, "Action": ["sts:AssumeRole"]}]}, "Path": "/", "ManagedPolicyArns": [{"Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSGlueServiceRole"}], "Policies": [{"PolicyName": "AWSCURCrawlerComponentFunction", "PolicyDocument": {"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"], "Resource": {"Fn::Sub": "arn:${AWS::Partition}:logs:*:*:*"}}, {"Effect": "Allow", "Action": ["glue:UpdateDatabase", "glue:UpdatePartition", "glue:CreateTable", "glue:UpdateTable", "glue:ImportCatalogToGlue"], "Resource": "*"}, {"Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject"], "Resource": {"Fn::Sub": "arn:${AWS::Partition}:s3:::report-bucket/cur/report-name/report-name*"}}]}}, {"PolicyName": "AWSCURKMSDecryption", "PolicyDocument": {"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Action": ["kms:Decrypt"], "Resource": "*"}]}}]}}], ["AWSCURCrawlerLambdaExecutor", {"Type": "AWS::IAM::Role", "Properties": {"AssumeRolePolicyDocument": {"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Principal": {"Service": ["lambda.amazonaws.com"]}, "Action": ["sts:AssumeRole"]}]}, "Path": "/", "Policies": [{"PolicyName": "AWSCURCrawlerLambdaExecutor", "PolicyDocument": {"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"], "Resource": {"Fn::Sub": "arn:${AWS::Partition}:logs:*:*:*"}}, {"Effect": "Allow", "Action": ["glue:StartCrawler"], "Resource": "*"}]}}]}}], ["AWSCURCrawler", {"Type": "AWS::Glue::Crawler", "DependsOn": ["AWSCURDatabase", "AWSCURCrawlerComponentFunction"], "Properties": {"Name": "AWSCURCrawler-report-name", "Description": "A recurring crawler that keeps your CUR table in Athena up-to-date.", "Role": {"Fn::GetAtt": ["AWSCURCrawlerComponentFunction", "Arn"]}, "DatabaseName": {"Ref": "AWSCURDatabase"}, "Targets": {"S3Targets": [{"Path": "s3://report-bucket/cur/report-name/report-name", "Exclusions": ["**.json", "**.yml", "**.sql", "**.csv", "**.gz", "**.zip"]}]}, "SchemaChangePolicy": {"UpdateBehavior": "UPDATE_IN_DATABASE", "DeleteBehavior": "DELETE_FROM_DATABASE"}}}], ["AWSCURInitializer", {"Type": "AWS::Lambda::Function", "DependsOn": "AWSCURCrawler", "Properties": {"Code": {"ZipFile": "const AWS = require('aws-sdk'); const response = require('./cfn-response'); exports.handler = function(event, context, callback) {\n  if (event.RequestType === 'Delete') {\n    response.send(event, context, response.SUCCESS);\n  } else {\n    const glue = new AWS.Glue();\n    glue.startCrawler({ Name: 'AWSCURCrawler-report-name' }, function(err, data) {\n      if (err) {\n        const responseData = JSON.parse(this.httpResponse.body);\n        if (responseData['__type'] == 'CrawlerRunningException') {\n          callback(null, responseData.Message);\n        } else {\n          const responseString = JSON.stringify(responseData);\n          if (event.ResponseURL) {\n            response.send(event, context, response.FAILED,{ msg: responseString });\n          } else {\n            callback(responseString);\n          }\n        }\n      }\n      else {\n        if (event.ResponseURL) {\n          response.send(event, context, response.SUCCESS);\n        } else {\n          callback(null, response.SUCCESS);\n        }\n      }\n    });\n  }\n};\n"}, "Handler": "index.handler", "Timeout": 30, "Runtime": "nodejs16.x", "ReservedConcurrentExecutions": 1, "Role": {"Fn::GetAtt": ["AWSCURCrawlerLambdaExecutor", "Arn"]}}}], ["AWSStartCURCrawler", {"Type": "Custom::AWSStartCURCrawler", "Properties": {"ServiceToken": {"Fn::GetAtt": ["AWSCURInitializer", "Arn"]}}}], ["AWSS3CUREventLambdaPermission", {"Type": "AWS::Lambda::Permission", "Properties": {"Action": "lambda:InvokeFunction", "FunctionName": {"Fn::GetAtt": ["AWSCURInitializer", "Arn"]}, "Principal": "s3.amazonaws.com", "SourceAccount": {"Ref": "AWS::AccountId"}, "SourceArn": {"Fn::Sub": "arn:${AWS::Partition}:s3:::report-bucket"}}}], ["AWSS3CURLambdaExecutor", {"Type": "AWS::IAM::Role", "Properties": {"AssumeRolePolicyDocument": {"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Principal": {"Service": ["lambda.amazonaws.com"]}, "Action": ["sts:AssumeRole"]}]}, "Path": "/", "Policies": [{"PolicyName": "AWSS3CURLambdaExecutor", "PolicyDocument": {"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"], "Resource": {"Fn::Sub": "arn:${AWS::Partition}:logs:*:*:*"}}, {"Effect": "Allow", "Action": ["s3:PutBucketNotification"], "Resource": {"Fn::Sub": "arn:${AWS::Partition}:s3:::report-bucket"}}]}}]}}], ["AWSS3CURNotification", {"Type": "AWS::Lambda::Function", "DependsOn": ["AWSCURInitializer", "AWSS3CUREventLambdaPermission", "AWSS3CURLambdaExecutor"], "Properties": {"Code": {"ZipFile": "const AWS = require('aws-sdk'); const response = require('./cfn-response'); exports.handler = function(event, context, callback) {\n  const s3 = new AWS.S3();\n  const putConfigRequest = function(notificationConfiguration) {\n    return new Promise(function(resolve, reject) {\n      s3.putBucketNotificationConfiguration({\n        Bucket: event.ResourceProperties.BucketName,\n        NotificationConfiguration: notificationConfiguration\n      }, function(err, data) {\n        if (err) reject({ msg: this.httpResponse.body.toString(), error: err, data: data });\n        else resolve(data);\n      });\n    });\n  };\n  const newNotificationConfig = {};\n  if (event.RequestType !== 'Delete') {\n    newNotificationConfig.LambdaFunctionConfigurations = [{\n      Events: [ 's3:ObjectCreated:*' ],\n      LambdaFunctionArn: event.ResourceProperties.TargetLambdaArn || 'missing arn',\n      Filter: { Key: { FilterRules: [ { Name: 'prefix', Value: event.ResourceProperties.ReportKey } ] } }\n    }];\n  }\n  putConfigRequest(newNotificationConfig).then(function(result) {\n    response.send(event, context, response.SUCCESS, result);\n    callback(null, result);\n  }).catch(function(error) {\n    response.send(event, context, response.FAILED, error);\n    console.log(error);\n    callback(error);\n  });\n};\n"}, "Handler": "index.handler", "Timeout": 30, "Runtime": "nodejs16.x", "ReservedConcurrentExecutions": 1, "Role": {"Fn::GetAtt": ["AWSS3CURLambdaExecutor", "Arn"]}}}], ["AWSPutS3CURNotification", {"Type": "Custom::AWSPutS3CURNotification", "Properties": {"ServiceToken": {"Fn::GetAtt": ["AWSS3CURNotification", "Arn"]}, "TargetLambdaArn": {"Fn::GetAtt": ["AWSCURInitializer", "Arn"]}, "BucketName": "report-bucket", "ReportKey": "cur/report-name/report-name"}}], ["AWSCURReportStatusTable", {"Type": "AWS::Glue::Table", "DependsOn": "AWSCURDatabase", "Properties": {"DatabaseName": "athenacurcfn_report_name", "CatalogId": {"Ref": "AWS::AccountId"}, "TableInput": {"Name": "cost_and_usage_data_status", "TableType": "EXTERNAL_TABLE", "StorageDescriptor": {"Columns": [{"Name": "status", "Type": "string"}], "InputFormat": "org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat", "OutputFormat": "org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat", "SerdeInfo": {"SerializationLibrary": "org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe"}, "Location": "s3://report-bucket/cur/report-name/cost_and_usage_data_status/"}}}}]]}
debug: Converting 11 Resources
debug: Converting Cloudformation resource AWSCURDatabase to Terraform.
debug: Converted name to awscur_database
debug: Converted CF type AWS::Glue::Database to search term glue database.
debug: Searcing for glue database in terraform docs...
debug: Best match was glue resource policy at /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/glue_resource_policy.html.markdown with score of 86.
debug: Found documentation file /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/glue_resource_policy.html.markdown
debug: Converted type from AWS::Glue::Database to aws_glue_resource_policy
debug: Unable to find items in section Attributes Reference of /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/glue_resource_policy.html.markdown
debug: Parsed the following arguments from the documentation: 
debug: ['policy', 'enable_hybrid']
debug: Parsed the following attributes from the documentation: 
debug: []
debug: Converting the intrinsic functions to Terraform expressions...
debug: []
debug: Overiding Properties
debug: Overiding params for {tf_type}
debug: Converting property names to argument names...
debug: Searching for Database Input instead of DatabaseInput
debug: No match found for DatabaseInput, commenting out this argument.
debug: Searching for Catalog Id instead of CatalogId
debug: No match found for CatalogId, commenting out this argument.
debug: Converted dict_keys(['DatabaseInput', 'CatalogId']) to dict_keys(['DatabaseInput', 'CatalogId'])
debug: Converted properties to {
debug:   DatabaseInput =   // CF Property(DatabaseInput) = {
debug:   //   Name = "athenacurcfn_report_name"
debug:   // }
debug:   CatalogId =   // CF Property(CatalogId) = data.aws_caller_identity.current.account_id
debug: }

debug: Converting Cloudformation resource AWSCURCrawlerComponentFunction to Terraform.
debug: Converted name to awscur_crawler_component_function
debug: Converted CF type AWS::IAM::Role to search term iam role.
debug: Searcing for iam role in terraform docs...
debug: Best match was iam role at /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown with score of 100.
debug: Found documentation file /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown
debug: Converted type from AWS::IAM::Role to aws_iam_role
debug: Parsed the following arguments from the documentation: 
debug: ['assume_role_policy', 'description', 'force_detach_policies', 'inline_policy', 'managed_policy_arns', 'max_session_duration', 'name', 'name_prefix', 'path', 'permissions_boundary', 'tags']
debug: Parsed the following attributes from the documentation: 
debug: ['arn', 'create_date', 'id', 'name', 'tags_all', 'unique_id']
debug: Converting the intrinsic functions to Terraform expressions...
debug: ['data.aws_caller_identity.current']
debug: ['data.aws_partition.current', 'data.aws_caller_identity.current']
debug: ['data.aws_partition.current', 'data.aws_caller_identity.current']
debug: Overiding Properties
debug: Overiding params for {tf_type}
debug: Converting property names to argument names...
debug: Searching for Assume Role Policy Document instead of AssumeRolePolicyDocument
debug: Converted AssumeRolePolicyDocument to assume_role_policy with 90% match.
debug: Checking if assume_role_policy has a section in /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown.
debug: assume_role_policy does not have a section in /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown
debug: assume_role_policy has Map value but no subsection in /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown
debug: Searching for Path instead of Path
debug: Converted Path to path with 100% match.
debug: Searching for Managed Policy Arns instead of ManagedPolicyArns
debug: Converted ManagedPolicyArns to managed_policy_arns with 100% match.
debug: Checking if managed_policy_arns has a section in /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown.
debug: managed_policy_arns does not have a section in /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown
debug: Searching for Policies instead of Policies
debug: Converted Policies to force_detach_policies with 90% match.
debug: Checking if force_detach_policies has a section in /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown.
debug: force_detach_policies does not have a section in /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown
debug: Converted dict_keys(['AssumeRolePolicyDocument', 'Path', 'ManagedPolicyArns', 'Policies']) to dict_keys(['assume_role_policy', 'path', 'managed_policy_arns', 'force_detach_policies'])
debug: Converted properties to {
debug:   assume_role_policy = {
debug:     Version = "2012-10-17"
debug:     Statement = [
debug:       {
debug:         Effect = "Allow"
debug:         Principal = {
debug:           Service = [
debug:             "glue.amazonaws.com"
debug:           ]
debug:         }
debug:         Action = [
debug:           "sts:AssumeRole"
debug:         ]
debug:       }
debug:     ]
debug:   }
debug:   path = "/"
debug:   managed_policy_arns = [
debug:     "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSGlueServiceRole"
debug:   ]
debug:   force_detach_policies = [
debug:     {
debug:       PolicyName = "AWSCURCrawlerComponentFunction"
debug:       PolicyDocument = {
debug:         Version = "2012-10-17"
debug:         Statement = [
debug:           {
debug:             Effect = "Allow"
debug:             Action = [
debug:               "logs:CreateLogGroup",
debug:               "logs:CreateLogStream",
debug:               "logs:PutLogEvents"
debug:             ]
debug:             Resource = "arn:${data.aws_partition.current.partition}:logs:*:*:*"
debug:           },
debug:           {
debug:             Effect = "Allow"
debug:             Action = [
debug:               "glue:UpdateDatabase",
debug:               "glue:UpdatePartition",
debug:               "glue:CreateTable",
debug:               "glue:UpdateTable",
debug:               "glue:ImportCatalogToGlue"
debug:             ]
debug:             Resource = "*"
debug:           },
debug:           {
debug:             Effect = "Allow"
debug:             Action = [
debug:               "s3:GetObject",
debug:               "s3:PutObject"
debug:             ]
debug:             Resource = "arn:${data.aws_partition.current.partition}:s3:::report-bucket/cur/report-name/report-name*"
debug:           }
debug:         ]
debug:       }
debug:     },
debug:     {
debug:       PolicyName = "AWSCURKMSDecryption"
debug:       PolicyDocument = {
debug:         Version = "2012-10-17"
debug:         Statement = [
debug:           {
debug:             Effect = "Allow"
debug:             Action = [
debug:               "kms:Decrypt"
debug:             ]
debug:             Resource = "*"
debug:           }
debug:         ]
debug:       }
debug:     }
debug:   ]
debug: }

debug: Converting Cloudformation resource AWSCURCrawlerLambdaExecutor to Terraform.
debug: Converted name to awscur_crawler_lambda_executor
debug: Converted CF type AWS::IAM::Role to search term iam role.
debug: Searcing for iam role in terraform docs...
debug: Best match was iam role at /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown with score of 100.
debug: Found documentation file /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown
debug: Converted type from AWS::IAM::Role to aws_iam_role
debug: Parsed the following arguments from the documentation: 
debug: ['assume_role_policy', 'description', 'force_detach_policies', 'inline_policy', 'managed_policy_arns', 'max_session_duration', 'name', 'name_prefix', 'path', 'permissions_boundary', 'tags']
debug: Parsed the following attributes from the documentation: 
debug: ['arn', 'create_date', 'id', 'name', 'tags_all', 'unique_id']
debug: Converting the intrinsic functions to Terraform expressions...
debug: ['data.aws_partition.current', 'data.aws_caller_identity.current']
debug: Overiding Properties
debug: Overiding params for {tf_type}
debug: Converting property names to argument names...
debug: Searching for Assume Role Policy Document instead of AssumeRolePolicyDocument
debug: Converted AssumeRolePolicyDocument to assume_role_policy with 90% match.
debug: Checking if assume_role_policy has a section in /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown.
debug: assume_role_policy does not have a section in /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown
debug: assume_role_policy has Map value but no subsection in /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown
debug: Searching for Path instead of Path
debug: Converted Path to path with 100% match.
debug: Searching for Policies instead of Policies
debug: Converted Policies to force_detach_policies with 90% match.
debug: Checking if force_detach_policies has a section in /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown.
debug: force_detach_policies does not have a section in /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown
debug: Converted dict_keys(['AssumeRolePolicyDocument', 'Path', 'Policies']) to dict_keys(['assume_role_policy', 'path', 'force_detach_policies'])
debug: Converted properties to {
debug:   assume_role_policy = {
debug:     Version = "2012-10-17"
debug:     Statement = [
debug:       {
debug:         Effect = "Allow"
debug:         Principal = {
debug:           Service = [
debug:             "lambda.amazonaws.com"
debug:           ]
debug:         }
debug:         Action = [
debug:           "sts:AssumeRole"
debug:         ]
debug:       }
debug:     ]
debug:   }
debug:   path = "/"
debug:   force_detach_policies = [
debug:     {
debug:       PolicyName = "AWSCURCrawlerLambdaExecutor"
debug:       PolicyDocument = {
debug:         Version = "2012-10-17"
debug:         Statement = [
debug:           {
debug:             Effect = "Allow"
debug:             Action = [
debug:               "logs:CreateLogGroup",
debug:               "logs:CreateLogStream",
debug:               "logs:PutLogEvents"
debug:             ]
debug:             Resource = "arn:${data.aws_partition.current.partition}:logs:*:*:*"
debug:           },
debug:           {
debug:             Effect = "Allow"
debug:             Action = [
debug:               "glue:StartCrawler"
debug:             ]
debug:             Resource = "*"
debug:           }
debug:         ]
debug:       }
debug:     }
debug:   ]
debug: }

debug: Converting Cloudformation resource AWSCURCrawler to Terraform.
debug: Converted name to awscur_crawler
debug: Converted CF type AWS::Glue::Crawler to search term glue crawler.
debug: Searcing for glue crawler in terraform docs...
debug: Best match was glue crawler at /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/glue_crawler.html.markdown with score of 100.
debug: Found documentation file /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/glue_crawler.html.markdown
debug: Converted type from AWS::Glue::Crawler to aws_glue_crawler
debug: Parsed the following arguments from the documentation: 
debug: ['database_name', 'name', 'role', 'classifiers', 'configuration', 'description', 'dynamodb_target', 'jdbc_target', 's3_target', 'mongodb_target', 'schedule', 'schema_change_policy', 'lake_formation_configuration', 'lineage_configuration', 'recrawl_policy', 'security_configuration', 'table_prefix', 'tags']
debug: Parsed the following attributes from the documentation: 
debug: ['id', 'arn', 'tags_all']
debug: Converting the intrinsic functions to Terraform expressions...
debug: Fn::GetAtt - Looking up resource "AWSCURCrawlerComponentFunction"
debug: Converted CF type AWS::IAM::Role to search term iam role.
debug: Searcing for iam role in terraform docs...
debug: Best match was iam role at /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown with score of 100.
debug: Fn::GetAtt - Parsing attributes for /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/iam_role.html.markdown
debug: Converted CF type AWS::Glue::Database to search term glue database.
debug: Searcing for glue database in terraform docs...
debug: Best match was glue resource policy at /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/glue_resource_policy.html.markdown with score of 86.
debug: Unable to find items in section Attributes Reference of /var/folders/k9/5_z0cvss6l522ps8g30x1cl40000gp/T/terraform_src/website/docs/r/glue_resource_policy.html.markdown
Traceback (most recent call last):
  File "/Users/almenon/.local/bin/cf2tf", line 8, in <module>
    sys.exit(cli())
  File "/Users/almenon/.local/pipx/venvs/cf2tf/lib/python3.9/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/Users/almenon/.local/pipx/venvs/cf2tf/lib/python3.9/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "/Users/almenon/.local/pipx/venvs/cf2tf/lib/python3.9/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/almenon/.local/pipx/venvs/cf2tf/lib/python3.9/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/Users/almenon/.local/pipx/venvs/cf2tf/lib/python3.9/site-packages/cf2tf/app.py", line 44, in cli
    config = TemplateConverter(tmpl_path.stem, cf_template, search_manger).convert()
  File "/Users/almenon/.local/pipx/venvs/cf2tf/lib/python3.9/site-packages/cf2tf/convert.py", line 92, in convert
    tf_resources = self.convert_to_tf(self.manifest)
  File "/Users/almenon/.local/pipx/venvs/cf2tf/lib/python3.9/site-packages/cf2tf/convert.py", line 139, in convert_to_tf
    tf_resources.extend(converter(resources))
  File "/Users/almenon/.local/pipx/venvs/cf2tf/lib/python3.9/site-packages/cf2tf/convert.py", line 316, in convert_resources
    resolved_values = self.resolve_values(
  File "/Users/almenon/.local/pipx/venvs/cf2tf/lib/python3.9/site-packages/cf2tf/convert.py", line 172, in resolve_values
    data[key] = self.resolve_values(
  File "/Users/almenon/.local/pipx/venvs/cf2tf/lib/python3.9/site-packages/cf2tf/convert.py", line 167, in resolve_values
    return functions.ref(self, value)
  File "/Users/almenon/.local/pipx/venvs/cf2tf/lib/python3.9/site-packages/cf2tf/conversion/expressions.py", line 834, in ref
    first_attr = next(iter(valid_attributes))
StopIteration
Almenon commented 1 year ago

Note that you can delete everything after line 116 (everything after the AWSCURCrawler block) and you will get the same problem. If you delete everything after line 90 (deleting AWSCURCrawler and everything below) the problem does not appear.

shadycuz commented 1 year ago

Thanks for reporting this, will take a look.

shadycuz commented 1 year ago

I have this fixed locally and it will be released in a few days, for now here is your converted template.

$ cf2tf crawler-cfn.yml 
// Converting crawler-cfn.yml to Terraform!
// Existing Terraform src code found at /tmp/terraform_src.

data "aws_partition" "current" {}

data "aws_caller_identity" "current" {}

resource "aws_glue_catalog_database" "awscur_database" {
  // CF Property(DatabaseInput) = {
  //   Name = "athenacurcfn_report_name"
  // }
  catalog_id = data.aws_caller_identity.current.account_id
}

resource "aws_iam_role" "awscur_crawler_component_function" {
  assume_role_policy = {
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = [
            "glue.amazonaws.com"
          ]
        }
        Action = [
          "sts:AssumeRole"
        ]
      }
    ]
  }
  path = "/"
  managed_policy_arns = [
    "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSGlueServiceRole"
  ]
  force_detach_policies = [
    {
      PolicyName = "AWSCURCrawlerComponentFunction"
      PolicyDocument = {
        Version = "2012-10-17"
        Statement = [
          {
            Effect = "Allow"
            Action = [
              "logs:CreateLogGroup",
              "logs:CreateLogStream",
              "logs:PutLogEvents"
            ]
            Resource = "arn:${data.aws_partition.current.partition}:logs:*:*:*"
          },
          {
            Effect = "Allow"
            Action = [
              "glue:UpdateDatabase",
              "glue:UpdatePartition",
              "glue:CreateTable",
              "glue:UpdateTable",
              "glue:ImportCatalogToGlue"
            ]
            Resource = "*"
          },
          {
            Effect = "Allow"
            Action = [
              "s3:GetObject",
              "s3:PutObject"
            ]
            Resource = "arn:${data.aws_partition.current.partition}:s3:::report-bucket/cur/report-name/report-name*"
          }
        ]
      }
    },
    {
      PolicyName = "AWSCURKMSDecryption"
      PolicyDocument = {
        Version = "2012-10-17"
        Statement = [
          {
            Effect = "Allow"
            Action = [
              "kms:Decrypt"
            ]
            Resource = "*"
          }
        ]
      }
    }
  ]
}

resource "aws_iam_role" "awscur_crawler_lambda_executor" {
  assume_role_policy = {
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = [
            "lambda.amazonaws.com"
          ]
        }
        Action = [
          "sts:AssumeRole"
        ]
      }
    ]
  }
  path = "/"
  force_detach_policies = [
    {
      PolicyName = "AWSCURCrawlerLambdaExecutor"
      PolicyDocument = {
        Version = "2012-10-17"
        Statement = [
          {
            Effect = "Allow"
            Action = [
              "logs:CreateLogGroup",
              "logs:CreateLogStream",
              "logs:PutLogEvents"
            ]
            Resource = "arn:${data.aws_partition.current.partition}:logs:*:*:*"
          },
          {
            Effect = "Allow"
            Action = [
              "glue:StartCrawler"
            ]
            Resource = "*"
          }
        ]
      }
    }
  ]
}

resource "aws_glue_crawler" "awscur_crawler" {
  name = "AWSCURCrawler-report-name"
  description = "A recurring crawler that keeps your CUR table in Athena up-to-date."
  role = aws_iam_role.awscur_crawler_component_function.arn
  database_name = aws_glue_catalog_database.awscur_database.arn
  delta_target {
    // CF Property(S3Targets) = [
    //   {
    //     Path = "s3://report-bucket/cur/report-name/report-name"
    //     Exclusions = [
    //       "**.json",
    //       "**.yml",
    //       "**.sql",
    //       "**.csv",
    //       "**.gz",
    //       "**.zip"
    //     ]
    //   }
    // ]
  }
  schema_change_policy {
    update_behavior = "UPDATE_IN_DATABASE"
    delete_behavior = "DELETE_FROM_DATABASE"
  }
}

resource "aws_lambda_function" "awscur_initializer" {
  code_signing_config_arn = {
    ZipFile = "const AWS = require('aws-sdk'); const response = require('./cfn-response'); exports.handler = function(event, context, callback) {
  if (event.RequestType === 'Delete') {
    response.send(event, context, response.SUCCESS);
  } else {
    const glue = new AWS.Glue();
    glue.startCrawler({ Name: 'AWSCURCrawler-report-name' }, function(err, data) {
      if (err) {
        const responseData = JSON.parse(this.httpResponse.body);
        if (responseData['__type'] == 'CrawlerRunningException') {
          callback(null, responseData.Message);
        } else {
          const responseString = JSON.stringify(responseData);
          if (event.ResponseURL) {
            response.send(event, context, response.FAILED,{ msg: responseString });
          } else {
            callback(responseString);
          }
        }
      }
      else {
        if (event.ResponseURL) {
          response.send(event, context, response.SUCCESS);
        } else {
          callback(null, response.SUCCESS);
        }
      }
    });
  }
};
"
  }
  handler = "index.handler"
  timeout = 30
  runtime = "nodejs16.x"
  reserved_concurrent_executions = 1
  role = aws_iam_role.awscur_crawler_lambda_executor.arn
}

resource "aws_ce_cost_category" "aws_start_cur_crawler" {
  // CF Property(ServiceToken) = aws_lambda_function.awscur_initializer.arn
}

resource "aws_lambda_permission" "awss3_cur_event_lambda_permission" {
  action = "lambda:InvokeFunction"
  function_name = aws_lambda_function.awscur_initializer.arn
  principal = "s3.amazonaws.com"
  source_account = data.aws_caller_identity.current.account_id
  source_arn = "arn:${data.aws_partition.current.partition}:s3:::report-bucket"
}

resource "aws_iam_role" "awss3_cur_lambda_executor" {
  assume_role_policy = {
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = [
            "lambda.amazonaws.com"
          ]
        }
        Action = [
          "sts:AssumeRole"
        ]
      }
    ]
  }
  path = "/"
  force_detach_policies = [
    {
      PolicyName = "AWSS3CURLambdaExecutor"
      PolicyDocument = {
        Version = "2012-10-17"
        Statement = [
          {
            Effect = "Allow"
            Action = [
              "logs:CreateLogGroup",
              "logs:CreateLogStream",
              "logs:PutLogEvents"
            ]
            Resource = "arn:${data.aws_partition.current.partition}:logs:*:*:*"
          },
          {
            Effect = "Allow"
            Action = [
              "s3:PutBucketNotification"
            ]
            Resource = "arn:${data.aws_partition.current.partition}:s3:::report-bucket"
          }
        ]
      }
    }
  ]
}

resource "aws_lambda_function" "awss3_cur_notification" {
  code_signing_config_arn = {
    ZipFile = "const AWS = require('aws-sdk'); const response = require('./cfn-response'); exports.handler = function(event, context, callback) {
  const s3 = new AWS.S3();
  const putConfigRequest = function(notificationConfiguration) {
    return new Promise(function(resolve, reject) {
      s3.putBucketNotificationConfiguration({
        Bucket: event.ResourceProperties.BucketName,
        NotificationConfiguration: notificationConfiguration
      }, function(err, data) {
        if (err) reject({ msg: this.httpResponse.body.toString(), error: err, data: data });
        else resolve(data);
      });
    });
  };
  const newNotificationConfig = {};
  if (event.RequestType !== 'Delete') {
    newNotificationConfig.LambdaFunctionConfigurations = [{
      Events: [ 's3:ObjectCreated:*' ],
      LambdaFunctionArn: event.ResourceProperties.TargetLambdaArn || 'missing arn',
      Filter: { Key: { FilterRules: [ { Name: 'prefix', Value: event.ResourceProperties.ReportKey } ] } }
    }];
  }
  putConfigRequest(newNotificationConfig).then(function(result) {
    response.send(event, context, response.SUCCESS, result);
    callback(null, result);
  }).catch(function(error) {
    response.send(event, context, response.FAILED, error);
    console.log(error);
    callback(error);
  });
};
"
  }
  handler = "index.handler"
  timeout = 30
  runtime = "nodejs16.x"
  reserved_concurrent_executions = 1
  role = aws_iam_role.awss3_cur_lambda_executor.arn
}

resource "aws_s3_bucket_notification" "aws_put_s3_cur_notification" {
  // CF Property(ServiceToken) = aws_lambda_function.awss3_cur_notification.arn
  // CF Property(TargetLambdaArn) = aws_lambda_function.awscur_initializer.arn
  bucket = "report-bucket"
  // CF Property(ReportKey) = "cur/report-name/report-name"
}

resource "aws_route_table" "awscur_report_status_table" {
  // CF Property(DatabaseName) = "athenacurcfn_report_name"
  vpc_id = data.aws_caller_identity.current.account_id
  // CF Property(TableInput) = {
  //   Name = "cost_and_usage_data_status"
  //   TableType = "EXTERNAL_TABLE"
  //   StorageDescriptor = {
  //     Columns = [
  //       {
  //         Name = "status"
  //         Type = "string"
  //       }
  //     ]
  //     InputFormat = "org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat"
  //     OutputFormat = "org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat"
  //     SerdeInfo = {
  //       SerializationLibrary = "org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe"
  //     }
  //     Location = "s3://report-bucket/cur/report-name/cost_and_usage_data_status/"
  //   }
  // }
}

It will likely have lots of things that need manually fixed, feel free to raise new issues. Thanks

Almenon commented 1 year ago

Thanks!

tatarevick commented 1 year ago

I encountered the same problem with my code after downloading the updated version of cf2tf. I would like to use it, but even after trying different YAML files, I keep receiving the same error.

debug: Converted type from AWS::IAM::Role to aws_iam_role debug: Unable to find section Attributes Reference in /tmp/terraform_src/website/docs/r/iam_role.html.markdown Traceback (most recent call last): File "/home/linuxbrew/.linuxbrew/opt/python@3.11/lib/python3.11/site-packages/cf2tf/terraform/doc_file.py", line 19, in parse_attributes attributes = parse_section("Attributes Reference", file) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/linuxbrew/.linuxbrew/opt/python@3.11/lib/python3.11/site-packages/cf2tf/terraform/doc_file.py", line 37, in parse_section section_location = find_section(name, file) ^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/linuxbrew/.linuxbrew/opt/python@3.11/lib/python3.11/site-packages/cf2tf/terraform/doc_file.py", line 117, in find_section raise Exception() Exception

The above exception was the direct cause of the following exception:

Traceback (most recent call last): File "/home/linuxbrew/.linuxbrew/bin/cf2tf", line 8, in sys.exit(cli()) ^^^^^ File "/home/linuxbrew/.linuxbrew/opt/python@3.11/lib/python3.11/site-packages/click/core.py", line 1157, in call return self.main(args, kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/linuxbrew/.linuxbrew/opt/python@3.11/lib/python3.11/site-packages/click/core.py", line 1078, in main rv = self.invoke(ctx) ^^^^^^^^^^^^^^^^ File "/home/linuxbrew/.linuxbrew/opt/python@3.11/lib/python3.11/site-packages/click/core.py", line 1434, in invoke return ctx.invoke(self.callback, ctx.params) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/linuxbrew/.linuxbrew/opt/python@3.11/lib/python3.11/site-packages/click/core.py", line 783, in invoke return __callback(args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/linuxbrew/.linuxbrew/opt/python@3.11/lib/python3.11/site-packages/cf2tf/app.py", line 44, in cli config = TemplateConverter(tmpl_path.stem, cf_template, search_manger).convert() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/linuxbrew/.linuxbrew/opt/python@3.11/lib/python3.11/site-packages/cf2tf/convert.py", line 92, in convert tf_resources = self.convert_to_tf(self.manifest) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/linuxbrew/.linuxbrew/opt/python@3.11/lib/python3.11/site-packages/cf2tf/convert.py", line 139, in convert_to_tf tf_resources.extend(converter(resources)) ^^^^^^^^^^^^^^^^^^^^ File "/home/linuxbrew/.linuxbrew/opt/python@3.11/lib/python3.11/site-packages/cf2tf/convert.py", line 300, in convert_resources valid_arguments, valid_attributes = doc_file.parse_attributes(docs_path) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/linuxbrew/.linuxbrew/opt/python@3.11/lib/python3.11/site-packages/cf2tf/terraform/doc_file.py", line 21, in parse_attributes raise Exception(f"Unable to find attributes in {file.name}") from e Exception: Unable to find attributes in /tmp/terraform_src/website/docs/r/iam_role.html.markdown

shadycuz commented 1 year ago

@tatarevick Can you post a new issue and provide a template that doesn't work?

tatarevick commented 1 year ago

Yep! Thanks :)