Casting a variable as an integer

Hi all,

I ran into a problem yesterday trying to set up a AWS instance via an
Ansible playbook. I'm running Ansible 2.7.1 on Gentoo (AMD64).

I have a host variable, `volume_size`, which is the size of the volume
in GB… and in my playbook, I tried putting the following code:

- name: Provision instances
  ec2_instance:
    state: present
    security_group: "{{hostvars[item]['security_group']}}"
    key_name: "{{aws_key_name}}"
    region: "{{hostvars[item]['region']}}"
    instance_type: "{{hostvars[item]['instance_type']}}"
    image_id: "{{hostvars[item]['image_id']}}"
    wait: true
    wait_timeout: "{{hostvars[item]['wait_timeout']}}"
    network:
      assign_public_ip: true
    tags:
      Name: '{{item}}'
      JiraStory: "{{hostvars[item].get('jira_story','N/A')}}"
    volumes:
    - device_name: /dev/sdb
      ebs:
        encrypted: "{{hostvars[item].get('encrypted','false') == 'true'}}"
        volume_size: "{{hostvars[item].get('volume_size',100)}}"
        volume_type: "{{hostvars[item].get('volume_type','gp2')}}"
  loop: "{{ groups.aws }}"
  when: "'aws' in groups"
  register: ec2
  tags:
  - aws

That yielded this error:

An exception occurred during task execution. To see the full traceback, use -vvv. The error was: Invalid type for parameter BlockDeviceMappings[0].Ebs.VolumeSize, value: 100, type: <type 'str'>, valid types: <type 'int'>, <type 'long'>
failed: [localhost] (item=awstest.on.widesky.cloud) => {"boto3_version": "1.7.19", "botocore_version": "1.10.19", "changed": false, "item": "awstest.on.widesky.cloud", "msg": "Failed to create new EC2 instance: Parameter validation failed:\nInvalid type for parameter BlockDeviceMappings[0].Ebs.Encrypted, value: true, type: <type 'str'>, valid types: <type 'bool'>\nInvalid type for parameter BlockDeviceMappings[0].Ebs.VolumeSize, value: 100, type: <type 'str'>, valid types: <type 'int'>, <type 'long'>"}
        to retry, use: --limit @/home/stuartl/vrt/projects/widesky/ops/deploy/site.retry

I thought, maybe it's being read from the hosts file as a string and
needs to be passed through the `int` constructor. Tried wrapping the
variable in `int()`, that failed, but then I read the jinja2 docs which
said to use `{{variable | int}}`. No problem…

    volumes:
    - device_name: /dev/sdb
      ebs:
        encrypted: "{{hostvars[item].get('encrypted','false') == 'true'}}"
        volume_size: "{{hostvars[item].get('volume_size',100) | int}}"
        volume_type: "{{hostvars[item].get('volume_type','gp2')}}"

I got the same error.

I suspect that the integer is then getting cast *back* to a string
before being passed onto `boto3`, which is then barfing on it.

Is there a way to tell Ansible that a particular template expression is
to be interpreted as an integer and not as a string?

You could try to do it the Ansible way and not the Python way to see if that works.

   volume_size: "{{hostvars[item].volume_size | default(100) | int}}"

I understood Ansible basically used the Python language within Jinja2.
What's the difference between these two lines?

  {{hostvars[item].volume_size | default(100) | int}}
  {{hostvars[item].get('volume_size',100) | int}}

I'd try it out, but I've now hit a second issue with `ec2_instance`, and
that is it keeps insisting on modifying the *existing* instance I
created for a customer instead of creating a *new* instance like I
intended. (This is in spite of the customer instance being a
"t3.xlarge", and my new instance having a type of "t3.nano".)

How does one tell `ec2_instance` to "leave the existing instance(s)
alone and create a new one"?

All software have bugs and the Ansible way is probably used by most and have therefore been thoroughly tested.
So the easiest way to check that out is to test it.

I understood Ansible basically used the Python language within Jinja2.
What's the difference between these two lines?

  {{hostvars[item].volume_size | default(100) | int}}
  {{hostvars[item].get('volume_size',100) | int}}

All software have bugs and the Ansible way is probably used by most and have therefore been thoroughly tested.
So the easiest way to check that out is to test it.

As explained, I'm unable to test because `ec2_instance` tries to clobber
an existing instance instead of creating a new one.

I also see that both call the `| int` filter in `jinja2`. So assuming
`jinja2` is doing its job, the return value in *both* cases should be an
`int`. `boto3` thinks though it is getting a `str`, which leads me to
believe something in between `jinja2` and `boto3` is casting it *back*
to a `str`.

From `jinja2`'s perspective, there's no such thing as the "Ansible way",

it's *all* Python. I therefore have trouble understanding what's
happening behind the scenes.

If you have Ansible 2.7+ and jinja2 >= 2.10 you should be able to preserve the int types with the DEFAULT_JINJA2_NATIVE configuration https://docs.ansible.com/ansible/latest/reference_appendices/config.html#default-jinja2-native. It requires you to explicitly enable through either ansible.cfg or with an environment variable which is documented in that link.

Thanks

Jordan