Parameters:
  IncludeRegions:
    Type: String
    Description: "Comma-separated list of regions to include (e.g., us-east-1,us-east-2,us-west-1,us-west-2) or left blank to use the region where the CloudFormation Stack is being created"
    Default: ""
    ConstraintDescription: Must be a comma-separated list of valid AWS region codes (e.g., us-east-1,us-east-2,us-west-1,us-west-2)
  RoleName:
    Type: String
    Description: The name of the IAM role to attach the read policy.
  CreateIAMUser:
    Type: String
    Description: "Whether to create an IAM user (true or false), this is to support EKS clusters that do not have an IAM OIDC provider configured"
    AllowedValues:
      - "true"
      - "false"
    Default: "false"
  Environment:
    Type: String
    Description: "nOps Environment"
    AllowedValues:
      - "PROD"
      - "UAT"
    Default: PROD
  Token:
    Type: String
    Description: Nops Client Token
    NoEcho: true
  AutoUpdate:
    Type: String
    AllowedValues:
      - true
      - false
    ConstraintDescription: AutoUpdate must be either true or false
    Default: True
    Description: Whether to update the stack  automatically when a new version is release or not
Conditions:
  CreateIAMUserCondition: !Equals [!Ref CreateIAMUser, "true"]
  CreateIAMRoleCondition: !Equals [!Ref CreateIAMUser, "false"]
  IsProd: !Equals [!Ref Environment, "PROD"]
  IsUAT: !Equals [!Ref Environment, "UAT"]
Resources:
  S3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      AccessControl: BucketOwnerFullControl
      BucketName: !Sub 'nops-container-cost-${AWS::AccountId}'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
  S3BucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    DependsOn:
      - S3Bucket
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Deny
          Principal: "*"
          Action: s3:*
          Resource:
          - !GetAtt [S3Bucket, Arn]
          - !Sub "${S3Bucket.Arn}/*"
          Condition:
            Bool:
              aws:SecureTransport: 'false'
  ReadPolicy:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyName: !Sub 'container-cost-read-only-${AWS::StackName}'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - 's3:GetObject'
              - 's3:ListBucket'
            Resource:
              - !Sub 'arn:aws:s3:::nops-container-cost-${AWS::AccountId}'
              - !Sub 'arn:aws:s3:::nops-container-cost-${AWS::AccountId}/*'
      Roles:
        - !Ref RoleName
  IAMUser:
    Type: AWS::IAM::User
    Condition: CreateIAMUserCondition
    Properties:
      UserName: !Sub 'nops-container-cost-s3'
  AttachPolicyToUser:
    Type: AWS::IAM::Policy
    Condition: CreateIAMUserCondition
    Properties:
      PolicyName: !Sub 'nops-container-cost-${AWS::StackName}-s3'
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - 's3:PutObject'
              - 's3:GetObject'
              - 's3:ListBucket'
            Resource:
              - !Sub 'arn:aws:s3:::nops-container-cost-${AWS::AccountId}'
              - !Sub 'arn:aws:s3:::nops-container-cost-${AWS::AccountId}/*'
      Users:
        - !Ref IAMUser
  
  NOPSCrossAccountRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub 'nops-cross-account-role-${AWS::StackName}'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: !If 
                - IsProd
                - "arn:aws:iam::202279780353:root"
                - !If 
                  - IsUAT
                  - "arn:aws:iam::844856862745:root"
                  - !Ref "AWS::NoValue"
            Action: 'sts:AssumeRole'
  NOPSCrossAccountPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: CrossAccountRolePolicy
      Roles:
        - !Ref NOPSCrossAccountRole
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - cloudformation:DescribeStackInstance
              - cloudformation:DescribeStackResources
              - cloudformation:DescribeStacks
              - cloudformation:GetTemplateSummary
              - cloudformation:UpdateStack
            Resource:
              - !Ref AWS::StackId

          - Effect: Allow
            Action:
              - 's3:PutObject'
              - 's3:GetObject'
              - 's3:ListBucket'
            Resource:
              - !Sub 'arn:aws:s3:::nops-container-cost-${AWS::AccountId}'
              - !Sub 'arn:aws:s3:::nops-container-cost-${AWS::AccountId}/*'

          - Effect: Allow
            Action:
              - 'eks:DescribeCluster'
              - 'eks:ListClusters'
            Resource: '*'

          - Effect: Allow
            Action:
              - 'ec2:DescribeRegions'
              - 'ec2:DescribeInstances'
              - 'ec2:DescribeTags'
            Resource: '*'

          - Effect: Allow
            Action:
              - 'cloudwatch:PutMetricData'
            Resource: '*'

          - Effect: Allow
            Action:
              - 'logs:CreateLogGroup'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
            Resource: '*'

          - Effect: Allow
            Action:
              - 'iam:PassRole'
              - 'iam:CreateRole'
              - 'iam:UpdateAssumeRolePolicy'
              - 'iam:DeleteRole'
              - 'iam:GetRole'
              - 'iam:GetRolePolicy'
              - 'iam:ListRolePolicies'
              - 'iam:ListRoles'
              - 'iam:PutRolePolicy'
              - 'iam:DeleteRolePolicy'
              - 'iam:ListAttachedRolePolicies'
              - 'iam:DetachRolePolicy'
            Resource:
              - !Sub "arn:aws:iam::${AWS::AccountId}:role/nops-ccost-*"
              - !Sub "arn:aws:iam::${AWS::AccountId}:role/nops-container-cost-lambda"
              - !GetAtt NOPSCrossAccountRole.Arn

          - Effect: Allow
            Action:
              - 'sts:GetCallerIdentity'
            Resource: '*'

          - Effect: Allow
            Action:
              - 'sqs:SendMessage'
              - 'sqs:ReceiveMessage'
              - 'sqs:DeleteMessage'
            Resource:
              - !Sub "arn:aws:sqs:us-west-2:${AWS::AccountId}:nops-k8s-*"
          
          - Effect: Allow
            Action:
              - 'lambda:InvokeFunction'
              - 'lambda:UpdateFunctionConfiguration'
            Resource:
              - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:nops-container-cost-agent-role-creation"
              - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:nops-container-cost-agent-role-management"

          - Effect: Allow
            Action:
              - events:DeleteRule
              - events:DescribeRule
              - events:PutRule
              - events:PutTargets
              - events:RemoveTargets
            Resource:
              - !GetAtt NOPSContainerCostScheduledCheckEventRule.Arn

  NOPSContainerCostRoleCreationFunction:
    Type: AWS::Lambda::Function
    Condition: CreateIAMRoleCondition
    Properties:
      Architectures:
        - arm64
      FunctionName: !Sub nops-container-cost-agent-role-creation
      Code:
        ZipFile: |
          import boto3
          import json
          import cfnresponse
          import os
          import http.client
          from urllib.parse import urlparse
          
          CLOUDFORMATION_VERSION = "0.1.9"
          
          
          def send_onboarding_confirmation(cluster_arns):
              env = os.environ.get('Env', 'PROD')
              base_url = "https://app.nops.io/svc/karpenter_manager" if env == "PROD" else "https://uat.nops.io/svc/karpenter_manager"
              url = f"{base_url}/agents/cloudformation/onboarding-confirmation"
          
              # Parse URL to extract components for HTTP connection
              parsed_url = urlparse(url)
              host = parsed_url.netloc
              path = parsed_url.path
          
              # Prepare payload and headers
              payload = json.dumps({
                  "cluster_arns": cluster_arns,
                  "cloudformation_version": CLOUDFORMATION_VERSION,
                  "stack_id": os.environ.get('STACK_ID'),
                  "stack_name": os.environ.get('STACK_NAME'),
                  "region_name": os.environ.get('AWS_REGION')
              })
          
              token = os.environ.get('Token')
              headers = {
                  "Content-Type": "application/json",
                  "X-Nops-Api-Key": token if token else ""
              }
          
              # Select connection type based on URL scheme
              connection = http.client.HTTPSConnection(host) if parsed_url.scheme == "https" else http.client.HTTPConnection(host)
          
              try:
                  # Send POST request
                  connection.request("POST", path, body=payload, headers=headers)
          
                  # Get and check the response
                  response = connection.getresponse()
                  if response.status == 200:
                      print("Onboarding confirmation sent successfully.")
                  else:
                      print(f"Failed to send onboarding confirmation. Status code: {response.status}, reason: {response.reason}")
          
                  # Optionally, read the response data if needed
                  response_data = response.read()
                  print(response_data.decode('utf-8'))
          
              except http.client.HTTPException as e:
                  print(f"HTTP error occurred: {e}")
              finally:
                  connection.close()
          
          
          def lambda_handler(event, context):
              response_data = {}
              try:
                  if event['RequestType'] in ['Create', 'Update']:
                      cluster_arns = manage_iam_roles_for_all_clusters(event['RequestType'])
                      if cluster_arns:
                          send_onboarding_confirmation(cluster_arns)
                  elif event['RequestType'] == 'Delete':
                      delete_iam_roles()
          
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
              except Exception as e:
                  response_data['Error'] = str(e)
                  cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
          
          def manage_iam_roles_for_all_clusters(request_type):
              eks = boto3.client('eks')
              iam = boto3.client('iam')
              sts = boto3.client('sts')
          
              env = os.environ.get('Env', 'PROD')
              nops_account = "202279780353" if env == "PROD" else "844856862745"
              account_id = sts.get_caller_identity()['Account']
              default_region = os.environ['AWS_REGION']
              default_regions = [default_region]
              included_regions = os.environ.get('IncludeRegions', '').split(',') if os.environ.get('IncludeRegions') else default_regions
              print(included_regions)
          
              sqs_policy = {
                  "Version": "2012-10-17",
                  "Statement": [
                      {
                          "Effect": "Allow",
                          "Action": [
                              "sqs:SendMessage",
                              "sqs:ReceiveMessage",
                              "sqs:DeleteMessage"
                          ],
                          "Resource": [
                              f"arn:aws:sqs:us-west-2:{nops_account}:nops-k8s-*"
                          ]
                      }
                  ]
              }
              cluster_arns = []
              for region in included_regions:
                  try:
                      eks_client = boto3.client('eks', region_name=region)
                      clusters = eks_client.list_clusters()['clusters']
          
                      for cluster_name in clusters:
                          try:
                              cluster_info = eks_client.describe_cluster(name=cluster_name)
                              oidc_id = cluster_info['cluster']['identity']['oidc']['issuer'].split('/')[-1]
                              role_name = f"nops-ccost-{cluster_name}_{region}"
                              oidc_arn = f"arn:aws:iam::{account_id}:oidc-provider/oidc.eks.{region}.amazonaws.com/id/{oidc_id}"
          
                              assume_role_policy = json.dumps({
                                  "Version": "2012-10-17",
                                  "Statement": [{
                                      "Effect": "Allow",
                                      "Principal": {"Federated": oidc_arn},
                                      "Action": "sts:AssumeRoleWithWebIdentity",
                                      "Condition": {"StringEquals": {f"oidc.eks.{region}.amazonaws.com/id/{oidc_id}:sub": "system:serviceaccount:nops:nops-container-insights"}}
                                  }]
                              })
          
                              inline_policy = {
                                  "Version": "2012-10-17",
                                  "Statement": [{
                                      "Effect": "Allow",
                                      "Action": [
                                          "s3:PutObject",
                                          "s3:GetObject",
                                          "s3:ListBucket"
                                      ],
                                      "Resource": [
                                          f"arn:aws:s3:::nops-container-cost-{account_id}",
                                          f"arn:aws:s3:::nops-container-cost-{account_id}/*"
                                      ]
                                  }]
                              }
          
                              if request_type in ['Create', 'Update']:
                                  create_or_update_role(iam, role_name, assume_role_policy, inline_policy, sqs_policy, region)
                              cluster_arns.append(cluster_info['cluster']['arn'])
                          except Exception as e:
                              print(f"Error managing role for cluster {cluster_name} in region {region}: {str(e)}", exc_info=True)
                  except Exception as e:
                      print(f"Error managing roles in region {region}: {str(e)}", exc_info=True)
              return cluster_arns
          
          def create_or_update_role(iam, role_name, assume_role_policy, inline_policy, sqs_policy, region):
              try:
                  # Check if the role exists
                  role = iam.get_role(RoleName=role_name)
                  current_assume_role_policy = role['Role']['AssumeRolePolicyDocument']
          
                  # Update the trust relationship if necessary
                  for statement in current_assume_role_policy['Statement']:
                      if 'Condition' in statement and 'StringEquals' in statement['Condition']:
                          condition_key = list(statement['Condition']['StringEquals'].keys())[0]
                          if statement['Condition']['StringEquals'][condition_key] == 'system:serviceaccount:nops-k8s-agent:nops-k8s-agent':
                              statement['Condition']['StringEquals'][condition_key] = 'system:serviceaccount:nops:nops-container-insights'
                              iam.update_assume_role_policy(RoleName=role_name, PolicyDocument=json.dumps(current_assume_role_policy))
                              print(f"Updated trust relationship for role {role_name}.")
          
                  # Update S3 inline policy
                  iam.put_role_policy(RoleName=role_name, PolicyName='S3Policy', PolicyDocument=json.dumps(inline_policy))
                  print(f"Updated role {role_name} with S3 policy.")
          
                  # Check if SQSPolicy exists
                  existing_policies = iam.list_role_policies(RoleName=role_name)['PolicyNames']
                  if 'SQSPolicy' not in existing_policies:
                      # Create and attach SQSPolicy if it doesn't exist
                      iam.put_role_policy(RoleName=role_name, PolicyName='SQSPolicy', PolicyDocument=json.dumps(sqs_policy))
                      print(f"Attached SQSPolicy to role {role_name}.")
                  else:
                      print(f"SQSPolicy already exists for role {role_name}.")
          
              except iam.exceptions.NoSuchEntityException:
                  # Role does not exist, create it and attach policies
                  iam.create_role(RoleName=role_name, AssumeRolePolicyDocument=assume_role_policy)
                  iam.put_role_policy(RoleName=role_name, PolicyName='S3Policy', PolicyDocument=json.dumps(inline_policy))
                  iam.put_role_policy(RoleName=role_name, PolicyName='SQSPolicy', PolicyDocument=json.dumps(sqs_policy))
                  print(f"Created role {role_name} and attached S3 and SQS policies.")
          
          def delete_iam_roles():
              iam = boto3.client('iam')
          
              # Use the IncludeRegions environment variable
              default_region = os.environ['AWS_REGION']
              default_regions = [default_region]
              included_regions = os.environ.get('IncludeRegions', '').split(',') if os.environ.get('IncludeRegions') else default_regions
          
              # Deleting IAM roles with the "nops-ccost" suffix
              paginator = iam.get_paginator('list_roles')
              for page in paginator.paginate():
                  for role in page['Roles']:
                      if 'nops-ccost' in role['RoleName']:
                          detach_all_policies(iam, role['RoleName'])
                          delete_all_inline_policies(iam, role['RoleName'])
                          iam.delete_role(RoleName=role['RoleName'])
          
          def detach_all_policies(iam, role_name):
              # Detach all managed policies from the role
              attached_policies = iam.list_attached_role_policies(RoleName=role_name)['AttachedPolicies']
              for policy in attached_policies:
                  iam.detach_role_policy(RoleName=role_name, PolicyArn=policy['PolicyArn'])
          
          def delete_all_inline_policies(iam, role_name):
              # Delete all inline policies from the role
              inline_policies = iam.list_role_policies(RoleName=role_name)['PolicyNames']
              for policy_name in inline_policies:
                  iam.delete_role_policy(RoleName=role_name, PolicyName=policy_name)


      Environment:
        Variables:
          IncludeRegions: !Ref IncludeRegions
          Env: !Ref Environment
          Token: !Ref Token
          STACK_NAME: !Ref "AWS::StackName"
          STACK_ID: !Ref "AWS::StackId"
      Handler: index.lambda_handler
      Role: !GetAtt NOPSContainerCostLambdaRole.Arn
      Runtime: python3.10
      Timeout: 60
  NOPSContainerCostLambdaRole:
    Type: AWS::IAM::Role
    Condition: CreateIAMRoleCondition
    Properties:
      RoleName: nops-container-cost-lambda
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: nops-container-cost-lambda-policy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - iam:GetRole
                  - iam:GetRolePolicy
                  - iam:ListAttachedRolePolicies
                  - iam:CreateRole
                  - iam:PutRolePolicy
                  - iam:AttachRolePolicy
                  - iam:DeleteRole
                  - iam:UpdateAssumeRolePolicy
                  - iam:ListRoles
                  - iam:DeleteRolePolicy
                  - iam:DetachRolePolicy
                  - iam:ListRolePolicies
                  - eks:DescribeCluster
                  - eks:ListClusters
                  - sts:GetCallerIdentity
                  - events:PutEvents
                  - ec2:DescribeRegions

                Resource:
                  - '*'
  NOPSContainerCostRoleCreationCustom:
    Type: AWS::CloudFormation::CustomResource
    Condition: CreateIAMRoleCondition
    DependsOn: NOPSContainerCostRoleCreationFunction
    Version: '0.1.9'
    Properties:
      ServiceToken: !GetAtt NOPSContainerCostRoleCreationFunction.Arn
      CloudFormationVersion: '0.1.9'
  NOPSContainerCostScheduledCheckEventRule:
    Type: AWS::Events::Rule
    Condition: CreateIAMRoleCondition
    Properties:
      EventBusName: default
      Name: nops-container-cost-agent-role-management-scheduled-check
      ScheduleExpression: rate(2 hours)
      State: ENABLED
      Targets:
        - Arn: !GetAtt NOPSContainerCostRoleManagementFunction.Arn
          Id: !Ref NOPSContainerCostRoleManagementFunction
  NOPSContainerCostScheduledCheckEventRulePermission:
    Type: AWS::Lambda::Permission
    Condition: CreateIAMRoleCondition
    Properties:
      FunctionName: !Ref NOPSContainerCostRoleManagementFunction
      Action: 'lambda:InvokeFunction'
      Principal: 'events.amazonaws.com'
      SourceArn: !GetAtt NOPSContainerCostScheduledCheckEventRule.Arn
  NOPSContainerCostRoleManagementFunction:
    Type: AWS::Lambda::Function
    Condition: CreateIAMRoleCondition
    Properties:
      Architectures:
        - arm64
      FunctionName: !Sub nops-container-cost-agent-role-management
      Code:
        ZipFile: |
          import boto3
          import json
          import os
          import re
          import http.client
          from urllib.parse import urlparse
          
          CLOUDFORMATION_VERSION = "0.1.9"
          
          
          def send_onboarding_confirmation(cluster_arns):
              env = os.environ.get('Env', 'PROD')
              base_url = "https://app.nops.io/svc/karpenter_manager" if env == "PROD" else "https://uat.nops.io/svc/karpenter_manager"
              url = f"{base_url}/agents/cloudformation/onboarding-confirmation"
          
              # Parse URL for host and path
              parsed_url = urlparse(url)
              host = parsed_url.netloc
              path = parsed_url.path
          
              # Prepare payload
              payload = json.dumps({
                  "cluster_arns": cluster_arns,
                  "cloudformation_version": CLOUDFORMATION_VERSION,
                  "stack_id": os.environ.get('STACK_ID'),
                  "stack_name": os.environ.get('STACK_NAME'),
                  "region_name": os.environ.get('AWS_REGION')
              })
          
              token = os.environ.get('Token')
              headers = {
                  "Content-Type": "application/json",
                  "X-Nops-Api-Key": token if token else "",
              }
          
              # Select connection type (HTTPS or HTTP)
              connection = http.client.HTTPSConnection(host) if parsed_url.scheme == "https" else http.client.HTTPConnection(host)
          
              try:
                  # Send POST request
                  connection.request("POST", path, body=payload, headers=headers)
          
                  # Get the response
                  response = connection.getresponse()
                  if response.status == 200:
                      print("Onboarding confirmation sent successfully.")
                  else:
                      print(f"Failed to send onboarding confirmation. Status code: {response.status}, reason: {response.reason}")
          
                  # Read response data if needed
                  response_data = response.read()
                  print(response_data.decode('utf-8'))
          
              except http.client.HTTPException as e:
                  print(f"HTTP error occurred: {e}")
              finally:
                  connection.close()

          
          def lambda_handler(event, context):
              # Use the IncludeRegions environment variable
              env = os.environ.get('Env', 'PROD')
              nops_account = "202279780353" if env == "PROD" else "844856862745"
              default_region = os.environ['AWS_REGION']
              default_regions = [default_region]
              included_regions = os.environ.get('IncludeRegions', '').split(',') if os.environ.get('IncludeRegions') else default_regions
              print(f"These are the included regions: {included_regions}")
          
              # Initialize the STS client to get the account ID
              account_id = os.environ.get('AccountId')
          
              # Initialize the boto3 clients
              iam_client = boto3.client('iam')
          
              sqs_policy = {
                  "Version": "2012-10-17",
                  "Statement": [
                      {
                          "Effect": "Allow",
                          "Action": [
                              "sqs:SendMessage",
                              "sqs:ReceiveMessage",
                              "sqs:DeleteMessage"
                          ],
                          "Resource": [
                              f"arn:aws:sqs:us-west-2:{nops_account}:nops-k8s-*"
                          ]
                      }
                  ]
              }
          
              # A set to keep track of the required roles
              required_roles = set()
          
              # Iterate through each included region to find EKS clusters
              for region in included_regions:
                  regional_eks = boto3.client('eks', region_name=region)
                  try:
                      clusters = regional_eks.list_clusters()['clusters']
                  except Exception as e:
                      print(f"Error retrieving clusters for region {region}: {e}")
                      continue
          
                  # Prepare the required role names based on the cluster names
                  for cluster_name in clusters:
                      required_roles.add(f"nops-ccost-{cluster_name}_{region}")
          
              # List existing IAM roles
              existing_roles = set()
              paginator = iam_client.get_paginator('list_roles')
              for page in paginator.paginate():
                  for role in page['Roles']:
                      role_name = role['RoleName']
                      if role_name.startswith("nops-ccost-"):
                          existing_roles.add(role_name)
          
              # Define the regex pattern to extract cluster name and region
              pattern = re.compile(r"^nops-ccost-(.+)_(.+)$")
          
              # For each existing role, attach the SQSPolicy if it does not already exist
              for role_name in existing_roles:
                  try:
                      # Check if the SQSPolicy is already attached
                      existing_policies = iam_client.list_role_policies(RoleName=role_name)['PolicyNames']
                      if 'SQSPolicy' not in existing_policies:
                          # Attach the SQSPolicy
                          iam_client.put_role_policy(
                              RoleName=role_name,
                              PolicyName='SQSPolicy',
                              PolicyDocument=json.dumps(sqs_policy)
                          )
                          print(f"Attached SQSPolicy to role {role_name}.")
                      else:
                          print(f"SQSPolicy already exists for role {role_name}.")
                  except Exception as e:
                      print(f"Error checking or attaching SQSPolicy for role {role_name}: {e}")
          
              # Process for creating new roles (missing roles)
              missing_roles = required_roles - existing_roles
              cluster_arns = []
              for role_name in missing_roles:
                  try:
                      # Use regex to match and capture cluster name and region
                      match = pattern.match(role_name)
                      if not match:
                          print(f"Skipping malformed role name: {role_name}")
                          continue
          
                      cluster_name, region_to_use = match.groups()
                      print(f"Creating role for cluster: {cluster_name} in region: {region_to_use}")
          
                      # Validate the region against AWS known region names
                      all_regions = [region_info['RegionName'] for region_info in boto3.client('ec2').describe_regions()['Regions']]
                      if region_to_use not in all_regions:
                          print(f"Skipping unknown region: {region_to_use}")
                          continue
          
                      # Initialize the regional EKS client
                      regional_eks = boto3.client('eks', region_name=region_to_use)
                      cluster_info = regional_eks.describe_cluster(name=cluster_name)['cluster']
          
                      oidc_issuer = cluster_info.get('identity', {}).get('oidc', {}).get('issuer')
                      if oidc_issuer:
                          # Extract the last segment of the OIDC URL to form the correct ARN
                          oidc_id = oidc_issuer.split('/')[-1]
                          oidc_arn = f"arn:aws:iam::{account_id}:oidc-provider/oidc.eks.{region_to_use}.amazonaws.com/id/{oidc_id}"
          
                          # Construct the trust relationship document
                          assume_role_policy = json.dumps({
                              "Version": "2012-10-17",
                              "Statement": [{
                                  "Effect": "Allow",
                                  "Principal": {"Federated": oidc_arn},
                                  "Action": "sts:AssumeRoleWithWebIdentity",
                                  "Condition": {"StringEquals": {f"oidc.eks.{region_to_use}.amazonaws.com/id/{oidc_id}:sub": "system:serviceaccount:nops:nops-container-insights"}}
                              }]
                          })
          
                          inline_policy = {
                              "Version": "2012-10-17",
                              "Statement": [{
                                  "Effect": "Allow",
                                  "Action": [
                                      "s3:PutObject",
                                      "s3:GetObject",
                                      "s3:ListBucket"
                                  ],
                                  "Resource": [
                                      f"arn:aws:s3:::nops-container-cost-{account_id}",
                                      f"arn:aws:s3:::nops-container-cost-{account_id}/*"
                                  ]
                              }]
                          }
          
                          # Create the IAM role and attach the policies
                          iam_client.create_role(
                              RoleName=role_name,
                              AssumeRolePolicyDocument=assume_role_policy
                          )
                          iam_client.put_role_policy(
                              RoleName=role_name,
                              PolicyName='S3Policy',
                              PolicyDocument=json.dumps(inline_policy)
                          )
                          iam_client.put_role_policy(
                              RoleName=role_name,
                              PolicyName='SQSPolicy',
                              PolicyDocument=json.dumps(sqs_policy)
                          )
                          print(f"Created role {role_name} with S3 and SQS policies.")
                          cluster_arns.append(cluster_info['arn'])
                  except Exception as e:
                      print(f"Error creating role {role_name}: {e}")
              if cluster_arns:
                  send_onboarding_confirmation(cluster_arns)
        
      Environment:
        Variables:
          IncludeRegions: !Ref IncludeRegions
          AccountId: !Sub ${AWS::AccountId}
          Env: !Ref Environment
          Token: !Ref Token
          STACK_NAME: !Ref "AWS::StackName"
          STACK_ID: !Ref "AWS::StackId"
      Handler: index.lambda_handler
      Role: !GetAtt NOPSContainerCostLambdaRole.Arn
      Runtime: python3.10
      Timeout: 60

Outputs:
  S3BucketSecureURL:
    Value: !Join ['', ['https://', !GetAtt [S3Bucket, DomainName]]]
    Description: Domain Name of the Amazon S3 bucket
  BucketName:
    Value: !Ref S3Bucket
