buildDocker.groovy hardcoded harbor.35.238.248.203.nip.io as the push target, so the registry could not move without editing this shared library and every consumer moving in the same commit. It now reads config.harbor_registry, whose default lives in homelabPipeline.groovy beside harbor_project and every other key. The stage errors rather than defaulting when the value is missing. Carrying a second copy of the literal would leave two defaults free to disagree, and an unset value would otherwise build an image named "null/<project>/<repo>" — which docker accepts as a hostname and then fails to resolve, pointing nowhere near the cause. The dind pod no longer mounts the registry CA. That mount existed because the registry was a nip.io name, which no public CA will issue for, so cert-manager signed Harbor from a private CA; the node pool was told to trust it for pulls, but a push comes from dockerd inside the build pod, which has its own trust store. harbor.infra.deployshed.com carries a Let's Encrypt certificate that both already trust, so the mount, its volume and the whole arrangement go away rather than being repointed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEsTefWWifp4ikvhHF5s6N
124 lines
6.8 KiB
Markdown
124 lines
6.8 KiB
Markdown
# devops-lib (GKE)
|
|
|
|
Jenkins Shared Library for the CI/CD pipeline on the GKE cluster. The GCP
|
|
counterpart of the homelab repo of the same name, and a copy rather than a
|
|
shared repo because several values here are cluster-specific in ways that
|
|
would break the other cluster if crossed over.
|
|
|
|
Register it in Jenkins under the name `devops-lib`, exactly as on the
|
|
homelab. Consuming repos then need no change at all — the same two-line
|
|
Jenkinsfile works on either cluster, and which library it resolves to is a
|
|
property of the Jenkins it runs on.
|
|
|
|
**What differs from the homelab copy**, all of it a consequence of GKE
|
|
being a real cloud rather than one VM:
|
|
|
|
- **The registry hostname** is `harbor.infra.deployshed.com`, in the push
|
|
target (`harbor_registry`, see the table below) and in all five fallback
|
|
Dockerfiles.
|
|
- **Harbor speaks TLS, with a public certificate.** The homelab's dind
|
|
passes `--insecure-registry`; here nothing is needed at all, because
|
|
Let's Encrypt issues for the real domain and dockerd already trusts it.
|
|
- **`helm_repo_url` uses cluster DNS**, since the clone happens inside a
|
|
build pod. The homelab points it at an ingress hostname.
|
|
- **`build-tools` is not in this repo.** It lives in
|
|
`devops-base-images-gcp` alongside the mirrored base images, and is
|
|
referenced here only by tag.
|
|
|
|
Everything else — the stage flow, the fallback templates, the hooks
|
|
contract — is unchanged. The library was already adapted from a much
|
|
larger, company-wide one; see git history for what was removed.
|
|
|
|
## Using it in a service repo
|
|
|
|
The entire Jenkinsfile is 2 lines:
|
|
|
|
```groovy
|
|
@Library('devops-lib') _
|
|
homelabPipeline(repo_name: 'my-service')
|
|
```
|
|
|
|
`repo_name` is the only required key. Everything else has a sensible
|
|
default — override any of them by passing extra keys to `homelabPipeline`,
|
|
or by committing a `config.yaml` to the service repo's own root (merged
|
|
in after checkout; repo-committed values win over the Jenkinsfile call).
|
|
|
|
| Key | Default | Notes |
|
|
|---|---|---|
|
|
| `service_name` | `repo_name` | Second path segment under `devops-helm-charts/values/` |
|
|
| `argo_app_name` | `repo_name` | Must match the ArgoCD Application's `metadata.name` |
|
|
| `harbor_registry` | `harbor.infra.deployshed.com` | The registry hostname images are pushed to and pulled from. Must be spelled identically here, in the `dockerconfigjson` `auths` key, and in Harbor's `externalURL` — docker matches both stored credentials and TLS trust by exact hostname, so a mismatch fails as `unauthorized` rather than as a name problem |
|
|
| `harbor_project` | `apps-registry` | Must already exist in Harbor, and be public unless you also wire an `imagePullSecret` — the app values assume anonymous pull. A push to a missing project fails as `unauthorized: project <name> not found` |
|
|
| `helm_repo_url` | `devops-helm-charts-gcp`, over cluster DNS | `http://gitea-http.gitea.svc.cluster.local:3000/gitadmin/…` — pod-to-pod, so it never leaves the cluster and comes back through the ingress |
|
|
| `image_tag_yq_path` | `.deployment.image.tag` | **Override this if the app's chart isn't `1.0.0`** — e.g. `sts-2.0.0` uses `.podtemplate.image.tag` instead. Getting this wrong doesn't fail loudly: `yq -i` creates the path if missing rather than erroring, silently leaving the real field un-bumped. |
|
|
| `dockerBuildVersion` | none | Only read when the repo has **no Dockerfile of its own** — picks a fallback template (see below). No default; either ship a Dockerfile or set this. |
|
|
|
|
## Pipeline stages
|
|
|
|
`checkOut → loadConfig → runHooks(pre_build) → buildDocker →
|
|
runHooks(post_build) → updateHelmTag → syncArgoApp → notify`, all inside
|
|
a `podTemplate` (`resources/org/homelab/dind-pod.yaml`) via
|
|
`node(POD_LABEL) { ... }`.
|
|
|
|
- **`loadConfig`** — if the repo has a `config.yaml` at its root, its
|
|
keys are merged into the pipeline config (repo values win).
|
|
- **`runHooks`** — reads `config.yaml`'s `hooks.pre_build`/`hooks.post_build`
|
|
lists, each `{name, script, interpreter, requirements, blocking,
|
|
timeout_seconds}`. Blocking by default; `blocking: false` demotes a
|
|
failure to advisory (log + continue). Script paths must be
|
|
repo-relative (no `..`, no absolute paths).
|
|
- **`buildDocker`** — uses the repo's own `Dockerfile` if present;
|
|
otherwise renders one from `resources/com/homelab/<lang>-Dockerfile`
|
|
based on `dockerBuildVersion` (e.g. `go-1.22`, `node-20`,
|
|
`python-3.12`, `java-21`, `php-8.3`). All fallback templates pull base
|
|
images from Harbor's `base-images` project (mirrored via the separate
|
|
`devops-base-images-gcp` repo), not Docker Hub directly. Only versions
|
|
actually mirrored there resolve — an unmirrored tag fails the build
|
|
rather than silently falling back to Docker Hub.
|
|
- **`updateHelmTag`** — clones `devops-helm-charts`, bumps the image tag
|
|
via `yq` at `image_tag_yq_path`, commits, pushes to `main`.
|
|
- **`syncArgoApp`** — calls the ArgoCD REST API to sync `argo_app_name`.
|
|
|
|
## Adding a new language's fallback template
|
|
|
|
1. Add the base image to `devops-base-images/images.txt`, re-mirror it
|
|
into Harbor.
|
|
2. Add `resources/com/homelab/<lang>-Dockerfile`, parametrized by
|
|
`${version}` (rendered via `constructTemplate.groovy`'s
|
|
`SimpleTemplateEngine` wrapper).
|
|
3. Add a case for it in `buildDocker.groovy`'s `templates` map.
|
|
|
|
## Build-tools image
|
|
|
|
The `docker-cli` container runs
|
|
`harbor.infra.deployshed.com/base-images/build-tools:1`, which bakes in
|
|
git, yq, bash, python3 with pip and venv, and curl, so nothing is installed
|
|
on demand on every build.
|
|
|
|
**It is not built here.** The Dockerfile lives in `devops-base-images-gcp`,
|
|
next to the mirrored base images, because it is the same kind of artefact:
|
|
built by hand, occasionally, and pushed to Harbor. A Jenkins job could not
|
|
build it anyway — it is the image Jenkins builds *in*.
|
|
|
|
`dind-pod.yaml` pins the tag, so rebuilding the image rolls nothing out
|
|
until that pin is bumped. Bump the tag rather than overwriting one.
|
|
|
|
## Registry trust
|
|
|
|
Nothing to configure. Harbor is reached at `harbor.infra.deployshed.com`,
|
|
which carries a publicly trusted Let's Encrypt certificate, so both
|
|
containerd on the node (pulls) and dockerd in the build pod (pushes) accept
|
|
it out of the box.
|
|
|
|
This used to be a real piece of setup, and the history is worth keeping
|
|
because reintroducing a nip.io registry name would bring it all back. A
|
|
nip.io address cannot have a public certificate — it is not on the public
|
|
suffix list, and every `*.nip.io` certificate on the internet shares one
|
|
rate limit — so cert-manager signed Harbor from a private CA instead. The
|
|
node pool was told to trust that CA at creation, which covered pulls; the
|
|
push came from dockerd inside the build pod, a separate client with its own
|
|
trust store, so `dind-pod.yaml` had to mount the CA at
|
|
`/etc/docker/certs.d/<registry host>/ca.crt` as well. The failure when that
|
|
mount was missing was thoroughly confusing: pushes failed TLS verification
|
|
while pulls of the very same image succeeded.
|