# 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 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/-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/-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//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.