Export with name ExRole is already exported by stack Root-role

Viewed 2186

I want to reuse this template but it when I up this template using nested stack it gives an error Export with name ExRole is already exported by stack Root-role. How can I improve the reuseability of the template. So that I can deploy same template in Prod, dev and other environments. I have tried using environment variable in the names of the role but how can I use it in the output and if the output is to be used in next template what should be the syntax?

Role:

---
AWSTemplateFormatVersion: 2010-09-09 
Parameters:
  Env:
    Type: String
Resources:
  ExRole:
      Type: 'AWS::IAM::Role'
      Properties:
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - ecs-tasks.amazonaws.com
              Action:
                - 'sts:AssumeRole'
        Path: /
        RoleName: !Sub "excutionrole-${Env}"
        ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
        Policies: 
          - PolicyName: AccessECR
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action: 
                    - ecr:BatchGetImage
                    - ecr:GetAuthorizationToken
                    - ecr:GetDownloadUrlForLayer 
                  Resource: '*'

  ContainerInstanceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'               
        Statement:
          - Effect: Allow
            Principal: 
                Service: 
                    - ec2.amazonaws.com
            Action: 
                - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role
      Path: '/'
      RoleName: !Sub "ContainerInstanceRole-${Env}"
  

  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties: 
      Roles: 
        - !Ref ContainerInstanceRole      

Outputs:
  
  ExRole:
    Description: Task excution role
    Value: !Ref ExRole
    Export:
        Name: "ExRole"
  InstanceProfile:
    Description: profile for container instances
    Value: !Ref InstanceProfile
    Export:
        Name: "InstanceProfile"            

Task:

---
AWSTemplateFormatVersion: 2010-09-09 
Parameters:
    ExRole:
      Type: String
    RDS:
      Type: String
 
    DBUSER:
      Type: String
      Default: mysqldb

    DBPASSWORD:
      Type: String
      Default: 1234123a 
    
    DBNAME:
      Type: String
      Default: mysqldb 
Resources:
    Task:
        Type: AWS::ECS::TaskDefinition
        Properties:
            Family: wordpress 
            Cpu: 1 vCPU
            ExecutionRoleArn: !Ref ExRole
            Memory: 1 GB
            NetworkMode: bridge
            RequiresCompatibilities:
                - EC2
            TaskRoleArn: !Ref ExRole
            ContainerDefinitions: 
              - Essential: true
                Image: wordpress:latest
                Name: wordpress
                PortMappings:  
                  - ContainerPort: 80
                    HostPort: 0
                    Protocol: tcp 
                Environment:
                  - Name: WORDPRESS_DB_HOST
                    Value: !Ref RDS 
                  - Name: WORDPRESS_DB_USER
                    Value: !Ref DBUSER 
                  - Name: WORDPRESS_DB_PASSWORD
                    Value: !Ref DBPASSWORD
                  - Name: WORDPRESS_DB_NAME
                    Value: !Ref DBNAME
    
Outputs:
  Task:
    Description: Contains all the task specifications
    Value: !Ref Task
    Export:
      Name: "Task"

Root:

Resources:
  Vpcstack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://${bucketname}.s3.us-east-2.amazonaws.com${bucketpath}/vpc.yml"
      Parameters:  
        Env: !Ref Env
        Cidr: !Ref Cidr
        Publicsubnet1: !Ref Publicsubnet1
        Publicsubnet2: !Ref Publicsubnet2
        Privatesubnet1: !Ref Privatesubnet1
        Privatesubnet2: !Ref Privatesubnet2  

  role:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://${bucketname}.s3.us-east-2.amazonaws.com${bucketpath}/role.yml"
      Parameters:  
        Env: !Ref Env
1 Answers

Generally when people use output, if the template is being used multiple times within the same parent stack they will prefix the export with a variable (such as the stack name) to make it unique.

This can be done using the sub intrinsic function such as in the example below

Outputs:
  ExRole:
    Description: Task excution role
    Value: !Ref ExRole
    Export:
        Name: !Sub "${AWS::StackName}-ExRole"
  InstanceProfile:
    Description: profile for container instances
    Value: !Ref InstanceProfile
    Export:
        Name: !Sub "${AWS::StackName}-InstanceProfile"

Then you would need to pass in this stack ID value as a parameter into the nested stack that needs to reference this file. This would again used the sub intrinsic function to reference the export name.

To get this value in the ImportValue intrinsic function you would reference it like below, to do this you would need to pass the stack name as a parameter to the stack:

Fn::ImportValue: !Sub "${NestedStack}-ExRole"

If you call the other stack from the parent stack you can ignore exporting and instead pass the output into the next stack using the GetAtt intrinsic function instead.

Resources:
  Vpcstack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://${bucketname}.s3.us-east-2.amazonaws.com${bucketpath}/vpc.yml"
      Parameters:  
        Env: !Ref Env
        Cidr: !Ref Cidr
        Publicsubnet1: !Ref Publicsubnet1
        Publicsubnet2: !Ref Publicsubnet2
        Privatesubnet1: !Ref Privatesubnet1
        Privatesubnet2: !Ref Privatesubnet2  

  role:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://${bucketname}.s3.us-east-2.amazonaws.com${bucketpath}/role.yml"
      Parameters:  
        Env: !Ref Env
        
  dbStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub "https://${bucketname}.s3.us-east-2.amazonaws.com${bucketpath}/db.yml"
      Parameters:  
        Role: !GetAtt role.Outputs.ExRole

You can also use the syntax of Fn::GetAtt: [role, Outputs.ExRole] also valid syntax.

Related