Repository navigation
feat(windows): bake a warm/fast-boot AMI into the import flow (#98) - #102
Merged
Merged
Conversation
`spawn image import` for Windows now produces a "warm" AMI by default so future launches skip the ~30 min Sysprep first boot. After the base AMI is built, spawn launches a seed from it, waits until the seed has FULLY finished first boot (SSM Online — which proves EC2Launch user-data ran and spored + the SSM agent are installed/running), images the seed, tags the warm AMI, and terminates the seed (always — deferred cleanup, even on failure). - buildWarmAMI (cmd/image.go): reuses setupSSHKey (RSA), CreateOrGetWindowsSecurityGroup, Launch (m7i.xlarge seed, Windows user-data, 3h safety TTL), WaitForRunning + WaitForSSMOnline, CreateAMI + WaitForAMI, TagAMIWindowsWarm, Terminate. - New pkg/aws/ssm.go WaitForSSMOnline: polls DescribeInstanceInformation until PingStatus=Online; caps the poll interval at the remaining timeout so short waits don't overshoot. - pkg/aws/imagebuilder.go TagAMIWindowsWarm: spawn:source=iso-import-warm + spawn:warm-parent=<base>. - Default ON; --no-warm opts out (raw base AMI only). --warm-instance-type (default m7i.xlarge, non-burstable). Warm requires waiting, so it forces a --wait if not given. Output prints both base and warm AMIs (warm recommended). Warm failure is non-fatal: the base AMI is still returned. - Generalize is intentionally omitted (non-generalized warm AMI fits spawn's ephemeral model); a follow-up will add sysprep /generalize for fleets. Tests: WaitForSSMOnline (online + timeout-respects-deadline), --no-warm default, warm-instance-type guard. make check + e2e tier0 green. Refs #98.
…98) A live full-import run showed the warm stage working end-to-end (base built → seed launched → graceful non-fatal fallback → seed terminated by deferred cleanup), but the warm AMI wasn't created because the seed's Sysprep first boot + EC2Launch user-data + SSM registration took longer than the 40-min budget. SSM registration is the long pole on an imported client AMI (~35-50 min observed). Raise the default WaitForSSMOnline budget to 75 min and expose it as --warm-timeout. The base AMI is still returned if the warm build is abandoned. Refs #98.
#98) Replace the brittle "wait up to N minutes for SSM Online or fail" gate with a progress-based one. A live run showed SSM registration on the imported client AMI is wildly variable (one seed Online ~28 min; another same-everything seed not registered at 75 min), so hard-requiring SSM made warm builds flaky. Now: gate the warm image on the Administrator password becoming available — the reliable "EC2Launch finished first boot / Sysprep done" signal (~12-20 min). Then give SSM/spored a bounded 10-min best-effort window to settle (nicer warm image), but image regardless — the agent + spored are installed either way and register on the warm AMI's own next boot. --warm-timeout (now 30 min default) is a safety-net for the password signal, not the normal path. (Root-causing the SSM-registration variance separately.) Refs #98.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
scttfrdmn
added a commit
that referenced
this pull request
Jun 12, 2026
…y docs (#116) * docs: update Windows beta guide for pre-warmed AMI build (#98/#102) The guide predated the warm-AMI feature (#102) and told beta testers to expect a slow ~30-min first boot on every launch, calling fast-boot "a future improvement" — which shipped. Brings it in line with current behavior: - §7 rewritten: the import pre-warms the AMI by default (launches a seed, lets it finish Sysprep, images it, auto-terminates the seed); warm launches are ~4 min, not ~30. The slow boot only happens on the base AMI / --no-warm. - §5: drop the `--wait` instruction (warm implies waiting); document the two AMI ids the import now prints (base + warm) and tell the user to launch the warm one. - §6: launch the warm AMI id. - §9: clean up BOTH AMIs (base + warm); add `spawn ami list`. - Version floor v0.40.0 → v0.42.0 (warm needs v0.41.0+; keyless SSM connect v0.42.0). - Set time/cost expectations up front: ~45–75 min build, the short-lived seed is billed but auto-terminated, two AMIs carry a small storage cost. - Fix a stale §2 cross-ref to the old §7 heading (made the NAT note self-contained). * docs: drop NAT-gateway jargon from beta guide region note A first-time beta tester doesn't need build-infra detail (NAT/S3 gateway) — the import auto-provisions networking. Reduce it to the actionable instruction (use us-east-1) with a plain reason; the full explanation stays in the infra README. * docs: state spawn relies on the AWS CLI for now; trim IAM jargon Add an upfront note that spawn currently leans on the AWS CLI (credentials + Session Manager connect paths), so a couple of steps are aws commands rather than spawn — set that expectation plainly instead of over-explaining each one. Trim the IAM-permissions laundry list to 'PowerUserAccess is simplest'. * docs: frame AWS CLI as how spawn works, not a temporary gap spawn has always worked with the AWS CLI (aws sso login for credentials, the CLI's Session Manager for SSM). Drop the 'for now' framing — aws steps are expected by design, not a shortfall. * docs: use aws login throughout (not aws configure sso/aws sso login) aws login (AWS CLI v2.34+) is the simpler sign-in: browser console login with auto-refreshing temp credentials, --profile support, and --remote for headless hosts. Replace the aws configure sso + aws sso login dance in §2, the quick-reference, and logout (aws logout). No SSO setup walkthrough needed. * docs: demote SSM plugin to optional in Windows beta prereqs The Session Manager plugin isn't needed for the happy path — plain 'spawn connect <name> --rdp' to the public IP uses no SSM. The plugin is only needed for the interactive SSM shell or '--rdp --via-ssm' tunnel. Move it from a hard prerequisite to an 'install if/when you use those paths' note. * docs: call the Windows SSM path 'PowerShell over SSM', not SSH-over-SSM On Windows there's no spawn SSH-user/key model — the spawn-driven non-RDP path is an interactive PowerShell session (and one-shot commands) over SSM, not SSH. Fix the intro, the §8B heading, and the readiness caveat to say so. (RDP to the public IP remains the no-SSM happy path.) * feat(connect): add --ssh for Windows (same SSH path as Linux) The imported Windows AMI already runs OpenSSH and trusts spawn's launch key for Administrator (buildWindowsUserData). Expose that: `spawn connect <name> --ssh` SSHes straight to the public IP as Administrator (PowerShell shell) — no SSM, no Session Manager plugin — mirroring the Linux default connect path. One-shot `--ssh -- '<cmd>'` runs the command in PowerShell (no bash -c wrap). Factor the ssh exec into sshToInstance + a pure buildSSHCommandArgs shared by the Linux and Windows paths. Errors clearly if there's no public IP or the launch key isn't on this machine. * docs: reflect new capabilities + rewrite Windows guide for flow README: - connect row now describes Linux SSH + Windows RDP/PowerShell-over-SSM/--ssh - add image (warm-AMI import) and ami rows to the command table - Go Library example: use the real API — launcher.Provision (the recommended full-spore entry point) instead of the nonexistent client.LaunchInstance; note client.Launch as the lower-level path - fix the Windows guide blurb (PowerShell-over-SSM/SSH, not "SSH-over-SSM"/SSO) Windows beta guide — readability/flow pass (was hard to read; explained things after using them): - intro no longer dumps the seed/Sysprep/warm-AMI mechanism; just sets plain time + cost expectations - define "AMI" where it first appears; explain "why two images" at the build step where you first see them (was a separate section 2 steps later) - document the new --ssh connect path (route C); fix "two ways in" -> three - drop fragile §-anchor links for plain "Step N" prose - fix the githu b.com URL typo and a stray trailing code fence - renumber 0-8 (folded the old standalone "why first boot is slow" section in) * fix(windows): open port 22 for all firewall profiles so --ssh works Live smoke test of `spawn connect --ssh` found it couldn't connect: on the imported Windows AMI, sshd was Running and listening on 0.0.0.0:22 and the EC2 security group allowed the client IP, but Windows Firewall still blocked inbound 22. Root cause: the OpenSSH Server feature only creates a *Private*-profile allow rule, while an EC2 instance's network is classified *Public*. RDP worked (its rule covers all profiles); SSH didn't. buildWindowsUserData now adds an explicit New-NetFirewallRule for TCP 22 with -Profile Any after starting sshd (idempotent — guarded on rule name). Re-ran the smoke test on a fresh instance with this fix: port 22 opened, and both `spawn connect <name> --ssh -- '<cmd>'` (one-shot PowerShell) and the interactive `--ssh` session connected as Administrator. Added a regression assertion. Also fix the launch success hint: it said "(SSH-over-SSM)" — now lists the three real Windows connect paths (--rdp, --ssh, PowerShell-over-SSM). * docs: add CHANGELOG.md (Keep a Changelog + SemVer 2.0.0) Establish a curated changelog per project policy. Seeds Unreleased (the --ssh feature + Windows firewall fix on this branch) and the recent 0.40-0.42 releases; older history points to GitHub Releases. GoReleaser still auto-generates the GitHub release notes from commits — this is the human-curated companion.
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.
spawn image importfor Windows now produces a warm (fast-boot) AMI bydefault, so launches skip the slow Sysprep first boot.
Why
The imported base AMI runs Windows Sysprep Specialize on every first launch.
Measured live: an instance from the base AMI takes ~28 min to become usable /
SSM-registered (and one outlier didn't register within 75 min). That tax repeats
on every launch.
What
After the base AMI is built, spawn launches a one-time seed from it, waits until
first boot finishes, images it into a warm AMI, and terminates the seed. Live
result: launching the warm AMI reaches SSM-ready in ~4 min vs ~28 min cold (7x).
buildWarmAMI(cmd/image.go): setupSSHKey (RSA) → CreateOrGetWindowsSecurityGroup→ Launch seed (m7i.xlarge, Windows user-data, 3h safety TTL) → WaitForRunning
→ gate on WaitForPasswordData → best-effort WaitForSSMOnline → CreateAMI +
WaitForAMI → TagAMIWindowsWarm → deferred Terminate seed (always, even on
failure).
pkg/aws/ssm.goWaitForSSMOnline(polls DescribeInstanceInformation;caps poll interval at remaining time so short waits don't overshoot).
TagAMIWindowsWarm:spawn:source=iso-import-warm+spawn:warm-parent=<base>.--no-warmopts out (raw base only).--warm-instance-type(m7i.xlarge),
--warm-timeout(30 min safety net). Output prints base + warm(warm recommended). Warm failure is non-fatal — the base AMI is still returned.
Design notes (from live debugging)
is reliable but slow+variable (~28 min typical; one stall >75 min). Earlier
attempts hard-required SSM Online and were flaky. Now the warm image is gated
on the Administrator password (reliable ~12-20 min = EC2Launch/Sysprep
done); SSM is a bounded best-effort settle. The agent is installed either way
and registers on the warm AMI's own (fast) next boot — verified.
EC2Launch user-data, so SSM can't register until ~28 min. The warm AMI is the
fix (~4 min). Confirmed the seed has spored + AmazonSSMAgent Running before imaging.
--generalizefor fleets is a follow-up (Windows warm AMI: optional --generalize (sysprep) for fleet/unique-SID use #101).
Verification
warm-instance-type guard.
make check+ e2e tier0 green.flow exercised; seed→SSM Online ~28 min; warm AMI created from a baked seed;
launch from warm AMI → SSM Online ~4 min,
spawn connectimmediate, spored Running.Closes #98.