Conversation
cb15b15 to
4dc818a
Compare
There was a problem hiding this comment.
Pull request overview
This PR reintroduces an Omaha (Nebraska) client flow by adding a new launcher binary/crate and wiring it into existing provisioning/update pathways so test images can download and (eventually) use it alongside Trident.
Changes:
- Added a new Rust workspace member
crates/launcherimplementing basic Omaha request/response handling and a CLI entrypoint. - Integrated
launcherinto netlaunch RCP file injection and the test image systemd unit that downloads binaries. - Updated build tooling to produce
bin/launcher(via AZL3 Docker build) and copylauncherduring Trident self-upgrade provisioning.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/pkg/netlaunch/netlaunch.go | Injects bin/launcher into the RCP-served files for test/provisioning flows. |
| tests/images/trident-mos/files/download-trident.service | Downloads /usr/bin/launcher alongside existing downloaded binaries. |
| Makefile | Builds launcher in build-azl3, installs to bin/launcher, and adds it to run-netlaunch prerequisites. |
| crates/trident/src/subsystems/management.rs | Copies /usr/bin/launcher into the target OS when SELF_UPGRADE_TRIDENT is enabled. |
| crates/launcher/src/omaha/xml.rs | Adds helper macro for deserializing nested “list” XML elements. |
| crates/launcher/src/omaha/status.rs | Defines Omaha status enums and helpers (+ unit tests). |
| crates/launcher/src/omaha/response.rs | Implements Omaha response parsing/validation (+ unit tests). |
| crates/launcher/src/omaha/request.rs | Implements Omaha request modeling and XML serialization (+ unit tests). |
| crates/launcher/src/omaha/mod.rs | Implements blocking Omaha request execution and event validation (+ unit tests). |
| crates/launcher/src/omaha/event.rs | Defines Omaha event payloads and acknowledgement parsing. |
| crates/launcher/src/omaha/app.rs | Adds AppVersion wrapper around semver::Version (+ unit tests). |
| crates/launcher/src/main.rs | Adds launcher CLI + Trident gRPC update trigger + query logic (+ unit tests). |
| crates/launcher/src/id.rs | Adds machine ID/hostname sourcing for Omaha identifiers. |
| crates/launcher/src/error.rs | Adds HarpoonError error model for launcher/Omaha operations. |
| crates/launcher/Cargo.toml | Defines the new launcher crate dependencies. |
| Cargo.toml | Adds crates/launcher to the workspace members. |
| Cargo.lock | Updates lockfile for the new crate/dependency graph. |
| fs::copy( | ||
| "/usr/bin/launcher", | ||
| path::join_relative(mount_path, "/usr/bin/launcher"), | ||
| ) | ||
| .structured(ServicingError::CopyTridentBinary)?; |
There was a problem hiding this comment.
SELF_UPGRADE_TRIDENT provisioning now unconditionally copies /usr/bin/launcher. On systems/images that set this flag but don’t yet have launcher installed, fs::copy will fail and block servicing. Consider making the launcher copy conditional on the source file existing (or otherwise guaranteeing launcher is always present when this flag is enabled), and map the error to a launcher-specific ServicingError variant so failures aren’t reported as “Copy Trident binary”.
| fs::copy( | |
| "/usr/bin/launcher", | |
| path::join_relative(mount_path, "/usr/bin/launcher"), | |
| ) | |
| .structured(ServicingError::CopyTridentBinary)?; | |
| let launcher_path = Path::new("/usr/bin/launcher"); | |
| if launcher_path.exists() { | |
| info!("Copying launcher binary to target OS"); | |
| fs::copy( | |
| launcher_path, | |
| path::join_relative(mount_path, "/usr/bin/launcher"), | |
| ) | |
| .structured(ServicingError::CopyLauncherBinary)?; | |
| } else { | |
| info!("Skipping launcher copy because source binary is not present"); | |
| } |
| .map_err(|e| HarpoonError::Internal(format!("Failed to serialize request XML: {e}")))?; | ||
|
|
||
| debug!("Sending Omaha request to '{url}'",); | ||
| trace!("Omaha request body:\n{}", String::from_utf8_lossy(&body)); |
There was a problem hiding this comment.
trace!("Omaha request body…") logs the full serialized request XML, which includes the machine identifier (machineid). Even at TRACE level, this can end up in collected logs and becomes a sensitive identifier leak. Consider redacting the machine ID (or skipping request-body logging entirely) while keeping higher-level request metadata.
| trace!("Omaha request body:\n{}", String::from_utf8_lossy(&body)); | |
| trace!("Omaha request prepared ({} bytes)", body.len()); |
| let document = reqwest::blocking::Client::new() | ||
| .get(package_url.clone()) | ||
| .send() | ||
| .map_err(|err| HarpoonError::FetchError(err.to_string()))? | ||
| .text() | ||
| .map_err(|err| HarpoonError::FetchError(err.to_string()))?; |
There was a problem hiding this comment.
download_document uses response.text() and then validates size/hash over the decoded String. This can change the byte stream (charset decoding, normalization), causing size/hash validation to fail even when the downloaded bytes are correct. Fetch and validate the raw bytes (e.g., bytes()), compute the digest over those bytes, and only decode to String after validation if needed.
|
|
||
| .PHONY: run-netlaunch run-netlaunch-stream | ||
| run-netlaunch: $(NETLAUNCH_CONFIG) $(TRIDENT_CONFIG) $(NETLAUNCH_ISO) bin/netlaunch validate artifacts/osmodifier $(RUN_NETLAUNCH_TRIDENT_BIN) | ||
| run-netlaunch: $(NETLAUNCH_CONFIG) $(TRIDENT_CONFIG) $(NETLAUNCH_ISO) bin/netlaunch artifacts/osmodifier $(RUN_NETLAUNCH_TRIDENT_BIN) bin/launcher |
There was a problem hiding this comment.
run-netlaunch now depends on bin/launcher, which (via build-azl3) forces an AZL3 Docker build even when RUN_NETLAUNCH_TRIDENT_BIN resolves to the native bin/trident path. If this wasn’t intended, consider building/using a launcher artifact that matches the selected trident binary path or gating the launcher build the same way. Also, validate was removed from run-netlaunch prerequisites, so config validation no longer runs automatically before launching.
| run-netlaunch: $(NETLAUNCH_CONFIG) $(TRIDENT_CONFIG) $(NETLAUNCH_ISO) bin/netlaunch artifacts/osmodifier $(RUN_NETLAUNCH_TRIDENT_BIN) bin/launcher | |
| run-netlaunch: validate $(NETLAUNCH_CONFIG) $(TRIDENT_CONFIG) $(NETLAUNCH_ISO) bin/netlaunch artifacts/osmodifier $(RUN_NETLAUNCH_TRIDENT_BIN) |
| [dependencies] | ||
| anyhow = { version = "1.0.82", features = ["backtrace"] } | ||
| log = "0.4.21" | ||
| quick-xml = { version = "0.37.1", features = ["serialize"] } | ||
| reqwest = { version = "0.12.4", default-features = false, features = [ | ||
| "blocking", | ||
| "default-tls", | ||
| "charset", | ||
| ] } # "http2" is enabled by default but causes a (false positive) CG alert |
There was a problem hiding this comment.
This crate pins many common dependency versions (anyhow, log, reqwest, serde, uuid, etc.) instead of using the workspace versions. In this repo, other crates consistently use { workspace = true }, and pinning here can introduce version skew/duplicate deps and complicate updates/security scanning. Consider switching these to workspace dependencies (and only overriding features where needed).
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| data, err := os.ReadFile("bin/launcher") | ||
| if err != nil { | ||
| return fmt.Errorf("failed to read local launcher binary from 'bin/launcher': %w", err) | ||
| } | ||
| rcpAgentConfBuilder.registerRcpFile(newRcpAgentFileDownload("launcher", "/usr/bin/launcher", 0755, data)) |
There was a problem hiding this comment.
injectRcpAgentConfig now unconditionally reads and injects bin/launcher, even though other injected binaries (trident/osmodifier) are optional and configurable via RcpConfiguration. This makes any RCP-enabled netlaunch fail unless the current working directory has bin/launcher, and there’s no way to override/disable it. Consider adding a LocalLauncherPath (and only injecting when set), or making launcher injection conditional based on an explicit config/flag.
| data, err := os.ReadFile("bin/launcher") | |
| if err != nil { | |
| return fmt.Errorf("failed to read local launcher binary from 'bin/launcher': %w", err) | |
| } | |
| rcpAgentConfBuilder.registerRcpFile(newRcpAgentFileDownload("launcher", "/usr/bin/launcher", 0755, data)) | |
| launcherPath := "bin/launcher" | |
| if _, err := os.Stat(launcherPath); err == nil { | |
| data, err := os.ReadFile(launcherPath) | |
| if err != nil { | |
| return fmt.Errorf("failed to read local launcher binary from '%s': %w", launcherPath, err) | |
| } | |
| rcpAgentConfBuilder.registerRcpFile(newRcpAgentFileDownload("launcher", "/usr/bin/launcher", 0755, data)) | |
| } else if !os.IsNotExist(err) { | |
| return fmt.Errorf("failed to stat local launcher binary at '%s': %w", launcherPath, err) | |
| } |
| bin/trident-azl3: build-azl3 | ||
| @cp -u target/azl3/release/trident bin/trident-azl3 | ||
| bin/launcher: build-azl3 | ||
| @cp -u target/azl3/release/launcher bin/launcher |
There was a problem hiding this comment.
bin/launcher depends on build-azl3, which always builds the full Trident release binary first. That means make bin/launcher (and anything that depends on it, like run-netlaunch) does extra work and slows iteration. Consider splitting out a build-launcher-azl3 target so building/copying launcher doesn’t force a full trident build.
| .PHONY: run-netlaunch run-netlaunch-stream | ||
| run-netlaunch: $(NETLAUNCH_CONFIG) $(TRIDENT_CONFIG) $(NETLAUNCH_ISO) bin/netlaunch validate artifacts/osmodifier $(RUN_NETLAUNCH_TRIDENT_BIN) | ||
| run-netlaunch: $(NETLAUNCH_CONFIG) $(TRIDENT_CONFIG) $(NETLAUNCH_ISO) bin/netlaunch artifacts/osmodifier $(RUN_NETLAUNCH_TRIDENT_BIN) bin/launcher | ||
| @echo "Using trident binary: $(RUN_NETLAUNCH_TRIDENT_BIN)" |
There was a problem hiding this comment.
run-netlaunch no longer depends on the validate target, so invalid $(TRIDENT_CONFIG) files won’t be caught early (previously bin/trident validate ... ran before launching). If this was removed to avoid using bin/trident on Ubuntu 24+, consider updating validate to use $(RUN_NETLAUNCH_TRIDENT_BIN) and keeping validation as a dependency here.
| let r = query_and_fetch_yaml_document( | ||
| &args.url, | ||
| "b0ec8f0d-1c13-4bf4-9efd-ea54464a7098", | ||
| "west-us", | ||
| &Version::new(0, 0, 0), |
There was a problem hiding this comment.
main hardcodes the app_id, track, and starting version when querying Omaha (only the server URL is configurable). This doesn’t match the PR description of a launcher that queries for a “specific app and track”, and makes the binary hard to reuse across environments. Consider adding CLI args for app_id/track/current version (and possibly IdSource) instead of embedding constants.
| config: match hash { | ||
| Some(hash) => format!("image:\n url: {url}\n sha384: {hash}"), | ||
| None => { | ||
| format!("image:\n url: {url}\n sha384: ignored") | ||
| } |
There was a problem hiding this comment.
trigger() formats the HostConfiguration as sha384: {hash} when a hash is provided. Trident’s image.sha384 expects either ignored or a 96-hex SHA384 value (Sha384Hash); passing a 64-hex SHA256 (as used elsewhere in this crate) will fail config parsing and block updates. Consider either wiring through the actual COSI metadata SHA384, or changing the type to Option (and/or falling back to ignored if you don’t have a SHA384).
| publish = false | ||
|
|
||
| [dependencies] | ||
| anyhow = { version = "1.0.82", features = ["backtrace"] } |
| ) | ||
| .structured(ServicingError::CopyTridentBinary)?; | ||
|
|
||
| fs::copy( |
There was a problem hiding this comment.
perhaps do it conditionally so we skip this if the launcher doesn't exist?
| /// look at the first package returned by the omaha server to fetch the | ||
| /// document. The function expects the document to be a single file with `.yaml` | ||
| /// extension. | ||
| pub fn query_and_fetch_yaml_document( |
There was a problem hiding this comment.
(happy to address in follow ups to make smaller changes) many strings/names/functions assumed we'd be pulling a HC from nebraska, but we are really really just pulling the image in this new iteration
🔍 Description
This PR restores much of the old harpoon code to connect to Omaha servers, and implements a simple launcher binary that queries an Omaha server then connects to trident and does an update with the indicated COSI file