Skip to content

Commit 722de00

Browse files
committed
chore: Merge main into PR #4825 branch
2 parents 127e7ea + fea9ea3 commit 722de00

403 files changed

Lines changed: 30089 additions & 7465 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/ci/check-ci-permissions.sh

Lines changed: 0 additions & 1022 deletions
This file was deleted.

.github/ci/check-core-ci-permissions.sh

Lines changed: 0 additions & 33 deletions
This file was deleted.

.github/ci/check-stale-ci-permissions.sh

Lines changed: 0 additions & 31 deletions
This file was deleted.

.github/ci/rest-command-bundles.md

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# REST command bundle contract
2+
3+
REST has 11 standalone command names, while the production Linux images also
4+
need `nicocli` and `nico-mcp`. Building each command in its own job repeated the
5+
same checkout and Go setup, and it left the Docker builds with no checked bundle
6+
they could reuse.
7+
8+
So, [`rest-command-manifest.json`](rest-command-manifest.json) is the one list of
9+
REST commands this workflow may build. One job builds everything for a target,
10+
checks each binary, and uploads one archive with the exact build record a later
11+
job needs.
12+
13+
## What can be built
14+
15+
The source manifest is an object with exactly three required fields:
16+
17+
- `schema_version` is the integer `1`.
18+
- `targets` is a non-empty array. Each entry has the non-empty strings `name`,
19+
`goos`, and `goarch`; `name` must be the literal `<goos>-<goarch>` value, and
20+
target names must be unique. The supported names are exactly `linux-amd64`,
21+
`linux-arm64`, and `darwin-arm64`.
22+
- `outputs` is a non-empty array. Each entry has `name`, `package`, `target`,
23+
`cgo_enabled`, `ldflags`, and `output`. The command name uses lowercase
24+
letters, numbers, and internal hyphens. `package` starts with `./`, contains
25+
no empty, `.`, or `..` path components, cannot escape through a symlink, and
26+
names an existing directory under `--repo-root`. `target` names a declared
27+
target, `cgo_enabled` is a Boolean, `ldflags` is an array of non-empty
28+
strings, and `output` is exactly `bin/<name>`.
29+
30+
`cgo_enabled: true` sets `CGO_ENABLED=1`; `false` sets it to `0`. Command names
31+
and `output` paths must be unique within one target. Every target needs at least
32+
one output and produces one `rest-command-bundle-<target>.tar.gz` archive;
33+
source-manifest order becomes resolved-manifest command order. Unknown fields
34+
and unsafe paths fail validation.
35+
36+
Linker flags may use only `${VERSION}`, `${SHORT_SHA}`,
37+
`${BUILD_TIME_LEGACY}`, and `${BUILD_TIME_RFC3339}`. Any other dollar form is
38+
rejected; literal-dollar escaping is unsupported, and a backslash does not
39+
escape a token.
40+
41+
The checked-in inventory has three deliberate boundaries:
42+
43+
- Linux amd64 and Linux arm64 each build 13 commands. The ten production image
44+
entrypoints are `api`, `migrations`, `sitemgr`, `workflow`, `site-agent`,
45+
`credsmgr`, `flow`, `psm`, `nsm`, and `nico-mcp`. They use the same settings
46+
and metadata fields as their production Dockerfiles. Bundle timestamps use
47+
canonical UTC values rather than inheriting a runner's local timezone. The
48+
API image also contains `nicocli`; both `nicocli` and `nico-mcp` are
49+
Linux-only bundle outputs.
50+
- `mock-core` and `mock-flow` keep the previous standalone behavior. The native
51+
Linux amd64 builds use CGO, while the cross-compiled Linux arm64 builds do
52+
not. That amd64 target therefore needs a working C compiler; build metadata,
53+
rather than `file` wording, verifies its `CGO_ENABLED=1` setting.
54+
- Darwin arm64 keeps the previous 11-command standalone contract. The API,
55+
site-agent, and flow commands keep their runtime version metadata without
56+
acquiring Linux static-linker flags or new CLI/MCP builds.
57+
58+
All three targets build `api`, `migrations`, `sitemgr`, `workflow`,
59+
`site-agent`, `mock-core`, `mock-flow`, `credsmgr`, `flow`, `psm`, and `nsm`.
60+
The two Linux targets also build `nicocli` and `nico-mcp`. The independent
61+
command-contract test pins the exact package, target, CGO, linker-flag, and
62+
output inventory; the source manifest remains the canonical machine-readable
63+
list.
64+
65+
The supported transition boundary is
66+
[CICD-08](https://github.com/NVIDIA/infra-controller/issues/4582). Until that
67+
issue changes image packaging to consume these bundles, the production
68+
Dockerfiles contain a second copy of the Linux service build settings. Keeping
69+
the two copies aligned is a review responsibility during this transition;
70+
CICD-08 removes the duplicate build recipe by making packaging consume these
71+
bundles.
72+
73+
## Commands
74+
75+
`rest_command_bundle.py` has three subcommands. Each accepts optional
76+
`--manifest PATH`, which defaults to the adjacent
77+
`.github/ci/rest-command-manifest.json`, and `--repo-root PATH`, which defaults
78+
to the repository's `rest-api/` directory. Relative path arguments resolve
79+
from the current working directory; `--repo-root` does not rebase the other
80+
paths. Absolute paths are accepted.
81+
82+
- `check` has no additional flags. It validates the source manifest and package
83+
directories, writes no files, and prints the output and target counts.
84+
- `build` requires `--target`, `--output-dir`, `--version`,
85+
`--build-timestamp`, `--candidate-sha`, and `--short-sha`; `--allow-dirty` is
86+
optional. It validates the checkout, compiles and inspects every command for
87+
one target, creates the output directory when missing, publishes the three
88+
bundle files, and prints build progress plus the verified output count and
89+
archive size. An existing output directory must be empty. The accepted
90+
targets are `linux-amd64`, `linux-arm64`, and `darwin-arm64`.
91+
- `verify` requires `--target`, `--bundle`, `--checksum`,
92+
`--resolved-manifest`, `--version`, `--build-timestamp`, `--candidate-sha`,
93+
and `--short-sha`; `--allow-dirty` is optional. It reads and verifies the
94+
three bundle files for a declared target, uses a temporary directory for
95+
binary inspection, removes that directory on exit, and prints the verified
96+
archive path. The bundle cannot choose its expected identity.
97+
98+
Each subcommand rejects flags owned by another subcommand. In particular,
99+
`check` rejects `--allow-dirty`, `build` rejects the verify-only bundle path
100+
flags, and `verify` rejects `--output-dir`. An undeclared target fails in
101+
`build` or `verify`; there is no target flag for `check`.
102+
103+
The version starts with a letter or number and otherwise accepts only letters,
104+
numbers, periods, underscores, and hyphens. The build timestamp uses UTC
105+
`YYYY-MM-DDTHH:MM:SSZ`. The candidate SHA is exactly 40 lowercase hexadecimal
106+
characters, while the short SHA is its 7- to 12-character prefix. Because that
107+
timestamp is also written into the gzip header, it must fall between
108+
`1970-01-01T00:00:00Z` and `2106-02-07T06:28:15Z`, inclusive.
109+
110+
Only `build` and `verify` accept `--allow-dirty`. By default, `build` rejects
111+
tracked or untracked changes anywhere in the repository worktree, and `verify`
112+
rejects a resolved manifest with `source_dirty: true`. The flag permits that
113+
state for local validation. CI does not pass it.
114+
115+
`build` stages the three bundle files inside the output directory, verifies
116+
them there, and publishes the resolved manifest and checksum before atomically
117+
renaming the archive into place. A failed build, verification, or publication
118+
normally removes staged and partially published files so the output directory
119+
is empty for a retry. If cleanup itself fails, the error names each path it
120+
could not remove.
121+
122+
All three commands require Python 3.10 or newer. `build` also requires Git,
123+
[Go 1.26.4 or newer](../../rest-api/go.mod), and the `file` utility; `verify` requires Go
124+
and `file`. Building `linux-amd64` also requires a C compiler for `mock-core`
125+
and `mock-flow`. CI checks the Python minimum, selects the module's minimum Go
126+
1.26.4, and checks `file --version` before compiling. A contract, command, tool,
127+
or verification failure prints `error: <message>` to standard error and exits
128+
with status 1. Argument parsing and missing required flags print `usage:` and
129+
exit with status 2.
130+
131+
## Bundle files
132+
133+
For a target such as `linux-amd64`, `build` writes:
134+
135+
- `rest-command-bundle-linux-amd64.tar.gz`;
136+
- `rest-command-bundle-linux-amd64.tar.gz.sha256`; and
137+
- `rest-command-bundle-linux-amd64.manifest.json`.
138+
139+
The UTF-8 checksum sidecar contains exactly one LF-terminated line: one
140+
lowercase SHA-256 digest, two spaces, the archive filename, and the final LF.
141+
The producer writes a level-9 gzip stream with no stored filename or optional
142+
header fields and with the build timestamp as its gzip timestamp. It contains a
143+
PAX-format tar stream ordered as `manifest.json`, `SHA256SUMS`, and then binary
144+
paths sorted by name. Those two metadata files use mode `0644`; every
145+
`bin/<name>` entry uses mode `0755`. All members are regular relative files with
146+
UID and GID `0`, empty owner and group names, and the build timestamp as their
147+
modification time.
148+
149+
The binary checksum file uses the same digest and two-space format, with one
150+
LF-terminated line for every command, `/`-separated relative paths, source
151+
inventory sorted by path, and no extra entries. The resolved and embedded
152+
manifests use UTF-8 JSON with sorted object keys, two-space indentation, and one
153+
final newline; non-ASCII characters are escaped. No required field is omitted.
154+
155+
The resolved manifest records string values for the source-manifest SHA-256,
156+
requested candidate and short SHA, version, and build timestamp; a Boolean
157+
`source_dirty`; the exact target object; and a non-empty command array. Each
158+
command entry records string values for its name, package, output, import path,
159+
Go version, `file` description, SHA-256, and VCS settings; a Boolean CGO choice;
160+
a positive integer size; and a possibly empty array whose linker-flag entries
161+
are non-empty strings.
162+
163+
The flat VCS keys are always present and non-empty. `vcs_modified` records Go's
164+
`vcs.modified` value or `unknown` when Go omits it. `vcs_revision` records the
165+
candidate SHA only when Go reports that exact revision; otherwise it is
166+
`unavailable`. Verification rejects every other revision value. Go normally
167+
records the repository revision for this module; the fallback keeps the bundle
168+
contract explicit in environments where those settings are unavailable.
169+
170+
The producer and its unit test own the PAX writer choice. The standalone
171+
verifier accepts any readable tar encoding, but still enforces the canonical
172+
JSON bytes, gzip header and timestamp, member order, ownership, timestamps,
173+
modes, paths, inventory, and contents described above.
174+
175+
## Embedded metadata
176+
177+
The Linux and Darwin `api` and `site-agent` builds set `metadata.Version` to
178+
`--version` and `metadata.BuildTime` to `YYYY-MM-DD HH:MM:SS`. Their `flow`
179+
builds set `metadata.Version` to `--version`, `metadata.BuildTime` to
180+
`YYYY-MM-DDTHH:MM:SSZ`, and `metadata.GitCommit` to `--short-sha`. Their full Go
181+
symbol paths live in the source manifest and are pinned by the command-contract
182+
test. Darwin builds omit only the Linux static-linker and stripping flags.
183+
184+
`go version -m -json` exposes linker flags as one aggregate setting. Missing,
185+
extra, or reordered flags fail verification, unrelated build settings are
186+
ignored, and the last value wins if Go reports one setting key more than once.
187+
Every resolved `-X` value must also appear in the binary. The script does not
188+
maintain a second allowlist of `-X` destinations; the source manifest and its
189+
independent contract test own those destinations.
190+
191+
## What verification checks
192+
193+
The verifier starts from the checked-in source manifest and the identity the
194+
caller supplied. From there it checks:
195+
196+
- the archive sidecar and exact archive inventory;
197+
- the byte-for-byte match between the external and embedded resolved manifest;
198+
- safe paths, regular-file types, exact modes, sizes, and checksums;
199+
- package import paths, `GOOS`, `GOARCH`, `CGO_ENABLED`, and linker flags from
200+
`go version -m -json`;
201+
- physical architecture and CGO-disabled Linux static linkage from `file -b`;
202+
and
203+
- every metadata value supplied through a `-X` linker flag.
204+
205+
Go normally records `vcs.revision` in these binaries. Before compiling, the
206+
builder confirms that Git `HEAD` is the requested candidate and rejects a dirty
207+
checkout unless local validation explicitly passes `--allow-dirty`. The
208+
resolved manifest records that source check and uses `unavailable` only when Go
209+
omits the VCS setting. The standalone verifier checks the recorded candidate,
210+
build contract, binary metadata, checksums, and file inventory, but it cannot
211+
cryptographically prove which source tree produced the binaries.

0 commit comments

Comments
 (0)