cor...@inaugust.com (James E. Blair) writes:
> That looks like a great place to start, thanks! I think I understand
> most of what needs to happen there. I'll put together a series of
> changes that work toward this and come back here with a summary (and
> perhaps further questions). I'm hoping this will be a useful way for
> folks to become familiar with this as we go. I should be able to show
> most of this working (or not) without even merging any changes.
>
> One of the exceptions to that is this change, which I have gone ahead
> and merged:
>
>
https://gerrit-review.googlesource.com/c/zuul/ops/+/254715
>
> And you can see Zuul updated the deployed config here:
>
>
https://gerrit-zuul.inaugust.com/t/gerrit/build/7dc1d142bdd94b3facb2beeec4bacf80/console
>
> The change adds a new node type ("label") to the system, therefore it's
> something that needs to actually be merged before it takes effect. Now
> that it has been, the next changes I write can use that label to run the
> new jobs.
Hi, I think I have a basic job for building plugins working!
First, let me jump to the end and show you what it will look like to add
this job to a plugin repo:
https://gerrit-review.googlesource.com/c/plugins/checks/+/255215
There's a lot of stuff I'm going to talk about below, and some of it is
a little complicated, but it's all in service of making it easy for a
plugin to add this job, and to build other jobs like it.
I'd also like to emphasize that the entire process for building and
adding this job to a repo is contained in the changes below (all but one
of which haven't even been merged) -- the configuration is entirely
dynamic and git-driven. Okay, let's dive in!
0) Add the required projects for Gerrit
https://gerrit-review.googlesource.com/c/zuul/ops/+/255012
This is the one change I had to merge in order for the rest of the
series to work. Zuul needs a list of all the projects it works with,
and that can't be dynamically updated; it's a configuration change that
has to merge and be deployed before it takes effect (it could be
dangerous to let someone add a project to Zuul without the
administrator's permission). This change adds all of the projects
needed to build Gerrit, plus one extra optional plugin.
Zuul doesn't automatically do anything with these projects, but since it
knows about them, it can check out their git repos, and they have the
option to add jobs if they want.
1) Add ensure-bazelisk role
https://gerrit-review.googlesource.com/c/zuul/jobs/+/254772
This might seem like a lot of boilerplate for what's essentially a curl
invocation. But I wrote it this way for two reasons. First, because
I don't intend to leave this role in this repo in the long term.
Installing bazelisk is in no way tied to Gerrit -- it's something that
any Zuul user might benefit from. So I'm going to propose this exact
change to the zuul-jobs project:
https://zuul-ci.org/docs/zuul-jobs/
That's a standard library of Zuul jobs and Ansible roles that can be
used in any Zuul in the world. Because Zuul is git-driven, any Zuul
system can have immediate access to those just by adding the zuul-jobs
git repo to the system. We're using it in our instance too; more on
that later.
Even if I didn't have a global audience in mind for this role, I think
it's still a good practice to factor out tasks like this into roles, and
document their parameters. In the same way that a good programming
language lets you break down problems into constituent parts and then
build larger structures out of them, Ansible and Zuul do the same for
CI/CD systems. Roles are essentially the unit of re-usability in
Ansible, so that's why this unit of work gets its own role.
The role gets its own "unit test" job too. Any future changes to the
role will at least run a simple job that executes the role, so we can
catch errors without having to run a heavyweight job. And it's handy
right now since we haven't built up any more complex jobs that use it
yet. With this, we can verify that the first step of our future plugin
build job works before we proceed.
The way all of these connect together is:
* The contents of zuul.d/project.yaml tell Zuul what jobs to run on
changes to this project. It says to run "test-ensure-bazelisk".
* The job "test-ensure-bazelisk" in zuul.d/test.jobs.yaml is our "unit
test" job.
* It calls the playbook at playbooks/test-ensure-bazelisk.yaml.
* The playbook invokes the role "ensure-bazelisk".
* The contents of roles/ensure-bazelisk comprise the role. The
entrypoint for the role is at roles/ensure-bazelisk/tasks/main.yaml.
That's where the actual work starts.
And yes, we absolutely could have just implemented this as a one-liner
shell script. Nothing in Zuul prohibits that kind of simplicity. But
this boilerplate will appear more useful later as we start to build up.
2) Add prepare-gerrit-repos role and gerrit-base job
https://gerrit-review.googlesource.com/c/zuul/jobs/+/254994
The bulk of this change is about setting up the gerrit repos, since
there are submodules involved. There's about as much documentation in
this change as there is code, and for the details, I'll refer you to the
README. The comments on the change are also enlightening. We will
probably change this in the future, but the current patch is functional,
and in most cases, functionally equivalent to other options under
discussion. Because it's modular, we can easily change this in the
future without disrupting the jobs that we build on it.
This change adds something new: our first "real" job. The previous job
we added was narrow in scope: unit testing an Ansible role. Here we've
added a job in zuul.d/jobs.yaml called "gerrit-base". As its name
implies, it will form the base of every future job in the system which
builds Gerrit (if we add linters or documentation build jobs, they might
not use gerrit-base).
Zuul achieves modularity not only by relying on Ansible roles, but also
its own internal inheritance system for jobs. The jobs we build later
will declare that gerrit-base is their parent, and they will inherit
everything you see here. Notably they will inherit the list of projects
that Zuul needs to check out in order to build Gerrit (Gerrit and its
required submodules). They will also inherit the pre-run playbook at
playbooks/gerrit-base/pre.yaml. That playbook is run before the main
part of the job. By separating them like this, we can put all of the
"setup" logic in the base job, and reserve the main playbook for the
interesting work.
In this same change, you can see that we add a job named
"test-gerrit-base" in test-jobs.yaml that inherits from "gerrit-base"
(I'm putting jobs that test roles in this repo in "test-jobs.yaml" and
real jobs in "jobs.yaml"). The line "parent: gerrit-base" is the magic
bit there. Note that it also adds a "run" playbook. When this job
runs, it will run the "pre-run" playbook from gerrit-base, and then the
"run" playbook here. The run playbook simply performs some independent
verification that the repos were set up correctly. This is our "unit
test" for this role.
Finally, take a look at patchset one of this change. I wrote this
before I added the projects in step 0, which means most of this change
was invalid Zuul configuration. In these cases, Zuul leaves robot
comments on the change pointing directly at the problem:
https://gerrit-review.googlesource.com/c/zuul/jobs/+/254994/1/zuul.d/jobs.yaml#22
After merging the change in step 0, Zuul accepted the config.
3) Add ensure-java role
https://gerrit-review.googlesource.com/c/zuul/jobs/+/255212
We need to install java. You probably get the pattern at this point.
This role can probably go to zuul-jobs too.
4) Add install-build-essential
https://gerrit-review.googlesource.com/c/zuul/jobs/+/255213
We need to install autotools, and zip. And also node. Let's take a
look at the gerrit-base pre-run playbook:
https://gerrit-review.googlesource.com/c/zuul/jobs/+/255213/7/playbooks/gerrit-base/pre.yaml
You'll notice that's starting to look like a concise list of things that
the job needs to do. It makes it pretty easy to see the overall
structure of that job. Most of the time we won't have to look at that,
we can just inherit from it and not worry about it. But if we need to
do some of those things, but in a different order, or omit one of them,
then because we've modularized them in this way, it's easy to do. We
could create a new job that does not inherit from gerrit-base and give
it a playbook with just the roles that we need for that job. That's our
escape valve if inheritance isn't the right solution for a problem. We
retain a huge amount of flexibility with minimal code duplication.
If you've spent any time with jenkins-job-builder (which I know some
folks here have -- the gerrit-ci scripts use it extensively) you may
think "hey that looks like a list of builders". The primary authors of
Zuul are also the original authors of JJB (hi!). That's one of the
things we found people really liked in JJB, so we've kept the habit of
building our Ansible playbooks in a similar way. You might also notice
that the "project" and even to some extent, "job" stanzas look like JJB.
That's no accident. One thing we did not borrow from JJB: template
substitution. At larger scales, we found the substitution rules to be
difficult to follow and maintain. Instead, we added inheritance. It
doesn't do everything that the template expansion did (on purpose!).
And as any honest object-oriented programmer will tell you, it can be
complex too. But how complex we make it is our choice. In my
experience, just a few levels deep is enough to solve most problems and
humans can still hold the inheritance hierarchy in their heads. If you
ever get lost, we have a browser:
https://ci.gerritcodereview.com/t/gerrit/jobs
Finally, note that we're also adding the install-nodejs role to the
playbook. We didn't define that. That's from the zuul-jobs repo:
https://zuul-ci.org/docs/zuul-jobs/js-roles.html#role-install-nodejs
If you were wondering what all the weird sphinx syntax in the role
README was about -- there's a sphinx extension called "zuul-sphinx"
which is used to render the documentation you see in the link above.
5) Add plugin build roles
https://gerrit-review.googlesource.com/c/zuul/jobs/+/255092
Two new roles that follow the pattern. And a new "real" job:
"gerrit-plugin-build". As its name implies, it is a job to build any
Gerrit plugin.
You should be able to add this job to any plugin repo and it will
automatically build that plugin. That's our goal in sight. :)
We also add an interesting new "unit test" job,
"test-gerrit-plugin-build". I wanted to verify that this would work for
any plugin before I wrote any changes to add it to plugin repos. This
job inherits from gerrit-plugin-build, but does two things: it adds the
"plugins/checks" repo to the list of required projects (so that Zuul
will check it out). And it sets a variable that tells the roles we just
wrote which plugin it should build. This variable normally just
defaults to the project under test, but in the case of this change, that
project is "zuul/jobs" which is obviously not a Gerrit plugin. So we
set the variable explicitly to "checks", and the job will build the
checks plugin.
5.1) DNM: test bazelisk test
https://gerrit-review.googlesource.com/c/zuul/jobs/+/255214
This is a throwaway change -- the checks plugin doesn't have junit
tests, but I wanted to make sure that the "bazelisk test" code in the
previous change works before I went any further, so I wrote a quick
change that builds the delete-project plugin instead of checks and runs
its tests. It works, I'm happy, so I abandoned the change. But this is
a useful process sometimes, and I wanted you to see it.
6) Add a Zuul job to build the plugin
https://gerrit-review.googlesource.com/c/plugins/checks/+/255215
This is the culmination of our work. We add the job that we made in
step 5 to the checks repo, and it runs. Note that in this case, we
don't have to override that variable -- it builds the checks plugin
because that's the project under test.
Up to this point all of the changes have been a git patch series. The
git parent of change 5 is change 4, and so forth up to 1. Naturally,
when Zuul looks at each of those changes, it checks out their parents as
well. This is true not only for the git repos on disk, but for Zuul's
own configuration. As of this moment, none of those changes have
merged, so the "running" configuration of Zuul doesn't have any of those
jobs we defined. If I visit
https://ci.gerritcodereview.com/t/gerrit/jobs
and search for "gerrit" no jobs appear, and they won't until those
changes merge. But because the central idea of Zuul is speculative
execution ("what would happen if this change did land?") Zuul
dynamically updates its configuration when it evaluates each of these
changes, so for that moment, in that context, the jobs do exist.
This feature doesn't stop at the git repository boundary though. One of
Zuul's best features is buried in the footer of the commit message of
this change:
Depends-On:
https://gerrit-review.googlesource.com/c/zuul/jobs/+/255092
This is a cross-repo dependency. It tells Zuul that not only should it
check out change 255215 in the plugins/checks repo, it should also check out
change 255092 from the zuul/jobs repo, and any git parents of that
change. And not only should it check out that change, it should also
use the Zuul configuration specified there.
What we have here, is a series of 6 changes spanning two repositories
that build a complete CI setup that should work for any Gerrit plugin,
and we've demonstrated that it works end-to-end before merging any of
the implementing changes.
In other Zuul installations, we use this all the time to make changes
that span projects. I expect this to be very useful with Gerrit plugins
in particular -- we can test a change to Gerrit with any plugin (or
vice-versa) with a simple Depends-On in the commit message.
Not to dive too far down the rabbit hole -- but it does keep going.
Zuul supports cross-source dependencies -- we regularly have changes in
Zuul's own Gerrit that "Depends-On" changes in Github.
I think this is a good stopping point. I think all these changes are
ready for further review and merging (just don't merge the change to the
checks repo before the others -- if Zuul were gating, it would prohibit
that because of the Depends-On line, but in its current configuration,
it's just advisory). I think once these changes merge, we can start to
add more jobs and repos.
I'm happy to answer further questions here, or in review. And while
email and Gerrit are probably the best channels for discussion like
this, Monty, Paladox, and I are all available in #zuul (and some of us
in #gerrit too) on Freenode IRC if that happens to be convenient.
-Jim