{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Cloudchipr cross-account read role",
  "Parameters": {
    "Data": {
      "Type": "CommaDelimitedList",
      "Description": "Takes as input the source Account ID and Unique Key as External ID for cross-account trust relationship"
    }
  },
  "Resources": {
    "CloudchiprStackIAMRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "AWS": {
                  "Fn::Join": [
                    "",
                    [
                      "arn:aws:iam::",
                      {
                        "Fn::Select": [
                          0,
                          {
                            "Ref": "Data"
                          }
                        ]
                      },
                      ":role/",
                      {
                        "Fn::Select": [
                          4,
                          {
                            "Ref": "Data"
                          }
                        ]
                      }
                    ]
                  ]
                }
              },
              "Condition": {
                "StringEquals": {
                  "sts:ExternalId": {
                    "Fn::Select": [
                      1,
                      {
                        "Ref": "Data"
                      }
                    ]
                  }
                }
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Path": "/",
        "RoleName": {
          "Fn::Select": [
            5,
            {
              "Ref": "Data"
            }
          ]
        },
        "Policies": [
          {
            "PolicyName": "CloudchiprStack",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "iam:DeleteRolePolicy",
                    "iam:DeleteRole"
                  ],
                  "Resource": {
                    "Fn::Join": [
                      "",
                      [
                        {
                          "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/"
                        },
                        {
                          "Fn::Select": [
                            5,
                            {
                              "Ref": "Data"
                            }
                          ]
                        }
                      ]
                    ]
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "iam:DeleteRolePolicy",
                    "iam:DeleteRole"
                  ],
                  "Resource": {
                    "Fn::Join": [
                      "",
                      [
                        {
                          "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/"
                        },
                        {
                          "Fn::Select": [
                            6,
                            {
                              "Ref": "Data"
                            }
                          ]
                        }
                      ]
                    ]
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "lambda:DeleteFunction",
                    "lambda:InvokeFunction"
                  ],
                  "Resource": {
                    "Fn::Join": [
                      "",
                      [
                        {
                          "Fn::Sub": "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:"
                        },
                        {
                          "Fn::Select": [
                            7,
                            {
                              "Ref": "Data"
                            }
                          ]
                        }
                      ]
                    ]
                  }
                },
                {
                  "Effect": "Allow",
                  "Resource": "*",
                  "Action": [
                    "account:Get*",
                    "account:List*",
                    "aoss:BatchGet*",
                    "aoss:Get*",
                    "aoss:List*",
                    "application-autoscaling:Describe*",
                    "application-cost-profiler:Get*",
                    "application-cost-profiler:List*",
                    "applicationinsights:Describe*",
                    "applicationinsights:List*",
                    "arc-zonal-shift:Get*",
                    "arc-zonal-shift:List*",
                    "athena:BatchGet*",
                    "athena:Get*",
                    "athena:List*",
                    "autoscaling-plans:Describe*",
                    "autoscaling-plans:Get*",
                    "autoscaling:Describe*",
                    "autoscaling:Get*",
                    "aws-portal:Get*",
                    "aws-portal:View*",
                    "billing:Get*",
                    "billing:List*",
                    "billingconductor:List*",
                    "budgets:Describe*",
                    "budgets:View*",
                    "ce:Describe*",
                    "ce:Get*",
                    "ce:List*",
                    "cloudformation:BatchDescribe*",
                    "cloudformation:Describe*",
                    "cloudformation:Detect*",
                    "cloudformation:EstimateTemplateCost",
                    "cloudformation:Get*",
                    "cloudformation:List*",
                    "cloudformation:ValidateTemplate",
                    "cloudfront:Describe*",
                    "cloudfront:Get*",
                    "cloudfront:List*",
                    "cloudsearch:Describe*",
                    "cloudsearch:List*",
                    "cloudtrail:Describe*",
                    "cloudtrail:Get*",
                    "cloudtrail:List*",
                    "cloudtrail:Lookup*",
                    "cloudwatch:Describe*",
                    "cloudwatch:Get*",
                    "cloudwatch:List*",
                    "compute-optimizer:Describe*",
                    "compute-optimizer:Get*",
                    "consolidatedbilling:Get*",
                    "consolidatedbilling:List*",
                    "cur:Describe*",
                    "cur:Get*",
                    "cur:ValidateReportDestination",
                    "bcm-data-exports:List*",
                    "bcm-data-exports:Get*",
                    "ce:StartCostAllocationTagBackfill",
                    "ce:UpdateCostAllocationTagsStatus",
                    "dax:BatchGet*",
                    "dax:ConditionCheckItem",
                    "dax:Describe*",
                    "dax:Get*",
                    "dax:List*",
                    "docdb-elastic:Get*",
                    "docdb-elastic:List*",
                    "drs:Describe*",
                    "drs:Get*",
                    "drs:List*",
                    "dynamodb:BatchGet*",
                    "dynamodb:ConditionCheck*",
                    "dynamodb:Describe*",
                    "dynamodb:Get*",
                    "dynamodb:List*",
                    "ebs:Get*",
                    "ebs:List*",
                    "ec2:Describe*",
                    "ec2:Get*",
                    "ec2:List*",
                    "ec2:Search*",
                    "ec2messages:Get*",
                    "ecr-public:BatchCheckLayerAvailability",
                    "ecr-public:Describe*",
                    "ecr-public:Get*",
                    "ecr-public:List*",
                    "ecs:Describe*",
                    "ecs:Get*",
                    "ecs:List*",
                    "eks:AccessKubernetesApi",
                    "eks:Describe*",
                    "eks:List*",
                    "elastic-inference:Describe*",
                    "elastic-inference:List*",
                    "elasticache:Describe*",
                    "elasticache:List*",
                    "elasticloadbalancing:Describe*",
                    "es:Describe*",
                    "es:ESCrossClusterGet",
                    "es:ESHttpGet",
                    "es:ESHttpHead",
                    "es:Get*",
                    "es:List*",
                    "events:Describe*",
                    "events:List*",
                    "events:TestEventPattern",
                    "evidently:Get*",
                    "evidently:List*",
                    "evidently:TestSegmentPattern",
                    "forecast:Describe*",
                    "forecast:Get*",
                    "forecast:InvokeForecastEndpoint",
                    "forecast:List*",
                    "freetier:Get*",
                    "glacier:Describe*",
                    "glacier:Get*",
                    "glacier:List*",
                    "glue:BatchGet*",
                    "glue:CheckSchemaVersionValidity",
                    "glue:Get*",
                    "glue:List*",
                    "grafana:Describe*",
                    "grafana:List*",
                    "iam:ListAccountAliases",
                    "imagebuilder:Get*",
                    "imagebuilder:List*",
                    "kafka-cluster:Connect",
                    "kafka-cluster:Describe*",
                    "kafka:Describe*",
                    "kafka:Get*",
                    "kafka:List*",
                    "kafkaconnect:Describe*",
                    "kafkaconnect:List*",
                    "kinesis:Describe*",
                    "kinesis:Get*",
                    "kinesis:List*",
                    "kinesisanalytics:Describe*",
                    "kinesisanalytics:DiscoverInputSchema",
                    "kinesisanalytics:Get*",
                    "kinesisanalytics:List*",
                    "kinesisvideo:Describe*",
                    "kinesisvideo:Get*",
                    "kinesisvideo:List*",
                    "kms:Describe*",
                    "kms:List*",
                    "lakeformation:Describe*",
                    "lakeformation:Get*",
                    "lakeformation:List*",
                    "lambda:Get*",
                    "lambda:List*",
                    "logs:Describe*",
                    "logs:FilterLogEvents",
                    "logs:Get*",
                    "logs:List*",
                    "logs:TestMetricFilter",
                    "logs:Unmask",
                    "machinelearning:Describe*",
                    "machinelearning:Get*",
                    "memorydb:Describe*",
                    "memorydb:List*",
                    "notifications:Get*",
                    "notifications:List*",
                    "organizations:Describe*",
                    "organizations:List*",
                    "osis:Get*",
                    "osis:List*",
                    "osis:ValidatePipeline",
                    "pipes:Describe*",
                    "pipes:List*",
                    "pricing:Describe*",
                    "pricing:Get*",
                    "pricing:List*",
                    "qldb:Describe*",
                    "qldb:Get*",
                    "qldb:List*",
                    "quicksight:Describe*",
                    "quicksight:Get*",
                    "quicksight:List*",
                    "rds:Describe*",
                    "rds:List*",
                    "redshift-data:Describe*",
                    "redshift-data:Get*",
                    "redshift-data:List*",
                    "redshift-serverless:Get*",
                    "redshift-serverless:List*",
                    "resource-explorer-2:BatchGet*",
                    "resource-explorer-2:Get*",
                    "resource-explorer-2:List*",
                    "resource-explorer:List*",
                    "resource-groups:Get*",
                    "resource-groups:List*",
                    "route53-recovery-cluster:Get*",
                    "route53-recovery-cluster:List*",
                    "route53-recovery-control-config:Describe*",
                    "route53-recovery-control-config:List*",
                    "route53-recovery-readiness:Get*",
                    "route53-recovery-readiness:List*",
                    "route53:Get*",
                    "route53:List*",
                    "route53:TestDNSAnswer",
                    "route53domains:CheckDomainAvailability",
                    "route53domains:CheckDomainTransferability",
                    "route53domains:Get*",
                    "route53domains:List*",
                    "route53domains:View*",
                    "route53resolver:Get*",
                    "route53resolver:List*",
                    "rum:BatchGet*",
                    "rum:Get*",
                    "rum:List*",
                    "s3-object-lambda:Get*",
                    "s3-object-lambda:List*",
                    "s3-outposts:Get*",
                    "s3-outposts:List*",
                    "s3:Describe*",
                    "s3:Get*",
                    "s3:List*",
                    "sagemaker-geospatial:Get*",
                    "sagemaker-geospatial:List*",
                    "sagemaker-groundtruth-synthetic:Get*",
                    "sagemaker-groundtruth-synthetic:List*",
                    "sagemaker:BatchDescribe*",
                    "sagemaker:BatchGet*",
                    "sagemaker:Describe*",
                    "sagemaker:Get*",
                    "sagemaker:InvokeEndpoint",
                    "sagemaker:InvokeEndpointAsync",
                    "sagemaker:List*",
                    "sagemaker:RenderUiTemplate",
                    "savingsplans:Describe*",
                    "savingsplans:List*",
                    "scheduler:Get*",
                    "scheduler:List*",
                    "schemas:Describe*",
                    "schemas:Get*",
                    "schemas:List*",
                    "sdb:DomainMetadata",
                    "sdb:Get*",
                    "sdb:List*",
                    "servicecatalog:Describe*",
                    "servicecatalog:Get*",
                    "servicecatalog:List*",
                    "servicequotas:Get*",
                    "servicequotas:List*",
                    "ses:BatchGetMetricData",
                    "ses:Describe*",
                    "ses:Get*",
                    "ses:List*",
                    "snowball:Describe*",
                    "snowball:Get*",
                    "snowball:List*",
                    "sns:Get*",
                    "sns:List*",
                    "sqlworkbench:BatchGet*",
                    "sqlworkbench:Get*",
                    "sqlworkbench:List*",
                    "sqs:Get*",
                    "sqs:List*",
                    "storagegateway:Describe*",
                    "storagegateway:List*",
                    "synthetics:Describe*",
                    "synthetics:Get*",
                    "synthetics:List*",
                    "tag:Describe*",
                    "tag:Get*",
                    "timestream:Describe*",
                    "timestream:Get*",
                    "timestream:List*",
                    "transfer:Describe*",
                    "transfer:List*",
                    "transfer:TestIdentityProvider",
                    "trustedadvisor:Describe*",
                    "trustedadvisor:Get*",
                    "trustedadvisor:List*",
                    "wellarchitected:Get*",
                    "wellarchitected:List*",
                    "backup:List*",
                    "config:*"
                  ]
                },
                {
                  "Action": "sts:AssumeRole",
                  "Resource": {
                    "Fn::Join": [
                      "",
                      [
                        "arn:aws:iam::*:role/",
                        {
                          "Fn::Select": [
                            15,
                            {
                              "Ref": "Data"
                            }
                          ]
                        }
                      ]
                    ]
                  },
                  "Effect": "Allow"
                },
                {
                  "Effect": "Allow",
                  "Action": "sts:AssumeRole",
                  "Resource": "arn:aws:iam::*:role/CloudchiprAccountReadAccessRole"
                },
                {
                  "Effect": "Allow",
                  "Action": "sts:AssumeRole",
                  "Resource": "arn:aws:iam::*:role/CloudchiprAccountReadWriteAccessRole"
                },
                {
                  "Action": "sts:AssumeRole",
                  "Resource": "arn:aws:iam::*:role/OrganizationAccountAccessRole",
                  "Effect": "Allow"
                }
              ]
            }
          },
          {
            "PolicyName": "SelfInspect",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Sid": "AllowRoleToInspectItself",
                  "Effect": "Allow",
                  "Resource": {
                    "Fn::Join": [
                      "",
                      [
                        {
                          "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/"
                        },
                        {
                          "Fn::Select": [
                            5,
                            {
                              "Ref": "Data"
                            }
                          ]
                        }
                      ]
                    ]
                  },
                  "Action": [
                    "iam:ListRolePolicies",
                    "iam:GetRolePolicy"
                  ]
                }
              ]
            }
          },
          {
            "PolicyName": "SelfChangeSet",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Sid": "AllowRoleToSelfGenerateChangeSets",
                  "Effect": "Allow",
                  "Action": [
                    "cloudformation:CreateChangeSet",
                    "cloudformation:DeleteChangeSet",
                    "cloudformation:DeleteStack",
                    "cloudformation:ListChangeSets",
                    "cloudformation:DescribeChangeSet"
                  ],
                  "Resource": {
                    "Fn::Sub": "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*"
                  }
                }
              ]
            }
          }
        ]
      }
    },
    "BasicLambdaExecutionRole": {
      "DependsOn": [
        "CloudchiprStackIAMRole"
      ],
      "Type": "AWS::IAM::Role",
      "Properties": {
        "RoleName": {
          "Fn::Select": [
            6,
            {
              "Ref": "Data"
            }
          ]
        },
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Policies": [
          {
            "PolicyName": "BasicLambdaExecutionPolicy",
            "PolicyDocument": {
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:CreateLogGroup",
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                  ],
                  "Resource": "arn:aws:logs:*:*:*"
                },
                {
                  "Effect": "Allow",
                  "Action": "organizations:DescribeOrganization",
                  "Resource": "*"
                },
                {
                  "Effect": "Allow",
                  "Action": "organizations:DescribeAccount",
                  "Resource": "*"
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "iam:ListAccountAliases",
                    "iam:CreateServiceLinkedRole",
                    "iam:GetRole"
                  ],
                  "Resource": "*"
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "s3:CreateBucket",
                    "s3:PutBucketPolicy",
                    "s3:PutBucketVersioning",
                    "s3:ListBucket"
                  ],
                  "Resource": {
                    "Fn::Join": [
                      "",
                      [
                        "arn:aws:s3:::",
                        {
                          "Fn::Select": [
                            9,
                            {
                              "Ref": "Data"
                            }
                          ]
                        }
                      ]
                    ]
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "cur:PutReportDefinition",
                    "bcm-data-exports:CreateExport",
                    "bcm-data-exports:ListExports",
                    "ce:StartCostAllocationTagBackfill",
                    "ce:UpdateCostAllocationTagsStatus",
                    "sustainability:GetCarbonFootprintSummary"
                  ],
                  "Resource": "*"
                }
              ]
            }
          },
          {
            "PolicyName": "CostOptimizationHubRecommendationPolicy",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "cost-optimization-hub:GetRecommendation",
                    "cost-optimization-hub:ListRecommendations",
                    "cost-optimization-hub:GetPreferences",
                    "cost-optimization-hub:UpdatePreferences",
                    "cost-optimization-hub:UpdateEnrollmentStatus",
                    "cost-optimization-hub:ListEnrollmentStatuses",
                    "organizations:EnableAWSServiceAccess"
                  ],
                  "Resource": "*"
                }
              ]
            }
          }
        ]
      }
    },
    "CloudchiprAppCallbackLambdaFunction": {
      "DependsOn": [
        "BasicLambdaExecutionRole"
      ],
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "ZipFile": "const https=require('https'),{S3Client,CreateBucketCommand,HeadBucketCommand,PutBucketPolicyCommand,PutBucketVersioningCommand}=require('@aws-sdk/client-s3'),{BCMDataExportsClient,ListExportsCommand,CreateExportCommand}=require('@aws-sdk/client-bcm-data-exports'),{CostOptimizationHubClient,ListEnrollmentStatusesCommand,UpdateEnrollmentStatusCommand}=require('@aws-sdk/client-cost-optimization-hub'),fromEnv=require('@aws-sdk/credential-providers')['fromEnv'],{IAMClient,ListAccountAliasesCommand,GetRoleCommand,CreateServiceLinkedRoleCommand,NoSuchEntityException}=require('@aws-sdk/client-iam'),{OrganizationsClient,DescribeOrganizationCommand,DescribeAccountCommand}=require('@aws-sdk/client-organizations'),{CostExplorerClient,UpdateCostAllocationTagsStatusCommand,StartCostAllocationTagBackfillCommand}=require('@aws-sdk/client-cost-explorer'),s3=new S3Client({credentials:fromEnv()}),bcm=new BCMDataExportsClient({region:'us-east-1',credentials:fromEnv()}),costOptimizationHub=new CostOptimizationHubClient({region:'us-east-1',credentials:fromEnv()}),iam=new IAMClient({region:'us-east-1',credentials:fromEnv()}),organizations=new OrganizationsClient({region:'us-east-1',credentials:fromEnv()}),ce=new CostExplorerClient({region:'us-east-1',credentials:fromEnv()}),requestMethodMap={Create:'patch',Update:'patch',Delete:'delete'},errorsMap=new Map;class ResponseError extends Error{constructor(e){super(`The HTTP response failed with status code ${e.statusCode}: `+e.statusMessage),this.result=e}}const sendHttpsRequest=(i,a)=>new Promise((r,n)=>{const e={...i};var t;e.url&&(t=new URL(e.url),e.path=t.pathname+t.search,e.hostname=t.hostname,delete e.url),e.port=443,console.debug(`send: opening request to host=${e.hostname} path=`+e.path);const o=https.request(e,t=>{let o=Buffer.from([]);t.on('data',e=>{o=Buffer.concat([o,e])}),t.on('end',()=>{console.debug(`send: finished receiving response statusCode=${t.statusCode}: `+t.statusMessage);var e={body:0<o.length?o.toString('utf8'):null,statusCode:t.statusCode,statusMessage:t.statusMessage,headers:{...t.headers}};200<=t.statusCode&&t.statusCode<300?r(e):n(new ResponseError(e))}),t.on('error',e=>{console.error('send: https response error',e),n(e)})});o.on('error',e=>{console.error('send: https request error',e),n(e)}),a&&o.write(a,()=>{console.debug('send: written request body')}),o.end(),o.on('close',()=>{console.debug('send: request closed')})}),respondToCloudFormation=(e,t)=>{if(!process.env.IS_TERRAFORM)return t=JSON.stringify(t),sendHttpsRequest({url:e.ResponseURL,method:'PUT',headers:{'content-type':'','content-length':t.length}},t)},createS3Bucket=async e=>{try{const t={Bucket:e,BucketEncryption:{ServerSideEncryptionConfiguration:[{ServerSideEncryptionByDefault:{SSEAlgorithm:'AES256'}}]},PublicAccessBlockConfiguration:{IgnorePublicAcls:!0,RestrictPublicBuckets:!0}};'us-east-1'!==process.env.AWS_REGION&&(t.CreateBucketConfiguration={LocationConstraint:process.env.AWS_REGION}),await s3.send(new CreateBucketCommand(t)),console.log(`Bucket ${e} created successfully.`)}catch(e){throw console.error('Error creating bucket:',e),errorsMap.set('createS3Bucket','Error creating bucket: '+e.message),e}},setS3BucketPolicy=async(e,t,o)=>{try{var r={Version:'2012-10-17',Statement:[{Effect:'Allow',Principal:{Service:['billingreports.amazonaws.com','bcm-data-exports.amazonaws.com']},Action:['s3:GetBucketAcl','s3:PutObject','s3:GetBucketPolicy'],Resource:['arn:aws:s3:::'+e,`arn:aws:s3:::${e}/*`],Condition:{ArnLike:{'aws:SourceArn':[`arn:aws:cur:us-east-1:${o}:definition/*`,`arn:aws:bcm-data-exports:us-east-1:${o}:export/*`]},StringEquals:{'aws:SourceAccount':o}}},{Sid:'AWSConfigBucketPermissionsCheck',Effect:'Allow',Principal:{Service:'config.amazonaws.com'},Action:'s3:GetBucketAcl',Resource:'arn:aws:s3:::'+e,Condition:{StringEquals:{'AWS:SourceAccount':o}}},{Sid:'AWSConfigBucketExistenceCheck',Effect:'Allow',Principal:{Service:'config.amazonaws.com'},Action:'s3:ListBucket',Resource:'arn:aws:s3:::'+e,Condition:{StringEquals:{'AWS:SourceAccount':o}}},{Sid:'AWSConfigBucketDelivery',Effect:'Allow',Principal:{Service:'config.amazonaws.com'},Action:'s3:PutObject',Resource:`arn:aws:s3:::${e}/AWSLogs/${o}/Config/*`,Condition:{StringEquals:{'AWS:SourceAccount':o,'s3:x-amz-acl':'bucket-owner-full-control'}}},{Sid:'AllowRoleAccessToBucket',Effect:'Allow',Principal:{AWS:''+t},Action:'s3:GetObject',Resource:['arn:aws:s3:::'+e,`arn:aws:s3:::${e}/*`]},{Sid:'AllowSSLRequestsOnly',Effect:'Deny',Principal:'*',Action:'s3:*',Resource:['arn:aws:s3:::'+e,`arn:aws:s3:::${e}/*`],Condition:{Bool:{'aws:SecureTransport':'false'}}}]},n={Bucket:e,Policy:JSON.stringify(r)};await s3.send(new PutBucketPolicyCommand(n)),console.log(`Bucket policy set for ${e}.`)}catch(e){throw console.error('Error setting bucket policy:',e),errorsMap.set('setS3BucketPolicy','Error setting bucket policy: '+e.message),e}},createServiceLinkedRoleForBCM=async()=>{try{await iam.send(new GetRoleCommand({RoleName:'AWSServiceRoleForBCMDataExports'})),console.log('Service-linked role AWSServiceRoleForBCMDataExports already exists.')}catch(e){if(!(e.name instanceof NoSuchEntityException||e.message.includes('cannot be found')))throw console.error('Failed to create Service-linked role AWSServiceRoleForBCMDataExports: '+e.message),errorsMap.set('createServiceLinkedRoleForBCM','Failed to create Service-linked role AWSServiceRoleForBCMDataExports:'+e.message),e;var t={AWSServiceName:'bcm-data-exports.amazonaws.com',Description:'Role required by BCM Data Exports to access account resources.'};await iam.send(new CreateServiceLinkedRoleCommand(t)),console.log('Service-linked role AWSServiceRoleForBCMDataExports created.')}},checkAndEnableCostOptimizationHub=async e=>{try{var t=new ListEnrollmentStatusesCommand({});const n=await costOptimizationHub.send(t);var o=n.items.find(e=>e.AccountId===process.env.AWS_ACCOUNT_ID);if(o&&'Active'===o.Status)return void console.log('Cost Optimization Hub is already enabled.');console.log('Cost Optimization Hub is not enabled. Enabling now...');var r=new UpdateEnrollmentStatusCommand({status:'Active',includeMemberAccounts:e});await costOptimizationHub.send(r),console.log('Cost Optimization Hub has been enabled.')}catch(e){throw console.error('Error checking or enabling Cost Optimization Hub:',e),errorsMap.set('checkAndEnableCostOptimizationHub','Error checking or enabling Cost Optimization Hub: '+e.message),e}},createBcmDataExport=async(e,o,r,n)=>{for(let t=1;t<=5;t++)try{var i,a={};const s=(await bcm.send(new ListExportsCommand(a)))['Exports'];return s.some(e=>e.ExportName===o)?void console.log(`BCM data export '${o}' for bucket ${e} already exists.`):(i={Export:{Name:o,DataQuery:{QueryStatement:r,TableConfigurations:n},DestinationConfigurations:{S3Destination:{S3Bucket:e,S3Region:'us-east-1',S3Prefix:'reports',S3OutputConfigurations:{Compression:'PARQUET',Format:'PARQUET',OutputType:'CUSTOM',Overwrite:'OVERWRITE_REPORT'}}},RefreshCadence:{Frequency:'SYNCHRONOUS'}}},await bcm.send(new CreateExportCommand(i)),void console.log(`BCM data export '${o}' created for bucket ${e}.`))}catch(e){if(!(t<5))throw console.log('Error creating BCM data export after multiple attempts:',e),errorsMap.set('createBcmDataExport','Error creating BCM data export after multiple attempts: '+e.message),e;console.log(`BCM data export create attempt ${t} failed. Retrying in 4 seconds...`),await new Promise(e=>setTimeout(e,4e3))}},createFocusExport=async(e,t)=>{await createBcmDataExport(e,t,'SELECT AvailabilityZone, BilledCost, BillingAccountId, BillingAccountName, BillingAccountType, BillingCurrency, BillingPeriodEnd, BillingPeriodStart, CapacityReservationId, CapacityReservationStatus, ChargeCategory, ChargeClass, ChargeDescription, ChargeFrequency, ChargePeriodEnd, ChargePeriodStart, CommitmentDiscountCategory, CommitmentDiscountId, CommitmentDiscountName, CommitmentDiscountQuantity, CommitmentDiscountType, CommitmentDiscountUnit, CommitmentDiscountStatus, ConsumedQuantity, ConsumedUnit, ContractedCost, ContractedUnitPrice, EffectiveCost, InvoiceId, InvoiceIssuerName, ListCost, ListUnitPrice, PricingCategory, PricingCurrency, PricingCurrencyContractedUnitPrice, PricingCurrencyEffectiveCost, PricingCurrencyListUnitPrice, PricingQuantity, PricingUnit, ProviderName, PublisherName, RegionId, RegionName, ResourceId, ResourceName, ResourceType, ServiceCategory, ServiceName, ServiceSubcategory, SkuId, SkuMeter, SkuPriceId, SkuPriceDetails, SubAccountId, SubAccountName, SubAccountType, Tags, x_Discounts, x_Operation, x_ServiceCode FROM FOCUS_1_2_AWS',{FOCUS_1_2_AWS:{TIME_GRANULARITY:'DAILY'}})},createCur2Export=async(e,t)=>{await createBcmDataExport(e,t,'SELECT bill_bill_type, bill_billing_entity, bill_billing_period_end_date, bill_billing_period_start_date, bill_invoice_id, bill_invoicing_entity, bill_payer_account_id, bill_payer_account_name, cost_category, discount, discount_bundled_discount, discount_total_discount, identity_line_item_id, identity_time_interval, line_item_availability_zone, line_item_blended_cost, line_item_blended_rate, line_item_currency_code, line_item_legal_entity, line_item_line_item_description, line_item_line_item_type, line_item_net_unblended_cost, line_item_net_unblended_rate, line_item_normalization_factor, line_item_normalized_usage_amount, line_item_operation, line_item_product_code, line_item_resource_id, line_item_tax_type, line_item_unblended_cost, line_item_unblended_rate, line_item_usage_account_id, line_item_usage_account_name, line_item_usage_amount, line_item_usage_end_date, line_item_usage_start_date, line_item_usage_type, pricing_currency, pricing_lease_contract_length, pricing_offering_class, pricing_public_on_demand_cost, pricing_public_on_demand_rate, pricing_purchase_option, pricing_rate_code, pricing_rate_id, pricing_term, pricing_unit, product, product_comment, product_fee_code, product_fee_description, product_from_location, product_from_location_type, product_from_region_code, product_instance_family, product_instance_type, product_instancesku, product_location, product_location_type, product_operation, product_pricing_unit, product_product_family, product_region_code, product_servicecode, product_sku, product_to_location, product_to_location_type, product_to_region_code, product_usagetype, reservation_amortized_upfront_cost_for_usage, reservation_amortized_upfront_fee_for_billing_period, reservation_availability_zone, reservation_effective_cost, reservation_end_time, reservation_modification_status, reservation_net_amortized_upfront_cost_for_usage, reservation_net_amortized_upfront_fee_for_billing_period, reservation_net_effective_cost, reservation_net_recurring_fee_for_usage, reservation_net_unused_amortized_upfront_fee_for_billing_period, reservation_net_unused_recurring_fee, reservation_net_upfront_value, reservation_normalized_units_per_reservation, reservation_number_of_reservations, reservation_recurring_fee_for_usage, reservation_reservation_a_r_n, reservation_start_time, reservation_subscription_id, reservation_total_reserved_normalized_units, reservation_total_reserved_units, reservation_units_per_reservation, reservation_unused_amortized_upfront_fee_for_billing_period, reservation_unused_normalized_unit_quantity, reservation_unused_quantity, reservation_unused_recurring_fee, reservation_upfront_value, resource_tags, savings_plan_amortized_upfront_commitment_for_billing_period, savings_plan_end_time, savings_plan_instance_type_family, savings_plan_net_amortized_upfront_commitment_for_billing_period, savings_plan_net_recurring_commitment_for_billing_period, savings_plan_net_savings_plan_effective_cost, savings_plan_offering_type, savings_plan_payment_option, savings_plan_purchase_term, savings_plan_recurring_commitment_for_billing_period, savings_plan_region, savings_plan_savings_plan_a_r_n, savings_plan_savings_plan_effective_cost, savings_plan_savings_plan_rate, savings_plan_start_time, savings_plan_total_commitment_to_date, savings_plan_used_commitment, split_line_item_actual_usage, split_line_item_net_split_cost, split_line_item_net_unused_cost, split_line_item_parent_resource_id, split_line_item_public_on_demand_split_cost, split_line_item_public_on_demand_unused_cost, split_line_item_reserved_usage, split_line_item_split_cost, split_line_item_split_usage, split_line_item_split_usage_ratio, split_line_item_unused_cost FROM COST_AND_USAGE_REPORT',{COST_AND_USAGE_REPORT:{INCLUDE_RESOURCES:'TRUE',INCLUDE_SPLIT_COST_ALLOCATION_DATA:'TRUE',TIME_GRANULARITY:'DAILY'}})},createCostOptimizationRecommendationExport=async(e,t)=>{await createBcmDataExport(e,t,'SELECT account_id, account_name, action_type, currency_code, current_resource_details, current_resource_summary, current_resource_type, estimated_monthly_cost_after_discount, estimated_monthly_cost_before_discount, estimated_monthly_savings_after_discount, estimated_monthly_savings_before_discount, estimated_savings_percentage_after_discount, estimated_savings_percentage_before_discount, implementation_effort, last_refresh_timestamp, recommendation_id, recommendation_lookback_period_in_days, recommendation_source, recommended_resource_details, recommended_resource_summary, recommended_resource_type, region, resource_arn, restart_needed, rollback_possible, tags FROM COST_OPTIMIZATION_RECOMMENDATIONS',{COST_OPTIMIZATION_RECOMMENDATIONS:{INCLUDE_ALL_RECOMMENDATIONS:'TRUE',FILTER:'{}'}})},createCarbonExport=async(e,t)=>{await createBcmDataExport(e,t,'SELECT last_refresh_timestamp, location, model_version, payer_account_id, product_code, region_code, total_lbm_emissions_unit, total_lbm_emissions_value, total_mbm_emissions_unit, total_mbm_emissions_value, usage_account_id, usage_period_end, usage_period_start FROM CARBON_EMISSIONS',{CARBON_EMISSIONS:{}})},triggerCostBackfill=async()=>{const e=new Date,t=(e.setUTCDate(1),e.setUTCHours(0,0,0,0),e.setUTCMonth(e.getUTCMonth()-12),e.toISOString());var o=t.split('.')[0]+'Z';for(let t=1;t<=5;t++)try{var r={BackfillFrom:o},n=await ce.send(new StartCostAllocationTagBackfillCommand(r));return void console.log('Cost allocation tag backfill started successfully:',n)}catch(e){console.error(`Cost backfill attempt ${t} failed:`,e),t<5?await new Promise(e=>setTimeout(e,4e3)):errorsMap.set('startCostAllocationTagBackfill','Failed to start cost allocation tag backfill after 5 attempts: '+e.message)}},enableCostAllocationTags=async(o=[])=>{for(let t=1;t<=5;t++)try{var e={CostAllocationTagsStatus:o.map(e=>({TagKey:e,Status:'Active'}))},r=await ce.send(new UpdateCostAllocationTagsStatusCommand(e));return console.log(`Enabled cost-allocation tags [${o.join(',')}]:`,r),r}catch(e){if(console.error(`EnableTags attempt ${t} failed:`,e),!(t<5))return errorsMap.set('enableCostAllocationTags',`Failed to enable tags [${o.join(',')}] after 5 attempts: `+e.message),null;await new Promise(e=>setTimeout(e,4e3))}},handler=async(t,e)=>{let o='',r='',n='';try{var i=(await organizations.send(new DescribeOrganizationCommand({}))).Organization;o=t.ResourceProperties.AccountId===i.MasterAccountId?(await organizations.send(new DescribeAccountCommand({AccountId:t.ResourceProperties.AccountId}))).Account.Name:(await iam.send(new ListAccountAliasesCommand({MaxItems:1}))).AccountAliases[0],r=i.Id,n=i.MasterAccountId}catch(e){console.info('The user is not a member of any organization.')}i=t.ResourceProperties.AccountId===n;try{var a=t.ResourceProperties.BucketName,s=t.ResourceProperties.DataExportCUR2Name,c=t.ResourceProperties.DataExportFOCUSName,_=t.ResourceProperties.DataExportCostOptimizationRecommendationName,l=t.ResourceProperties.DataExportCarbonEmissionsName,u=t.ResourceProperties.IAMRole,d=t.ResourceProperties.AccountId;console.log('Properties: '),console.log(t.ResourceProperties),console.log('Is root account: '+i);try{await s3.send(new HeadBucketCommand({Bucket:a})),console.log(`Bucket ${a} already exists.`)}catch(e){if('NotFound'!==e.name)throw console.error('Error checking bucket:',e),e;await createS3Bucket(a)}await s3.send(new PutBucketVersioningCommand({Bucket:a,VersioningConfiguration:{Status:'Enabled'}})),console.log(`Versioning enabled for bucket ${a}.`),await setS3BucketPolicy(a,u,d),await createCur2Export(a,s),await createFocusExport(a,c),await createServiceLinkedRoleForBCM(),await checkAndEnableCostOptimizationHub(i),await createCostOptimizationRecommendationExport(a,_),await createCarbonExport(a,l)}catch(e){console.error('Failed to create data exports, reason:',e.message),errorsMap.set('handler','Failed to create data exports, reason: '+e.message)}i&&(console.log('Enabling cost allocation tags...'),await enableCostAllocationTags(['aws:createdBy']),console.log('Triggering cost backfill...'),await triggerCostBackfill());u={provider_confirmation_token:t.ResourceProperties.ConfirmationToken,provider_account_id:t.ResourceProperties.AccountId,provider_organisation_id:r,provider_root_account_id:n,provider_account_name:o,provider_region:process.env.AWS_REGION,sub_accounts_assume_role_name:t.ResourceProperties.SubAccountsAssumeRoleName,errors:JSON.stringify(Object.fromEntries(errorsMap))};const m={StackId:t.StackId,RequestId:t.RequestId,LogicalResourceId:t.LogicalResourceId,PhysicalResourceId:e.logStreamName,Data:{message:''}};d=requestMethodMap[t.RequestType];try{if('delete'===d)return m.Status='SUCCESS',m.Data.message='Cloudchipr Role deleted successfully',await respondToCloudFormation(t,m),m;let e='';return e=t.ResourceProperties.ExecutionType&&'UPDATE'!==t.ResourceProperties.ExecutionType?(await sendHttpsRequest({method:d,url:''+process.env.C8R_API_ENDPOINT+t.ResourceProperties.C8RUniqueId,headers:{'content-length':JSON.stringify(u).length,'content-type':'application/json','User-Agent':'c8r/1.0.0'}},JSON.stringify(u))).body:'No need to request for account confirmation, ExecutionType: '+t.ResourceProperties.ExecutionType,m.Status='SUCCESS',m.Data.message='Cloudchipr Role created successfully',console.info('RoleCreateCallback:',d,'response from webapp',e),await respondToCloudFormation(t,m),m}catch(e){return m.Status='FAILED',m.Data.message='Error encountered during Role creation.',console.error('RoleCreateCallback: Error response from',d,'request to webapp',e,e.result),await respondToCloudFormation(t,m),m}};module.exports.handler=handler;"
        },
        "FunctionName": {
          "Fn::Select": [
            7,
            {
              "Ref": "Data"
            }
          ]
        },
        "Description": "Report Cloudchipr Role ARN to Cloudchipr",
        "Handler": "index.handler",
        "Role": {
          "Fn::GetAtt": "BasicLambdaExecutionRole.Arn"
        },
        "Runtime": "nodejs24.x",
        "Timeout": 30,
        "Environment": {
          "Variables": {
            "C8R_API_ENDPOINT": "https://api-bff.cloudchipr.com/providers/aws/account-attempts/"
          }
        }
      }
    },
    "CloudchiprAppCallback": {
      "DependsOn": [
        "CloudchiprAppCallbackLambdaFunction"
      ],
      "Type": "Custom::CloudChiprAppCallback",
      "Properties": {
        "ServiceToken": {
          "Fn::GetAtt": "CloudchiprAppCallbackLambdaFunction.Arn"
        },
        "RoleArn": {
          "Fn::GetAtt": "CloudchiprStackIAMRole.Arn"
        },
        "AccountId": {
          "Ref": "AWS::AccountId"
        },
        "C8RUniqueId": {
          "Fn::Select": [
            2,
            {
              "Ref": "Data"
            }
          ]
        },
        "ConfirmationToken": {
          "Fn::Select": [
            3,
            {
              "Ref": "Data"
            }
          ]
        },
        "BucketName": {
          "Fn::Select": [
            9,
            {
              "Ref": "Data"
            }
          ]
        },
        "ExecutionType": {
          "Fn::Select": [
            10,
            {
              "Ref": "Data"
            }
          ]
        },
        "IAMRole": {
          "Fn::Join": [
            "",
            [
              {
                "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:role/"
              },
              {
                "Fn::Select": [
                  5,
                  {
                    "Ref": "Data"
                  }
                ]
              }
            ]
          ]
        },
        "UniqueRequestId": {
          "Fn::Select": [
            11,
            {
              "Ref": "Data"
            }
          ]
        },
        "DataExportCUR2Name": {
          "Fn::Select": [
            12,
            {
              "Ref": "Data"
            }
          ]
        },
        "DataExportFOCUSName": {
          "Fn::Select": [
            13,
            {
              "Ref": "Data"
            }
          ]
        },
        "DataExportCostOptimizationRecommendationName": {
          "Fn::Select": [
            14,
            {
              "Ref": "Data"
            }
          ]
        },
        "DataExportCarbonEmissionsName": {
          "Fn::Select": [
            15,
            {
              "Ref": "Data"
            }
          ]
        },
        "SubAccountsAssumeRoleName": {
          "Fn::Select": [
            16,
            {
              "Ref": "Data"
            }
          ]
        }
      }
    }
  },
  "Outputs": {
    "StatusMessage": {
      "Value": {
        "Fn::GetAtt": "CloudchiprAppCallback.message"
      },
      "Description": "Response from Cloudchipr API Callback function"
    }
  }
}
