Add prepare-gerrit-repos role and gerrit-base job
This role sets up the gerrit submodules, and the base job defines
all of the repos which are needed to build gerrit. A test job
that inherits from it is added to exercise it.
Change-Id: Ibe5bb0866052c394a53f5f534daa6927a6880da2
diff --git a/playbooks/gerrit-base/pre.yaml b/playbooks/gerrit-base/pre.yaml
new file mode 100644
index 0000000..a15f0eb
--- /dev/null
+++ b/playbooks/gerrit-base/pre.yaml
@@ -0,0 +1,5 @@
+- name: Preparatory steps for all Gerrit build jobs
+ hosts: all
+ roles:
+ - ensure-bazelisk
+ - prepare-gerrit-repos
diff --git a/playbooks/test-gerrit-base.yaml b/playbooks/test-gerrit-base.yaml
new file mode 100644
index 0000000..eaa4fcf
--- /dev/null
+++ b/playbooks/test-gerrit-base.yaml
@@ -0,0 +1,20 @@
+- hosts: all
+ tasks:
+ - name: List submodule git dir
+ command: "ls src/gerrit.googlesource.com/gerrit/{{ item }}/.git"
+ args:
+ chdir: "{{ ansible_user_dir }}"
+ loop:
+ - modules/jgit
+ - plugins/codemirror-editor
+ - plugins/commit-message-length-validator
+ - plugins/delete-project
+ - plugins/download-commands
+ - plugins/gitiles
+ - plugins/hooks
+ - plugins/plugin-manager
+ - plugins/replication
+ - plugins/reviewnotes
+ - plugins/singleusergroup
+ - plugins/webhooks
+ - polymer-bridges
diff --git a/roles/prepare-gerrit-repos/README.rst b/roles/prepare-gerrit-repos/README.rst
new file mode 100644
index 0000000..81ec7bb
--- /dev/null
+++ b/roles/prepare-gerrit-repos/README.rst
@@ -0,0 +1,67 @@
+Prepare gerrit submodules
+
+Gerrit has a number of repos which are submodules of the Gerrit repo,
+and some plugins are expected to be built by being copied into the
+Gerrit repo. This is generally compatible with the way Zuul operates,
+but special care needs to be taken.
+
+Zuul prepares the git repository states for all of the projects
+involved in testing a change. These projects may include the project
+that the change is against, any other project if the change has a
+"Depends-On" footer pointing to a change in that other project, and
+any projects which the job specifies with "required-projects". These
+git repository states represent the proposed future state of the
+world, in all branches, with dependent changes applied.
+
+This matches well with Gerrit's submodule subscription system, in that
+if a change to Gerrit "Depends-On" a change to a plugin, then as soon
+as that plugin change merges, The submodule in the Gerrit repo will be
+updated to the new plugin sha. In other words, we can say with high
+confidence that the state of the world that was tested in Zuul is what
+actually resulted after the merge.
+
+However, Zuul itself does not perform any actions on submodules. A
+simple "git submodule update --init" would discard the state of the
+repositories that Zuul prepared and would invalidate our testing. But
+since Zuul has already prepared the repos, we don't need to use a "git
+submodule" command, we just need to move them into the correct
+location. That is what this role does.
+
+There is one edge case: if a repo does not have the branch that is
+being tested, then Gerrit's submodule subscription does not work.
+That means that Zuul may not have checked out the same git sha as the
+submodule pointer in the gerrit repo, and we can not assume that if a
+change in a plugin lands, that the submodule pointer will be updated.
+In this case, this role falls back to performing a "git submodule
+update --init". If, however, there is a dependent change in that
+repo, then this role will fail the job. That is an untestable
+situation that can only be resolved by merging the dependent change
+and manually updating the submodule pointer in the Gerrit repo.
+
+The best way to avoid that situation is to ensure that all the
+dependent projects have the same branches as Gerrit itself.
+
+**Role Variables**
+
+.. zuul:rolevar:: gerrit_project_mapping
+ :type: dict
+
+ A dictionary to map Gerrit sub-projects to their location in the
+ gerrit repo. This role iterates over every Zuul project and
+ assumes that it should be copied into the gerrit project with its
+ full project name. For example, the `plugins/download-commands`
+ project will be copied into the ``plugins/download-commands``
+ directory under gerrit. To specify an alternate location, add an
+ entry to this dictionary in the form ``project_name:
+ destination_dir``. To omit copying the project into the gerrit
+ repo, supply the empty string.
+
+ The following is the default value; it instructs the role not to
+ copy the gerrit project into itself, and to copy the jgit project
+ into ``modules/jgit``:
+
+ .. code-block:: yaml
+
+ gerrit_project_mapping:
+ gerrit: ''
+ jgit: modules/jgit
diff --git a/roles/prepare-gerrit-repos/defaults/main.yaml b/roles/prepare-gerrit-repos/defaults/main.yaml
new file mode 100644
index 0000000..ce0cbe7
--- /dev/null
+++ b/roles/prepare-gerrit-repos/defaults/main.yaml
@@ -0,0 +1,4 @@
+gerrit_project_mapping:
+ gerrit: ''
+ jgit: modules/jgit
+ 'zuul/jobs': ''
diff --git a/roles/prepare-gerrit-repos/tasks/main.yaml b/roles/prepare-gerrit-repos/tasks/main.yaml
new file mode 100644
index 0000000..d32320e
--- /dev/null
+++ b/roles/prepare-gerrit-repos/tasks/main.yaml
@@ -0,0 +1,20 @@
+# In case there is no matching branch we may need to check out the
+# actual sha defined in the parent repo. The default zuul remote,
+# file:///dev/null, doesn't work here because relative paths cause
+# it to be file:///dev/plugins/download-commands, which isn't a
+# thing. Removing the origin causes git to use relative local
+# filesystem paths.
+- name: Remove origin remote
+ command: "git remote rm origin"
+ args:
+ chdir: "{{ gerrit_root }}"
+
+- name: Move plugin repos into gerrit tree
+ include_tasks:
+ file: repo.yaml
+ vars:
+ project_dest: "{{ gerrit_project_mapping.get(project.name, project.name) }}"
+ when: "project_dest != ''"
+ loop: "{{ zuul.projects.values() | list }}"
+ loop_control:
+ loop_var: project
diff --git a/roles/prepare-gerrit-repos/tasks/repo.yaml b/roles/prepare-gerrit-repos/tasks/repo.yaml
new file mode 100644
index 0000000..f4da53f
--- /dev/null
+++ b/roles/prepare-gerrit-repos/tasks/repo.yaml
@@ -0,0 +1,44 @@
+- name: Prepare project
+ debug:
+ msg: "{{ project.name }} {{ project_dest }}"
+
+- name: Check if zuul.branch exists in repo
+ set_fact:
+ # If zuul checked out the branch we're testing, then it exists.
+ project_branch_exists: "{{ zuul.branch == project.checkout }}"
+
+- name: Check if repo has a dependent change
+ set_fact:
+ repo_has_dependent_change: "{{ zuul['items'] | selectattr('project.canonical_name', 'eq', zuul.project.canonical_name) | list | length | bool }}"
+
+- name: Check for unsatisfiable source repo condition
+ when:
+ - "project.canonical_name != zuul.project.canonical_name"
+ - "not project_branch_exists"
+ - "repo_has_dependent_change"
+ fail:
+ msg: >-
+ The repository {{ project.name }} does not contain the branch
+ under test ({{ zuul.branch }}), but this change depends on a
+ change to that project and branch. While Zuul is able to check
+ out the repos in the requested state, the branch mismatch means
+ that Gerrit's submodule subscription would not automatically
+ update the submodule pointer, and the merged state would not
+ reflect the tested state.
+
+ This configuration would be testable by creating a {{
+ zuul.branch }} branch in the {{ project.name }} repo.
+ Alternatively, you can merge the dependent change, manually
+ update the submodule pointer, then test this change again.
+
+# If there is no matching branch we need to check out the actual sha
+# defined in the parent repo.
+- name: Update submodule
+ when: "not project_branch_exists"
+ command: "git submodule update --init {{ project.name }}"
+ args:
+ chdir: "{{ gerrit_root }}"
+
+- name: Move repo into place
+ when: "project_branch_exists"
+ command: "mv -T -f {{ ansible_user_dir }}/{{ project.src_dir }} {{ gerrit_root }}/{{ project_dest }}"
diff --git a/roles/prepare-gerrit-repos/vars/main.yaml b/roles/prepare-gerrit-repos/vars/main.yaml
new file mode 100644
index 0000000..9f72e90
--- /dev/null
+++ b/roles/prepare-gerrit-repos/vars/main.yaml
@@ -0,0 +1 @@
+gerrit_root: "{{ ansible_user_dir }}/{{ zuul.projects['gerrit.googlesource.com/gerrit'].src_dir }}"
diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml
new file mode 100644
index 0000000..8107e4f
--- /dev/null
+++ b/zuul.d/jobs.yaml
@@ -0,0 +1,22 @@
+- job:
+ name: gerrit-base
+ description: |
+ Base job for building gerrit
+
+ This job sets up all the repos which are required for a gerrit build.
+ pre-run: playbooks/gerrit-base/pre.yaml
+ required-projects:
+ - gerrit
+ - jgit
+ - plugins/codemirror-editor
+ - plugins/commit-message-length-validator
+ - plugins/delete-project
+ - plugins/download-commands
+ - plugins/gitiles
+ - plugins/hooks
+ - plugins/plugin-manager
+ - plugins/replication
+ - plugins/reviewnotes
+ - plugins/singleusergroup
+ - plugins/webhooks
+ - polymer-bridges
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 73a1f7f..2c55f4a 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -2,3 +2,4 @@
check:
jobs:
- test-ensure-bazelisk
+ - test-gerrit-base
diff --git a/zuul.d/test-jobs.yaml b/zuul.d/test-jobs.yaml
index 52002b4..e6765df 100644
--- a/zuul.d/test-jobs.yaml
+++ b/zuul.d/test-jobs.yaml
@@ -14,3 +14,13 @@
files:
- roles/ensure-bazelisk/.*
- playbooks/test-ensure-bazelisk.yaml
+
+- job:
+ name: test-gerrit-base
+ parent: gerrit-base
+ run: playbooks/test-gerrit-base.yaml
+ nodeset: temp-debian
+ files:
+ - roles/ensure-bazelisk/.*
+ - roles/prepare-gerrit-repos/.*
+ - playbooks/test-gerrit-base.yaml