From 0b9fad9583794e3f35ff18250de8f6cf9ec31353 Mon Sep 17 00:00:00 2001
From: Ian Wienand <iwienand@redhat.com>
Date: Wed, 19 Aug 2020 15:26:58 +1000
Subject: [PATCH] update-json-file: add role to combine values into a .json

Ansible doens't really have a great built-in way to modify a json file
(unlike ini files).  The extant docker role does what seems to be the
usual standard, which is slurp in the file, parse it and then write it
back out.

In a follow-on change (I338616c41a65b007d56648fdab6da2a6a6b909f4) we
need to set some more values in the docker configuration .json file,
which made me think it's generic enough that we can have a role to
basically run read the file, |combine and write it back out.

This adds such a role with various options, and converts the existing
json configuration update in ensure-docker to use it.

Change-Id: I155a409945e0175249cf2dc630b839c7a97fb452
---
 doc/source/general-roles.rst                |  1 +
 roles/ensure-docker/tasks/docker-setup.yaml | 32 +++-----------
 roles/update-json-file/README.rst           | 48 +++++++++++++++++++++
 roles/update-json-file/defaults/main.yaml   |  3 ++
 roles/update-json-file/tasks/main.yaml      | 38 ++++++++++++++++
 test-playbooks/update-json-file.yaml        | 40 +++++++++++++++++
 zuul-tests.d/general-roles-jobs.yaml        | 10 +++++
 7 files changed, 146 insertions(+), 26 deletions(-)
 create mode 100644 roles/update-json-file/README.rst
 create mode 100644 roles/update-json-file/defaults/main.yaml
 create mode 100644 roles/update-json-file/tasks/main.yaml
 create mode 100644 test-playbooks/update-json-file.yaml

diff --git a/doc/source/general-roles.rst b/doc/source/general-roles.rst
index b856c517d..e439836a6 100644
--- a/doc/source/general-roles.rst
+++ b/doc/source/general-roles.rst
@@ -45,6 +45,7 @@ General Purpose Roles
 .. zuul:autorole:: start-zuul-console
 .. zuul:autorole:: test-setup
 .. zuul:autorole:: trigger-readthedocs
+.. zuul:autorole:: update-json-file
 .. zuul:autorole:: upload-artifactory
 .. zuul:autorole:: upload-git-mirror
 .. zuul:autorole:: validate-dco-license
diff --git a/roles/ensure-docker/tasks/docker-setup.yaml b/roles/ensure-docker/tasks/docker-setup.yaml
index e398c70ee..52c39b2c2 100644
--- a/roles/ensure-docker/tasks/docker-setup.yaml
+++ b/roles/ensure-docker/tasks/docker-setup.yaml
@@ -15,34 +15,14 @@
 - name: Update docker daemon configuration
   when: docker_userland_proxy is defined
   block:
-    - name: Check if docker daemon configuration exists
-      stat:
-        path: /etc/docker/daemon.json
-      register: docker_config_stat
-    - name: Load docker daemon configuration
-      when: docker_config_stat.stat.exists
-      slurp:
-        path: /etc/docker/daemon.json
-      register: docker_config
-    - name: Parse docker daemon configuration
-      when: docker_config_stat.stat.exists
-      set_fact:
-        docker_config: "{{ docker_config.content | b64decode | from_json }}"
-    - name: Set default docker daemon configuration
-      when: not docker_config_stat.stat.exists
-      set_fact:
-        docker_config: {}
-    - name: Add registry to docker daemon configuration
+    - name: Add proxy config
+      include_role:
+        name: update-json-file
       vars:
-        new_config:
+        update_json_file_name: /etc/docker/daemon.json
+        update_json_file_combine:
           userland-proxy: "{{ docker_userland_proxy }}"
-      set_fact:
-        docker_config: "{{ docker_config | combine(new_config) }}"
-    - name: Save docker daemon configuration
-      copy:
-        content: "{{ docker_config | to_nice_json }}"
-        dest: /etc/docker/daemon.json
-      become: true
+        update_json_file_become: true
 
 - name: Reset ssh connection to pick up docker group
   meta: reset_connection
diff --git a/roles/update-json-file/README.rst b/roles/update-json-file/README.rst
new file mode 100644
index 000000000..1f93f211a
--- /dev/null
+++ b/roles/update-json-file/README.rst
@@ -0,0 +1,48 @@
+Update JSON file
+
+This role reads a JSON file, merges it with supplied values using
+Ansible's ``combine`` filter and writes it back out.  It is useful for
+updating configuration files.  Note this role is not currently
+idempotent and will write the file each time.
+
+**Role Variables**
+
+.. zuul:rolevar:: update_json_file_name
+   :type: path
+
+   The path to the file to edit.
+
+.. zuul:rolevar:: update_json_file_combine
+   :type: object
+
+   The data to be combined with the existing file data.  This uses the
+   Jinja ``combine`` filter.
+
+.. zuul:rolevar:: update_json_file_debug
+   :default: false
+   :type: bool
+
+   If enabled, output the combined result in a debug task.
+
+.. zuul:rolevar:: update_json_file_default
+   :default: {}
+
+   The default value if the given file does not exist.
+
+.. zuul:rolevar:: update_json_file_become
+   :type: bool
+   :default: false
+
+   The ``become:`` status when writing out the new file.
+
+.. zuul:rolevar:: update_json_file_mode
+
+   The mode for the combined file.
+
+.. zuul:rolevar:: update_json_file_user
+
+   The user for the combined file.
+
+.. zuul:rolevar:: update_json_file_group
+
+   The group for the combined file.
diff --git a/roles/update-json-file/defaults/main.yaml b/roles/update-json-file/defaults/main.yaml
new file mode 100644
index 000000000..de0dd5d71
--- /dev/null
+++ b/roles/update-json-file/defaults/main.yaml
@@ -0,0 +1,3 @@
+update_json_file_debug: false
+update_json_file_become: false
+update_json_file_default: {}
diff --git a/roles/update-json-file/tasks/main.yaml b/roles/update-json-file/tasks/main.yaml
new file mode 100644
index 000000000..7362f17af
--- /dev/null
+++ b/roles/update-json-file/tasks/main.yaml
@@ -0,0 +1,38 @@
+- name: Check if file exists
+  stat:
+    path: '{{ update_json_file_name }}'
+  register: _stat
+
+- name: Load existing file
+  when: _stat.stat.exists
+  slurp:
+    path: '{{ update_json_file_name }}'
+  register: _file
+
+- name: Parse exisiting file
+  when: _stat.stat.exists
+  set_fact:
+    _config: "{{ _file.content | b64decode | from_json }}"
+
+- name: Set default for non existing file
+  when: not _stat.stat.exists
+  set_fact:
+    _config: '{{ update_json_file_default }}'
+
+- name: Combine new configuration
+  set_fact:
+    _config: "{{ _config | combine(update_json_file_combine) }}"
+
+- name: Debug _config variable
+  debug:
+    var: _config
+  when: update_json_file_debug
+
+- name: Save new file
+  copy:
+    content: "{{ _config | to_nice_json }}"
+    dest: '{{ update_json_file_name }}'
+    mode: '{{ update_json_file_mode | default(omit) }}'
+    owner: '{{ update_json_file_owner | default(omit) }}'
+    group: '{{ update_json_file_group | default(omit) }}'
+  become: '{{ update_json_file_become }}'
diff --git a/test-playbooks/update-json-file.yaml b/test-playbooks/update-json-file.yaml
new file mode 100644
index 000000000..1f5c4b371
--- /dev/null
+++ b/test-playbooks/update-json-file.yaml
@@ -0,0 +1,40 @@
+- hosts: all
+  tasks:
+
+    - include_role:
+        name: update-json-file
+      vars:
+        update_json_file_name: test.json
+        update_json_file_default:
+          foo: bar
+        update_json_file_combine:
+          moo: boo
+        update_json_file_debug: true
+
+    - include_role:
+        name: update-json-file
+      vars:
+        update_json_file_name: test.json
+        update_json_file_combine:
+          new: content
+          a:
+            - list
+            - of
+            - items
+        update_json_file_debug: true
+
+    - name: Load resulting merged file
+      slurp:
+        path: 'test.json'
+      register: _file
+
+    - name: Parse merged file
+      set_fact:
+        _config: "{{ _file.content | b64decode | from_json }}"
+
+    - assert:
+        that:
+          - _config['foo'] == 'bar'
+          - _config['moo'] == 'boo'
+          - _config['new'] == 'content'
+          - _config['a'] == ['list', 'of', 'items']
diff --git a/zuul-tests.d/general-roles-jobs.yaml b/zuul-tests.d/general-roles-jobs.yaml
index 4500fcdea..2c1c5a8b9 100644
--- a/zuul-tests.d/general-roles-jobs.yaml
+++ b/zuul-tests.d/general-roles-jobs.yaml
@@ -642,6 +642,14 @@
         - name: fedora-32
           label: fedora-32
 
+- job:
+    name: zuul-jobs-test-update-json-file
+    description: Test the json edit role
+    run: test-playbooks/update-json-file.yaml
+    files:
+      - test-playbooks/update-json-file.yaml
+      - roles/update-json-file/.*
+
 # -* AUTOGENERATED *-
 #  The following project section is autogenerated by
 #    tox -e update-test-platforms
@@ -692,6 +700,7 @@
         - zuul-jobs-test-upload-git-mirror
         - zuul-jobs-test-shake-build
         - zuul-jobs-test-ensure-zookeeper
+        - zuul-jobs-test-update-json-file
     gate:
       jobs: &id001
         - zuul-jobs-test-add-authorized-keys
@@ -732,5 +741,6 @@
         - zuul-jobs-test-upload-git-mirror
         - zuul-jobs-test-shake-build
         - zuul-jobs-test-ensure-zookeeper
+        - zuul-jobs-test-update-json-file
     periodic-weekly:
       jobs: *id001