1

Ansible cloudformation module uses these environment variables of shell:

$ export AWS_PROFILE=djangoapp
$ export AWS_DEFAULT_REGION=ca-central-1
$ aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                djangoapp           manual    --profile
access_key     ****************WKWG shared-credentials-file    
secret_key     ****************/I4Z shared-credentials-file    
    region             ca-central-1              env    AWS_DEFAULT_REGION
$ cat ~/.aws/config
[djangoapp]
region = ca-central-1
$ cat ~/.aws/credentials 
[djangoapp]
aws_access_key_id = ****************KWG
aws_secret_access_key = ********************/I4Z
$
$ aws ec2 describe-vpcs
{
    "Vpcs": [
        {
            "CidrBlock": "172.31.0.0/16",
            "DhcpOptionsId": "dopt-aaaaa8",
            "State": "available",
            "VpcId": "vpc-ccccccc",
            "OwnerId": "444444444",
            "InstanceTenancy": "default",
            "CidrBlockAssociationSet": [
                {
                    "AssociationId": "vpc-cidr-assoc-fffff",
                    "CidrBlock": "172.31.0.0/16",
                    "CidrBlockState": {
                        "State": "associated"
                    }
                }
            ],
            "IsDefault": true
        }
    ]
}

./site.yml

---
- name: Todobackend deployment playbook
  hosts: localhost
  connection: local
  gather_facts: no
  vars_files:
    - secrets.yml
  environment:
    AWS_DEFAULT_REGION: "{{ lookup('env', 'AWS_DEFAULT_VERSION') | default('ca-central-1', true) }}"
  tasks:
    - include: tasks/create_stack.yml
    - include: tasks/deploy_app.yml

./tasks/create_stack.yml

---
- name: task to create/update stack
  cloudformation:
    stack_name: todobackend
    state: present
    template: templates/stack.yml
    template_format: yaml
    template_parameters:
      VpcId: "{{ vpc_id }}"
      SubnetId: "{{ subnet_id }}"
      KeyPair: "{{ ec2_keypair }}"
      InstanceCount: "{{ instance_count | default(1) }}"
      DbSubnets: "{{ db_subnets | join(',') }}"
      DbAvailabilityZone: "{{ db_availability_zone }}"
      DbUsername: "{{ db_username }}"
      DbPassword: "{{ db_password }}"
    tags:
      Environment: test
  register: cf_stack

- name: Debug output
  debug: msg="{{ cf_stack }}"
  when: debug is defined

./templates/stack.yml

AWSTemplateFormatVersion: "2010-09-09"
Description: "Todobackend Stack"

# Stack Parameters
Parameters:
  VpcId:
    Type: "AWS::EC2::VPC::Id"
    Description: "The target VPC Id"
  SubnetId:
    Type: "AWS::EC2::Subnet::Id"
    Description: "The target Subnet Id in availability zone a"
  KeyPair:
    Type: "String"
    Description: "The key pair that is allowed SSH access"
  InstanceCount:
    Type: "Number"
    Description: "The desired number of application instances"
  DbSubnets:
    Type: "List<AWS::EC2::Subnet::Id>"
    Description: "The target DB Subnet Group subnet Ids"
  DbAvailabilityZone:
    Type: "String"
    Description: "The target availability zone for the database instance"
  DbUsername:
    Type: "String"
    Description: "The RDS database username"
  DbPassword:
    Type: "String"
    Description: "The RDS database password"
    NoEcho: "true"

# Stack Resources
Resources:
  # Configure auto scaing group
  AutoScalingGroup:
    Type: "AWS::AutoScaling::AutoScalingGroup"
    Properties:
      VPCZoneIdentifier: [ { "Ref": "SubnetId" } ]
      LaunchConfigurationName: { "Ref": "AutoScalingLaunchConfiguration" }
      MinSize: 0
      MaxSize: 2
      DesiredCapacity: { "Ref": "InstanceCount" }
      Tags:
        - Key: "Name"
          Value: { "Fn::Join": ["", [ { "Ref": "AWS::StackName" }, " -instance" ] ] }
          PropagateAtLaunch: "true"

  AutoScalingLaunchConfiguration:
    Type: "AWS::AutoScaling::LaunchConfiguration"
    Properties:
      ImageId: ami-05958d7635caa4d04
      InstanceType: t2.micro
      keyName: { "Ref": "KeyPair" }
      IamInstanceProfile: { "Ref": "EC2InstanceProfile" }
      SecurityGroups:
        - { "Ref": "EC2InstanceSecurityGroup" }
      UserData: {
        "Fn::Base64": { "Fn::Join": ["", [
          "#!/bin/bash\n",
          "echo ECS_CLUSTER=", { "Ref": "EcsCluster"}, " >> /etc/ecs/ecs.config\n"
        ] ] }
      }

  EC2InstanceSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: "todobackend-sg"
      VpcId: { "Ref": "VpcId" }
      SecurityGroupIngress:
        - IpProtocol: "tcp"
          FromPort: "8080"
          ToPort: "8080"
          SourceSecurityGroupId: { "Ref": "ElbSecurityGroup" }
        - IpProtocol: "tcp"
          FromPort: "22"
          ToPort: "22"
          CidrIp: "0.0.0.0/0"
      Tags:
        - Key: "Name"
          Value: { "Fn::Join": ["", [ { "Ref": "AWS::StackName" }, "-instance-sg" ] ] }

  EC2InstanceProfile:
    Type: "AWS::IAM::InstanceProfile"
    Properties:
      Path: "/"
      Roles: [ { "Ref": "EC2InstanceRole" } ]

  EC2InstanceRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument: {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Principal": { "Service": [ "ec2.amazonaws.com" ] },
            "Action": [ "sts:AssumeRole"]
          }
        ]
      }
      Path: "/"
      ManagePolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceForEc2Role"

  # Configure RDS
  DbInstance:
    Type: "AWS::RDS::DBInstance"
    Properties:
      DBSubnetGroupName: { "Ref": "DbSubnetGroup" }
      MultiAZ: "false"
      AvailabilityZone: { "Ref": "DBAvailabilityZone" }
      AllocatedStorage: 8
      StorageType: "gp2"
      DBInstanceClass: "db.t2.micro"
      DBName: "todobackend"
      Engine: "MySQL"
      EngineVersion: "5.6"
      MasterUserName: { "Ref": "DbUserName" }
      MasterUserPassword: { "Ref": "DbPassword" }
      VPCSecurityGroups:
        - { "Ref": "DbSecurityGroup" }
      Tags:
        - Key: "Name"
          Value: { "Fn::Join": ["", [ { "Ref": "AWS::Stackname" }, "-db" ] ] }

  DbSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: "Todobackend DB Security Group"
      VpcId: { "Ref": "VpcId" }
      SecurityGroupIngress:
        - IpProtocol: "tcp"
          FromPort: "3306"
          ToPort: "3306"
          SourceSecurityGroupId: { "Ref": "EC2InstanceSecurityGroup" }

  DbSubnetGroup:
    Type: "AWS::RDS::DBSubnetGroup"
    Properties:
      DBSubnetGroupDescription: "Todobackend DB Subnet Group"
      SubnetIds: { "Ref": "DbSubnets" }
      Tags:
        - Key: "Name"
          Value: { "Fn::Join": ["", [ { "Ref": "AWS::StackName" }, "-db-subnet-group" ] ] }


  # Configure ELB
  ElasticLoadBalancer:
    Type: "AWS::ElasticLoadBalancing::LoadBalancer"
    Properties:
      CrossZone: "false"
      SecurityGroups: [ { "Ref": "ElbSecurityGroup" } ]
      Listeners:
        - LoadBalancerPort: "80"
          InstancePort: "8000"
          Protocol: "http"
      HealthCheck:
        Target: "HTTP:8000/todos"
        HealthyThreshold: "2"
        UnhealthyThreshold: "10"
        Interval: "30"
        Timeout: "5"
      Subnets: [ { "Ref": "SubnetId" } ]
      Tags:
        - Key: "Name"
          Value: { "Fn::Join": ["", [ { "Ref": "AWS::StackName" }, "-elb" ] ] }

  ElbSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: "Todobackend ELB Security Group"
      VpcId: { "Ref": "VpcId" }
      SecurityGroupIngress:
        - IpProtocol: "tcp"
          FromPort: "80"
          ToPort: "80"
          CidrIp: "0.0.0.0/0"
      Tags:
        - Key: "Name"
          Value: { "Fn::Join": ["", [ { "Ref": "AWS::StackName" }, "-elb-sg" ] ] }

  # Configure ECS
  EcsCluster:
    Type: "AWS::ECS::EcsCluster"

  TodobackendTaskDefinition:
    Type: "AWS::ECS::TaskDefinition"
    Properties:
      ContainerDefinitions:

        - Name: todobackend
          Image: shamdockerhub/todobackend
          Memory: 450
          Environment:
            - Name: DJANGO_SETTINGS_MODULE
              Value: todobackend.settings.release
            - Name: MYSQL_HOST
              Value: { "Fn::GetAtt": ["DbInstance", "Endpoint.Address"] }
            - Name: MYSQL_USER
              Value: { "Ref": "DbUsername" }
            - Name: MYSQL_PASSWORD
              Value: { "Ref": "DbPassword" }
          MountPoints:
            - ContainerPath: /var/www/todobackend
              SourceVolume: webroot
          Command:
            - uwsgi
            - "--socket /var/www/todobackend/todobackend.sock"
            - "--chmod-socket=666"
            - "--module todobackend.wsgi"
            - "--master"
            - "--die-on-term"

        - Name: nginx
          Image: shamdockerhub/todobackend-nginx
          Memory: 300
          PortMappings:
            - ContainerPort: "8000"
              HostPort: "8000"
          MountPoints:
            - ContainerPath: /var/www/todobackend
              SourceVolume: webroot

      Volumes:
        - Name: webroot
          Host:
            SourcePath: /ecs/webroot

  TodobackendService:
    Type: "AWS::ECS::Service"
    Properties:
      TaskDefinition: { "Ref": "TodobackendTaskDefinition" }
      Cluster: { "Ref": "EcsCluster" }
      LoadBalancers:
        - ContainerName: "nginx"
          ContainerPort: "8000"
          LoadBalancerName: { "Ref": "ElasticLoadBalancer" }
      Role: { "Ref": "EcsServiceRole" }
      DesiredCount: 0

  EcsServiceRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument: {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Principal": {
              "Service": { "ecs.amazonaws.com" }
            },
            "Action": [ "sts:AssumeRole" ]
          }
        ]
      }
      Path: "/"
      ManagePolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole

  TodobackendAdhocTaskDefinition: # Application management task
    Type: "AWS::ECS::TaskDefinition"
    Properties:
      ContainerDefinitions:
        - Name: todobackend
          Image: shamdockerhub/todobackend
          Memory: 245
          Environment:
            - Name: DJANGO_SETTINGS_MODULE
              Value: todobackend.settings.release
            - Name: MYSQL_HOST
              Value: { "Fn::GetAtt": ["DbInstance", "Endpoint.Address"] }
            - Name: MYSQL_USER
              Value: { "Ref": "DbUsername" }
            - Name: MYSQL_PASSWORD
              Value: { "Ref": "DbPassword" }
          MountPoints:
            - ContainerPath: /var/www/todobackend
              SourcePath: webroot

      Volumes:
        - Name: webroot
          Host:
            SourcePath: /ecs/webroot


  # Stack outputs
  Outputs:
    ElbDomainName:
      Description: "Public DNS name of Elastic Load Balancer"
      Value: { "Fn:GetAtt": [ "ElasticLoadBalancer", "DNSName" ] }
    EcsCluster:
      Description: "Amazon resource name (ARN) of Todobackend Ecs Cluster"
      Value: { "Ref": "EcsCluster" }
    TodobackendTaskDefinition:
      Description: "Amazon resource name (ARN) of Todobackend Task definition"
      Value: { "Ref": "TodobackendTaskDefinition"}
    TodobackendAdhocTaskDefinition:
      Description: "Amazon resource name(ARN) of Todobackend Adhoc Task Definition"
      Value: { "Ref": "TodobackendAdhocTaskDefinition" }
    TodobackendService:
      Description: "Amazon resource name (ARN) of Todobackend service"
      Value: { "Ref": "TodobackendService" }

Below is the CreateStack operation error:

$ ansible-playbook  site.yml --ask-vault-pass -e debug=true -vvv
ansible-playbook 2.5.1
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/user1/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/dist-packages/ansible
  executable location = /usr/bin/ansible-playbook
  python version = 2.7.15+ (default, Oct  7 2019, 17:39:04) [GCC 7.4.0]
Using /etc/ansible/ansible.cfg as config file
Vault password: 
Parsed /etc/ansible/hosts inventory source with ini plugin
 [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match
'all'

Read vars_file 'secrets.yml'
statically imported: /home/user1/git/ContDelivery_course/DjangoApp/todobackend-deploy/tasks/create_stack.yml
Read vars_file 'secrets.yml'
 [WARNING]: file /home/user1/git/ContDelivery_course/DjangoApp/todobackend-deploy/tasks/deploy_app.yml is
empty and had no tasks to include


PLAYBOOK: site.yml *****************************************************************************************************
1 plays in site.yml
Read vars_file 'secrets.yml'
Read vars_file 'secrets.yml'

PLAY [Todobackend deployment playbook] *********************************************************************************
META: ran handlers
Read vars_file 'secrets.yml'

TASK [task to create/update stack] *************************************************************************************
task path: /home/user1/git/ContDelivery_course/DjangoApp/todobackend-deploy/tasks/create_stack.yml:2
Using module file /usr/lib/python2.7/dist-packages/ansible/modules/cloud/amazon/cloudformation.py
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: mohet01-ubuntu
<127.0.0.1> EXEC /bin/sh -c 'echo ~ && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/user1/.ansible/tmp/ansible-tmp-1576716480.56-111176828564019 `" && echo ansible-tmp-1576716480.56-111176828564019="` echo /home/user1/.ansible/tmp/ansible-tmp-1576716480.56-111176828564019 `" ) && sleep 0'
<127.0.0.1> PUT /home/user1/.ansible/tmp/ansible-local-7506yaa0Y9/tmpl7pqXl TO /home/user1/.ansible/tmp/ansible-tmp-1576716480.56-111176828564019/cloudformation.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /home/user1/.ansible/tmp/ansible-tmp-1576716480.56-111176828564019/ /home/user1/.ansible/tmp/ansible-tmp-1576716480.56-111176828564019/cloudformation.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c 'AWS_DEFAULT_REGION=ca-central-1 /usr/bin/python2 /home/user1/.ansible/tmp/ansible-tmp-1576716480.56-111176828564019/cloudformation.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c 'rm -f -r /home/user1/.ansible/tmp/ansible-tmp-1576716480.56-111176828564019/ > /dev/null 2>&1 && sleep 0'
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_bfmm8l/ansible_module_cloudformation.py", line 314, in create_stack
    cfn.create_stack(**stack_params)
  File "/tmp/ansible_bfmm8l/ansible_modlib.zip/ansible/module_utils/cloud.py", line 150, in retry_func
    raise e
ClientError: An error occurred (ValidationError) when calling the CreateStack operation: [/Resources/EcsServiceRole/Type/AssumeRolePolicyDocument/Statement/0/Principal/Service/ecs.amazonaws.com] 'null' values are not allowed in templates

fatal: [localhost]: FAILED! => {
    "changed": false, 
    "invocation": {
        "module_args": {
            "aws_access_key": null, 
            "aws_secret_key": null, 
            "changeset_name": null, 
            "create_changeset": false, 
            "disable_rollback": false, 
            "ec2_url": null, 
            "notification_arns": null, 
            "profile": null, 
            "region": null, 
            "role_arn": null, 
            "security_token": null, 
            "stack_name": "todobackend", 
            "stack_policy": null, 
            "state": "present", 
            "tags": {
                "Environment": "test"
            }, 
            "template": "templates/stack.yml", 
            "template_body": null, 
            "template_format": "yaml", 
            "template_parameters": {
                "DbAvailabilityZone": "ca-central-1a", 
                "DbPassword": "ccccc", 
                "DbSubnets": "subnet-22222,subnet-33333", 
                "DbUsername": "todobackend", 
                "InstanceCount": "1", 
                "KeyPair": "admin", 
                "SubnetId": "subnet-33333", 
                "VpcId": "vpc-111111"
            }, 
            "template_url": null, 
            "termination_protection": null, 
            "validate_certs": true
        }
    }, 
    "msg": "Failed to create stack todobackend: An error occurred (ValidationError) when calling the CreateStack operation: [/Resources/EcsServiceRole/Type/AssumeRolePolicyDocument/Statement/0/Principal/Service/ecs.amazonaws.com] 'null' values are not allowed in templates An error occurred (ValidationError) when calling the CreateStack operation: [/Resources/EcsServiceRole/Type/AssumeRolePolicyDocument/Statement/0/Principal/Service/ecs.amazonaws.com] 'null' values are not allowed in templates - <class 'botocore.exceptions.ClientError'>."
}
    to retry, use: --limit @/home/user1/git/ContDelivery_course/DjangoApp/todobackend-deploy/site.retry

PLAY RECAP *************************************************************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1   

$

Why module_args dictionary have null values? How to resolve this error?

Ansible 2.5.1 is using Python 2.7

overexchange
  • 15,768
  • 30
  • 152
  • 347
  • I think the null values in module_args are fine (it just means that the module does not explicitly specify them, so boto will use the defaults). As the error message complains about the null values in the *template* itself, I would guess templates/stack.yml has a null (or undefined) value somewhere and that is the one that causes the issue. – Istvan Szekeres Dec 20 '19 at 00:13
  • @IstvanSzekeres Added `templates/stack.yml` into the query. Which resource has null value issue in `templates/stack.yml`? Looks like `DbUsername` typo while referring it – overexchange Dec 20 '19 at 00:27
  • @IstvanSzekeres Is `"template_body"` within `module_args` supposed to have yml script? – overexchange Dec 20 '19 at 00:35
  • According to https://docs.ansible.com/ansible/latest/modules/cloudformation_module.html one of (template / template_url / template_body) must be specified (=non-null), in your case template_url and template_body is null but it is ok as template is not null. This is why I guessed the issue could be in the file referenced by the template. – Istvan Szekeres Dec 20 '19 at 00:47
  • I cannot spot anything obviously wrong in the template, does it work if you run it directly (i.e. not with ansible)? – Istvan Szekeres Dec 20 '19 at 00:48
  • @IstvanSzekeres yes, `EcsServiceRole` has issue, it should be `[ "ecs.amazonaws.com"]`. I used curly braces – overexchange Dec 20 '19 at 00:56

0 Answers0