Update checkers in Gerrit after deployment

Add a playbook which updates the configuration of checkers in Gerrit.
It accepts a list of dictionaries as input, each corresponding to
a checker.  If the UUID of the checker does not already exist in
Gerrit, it is created.  If it does exist, it is updated.  Fields
which are not present in the input are not touched (nor are they
considered when comparing the declared state to the existing state).

Change-Id: I3e70b15717aa1ff89cdcf5dc4dc52187aa0347aa
diff --git a/.zuul.yaml b/.zuul.yaml
index 322bb2a..a67c3e3 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -4,7 +4,9 @@
     secrets:
       - secret: deploy-zuul-credentials
         name: zuul_deploy
-    run: playbooks/deploy.yaml
+    run:
+      - playbooks/deploy.yaml
+      - playbooks/checkers.yaml
     # Run on the executor only (an empty nodeset)
     nodeset:
       nodes: []
diff --git a/checkers.yaml b/checkers.yaml
new file mode 100644
index 0000000..69cb6d9
--- /dev/null
+++ b/checkers.yaml
@@ -0,0 +1,33 @@
+# A list of checkers which should be defined in Gerrit.
+#
+# If the UUID of the checker does not already exist in Gerrit, it is
+# created.  If it does exist, it is updated.  Fields which are not
+# present in the input are not touched (nor are they considered when
+# comparing the declared state to the existing state).
+
+checkers:
+  - uuid: "opendev-zuul-third-party-check:plugins_checks"
+    repository: "plugins/checks"
+    name: "Zuul Tests"
+    description: "Tests of Checks plugin with Zuul"
+    status: "DISABLED"
+
+  - uuid: "zuul-check:plugins_checks"
+    repository: "plugins/checks"
+    name: "Zuul Check Pipeline"
+    description: "Run Zuul jobs on new patchsets"
+
+  - uuid: "zuul-check:zuul_config"
+    repository: "zuul/config"
+    name: "Zuul Check Pipeline"
+    description: "Run Zuul jobs on new patchsets"
+
+  - uuid: "zuul-check:zuul_jobs"
+    repository: "zuul/jobs"
+    name: "Zuul Check Pipeline"
+    description: "Run Zuul jobs on new patchsets"
+
+  - uuid: "zuul-check:zuul_ops"
+    repository: "zuul/ops"
+    name: "Zuul Check Pipeline"
+    description: "Run Zuul jobs on new patchsets"
diff --git a/playbooks/checkers.yaml b/playbooks/checkers.yaml
new file mode 100644
index 0000000..8f86c78
--- /dev/null
+++ b/playbooks/checkers.yaml
@@ -0,0 +1,16 @@
+- name: Update checkers
+  gather_facts: false
+  hosts: localhost
+  tasks:
+    - name: Add scheduler to inventory
+      add_host:
+        name: 'zuul-scheduler-0'
+        ansible_kubectl_namespace: zuul
+        ansible_connection: kubectl
+    - name: Load checker data
+      include_vars:
+        file: "../checkers.yaml"
+    - name: Update Checkers
+      delegate_to: 'zuul-scheduler-0'
+      update_checkers:
+        checkers: "{{ checkers }}"
diff --git a/playbooks/library/update_checkers.py b/playbooks/library/update_checkers.py
new file mode 100644
index 0000000..5c2b7c4
--- /dev/null
+++ b/playbooks/library/update_checkers.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from ansible.module_utils.basic import AnsibleModule
+
+import requests
+import json
+
+TOKEN_URL = ('http://metadata.google.internal/computeMetadata/v1/'
+             'instance/service-accounts/default/token')
+GERRIT_URL = 'https://gerrit-review.googlesource.com'
+
+
+def get_checkers(token):
+    url = GERRIT_URL + '/a/plugins/checks/checkers/'
+    data = requests.get(url, cookies={'o': token}).text
+    return json.loads(data[4:])
+
+
+def update_checker(token, existing, spec):
+    """If the contents of spec differ from existing, update the checker"""
+    update = False
+    for k, v in spec.items():
+        if existing[k] != v:
+            update = True
+            break
+    if update:
+        url = GERRIT_URL + '/a/plugins/checks/checkers/' + spec['uuid']
+        data = requests.post(url, cookies={'o': token}, json=spec).text
+        return json.loads(data[4:])
+
+
+def create_checker(token, spec):
+    """Create a checker as specified"""
+    url = GERRIT_URL + '/a/plugins/checks/checkers/'
+    data = requests.post(url, cookies={'o': token}, json=spec).text
+    return json.loads(data[4:])
+
+
+def main():
+    module = AnsibleModule(
+        argument_spec=dict(
+            checkers=dict(required=True, type='list'),
+        )
+    )
+
+    data = requests.get(TOKEN_URL,
+                        headers={'Metadata-Flavor': 'Google'}).json()
+    token = data['access_token']
+    existing_checkers = get_checkers(token)
+    existing_checker_map = {c['uuid']:c for c in existing_checkers}
+
+    updated_checkers = []
+    for spec in module.params.get('checkers'):
+        if not spec.get('uuid'):
+            module.fail_json(msg="Checker UUID is required",
+                             checker=spec)
+        existing = existing_checker_map.get(spec['uuid'])
+        if existing:
+            updated = update_checker(token, existing, spec)
+            if updated:
+                updated_checkers.append(updated)
+        else:
+            updated = create_checker(token, spec)
+            if updated:
+                updated_checkers.append(updated)
+
+    module.exit_json(changed=(len(updated_checkers) > 0),
+                     updated_checkers=updated_checkers,
+                     existing_checkers=existing_checkers)
+
+
+if __name__ == "__main__":
+    main()