I'm new to Ansible (about 1 week experience). I have this playbook that creates a deployment in my Kubernetes cluster using the module kubernetes.core.k8s
.
...
vars:
worker_count: "{{ lookup('ansible.builtin.env', 'worker_count') }}"
tasks:
- name: Create a deployment
kubernetes.core.k8s:
state: present
definition:
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
namespace: default
labels:
app: worker
spec:
replicas: "{{ worker_count }}" <--- the problem
selector:
matchLabels:
app: worker
...
In the vars
section, I retrieve worker_count
from env var, and use it in the replicas
field.
When I tried to run it with Python package ansible_runner
:
import ansible_runner
ansible_runner.run(playbook="path/to/pb", envvars={"worker_count": 5})
I got the error (I tried running the playbook with ansible-playbook
CLI and got the same error).
fatal: [localhost]: FAILED! => {"changed": false, "error": 400, "msg": "Deployment worker: Failed to create object: b'{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Deployment in version \\"v1\\" cannot be handled as a Deployment: json: cannot unmarshal string into Go struct field DeploymentSpec.spec.replicas of type int32","reason":"BadRequest","code":400}\n'", "reason": "Bad Request", "status": 400}
So I tried to change the replicas
line to:
replicas: "{{ worker_count | int }}"
But it still gives the same error. What I understand is by using the additional | int
, it is cast to integer but then returned as string again. You can do integer operations (addition, division, etc.) to the variable, but it will still return the result in String with the quotes "
(source1, source2). That's why it still gives the same error.
So I figured I need to return the variable without the quote, but I can't find a way for this after hours looking on the internet.
Note: In the below examples, Ansible callback yaml is used to render stdout.
See:
Or, display the help in CLI
ansible-doc -t callback yaml
The below playbook explains what's going on
shell> cat pb2.yml
- hosts: localhost
vars:
w1: 5
w2: '5'
w3: "{{ w1 }}"
w4: "{{ w1 + 1 - 1 }}"
tasks:
- debug:
msg: |
w1: {{ w1 }}
w1|type_debug: {{ w1|type_debug }}
w2: {{ w2 }}
w2|type_debug: {{ w2|type_debug }}
w3: {{ w3 }}
w3|type_debug: {{ w3|type_debug }}
w4: {{ w4 }}
w4|type_debug: {{ w4|type_debug }}
gives (abridged)
shell> ansible-playbook pb2.yml
...
msg: |-
w1: 5
w1|type_debug: int
w2: 5
w2|type_debug: AnsibleUnicode
w3: 5
w3|type_debug: int
w4: 5
w4|type_debug: str
The type of w1 is an integer and the type of w2 is a string. The type of w3 is also an integer because Jinja is not used in this simple assignment. But Jinja is used as long as there are any operations in the template. This is the reason for w4 being a string.
The result of a Jinja template is a string. You can change it by using DEFAULT_JINJA2_NATIVE:
This option preserves variable types during template operations.
This makes the type of w4 an integer.
shell> ANSIBLE_JINJA2_NATIVE=true ansible-playbook pb2.yml
...
w4|type_debug: int
The below playbook will test the environment variable worker_count
- hosts: localhost
vars:
worker_count: "{{ lookup('env', 'worker_count') }}"
definition:
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
namespace: default
labels:
app: worker
spec:
replicas: "{{ worker_count }}"
selector:
matchLabels:
app: worker
tasks:
- debug:
msg: |
worker_count: {{ worker_count }}
worker_count|type_debug: {{ worker_count|type_debug }}
definition.spec.replicas: {{ definition.spec.replicas }}
definition.spec.replicas|type_debug: {{ definition.spec.replicas|type_debug }}
gives (abridged)
shell> worker_count=5 ansible-playbook pb.yml
...
msg: |-
worker_count: 5
worker_count|type_debug: AnsibleUnsafeText
definition.spec.replicas: 5
definition.spec.replicas|type_debug: AnsibleUnsafeText
You'll get strings unless you set ANSIBLE_JINJA2_NATIVE=true
shell> ANSIBLE_JINJA2_NATIVE=true worker_count=5 ansible-playbook pb.yml
...
msg: |-
worker_count: 5
worker_count|type_debug: int
definition.spec.replicas: 5
definition.spec.replicas|type_debug: int
Ansible Runner recognizes this option too. The Python script
shell> cat pb.py
import ansible_runner
r = ansible_runner.run(private_data_dir="/scratch/tmp7/test-565",
playbook="pb.yml",
envvars={"worker_count": 5})
gives (abridged)
shell> python3 pb.py
PLAY [localhost] ***************************************************************
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": "worker_count: 5\nworker_count|type_debug: AnsibleUnsafeText\ndefinition.spec.replicas: 5\ndefinition.spec.replicas|type_debug: AnsibleUnsafeText\n"
}
...
You'll also get strings unless you set ANSIBLE_JINJA2_NATIVE=true
shell> ANSIBLE_JINJA2_NATIVE=true python3 pb.py
PLAY [localhost] ***************************************************************
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": "worker_count: 5\nworker_count|type_debug: int\ndefinition.spec.replicas: 5\ndefinition.spec.replicas|type_debug: int\n"
}
...