How to escape JSON in Ansible playbook - docker

I have the following YAML Ansible playbook file which I intent do use to capture some information from my docker containers:
---
# Syntax check: /usr/bin/ansible-playbook --syntax-check --inventory data/config/host_inventory.yaml data/config/ansible/docker_containers.yaml
- hosts: hosts
gather_facts: no
tasks:
- name: Docker ps output - identify running containers
shell: "/usr/bin/docker ps --format '{\"ID\":\"{{ .ID }}\", \"Image\": \"{{ .Image }}\", \"Names\":\"{{ .Names }}\"}'"
register: docker_ps_output
- name: Show content of docker_ps_output
debug:
msg: docker_ps_output.stdout_lines
But escaping is not working, Ansible gives me the middle finger when I try to run the playbook:
PLAY [hosts] ***********************************************************************************************************************************************************
TASK [Docker ps output - identify running containers] **********************************************************************************************************************************************
fatal: [myhost.com]: FAILED! => {"msg": "template error while templating string: unexpected '.'. String: /usr/bin/docker ps --format ''{\"ID\":\"{{ .ID }}\", \"Image\": \"{{ .Image }}\", \"Names\":\"{{ .Names }}\"}''"}
to retry, use: --limit #/tmp/docker_containers.retry
PLAY RECAP *****************************************************************************************************************************************************************************************
myhost.com : ok=0 changed=0 unreachable=0 failed=1
The original command I'm trying to run:
/usr/bin/docker ps --format '{"ID":"{{ .ID }}", "Image": "{{ .Image }}", "Names":"{{ .Names }}"}'

I would suggest to use a block scalar. Your problem is that {{ .ID }} etc is processed by Ansible's Jinja templating engine when it should not. Probably the most readable way around this is:
---
# Syntax check: /usr/bin/ansible-playbook --syntax-check --inventory data/config/host_inventory.yaml data/config/ansible/docker_containers.yaml
- hosts: hosts
gather_facts: no
tasks:
- name: Docker ps output - identify running containers
shell: !unsafe >-
/usr/bin/docker ps --format
'{"ID":"{{ .ID }}", "Image": "{{ .Image }}", "Names":"{{ .Names }}"}'
register: docker_ps_output
- name: Show content of docker_ps_output
debug:
msg: docker_ps_output.stdout_lines
>- starts a folded block scalar, in which you do not need to escape anything and newlines are folded into spaces. The tag !unsafe prevents the value to be processed with Jinja.

If you want to avoid templating, you need to cover double-brackets by another double-brackets:
{{ thmthng }}
should look like:
{{ '{{' }} thmthng {{ '}}' }}
Your playbook:
---
- hosts: hosts
gather_facts: no
tasks:
- name: Docker ps output - identify running containers
shell: "docker ps -a --format '{\"ID\": \"{{ '{{' }} .ID {{ '}}' }}\", \"Image\": \"{{ '{{' }} .Image {{ '}}' }}\", \"Names\" : \"{{ '{{' }} .Names {{ '}}' }}}\"'"
register: docker_ps_output
- name: Show content of docker_ps_output
debug:
var: docker_ps_output.stdout_lines

Related

Restart multiple Docker containers using Ansible

how do i dynamically restart all my docker containers from Ansible? I mean i know a way where i can define my containers in a variable and loop through them but what i want to achieve is this -
Fetch the currently running containers and restart all or some of them one by one through some loop.
How to achieve this using Ansible?
Docker explanation
Retrieve name/image for all the running container:
docker container ls -a --format '{{.Names}} {{.Image}}'
You could also filter the output of the docket container command to a specific image name, thanks to the --filter ancestor=image_name option:
docker container ls -a --filter ancestor=alpine --format '{{.Names}} {{.Image}}'
Ansible integration:
First I would define some filters as Ansible variables:
vars:
- image_v1: '--filter ancestor=my_image:v1'
- image_v2: '--filter ancestor=my_image:v2'
Them I will execute the docker container command in a dedicated task and save the command output to an Ansible variable:
- name: Get images name
command: docker container ls -a {{ image_v1 }} {{ image_v2 }} --format "{{ '{{' }}.Names {{ '}}' }} {{ '{{' }}.Image {{ '}}' }}"
register: docker_images
Finally I will iterate over it and use it into the docker_container ansible module:
- name: Restart images
docker_container:
name: "{{ item.split(' ')[0]}}"
image: "{{ item.split(' ')[1]}}"
state: started
restart: yes
loop: "{{ docker_images.stdout_lines}}"
final playbook.yml
---
- hosts: localhost
gather_facts: no
vars:
- image_v1: '--filter ancestor=my_image:v1'
- image_v2: '--filter ancestor=my_image:v2'
tasks:
- name: Get images name
command: docker container ls -a {{ image_v1 }} {{ image_v2 }} --format "{{ '{{' }}.Names {{ '}}' }} {{ '{{' }}.Image {{ '}}' }}"
register: docker_images
- name: Restart images
docker_container:
name: "{{ item.split(' ')[0]}}"
image: "{{ item.split(' ')[1]}}"
state: started
restart: yes
loop: "{{ docker_images.stdout_lines}}"

Ansible workflow for building Docker image and recreating Docker container if image was changed?

I've been struggling with figuring out what the proper Ansible workflow is for deploying a Docker image and recreating a Docker container if the image has changed.
Here's the task list of a role I initially thought would work:
- name: Deploy Source
synchronize:
archive: yes
checksum: yes
compress: yes
dest: '/tmp/{{ app_name }}'
src: ./
- name: Build Docker Image
docker_image:
name: '{{ docker_image_name }}'
path: '/tmp/{{ app_name }}'
rm: yes
state: present
register: build_docker_image
- name: Create Docker Container
docker_container:
image: '{{ docker_image_name }}'
keep_volumes: yes
name: '{{ docker_container_name }}'
recreate: '{{ true if build_docker_image.changed else omit }}'
state: started
This does not work because the Ansible docker_image module does not offer a state: latest option. state: present only checks if the image exists and not if it's up to date. This means that even if the Dockerfile has changed, the image will not be rebuilt. docker_image does offer a force: yes option, but this will always recreate the image regardless of whether there was a change to the Dockerfile. When force: yes is used, it makes sense to me that it's better to always recreate containers running the image to prevent them from pointing to dangling Docker images.
What am I missing? Is there a better alternative?
User viggeh provided a workaround on the Ansible GitHub which I've adapted to my needs as follows:
- name: Deploy Source
synchronize:
archive: yes
checksum: yes
compress: yes
dest: '/tmp/{{ app_name }}'
src: ./
- name: Get Existing Image ID
command: 'docker images --format {% raw %}"{{.ID}}"{% endraw %} --no-trunc {{ docker_image_name }}:{{ docker_image_tag }}'
register: image_id
changed_when: image_id.rc != 0
- name: Build Docker Image
docker_image:
force: yes
name: '{{ docker_image_name }}'
path: '/tmp/{{ app_name }}'
rm: yes
state: present
tag: '{{ docker_image_tag }}'
register: image_build
changed_when: image_id.stdout != image_build.image.Id
- name: Create Docker Container
docker_container:
image: '{{ docker_image_name }}'
keep_volumes: yes
name: '{{ docker_container_name }}'
recreate: '{{ True if image_build.changed else omit }}'
state: started

Golang template in Ansible

I'm passing as log_options the following dict in the Ansible package docker_container:
log_options:
tag: "{% raw %}{{.ImageName}}/{{.Name}}/{{.ID}}{% endraw %}"
I've already tried with the above trick but it doesn't work the tag is not created in the container. Any idea?
You should escape every double curly brackets separately, not whole expression:
tag: "{{ '{{' }}.ImageName{{ '}}' }}/{{ '{{' }}.Name{{ '}}' }}/{{ '{{' }}.ID{{ '}}' }}"

Error while using vagrant to deploy ruby/rbenv using railsbox

I used railbox to create a configuration that I can deploy through vagrant.
However, the setup is stopped by the following error.
==> myapp: fatal: [127.0.0.1]: FAILED! => {"failed": true, "msg": "the field 'args' has an invalid value, which appears to include a variable that is undefined. The error was: 'ansible.vars.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'git'\n\nThe error appears to have been in '/ansible/roles/ruby/tasks/rbenv.yml': line 15, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: Install plugins\n ^ here\n"}
The content of rbenv.yml:
---
- name: Install libffi-dev
apt: name=libffi-dev
- name: Clone rbenv repository to ~/.rbenv
git: repo={{ rbenv_repo }} dest={{ rbenv_path }} version={{ rbenv_version }} accept_hostkey=yes
sudo_user: '{{ user_name }}'
- name: Create rbenv.sh
template: src=rbenv.sh.j2 dest={{ profile_d_path }}/rbenv.sh owner={{ user_name }} group={{ group_name }}
- name: Create plugins directory
file: path={{ rbenv_plugins_path }} state=directory owner={{ user_name }} group={{ group_name }}
- name: Install plugins
git: repo={{ item.git }} dest={{ rbenv_plugins_path }}/{{ item.name }} version={{ item.version }} accept_hostkey=yes
sudo_user: '{{ user_name }}'
with_items: rbenv_plugins
- name: Check if ruby installed
shell: '{{ rbenv_bin }} versions | grep -q {{ rbenv_ruby_version }}'
register: ruby_installed
ignore_errors: yes
sudo_user: '{{ user_name }}'
- name: Install ruby
command: '{{ rbenv_bin }} install {{ rbenv_ruby_version }}'
sudo_user: '{{ user_name }}'
when: ruby_installed|failed
- name: Set global ruby version
command: '{{ rbenv_bin }} global {{ rbenv_ruby_version }}'
sudo_user: '{{ user_name }}'
- name: Rehash rbenv
command: '{{ rbenv_bin }} rehash'
sudo_user: '{{ user_name }}'
What is wrong with the yml file?
If you run a recent version of Ansible (I believe it was changed for 2.2) you need to write with jinja2 syntax
- name: Install plugins
git: repo={{ item.git }} dest={{ rbenv_plugins_path }}/{{ item.name }} version={{ item.version }} accept_hostkey=yes
sudo_user: '{{ user_name }}'
with_items: '{{ rbenv_plugins }}'

Why ansible keeps recreating docker containers with state "started"

I have a docker container managed by Ansible. Every time I start the container with Ansible it is recreated instead of just started.
Here are the Ansible commands I use to stop/start the container:
ansible-playbook <playbook> -i <inventory> --extra-vars "state=stopped"
ansible-playbook <playbook> -i <inventory> --extra-vars "state=started"
Here's the Ansible taks I use to manage container. The only thing that changes between "stop" and "start" command is {{ state }}.
- docker:
name: "{{ postgres_container_name }}"
image: "{{ postgres_image_name }}"
state: "{{ state }}"
ports:
- "{{ postgres_host_port }}:{{ postgres_guest_port }}"
env:
POSTGRES_USER: "{{ postgres_user }}"
POSTGRES_PASSWORD: "{{ postgres_password }}"
POSTGRES_DB: "{{ postgres_db }}"
When I start, stop and start the container I get the following verbose output from Ansible command:
changed: [127.0.0.1] => {"ansible_facts": {"docker_containers": [{"Id": "ab1c0f6cc30de33aba31ce93671267783ba08a1294df40556870e66e8bf77b6d", "Warnings": null}]}, "changed": true, "containers": [{"Id": "ab1c0f6cc30de33aba31ce93671267783ba08a1294df40556870e66e8bf77b6d", "Warnings": null}], "msg": "removed 1 container, started 1 container, created 1 container.", "reload_reasons": null, "summary": {"created": 1, "killed": 0, "pulled": 0, "removed": 1, "restarted": 0, "started": 1, "stopped": 0}}
It states that the container changed, was removed, created and started.
Could you tell me why Ansible sees my container as changed and recreates it instead of starts?
Ansible's docker module will first remove any stopped containers with the same name when you use it with the state of started.
The module docs don't really make it all that clear but there is a comment explaining this in the source code in the started function.

Resources