---
AWSTemplateFormatVersion: 2010-09-09
Description: Mavvrik AWS Usage Account Onboarding v3 - Org Stack

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: { default: "Inputs" }
        Parameters: [tenant, token, accessType, orgUnitIds, autoDeployment, deployedFrom]
    ParameterLabels:
      tenant: { default: Tenant }
      token: { default: Token }
      accessType: { default: Access Type (Optional) }
      orgUnitIds: { default: Organizational Unit IDs (Optional) }
      autoDeployment: { default: Auto Deployment (Optional) }
      deployedFrom: { default: Deployed From (Optional) }

Parameters:
  tenant:
    Type: String
    MinLength: 3
    Description: Mavvrik assigned tenant id

  token:
    Type: String
    MinLength: 3
    NoEcho: true
    Description: Not supposed to be changed manually. Don't proceed if its not automatically populated

  accessType:
    Type: String
    Default: ""
    AllowedPattern: ^(|x-loginasadmin|x-loginasroot)$
    ConstraintDescription: must be empty, x-loginasadmin, or x-loginasroot
    Description: Not supposed to be changed manually. Leave empty if its not automatically populated

  orgUnitIds:
    Type: CommaDelimitedList
    Description: Comma seperated list of Org Units. e.g = "ou-gxgz-t7tiymqh,ou-gxgz-5jkpxnpr". If kept empty, root OU will be used

  autoDeployment:
    Type: String
    Default: "true"
    AllowedValues: ["true", "false"]
    Description: Automatically configure new accounts added to the organization. Default is true

  deployedFrom:
    Type: String
    Default: "Management Account"
    AllowedValues: ["Management Account", "Member Account (Delegated Admin)"]
    Description: Which account this stack is launched from. Choose "Member Account (Delegated Admin)" only if this account is registered as a CloudFormation StackSets delegated administrator for the organization. Must match the account this stack runs in. Default is "Management Account"

Conditions:
  OrgUnitIdsNotAvailable: !Equals [!Join ["", !Ref orgUnitIds], ""] # orgUnitIds.join('') == ''
  AutoDeploymentEnabled: !Equals [!Ref autoDeployment, "true"]
  DeployedFromManagementAccount: !Equals [!Ref deployedFrom, "Management Account"]

Resources:
  FindOrgIdFunctionExecutionRole:
    Type: AWS::IAM::Role
    Condition: OrgUnitIdsNotAvailable
    Properties:
      Path: /mavvrik/
      Description: Mavvrik > A role required to run a lambda function which finds out root org unit id
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal: { Service: lambda.amazonaws.com }
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/AWSOrganizationsReadOnlyAccess
      Tags: [{ Key: "app", Value: "Mavvrik" }, { Key: "version", Value: "v3" }, { Key: "variant", Value: "v3" }]

  FindOrgIdFunction:
    Type: AWS::Lambda::Function
    Condition: OrgUnitIdsNotAvailable
    Properties:
      Description: Mavvrik > a lambda function which finds out root org unit id
      Role: !GetAtt FindOrgIdFunctionExecutionRole.Arn
      Runtime: python3.13
      Handler: index.handler
      Timeout: 300
      Code:
        ZipFile: |
          import traceback
          import boto3
          import cfnresponse
          client = boto3.client("organizations")
          def process() -> dict:
              resp = client.list_roots()
              return {
                  "Id": resp["Roots"][0]["Id"],
                  "Arn": resp["Roots"][0]["Arn"],
                  "Name": resp["Roots"][0]["Name"],
              }
          def handler(event, context):
              req_type = event.get("RequestType")
              if req_type not in ["Create", "Update"]:
                  print("No action required for requests other than `Create` or `Update`")
                  return cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
              try:
                  response = process()
                  return cfnresponse.send(event, context, cfnresponse.SUCCESS, response)
              except Exception as err:
                  print(traceback.format_exc())
                  return cfnresponse.send(event, context, cfnresponse.FAILED, {}, reason=f"err={err}, cloudwatch-log-stream={context.log_stream_name}")
          if __name__ == "__main__":
              handler({"RequestType": "Create"}, None)
      Tags: [{ Key: "app", Value: "Mavvrik" }, { Key: "version", Value: "v3" }, { Key: "variant", Value: "v3" }]

  FindOrgIdFunctionCall:
    Type: AWS::CloudFormation::CustomResource
    Condition: OrgUnitIdsNotAvailable
    Properties: { ServiceToken: !GetAtt FindOrgIdFunction.Arn }

  StackSet:
    Type: AWS::CloudFormation::StackSet
    Properties:
      StackSetName: !Sub ["DigitalExUsageOrgOnboarding-${Suffix}", { Suffix: !Select [0, !Split ["_", !Ref tenant]] }]
      TemplateURL: "https://mavvrik-assets.s3.amazonaws.com/cloudwiz-io/usage/v3/template.yaml"
      Description: Mavvrik > Onboard all the Accounts in the AWS Organization as Usage Accounts on Mavvrik Platform

      CallAs: !If [DeployedFromManagementAccount, SELF, DELEGATED_ADMIN]
      Capabilities: ["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"]
      PermissionModel: SERVICE_MANAGED
      ManagedExecution: { Active: true }
      AutoDeployment:
        Enabled: !If [AutoDeploymentEnabled, true, false]
        RetainStacksOnAccountRemoval: !If [AutoDeploymentEnabled, false, !Ref "AWS::NoValue"]

      # Ref. https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudformation-stackset-operationpreferences.html#cfn-cloudformation-stackset-operationpreferences-failuretolerancepercentage
      OperationPreferences:
        MaxConcurrentPercentage: 50
        FailureTolerancePercentage: 50

      StackInstancesGroup:
        - Regions: [!Ref "AWS::Region"]
          DeploymentTargets:
            OrganizationalUnitIds: !If [OrgUnitIdsNotAvailable, [!GetAtt FindOrgIdFunctionCall.Id], !Ref orgUnitIds]
      Parameters:
        - ParameterKey: tenant
          ParameterValue: !Ref tenant
        - ParameterKey: token
          ParameterValue: !Ref token
        - ParameterKey: accessType
          ParameterValue: !Ref accessType
        - ParameterKey: parentStackId
          ParameterValue: !Ref "AWS::StackId"
        - ParameterKey: parentStackOrgUnitIds
          ParameterValue: !If [OrgUnitIdsNotAvailable, !GetAtt FindOrgIdFunctionCall.Id, !Join [",", !Ref orgUnitIds]]
      Tags: [{ Key: "app", Value: "Mavvrik" }, { Key: "version", Value: "v3" }, { Key: "variant", Value: "v3" }]

Outputs:
  orgUnitIds:
    Value: !If [OrgUnitIdsNotAvailable, !GetAtt FindOrgIdFunctionCall.Id, !Join [",", !Ref orgUnitIds]]
  StackSetId:
    Value: !GetAtt StackSet.StackSetId