This is an automated email from the ASF dual-hosted git repository. sciabarracom pushed a commit to branch 0.9.0 in repository https://gitbox.apache.org/repos/asf/openserverless.git
commit c99698a06a47b04ce5cd2a59f2db32904ed9d1c8 Author: Michele Sciabarra <[email protected]> AuthorDate: Fri Sep 4 11:19:33 2026 +0200 build spec --- BUILD.md | 321 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 321 insertions(+) diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..61a1c07 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,321 @@ +# BUILD.md — Specification + +> **Status: specification only.** This document describes what the build +> documentation must explain and how the build system is supposed to behave. +> It is not an implementation guide, and nothing here is to be implemented +> as part of writing this document. +> +> **The Taskfiles are expected to change to match this spec.** Where the current +> `Taskfile.yml` of a component disagrees with the contract described here, the +> spec is the target and the Taskfile is what gets updated — not the other way +> round. Section 9 lists the known divergences. + +--- + +## 1. Summary + +OpenServerless is not one build. It is a set of independently versioned +components, each living in its own submodule, each with its own `Taskfile.yml` +and its own GitHub Actions workflow. The final documentation must make the +reader understand three things, in this order: + +1. **The common rules** — how tags drive every build, what a tag looks like, + what `task tag` / `task build` / `task ci` mean, and where images are pushed. +2. **The three build cases** — which one applies to the component being worked on. +3. **The three usage scenarios** — building locally, building from a personal + fork, and consuming what the fork produced. + +### The three build cases + +| Case | Components | Distinguishing feature | +|---|---|---| +| **A — single image** | `operator` (olaris-op), `admin-api`, `streamer`, `devcontainer` | One tag → one image | +| **B — multiple images** | `runtimes` | One tag → a whole family of images, tag selects which family | +| **C — binary** | `cli` (`ops`) | No image; produces release binaries | + +### The three usage scenarios + +| Scenario | What the reader wants | Where artifacts land | +|---|---|---| +| **Local** | Build on my machine, run it against my cluster | Local Docker daemon / kind, nothing pushed | +| **Fork** | Build my own version in CI from my fork | My registry namespace | +| **Consume** | Point an OpenServerless install at what I built | `opsroot.json` / `runtimes.json` overrides | + +### The one rule that governs everything + +**All CI builds are triggered by pushing a git tag.** No branch push builds an +image. Every component workflow is configured as `on: push: tags:` with +`branches-ignore: '*'`. The tag is not metadata attached to a build — the tag +*is* the build trigger, and its text *is* the image tag. + +--- + +## 2. Common rules + +These apply to every component and must be stated once, up front, in the final +documentation. The per-case sections must then only describe deviations. + +### 2.1 The three tasks + +Every component exposes the same conceptual trio. The documentation must +present them as a progression, because the reader will use them in this order: + +| Task | Runs where | Effect | Pushes? | +|---|---|---|---| +| `task tag` | local | Creates (and force-replaces) the git tag that names this build | no | +| `task build` | local | Builds using the current tag, loads into the local Docker daemon | no | +| `task ci` | local, triggers remote | Pushes the tag, which starts the GitHub Actions build | yes, via CI | + +Two points the documentation must make explicitly, because they are the two +things newcomers get wrong: + +- `task tag` **deletes existing local tags** before creating the new one. This + is deliberate — snapshot tags are disposable — but it must be flagged, not + discovered. +- `task ci` does not itself build anything. It pushes a tag. The build happens + in GitHub Actions on the repository that received the tag. This is why forking + matters: the tag lands in *your* fork, so *your* Actions run, using *your* + secrets, pushing to *your* registry. + +These three names are a **contract**: every component must expose `tag`, +`build`, and `ci` with exactly these semantics, regardless of what it builds. +Components that currently use different names or a different split must be +aligned to it (see section 9). + +A manual trigger path also exists: the workflow can be started from the GitHub +Actions UI, in which case it creates the tag itself. The documentation must +mention this as the alternative for people who cannot or do not want to push +tags from the command line. + +### 2.2 Tag format + +Tags are timestamps in a compacted, sortable form. The documentation must +explain the encoding, because the tags are otherwise unreadable: + +``` +26i4k36-SNAPSHOT +││││││└─ minutes +│││││└── hour, encoded as a letter: a=00 … x=23 +││││└─── day of month +││└───── month, encoded as a letter: a=January … l=December +└─────── year (two digits) +``` + +So `26i4k36` is 2026, September (`i` = 9th letter), day 4, hour 10 (`k` = 11th +letter, zero-based → 10), minute 36. + +The `-SNAPSHOT` suffix marks a non-release build. The documentation must state +what its presence and absence mean for consumers. + +Case B (runtimes) prefixes the timestamp with the runtime family: + +``` +<runtime>_26i4k36-SNAPSHOT +``` + +for example `python_26i4k36-SNAPSHOT` or `nodejs_26i4k36-SNAPSHOT`. The prefix +is not cosmetic: CI parses it to decide which subset of images to build. This +must be stated as a rule, not shown as an example. + +Components carry their own base version prefix where applicable (e.g. the CLI +produces `v0.9.0-<timestamp>.SNAPSHOT`). The documentation must show one real +tag per case rather than describing the format abstractly. + +### 2.3 Where images go + +The destination registry is decided by **who owns the repository the tag was +pushed to**, not by anything in the tag: + +- Tag pushed to the Apache repository → Apache official namespace. +- Tag pushed to a fork → the fork owner's namespace, using the fork's secrets. + +The documentation must be explicit that a contributor without Apache +credentials is not blocked — forking is the supported path, not a workaround. + +### 2.4 Registry configuration + +Where credentials and namespace come from differs by case, and this is the +single largest source of confusion. The documentation must present it as a +table rather than prose: + +| Case | Configured via | Notes | +|---|---|---| +| A — single image | `.env` in the component dir: `REGISTRY`, `NAMESPACE` | `REGISTRY` is `ghcr`, `dockerhub`, or `apache`; the task derives the full image name from it | +| B — runtimes | Repository secrets: `DOCKERHUB_REGISTRY`, `DOCKERHUB_USER`, `DOCKERHUB_TOKEN` | Read as env vars by the Taskfile; must be set in the fork's GitHub settings | +| C — cli | None | No registry involved | + +The documentation must say plainly: **for Case B, forking is not enough — the +three secrets must be configured in the fork before any CI build can succeed.** + +--- + +## 3. Case A — single image + +Applies to: `operator` (`olaris-op`), `admin-api`, `streamer`, `devcontainer`. + +One tag produces one image. This is the simplest case and should be documented +first, in full, so the later cases can be described as deltas. + +The documentation must cover: + +- **Prerequisites** — Docker with buildx, Task, and (for CI) a fork. +- **Local flow** — `task tag`, then `task build`. State that the resulting image + is tagged with the timestamp and exists only in the local Docker daemon. +- **CI flow** — `task ci` pushes the tag; the `image` workflow builds multi-arch + (`linux/amd64`, `linux/arm64`) and pushes. +- **Fork setup** — set `REGISTRY` and `NAMESPACE` in `.env`. Show the two common + values (`ghcr` → `ghcr.io/<user>/…`, `dockerhub` → `docker.io/<user>/…`). +- **What CI does beyond building** — license check (skywalking-eyes) and unit + tests run before the image is built. A build can fail for reasons unrelated to + the image; the documentation must say so, so failures are not mysterious. +- **Verification** — how to confirm the image exists, locally and in the registry. + +--- + +## 4. Case B — multiple images (runtimes) + +Applies to: `runtimes`. + +Structurally the same as Case A, but with three differences that the +documentation must call out as the *entire* reason this case is separate: + +1. **The tag selects the work.** `task tag RT=<runtime>` requires the runtime + family. CI parses the tag prefix and builds only the matching family — + `python_…` builds the Python runtimes, `common-…` builds the shared base + image. The documentation must list the recognized prefixes and state what + happens when a tag matches none of them. + +2. **One tag, many images.** A single build produces one image per language + version in the family (e.g. several Python versions). The documentation must + show the resulting image naming scheme: + + ``` + <registry>/<namespace>/openserverless-runtime-<rt>:<ver>-<tag> + ``` + +3. **Secrets are mandatory.** Unlike Case A, registry configuration is not in a + local `.env` — it comes from repository secrets. The documentation must list + `DOCKERHUB_REGISTRY`, `DOCKERHUB_USER`, `DOCKERHUB_TOKEN` and say where to + set them (fork → Settings → Secrets and variables → Actions). + +The documentation must also explain the relationship between the **common base +image** and the language runtimes: the base is built and tagged separately, and +language runtimes depend on it. Building a language family without a matching +base is a documented failure mode, not a bug to be discovered. + +Finally, this section must explain how the built runtimes are made visible to an +installation — the generated `runtimes.json` / `runtimes.env` and how they are +consumed. + +--- + +## 5. Case C — binary (cli) + +Applies to: `cli`, which produces the `ops` binary. + +No Docker image is involved. The documentation must cover: + +- **Local flow** — `task build` compiles `ops` into the repository root. Note + that `task build` alone is enough for local use; tagging is only needed when + the embedded version string matters. +- **Tagging** — `task tag` writes `version.txt` and `branch.txt`, commits them, + and creates the tag. The documentation must state that this **creates a + commit**, which is a meaningful difference from the other cases. +- **CI flow** — `task trigger` (the CLI's equivalent of `task ci`) refuses to run + with a dirty working tree, then tags and pushes. The release workflow builds + and publishes binaries for all supported platforms. +- **Installation** — how to put the built `ops` on the `PATH`, and how to verify + which version is running. + +--- + +## 6. The Apache image + +The documentation must include a dedicated section on building the Apache +OpenWhisk-derived images (controller, invoker, scheduler, standalone, scala +base), because it does not follow the pattern of the three cases above. + +It must explain: + +- **Why it is different** — it lives in the `build` submodule, has its own + Taskfile, vendors an OpenWhisk source tree, and builds through a dedicated + devcontainer rather than on the host. +- **The toolchain constraint** — the OpenWhisk Gradle build requires Java 11; + building on a host with a newer JDK fails with `Unsupported class file major + version`. The build therefore runs inside `ops ide devcontainer`. This must be + stated as a rule with its symptom, so the error is self-diagnosing. +- **The local flow** — `task build` in the `build` directory, and what it does + end to end: build images, update `opsroot.json`, load images into kind, roll + out the controller. +- **The two-build-systems caveat** — the root `Taskfile.yml` and + `build/Taskfile.yml` are separate. The root one builds CLI, operator, + runtimes, streamer, admin-api and updates only `operator`, `streamer`, + `systemapi` in `opsroot.json`. It does **not** build or update the OpenWhisk + images. The documentation must state this explicitly; it is the single most + common wrong assumption. +- **Verification** — how to confirm the running pod uses the freshly built + image, not a cached upstream one. + +--- + +## 7. Consuming your own build + +A section the reader reaches after a successful build, answering: *I built it — +now how do I actually run it?* + +It must cover: + +- Overriding component images in `~/.ops/<version>/olaris/opsroot.json`. +- Overriding runtimes via the generated runtimes configuration. +- Loading locally built images into kind (they are not automatically visible to + the cluster — this must be stated, with the symptom: the pod pulls the old + upstream image instead). +- Confirming the override took effect at the pod level, not just in the custom + resource. A change visible in the CR but not in the resulting workload is a + known failure mode and must be documented as such. + +--- + +## 8. Requirements for the final document + +- **Summary first.** The common rules and the two 3×3 tables come before any + component-specific instruction. +- **Cases are separated.** No section may mix Case A, B, and C instructions. + Cases B and C are written as deltas from A, not as full restatements. +- **Every command is shown in context** — which directory it runs in, and what + it produces. +- **Every failure mode listed above appears with its symptom**, so the reader + can match an error message to a cause. +- **Scenario-oriented, not task-oriented.** The reader arrives with a goal + ("build my own runtime image and test it"), not with a task name. + +--- + +## 9. Taskfile alignment + +The contract in section 2 is the target state. The Taskfiles do not all +implement it today, and updating them is in scope for the work this spec +governs. The known divergences: + +| Component | Divergence | Required change | +|---|---|---| +| `cli` | The CI trigger is called `trigger`, not `ci` | Expose `ci` (keeping `trigger` as an alias if desired) | +| `streamer` | The tagging task is called `image-tag`, not `tag` | Expose `tag` | +| `streamer`, `admin-api` | `ci` is absent — the tag is pushed by hand | Add `ci`: create the tag and push it | +| `runtimes` | The CI entry point is `ci-build`, not `ci`; `tag` prints the new tag but does not create it | Expose `ci`; make `tag` actually create the tag | +| `runtimes` | `ci-build` calls `build-lang` without `task`, so the language path is broken | Fix the invocation | +| `admin-api` | Tag suffix is `-SNAPSHOT`; `streamer` has no suffix | Make the suffix consistent across single-image components | +| all single-image | Tag timestamp is `date +%y%m%d%H%M`, not the letter-encoded form of section 2.2 | Adopt one encoding everywhere, and document which | + +Two decisions must be made explicitly before the Taskfiles are touched, because +the documentation cannot be written without them: + +1. **One tag encoding or two.** Either every component uses the letter-encoded + timestamp, or the plain numeric one. The current mix must not be documented + as if it were intentional. +2. **Where registry configuration lives.** Case A reads `.env`, Case B reads + repository secrets. Either this difference is justified and documented as a + rule, or the two are unified. + +Until these are settled, the documentation must not describe the current +behaviour as stable.
