Phase 6c: azure-devops runner port - #61
Closed
grubmeshi wants to merge 5 commits into
Closed
Conversation
06A specified these for 06B (gitlab) to ship as first consumer, but phase-6b-gitlab had not landed them at authoring time (git diff phase-6a-manual..phase-6b-gitlab was empty). Ships them here to the 06A-specified contract instead, per PLAN_DETAIL_06C_azdevops.md's STOP-C4 resolution -- flagged in CROSS_REPO_TODO.md for reconciliation if 06B lands its own copy in parallel. Mirrors internal/tf.Decryptor's existing shape (Decryptor interface, certDecryptor, NoopDecryptor) and the Kotlin MeshCertDecryptionService input-only decrypt asymmetry (STRING/CODE/FILE decrypted, other sensitive types left as ciphertext, impl secrets untouched).
Kotlin's hand-rolled PrivateKeyLoader/MeshCertDecryptionService parser strips the BEGIN/END markers and every newline by hand, so every Kotlin runner image's classpath runner-config.yml ships its baked dev privateKey with no newline after "-----BEGIN PRIVATE KEY-----". Go's stdlib encoding/pem.Decode rejects that shape outright. azure-devops is the first Go port to actually decrypt (manual never does), so this was the first real exercise of config.ResolvePrivateKey against a real Kotlin-shipped key -- without this fix, both this port's own baked dev key and any customer's existing Kotlin runner-config.yml would silently fail to decrypt on the new Go image (a config-compat break, D7/D10). normalizePEM is a pure additive fallback: only tried when a bare pem.Decode already failed, so an already-valid multi-line PEM is unaffected. Verified end-to-end against the actual containers/azure-devops-block-runner baked key via both a unit test and a docker-run smoke.
…s.md) The heaviest of the four Kotlin ports (AzureDevOpsPipelinePoller/ StatusUpdater/StatusMapper had zero direct Kotlin tests): trigger a pipeline run, then either hand over IN_PROGRESS (async) or poll to a terminal report with per-stage steps (sync), on the 06A template (dispatch.RunHandler, the unified report.Reporter, config compat helpers, StandaloneClaimClassifier). - statusmapper.go: pure S-P1-S-P6 mapping-table twins of AzureDevOpsStatusMapper.kt, incl. the pinned else-holes (completed+succeededWithIssues/absent -> IN_PROGRESS). - client.go: the ADO wire client (trigger/get-run/get-timeline), the ExternalCallError type (per-package, per umbrella §4 row 14 -- not a shared meshapi type), renderValue (UseNumber-fidelity stringification, flagged JSON-not-toString delta for composite values, §16.6), redirect policy, explicit timeouts (§16.10), no base-URL sanitization (§16.7). - updates.go: RunStatus builders for every U-P1-U-P8 update shape (trigger-success, state-only, stage-batch with the one-way COMPLETED dedup, final, failed, terminal abort/failed). - handler.go: the failure ladder (register -> parse impl -> decrypt PAT -> decrypt inputs -> trigger), byte-identical U-P8 message pair selection. - poll.go: the sync poll loop, ctx-aware (plan-05 H7 amendment: reports a TERMINAL status on shutdown cancellation, ABORTED falling back to FAILED, never SUCCEEDED) with escalation semantics verified line-by-line against the Kotlin nested try/catch (a PATCH failure during a stage/fallback update is silently absorbed like a GET failure; only the timeout and final-update reports get one escalation attempt before propagating). - config.go/loadconfig.go/reporterfactory.go/singlerun.go: persona config, the R12 single-run exit tail with the sanctioned K-P2 delta, single-run mode wiring. Test suite (D16 scenario style, D6 gate ≥90%, actual 93.2%, -race clean): reproduces all 28 planned pins (6 S + 8 U + 5 P + 6 A + 2 K + 1 F) via a fake Azure DevOps httptest transport, the shared meshapitest mock, and an injected Clock (sleep-free, no goroutine races -- After() fires instantly and Now() only advances via an explicit per-test bump, so even the 30-min timeout pin runs in milliseconds). No new Kotlin JUnit tests were added (gradle is not runnable in this environment, matching the 6a precedent) -- the Go scenario suite is the surviving pin; see CROSS_REPO_TODO.md.
- cmd/azdevops/main.go + polling.go: the fit azure-devops-block-runner
binary (D1/D8) -- single-run vs polling mode selection, mgmt listener on
8101 (PORT alias), cert-based decryptor wiring.
- cmd/bbrunner: register the azdevops persona subcommand
(`bbrunner azdevops`) alongside tf/manual, for local-dev / mux
replacement (§4.1).
- containers/azure-devops-block-runner/{Dockerfile,runner-config.yml}: the
06A per-app-Dockerfile pattern, direct entrypoint, the baked local-dev
private key (umbrella §10.5) -- verified with a real `docker build` +
`docker run` smoke (config loads, private key resolves and parses,
dispatch loop starts, /healthz answers OK).
- tools/coverage/thresholds.txt: gate internal/azdevops at 90% (actual
93.2%, no exclusions -- whole package hermetically testable).
- .golangci.yml: azdevops/azdevops-test depguard groups mirroring the
manual persona's (no k8s, no go-git, no tofu).
Adds PLAN_DETAIL_06C_azdevops.md (from the plan branch) to this stacked branch, and records the phase-6c deferrals in CROSS_REPO_TODO.md: the Kotlin module removal / CI flip / meshfed-release edit all wait on the §11 acceptance gate (live Azure DevOps org + side-by-side transcript equivalence), which this environment cannot execute -- same rationale as phase-6a. Also flags the two STOP-C4/crypto template-gap fixes this slice made for the next port to be aware of.
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 6c — azure-devops runner port
Third of the four stacked Kotlin→Go port PRs. Base:
phase-6b-gitlab.Ports the Azure DevOps runner to Go (
internal/azdevops,cmd/azdevops,cmd/bbrunner azdevops, container) — the heaviest port (sync poll loop + stage-step fan-out, injectedClock, no real sleeps).internal/azdevopsnew at 93.2% (gate 90). Build, race tests, lint (0 issues), coverage gate all pass on the stacked tree.Consolidation reconciliation (STOP-C4)
06C was authored in parallel with 06B and independently shipped
meshapi.Decryptor+ a typedmeshapi.DecryptInputs. During stacking these were reconciled with 06B's:meshapi.Decryptor/NoopDecryptor/NewCertDecryptor(with the Kotlin empty-string guard) and byte-basedmeshapi.DecryptInputsare kept.meshapi.DecryptInputSpecs(internal/meshapi/decryptinputspecs.go) — the two consumers need different shapes (gitlab forwards the whole raw run document; azure-devops holds parsed[]BuildingBlockInputSpecDTO). Identical STRING/CODE/FILE branch rule.internal/azdevops/handler.gocall site updated accordingly.Sanctioned deltas / deferrals
normalizePEMfallback ininternal/crypto— azure-devops is the first Go port that actually decrypts (config.ResolvePrivateKey). Coexists with 06B's multi-line re-wrap (normalize is a no-op on already-valid PEM). Prevents a silent config-compat break (D7/D10).🤖 Generated with Claude Code