DontShaveTheYak / cf2tf

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

Nested attributes in !Sub are not being parsed correctly #128

Closed depopry closed 1 year ago

depopry commented 1 year ago

I am getting the following error ValueError: Fn::GetAtt - The values must contain the logicalNameOfResource and attributeName. when trying to convert the CF provided below.

As I understand it, the CF is valid

AWSTemplateFormatVersion: '2010-09-09'
Description: Main CloudFormation template that builds shared resources and modules stacks
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: 'Deployment parameters'
        Parameters:
          - DestinationBucket
          - ManagementAccountRole
          - ManagementAccountID
          - MultiAccountRoleName
          - Schedule
          - RolePrefix
          - CFNTemplateSourceBucket
      - Label:
          default: 'Available modules'
        Parameters:
          - IncludeTAModule
          - IncludeRightsizingModule
          - IncludeInventoryCollectorModule
          - IncludeComputeOptimizerModule
          - ComputeOptimizerRegions
          - IncludeECSChargebackModule
          - IncludeRDSUtilizationModule
          - IncludeOrgDataModule
          - IncludeBudgetsModule
          - IncludeTransitGatewayModule
    ParameterLabels:
      DestinationBucket:
        default: 'Destination S3 bucket'
      ManagementAccountRole:
        default: 'Management account role'
      ManagementAccountID:
        default: 'Comma Delimited list of Account IDs for all Management Account IDs'
      MultiAccountRoleName:
        default: 'Multi Account Role Name'
      Schedule:
        default: "Schedule swap to cron(0 8 1 * ? *) for 1st day of the month"
      RolePrefix:
        default: "Role Prefix"
      CFNTemplateSourceBucket:
        default: "DO NOT CHANGE - A bucket that contains WA-Labs CloudFormation templates. Must be allways 'aws-well-architected-labs'"
      IncludeTAModule:
        default: 'Include AWS Trusted Advisor Data Collection Module'
      IncludeRightsizingModule:
        default: 'Include Rightsizing Recommendations Data Collection Module'
      IncludeInventoryCollectorModule:
        default: 'Include Inventory Collector Module'
      IncludeComputeOptimizerModule:
        default: 'Include AWS Compute Optimizer Data Collection Module'
      ComputeOptimizerRegions:
        default: "Comma Delimited list of AWS regions where AWS Compute Optimizer data will be collected."
      IncludeECSChargebackModule:
        default: 'Include ECS Chargeback Data Collection Module'
      IncludeRDSUtilizationModule:
        default: 'Include RDS Utilization Data Collection Module'
      IncludeOrgDataModule:
        default: 'Include AWS Organization Data Collection Module'
      IncludeBudgetsModule:
        default: 'Include AWS Budgets Collection Module'
      IncludeTransitGatewayModule:
        default: 'Include AWS TransitGateway Collection Module'
Mappings:
  RegionMap:
    eu-west-1:        {CodeBucket: aws-well-architected-labs-ireland }
    us-east-2:        {CodeBucket: aws-well-architected-labs-ohio }
    us-east-1:        {CodeBucket: aws-well-architected-labs-virginia }
    us-west-1:        {CodeBucket: aws-well-architected-labs-california }
    us-west-2:        {CodeBucket: aws-well-architected-labs-oregon }
    ap-southeast-1:   {CodeBucket: aws-well-architected-labs-singapore }
    eu-central-1:     {CodeBucket: aws-well-architected-labs-frankfurt }
    eu-west-2:        {CodeBucket: aws-well-architected-labs-london }
    eu-north-1:       {CodeBucket: aws-well-architected-labs-stockholm }
    ap-southeast-2:   {CodeBucket: aws-well-architected-labs-ap-sydney }
    ap-south-1:       {CodeBucket: aws-well-architected-labs-mumbai }
    ap-northeast-3:   {CodeBucket: aws-well-architected-labs-osaka }
    ap-northeast-2:   {CodeBucket: aws-well-architected-labs-seoul }
    ap-northeast-1:   {CodeBucket: aws-well-architected-labs-tokyo }
    ca-central-1:     {CodeBucket: aws-well-architected-labs-canada }
    eu-west-3:        {CodeBucket: aws-well-architected-labs-paris }
    sa-east-1:        {CodeBucket: aws-well-architected-labs-san-paulo }
Parameters:
  DestinationBucket:
    Type: String
    Description: Name of the S3 Bucket that needs to be created to hold information
    AllowedPattern: (?=^.{3,63}$)(?!^(\d+\.)+\d+$)(^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9\-])$)
    Default: costoptimizationdata
  ManagementAccountRole:
    Type: String
    Description: The name of the IAM role that will be deployed in the management account which can retrieve AWS Organization data. KEEP THE SAME AS WHAT IS DEPLOYED INTO MANAGEMENT ACCOUNT
    Default: Lambda-Assume-Role-Management-Account
  ManagementAccountID:
    Type: String
    AllowedPattern: ([a-z0-9\-, ]*?$)
    Description: "(Ex: 123456789,098654321,789054312) List of Payer IDs you wish to collect data for. Can just be one Accounts"
  MultiAccountRoleName:
    Type: String
    Description: The name of the IAM role that will be deployed from the management account to linked accounts as a read only role. KEEP THE SAME AS WHAT IS DEPLOYED INTO MANAGEMENT ACCOUNT
    Default: "Optimization-Data-Multi-Account-Role"
  Schedule:
    Type: String
    Description: Cron job to trigger the lambda using cloudwatch event
    Default: "rate(14 days)"
  RolePrefix:
    Type: String
    Description: This prefix will be placed in front of all roles created. Note you may wish to add a dash at the end to make more readable e.g. prefix-
    Default: "WA-"
  CFNTemplateSourceBucket:
    Type: String
    Description: "DO NOT CHANGE - A bucket that contains WA-Labs CloudFormation templates. Must be allways 'aws-well-architected-labs'"
    Default: "aws-well-architected-labs"
  IncludeTAModule:
    Type: String
    Description: Collects AWS Trusted Advisor recommendations data
    AllowedValues:
      - "yes"
      - "no"
  IncludeRightsizingModule:
    Type: String
    Description: "Collects AWS Cost Explorer Rightsizing Recommendations"
    AllowedValues:
      - "yes"
      - "no"
  IncludeInventoryCollectorModule:
    Type: String
    Description: Collects data about AMIs, EBS volumes and snapshots
    AllowedValues:
      - "yes"
      - "no"
  IncludeComputeOptimizerModule:
    Type: String
    Description: Collects AWS Compute Optimizer service recommendations
    AllowedValues:
      - "yes"
      - "no"
  ComputeOptimizerRegions:
    Default: ""
    Type: String
    Description: "Ex: us-east-1,us-east-2,us-west-1,us-west-2,eu-central-1,eu-west-1,eu-west-2,eu-west-3  if empty, the current region will be used. You can add regions later by updating the stack."
    AllowedPattern: ([a-z0-9\-, ]*?$)
  IncludeECSChargebackModule:
    Type: String
    Description: Collects data which shows costs associated with ECS Tasks leveraging EC2 instances within a Cluster
    AllowedValues:
      - "yes"
      - "no"
  IncludeRDSUtilizationModule:
    Type: String
    Description: Collects RDS CloudWatch metrics from your accounts
    AllowedValues:
      - "yes"
      - "no"
  IncludeOrgDataModule:
    Type: String
    Description: Collects AWS Organizations data such as account Id, account name, organization parent and specified tags
    AllowedValues:
      - "yes"
      - "no"
  IncludeBudgetsModule:
    Type: String
    Description: Collects AWS Budgets
    AllowedValues:
      - "yes"
      - "no"
  IncludeTransitGatewayModule:
    Type: String
    Description: Collects AWS TransitGateway data
    AllowedValues:
      - "yes"
      - "no"
Outputs:
  S3Bucket:
    Description: Name of S3 Bucket which will store the AWS Cost Explorer Rightsizing recommendations
    Value:
      Ref: S3Bucket
  S3BucketARN:
    Description: ARN of S3 Bucket which will store the AWS Cost Explorer Rightsizing recommendations
    Value:
      Fn::GetAtt:
        - S3Bucket
        - Arn
  RoleARN:
    Description: "The arn of the IAM role that deployed in the management account which can retrieve AWS Organization data"
    Value: !Sub "arn:aws:iam::${ManagementAccountID}:role/${ManagementAccountRole}"
  TaskQueuesUrl:
    Description: "SQS topics created for deployed modules"
    Value:
      Fn::Join:
        - ','
        - - !If [DeployTAModule, !Sub "${TrustedAdvisorModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
          - !If [DeployInventoryCollectorModule, !Sub "${InventoryCollectorModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
          - !If [DeployEcsChargebackModule, !Sub "${EcsChargebackModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
          - !If [DeployRDSUtilizationModule, !Sub "${RDSUtilizationModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
          - !If [DeployBudgetsModule, !Sub "${BudgetsModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
          - !If [DeployTransitGatewayModule, !Sub "${TransitGatewayModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
Conditions:
  DeployTAModule: !Equals
    - !Ref IncludeTAModule
    - "yes"
  DeployRightsizingModule: !Equals
    - !Ref IncludeRightsizingModule
    - "yes"
  DeployInventoryCollectorModule: !Equals
    - !Ref IncludeInventoryCollectorModule
    - "yes"
  DeployComputeOptimizerModule: !Equals
    - !Ref IncludeComputeOptimizerModule
    - "yes"
  DeployEcsChargebackModule: !Equals
    - !Ref IncludeECSChargebackModule
    - "yes"
  DeployRDSUtilizationModule: !Equals
    - !Ref IncludeRDSUtilizationModule
    - "yes"
  DeployOrgDataModule: !Equals
    - !Ref IncludeOrgDataModule
    - "yes"
  DeployBudgetsModule: !Equals
    - !Ref IncludeBudgetsModule
    - "yes"
  DeployTransitGatewayModule: !Equals
    - !Ref IncludeTransitGatewayModule
    - "yes"
  DeployPricingModule: !Or
    - !Condition DeployInventoryCollectorModule
    - !Condition DeployRDSUtilizationModule
  DeployAccountCollector: !Or
    - !Condition DeployTAModule
    - !Condition DeployInventoryCollectorModule
    - !Condition DeployRDSUtilizationModule
    - !Condition DeployEcsChargebackModule
    - !Condition DeployBudgetsModule
    - !Condition DeployRDSUtilizationModule
    - !Condition DeployTransitGatewayModule
  ComputeOptimizerRegionsIsEmpty: !Equals
    - !Join [ '', !Split [ ' ', !Ref ComputeOptimizerRegions  ] ] # remove spaces
    - ""
  ProdCFNTemplateUsed: !Equals [ !Ref CFNTemplateSourceBucket,  'aws-well-architected-labs' ]
#Reusable Resources:
Resources:
  S3CrawlerQue:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 300
      ReceiveMessageWaitTimeSeconds: 20
      DelaySeconds: 2
      QueueName: S3CrawlerQue
  CrawlerSQSPolicy:
    DependsOn:
      - S3CrawlerQue
    Type: AWS::SQS::QueuePolicy
    Properties:
      Queues:
        - Ref: S3CrawlerQue
      PolicyDocument:
        Id: SQSPolicy
        Statement:
          - Sid: SQSEventPolicy
            Effect: Allow
            Principal: {"Service": "s3.amazonaws.com"}
            Action: sqs:SendMessage
            Resource: !GetAtt S3CrawlerQue.Arn
            Condition:
              ArnLike:
                aws:SourceArn: !Sub "arn:aws:s3:::${DestinationBucket}${AWS::AccountId}"
              StringEquals:
                'aws:SourceAccount': !Sub '${AWS::AccountId}'
  S3Bucket:
    DependsOn:
      - CrawlerSQSPolicy
      - S3CrawlerQue
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName:
        !Sub "${DestinationBucket}${AWS::AccountId}"
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      PublicAccessBlockConfiguration:
        BlockPublicAcls : true
        BlockPublicPolicy : true
        IgnorePublicAcls : true
        RestrictPublicBuckets : true
      NotificationConfiguration:
        QueueConfigurations:
          - Event: s3:ObjectCreated:*
            Queue: !Sub "arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:S3CrawlerQue"
            Filter:
              S3Key:
                Rules:
                  - Name: prefix
                    Value: Compute_Optimizer/
  GlueRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${RolePrefix}AWS-OPTICS-Glue-Crawler"
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - glue.amazonaws.com
        Version: 2012-10-17
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole
      Path: /
      Policies:
        - PolicyName: "Put-S3"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "s3:PutObject"
                  - "s3:GetObject"
                Resource: !Join
                  - ''
                  - - !GetAtt S3Bucket.Arn
                    - '*'
  LambdaAnalyticsRole: #Execution role for the custom resource
    Type: AWS::IAM::Role
    Properties:
      Path:
        Fn::Sub: /${RolePrefix}/
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
  LambdaAnalytics:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.9
      FunctionName: !Sub ${RolePrefix}DataCollectionLab-Analytics
      Handler: index.lambda_handler
      MemorySize: 128
      Role: !GetAtt LambdaAnalyticsRole.Arn
      Timeout: 15
      Environment:
        Variables:
          WA_ANALYTICS_ENDPOINT: https://okakvoavfg.execute-api.eu-west-1.amazonaws.com/
      Code:
        ZipFile: |
          import os
          import json
          import uuid
          import urllib3
          import boto3

          endpoint = os.environ['WA_ANALYTICS_ENDPOINT']
          account_id = boto3.client("sts").get_caller_identity()["Account"]

          def lambda_handler(event, context):
              print(json.dumps(event))
              try:
                  if event['RequestType'].upper() not in ['CREATE', 'UPDATE', 'UPDATE']:
                      raise Exception(f"Unknown RequestType {event['RequestType']}")
                  action = event['RequestType'].upper()
                  name = event['ResourceProperties']['Name']
                  method = {'CREATE':'PUT', 'UPDATE': 'PATCH', 'DELETE': 'DELETE'}.get(action)
                  via_key = {'CREATE':'created_via', 'UPDATE': 'updated_via', 'DELETE': 'deleted_via'}.get(action)
                  payload = {'dashboard_id': 'data-collection-lab/' + name, 'account_id': account_id, via_key: 'CFN'}
                  r =  urllib3.PoolManager().request(method, endpoint, body=json.dumps(payload).encode('utf-8'), headers={'Content-Type': 'application/json'})
                  if r.status != 200:
                      raise Exception(f"There has been an issue logging action, server did not respond with a 200 response, actual status: {r.status}, response data {r.data.decode('utf-8')}. This issue will be ignored")
                  res, reason = 'SUCCESS', 'success'
              except Exception as exc:
                  res, reason = 'SUCCESS', f"{exc} . This issue will be ignored"
              body = {
                  'Status': res,
                  'Reason': reason,
                  'PhysicalResourceId': event.get('PhysicalResourceId', str(uuid.uuid1())),
                  'StackId': event.get('StackId'),
                  'RequestId': event.get('RequestId'),
                  'LogicalResourceId': event.get('LogicalResourceId'),
                  'NoEcho': False,
                  'Data':  {'Reason': reason},
              }
              json_body=json.dumps(body)
              print(json_body)
              url = event.get('ResponseURL')
              if not url: return
              try:
                  response = urllib3.PoolManager().request('PUT', url, body=json_body, headers={'content-type' : '', 'content-length' : str(len(json_body))}, retries=False)
                  print(f"Status code: {response}")
              except Exception as exc:
                  print("Failed sending PUT to CFN: " + str(exc))
  TrustedAdvisorModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployTAModule
    Properties:
      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-trusted-advisor.yaml"
      Parameters:
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        GlueRoleARN: !GetAtt GlueRole.Arn
        MultiAccountRoleName: !Sub "${RolePrefix}${MultiAccountRoleName}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNTemplateSourceBucket ]
        RolePrefix: !Ref RolePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
  RightsizeModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployRightsizingModule
    Properties:
      Parameters:
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        MultiAccountRoleName: !Sub "${RolePrefix}${MultiAccountRoleName}"
        ManagementRoleName: !Sub "${RolePrefix}${ManagementAccountRole}"
        ManagementAccountID: !Ref ManagementAccountID
        GlueRoleARN: !GetAtt GlueRole.Arn
        RolePrefix: !Ref RolePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-cost-explorer-rightsizing.yaml"
  InventoryCollectorModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployInventoryCollectorModule
    Properties:
      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-inventory.yaml"
      Parameters:
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        GlueRoleARN: !GetAtt GlueRole.Arn
        MultiAccountRoleName: !Sub "${RolePrefix}${MultiAccountRoleName}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNTemplateSourceBucket ]
        RolePrefix: !Ref RolePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
  PricingModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployPricingModule
    Properties:
      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-pricing.yaml"
      Parameters:
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        RolePrefix: !Ref RolePrefix
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNTemplateSourceBucket ]
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
  ComputeOptimizerModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployComputeOptimizerModule
    Properties:
      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-compute-optimizer.yaml"
      Parameters:
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        DestinationBucket: !Ref S3Bucket
        GlueRoleARN: !GetAtt GlueRole.Arn
        ManagementRoleName: !Sub "${RolePrefix}${ManagementAccountRole}"
        ManagementAccountID: !Ref ManagementAccountID
        S3CrawlerQue: !GetAtt S3CrawlerQue.Arn
        Schedule: !Ref Schedule
        RolePrefix: !Ref RolePrefix
        BucketPrefix:  !Ref DestinationBucket
        DeployRegions:
          Fn::If:
            - ComputeOptimizerRegionsIsEmpty
            - !Sub "${AWS::Region}"
            - !Join [ '', !Split [ ' ', !Ref ComputeOptimizerRegions  ] ] # remove spaces
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
  EcsChargebackModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployEcsChargebackModule
    Properties:
      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-ecs-chargeback.yaml"
      Parameters:
        DestinationBucket: !Ref S3Bucket
        GlueRoleArn: !GetAtt GlueRole.Arn
        MultiAccountRoleName: !Sub "${RolePrefix}${MultiAccountRoleName}"
        CodeBucket: !If [ ProdCFNTemplateUsed, !FindInMap [RegionMap, !Ref "AWS::Region", CodeBucket], !Ref CFNTemplateSourceBucket ]
        RolePrefix: !Ref RolePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
  RDSUtilizationModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployRDSUtilizationModule
    Properties:
      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-rds-usage.yaml"
      Parameters:
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        GlueRoleArn: !GetAtt GlueRole.Arn
        MultiAccountRoleName: !Sub "${RolePrefix}${MultiAccountRoleName}"
        RolePrefix: !Ref RolePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
  OrgDataModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployOrgDataModule
    Properties:
      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-organization.yaml"
      Parameters:
        DestinationBucket: !Ref S3Bucket
        GlueRoleARN: !GetAtt GlueRole.Arn
        Schedule: !Ref Schedule
        ManagementRoleName: !Sub "${RolePrefix}${ManagementAccountRole}"
        ManagementAccountID: !Ref ManagementAccountID
        RolePrefix: !Ref RolePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
  BudgetsModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployBudgetsModule
    Properties:
      Parameters:
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        GlueRoleArn: !GetAtt GlueRole.Arn
        MultiAccountRoleName: !Sub "${RolePrefix}${MultiAccountRoleName}"
        RolePrefix: !Ref RolePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-budgets.yaml"
  TransitGatewayModule:
    Type: AWS::CloudFormation::Stack
    Condition: DeployTransitGatewayModule
    Properties:
      Parameters:
        DestinationBucket: !Ref S3Bucket
        DestinationBucketARN: !GetAtt S3Bucket.Arn
        GlueRoleArn: !GetAtt GlueRole.Arn
        MultiAccountRoleName: !Sub "${RolePrefix}${MultiAccountRoleName}"
        RolePrefix: !Ref RolePrefix
        LambdaAnalyticsARN: !GetAtt LambdaAnalytics.Arn
      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/module-transit-gateway.yaml"
  AccountCollector:
    Type: AWS::CloudFormation::Stack
    Condition: DeployAccountCollector
    Properties:
      TemplateURL: !Sub "https://${CFNTemplateSourceBucket}.s3.amazonaws.com/Cost/Labs/300_Optimization_Data_Collection/account-collector.yaml"
      Parameters:
        RoleARN: !Sub "arn:aws:iam::${ManagementAccountID}:role/${RolePrefix}${ManagementAccountRole}"
        ManagementRoleName: !Sub "${RolePrefix}${ManagementAccountRole}"
        ManagementAccountID: !Ref ManagementAccountID
        RolePrefix: !Ref RolePrefix
        Schedule: !Ref Schedule
        TaskQueuesUrl:
          Fn::Join:
            - ','
            - - !If [DeployTAModule, !Sub "${TrustedAdvisorModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
              - !If [DeployInventoryCollectorModule, !Sub "${InventoryCollectorModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
              - !If [DeployEcsChargebackModule, !Sub "${EcsChargebackModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
              - !If [DeployRDSUtilizationModule, !Sub "${RDSUtilizationModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
              - !If [DeployBudgetsModule, !Sub "${BudgetsModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
              - !If [DeployTransitGatewayModule, !Sub "${TransitGatewayModule.Outputs.SQSUrl}", Ref: AWS::NoValue]
shadycuz commented 1 year ago

@depopry Can you show a little bit more of the stack trace that came with the error?

shadycuz commented 1 year ago

@depopry I ran the template, it's likely from this !Sub "${TrustedAdvisorModule.Outputs.SQSUrl}"

This is a bug and I will try and look into it.

shadycuz commented 1 year ago

So this issue is here: https://github.com/DontShaveTheYak/cf2tf/blob/a0ad1ea6bdfde200976ea31a26b37968f8cf6827/src/cf2tf/conversion/expressions.py#L703

The get_att() function will do the split. So the split just needs to be removed.

shadycuz commented 1 year ago

This was fixed a while ago, closing =)