fix: pass raw user-data bytes to ModifyInstanceAttribute on warm start#66
Merged
Conversation
#66) warmStartInstance() rewrites a stopped pool instance's user-data via ModifyInstanceAttribute before restarting it, so the per-boot register service re-registers with a fresh token/label. That field is a BLOB (BlobAttributeValue.Value: Uint8Array), unlike the plain-string UserData that RunInstances takes. The EC2 query protocol base64-encodes a blob's bytes on the wire, and EC2 base64-decodes them once before storing what IMDS serves. The code handed the SDK an already-base64'd STRING (Buffer.from(userData).toString('base64')), so the wire carried base64(base64(userData)); EC2 decoded only the outer layer and IMDS ended up serving the base64 TEXT of the script instead of the script. The per-boot register step reads user-data back from IMDS and greps out GH_REPO_URL with sed; against a single base64 blob line there is no GH_REPO_URL='...' match, so GH_REPO_URL is empty and config.sh aborts with "Invalid configuration provided for url. Terminating unattended configuration." Every reuse: stop warm start failed registration this way; cold launches were unaffected because RunInstances' UserData is a string that the caller correctly pre-encodes (startEc2Instance). Hand ModifyInstanceAttribute the raw user-data bytes and let the SDK do the single base64 layer. Tests: - Strengthen the warmStartInstance unit test to assert the SDK receives the raw script bytes (Buffer/Uint8Array), not a pre-encoded string. The old assertion base64-decoded the value, which only passed because the value was wrongly pre-encoded; it now fails against that bug. - Add tests/warmpool-userdata-encoding.test.js: runs a real EC2Client through a capturing requestHandler and asserts raw bytes round-trip to the script verbatim while a pre-base64'd string double-encodes and loses it. Signed-off-by: kurok <22548029+kurok@users.noreply.github.com>
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.
Summary
Fixes warm-pool (
reuse: stop) registration, which failed 100% of the time on every warm restart with:Root cause — double base64 encoding
warmStartInstance()rewrites a stopped pool instance's user-data viaModifyInstanceAttributebefore restarting it, so the per-boot register service re-registers with a fresh token/label.That field is a blob (
BlobAttributeValue.Value: Uint8Array), unlike the plain stringUserDatathatRunInstancestakes. The EC2 query protocol base64-encodes a blob's bytes on the wire, and EC2 base64-decodes them once before storing what IMDS serves.The code handed the SDK an already-base64'd string:
So the wire carried
base64(base64(userData)); EC2 decoded only the outer layer and IMDS served the base64 text of the script instead of the script. The per-boot register step reads user-data back from IMDS andsed-grepsGH_REPO_URL='...'out of it — against a single base64 blob line there is no match,GH_REPO_URLis empty, andconfig.sh --url ""aborts with the error above.Cold launches were unaffected:
RunInstances'UserDatais a string the caller correctly pre-encodes (startEc2Instance).Fix
Verification
Captured the actual serialized
ModifyInstanceAttributerequest body through a realEC2Client:UserData.ValueBuffer.from(userData).toString('base64')(old)IyEvYmluL2Jhc2gK…— base64 text, noGH_REPO_URLlineBuffer.from(userData)(new)#!/bin/bash\nGH_REPO_URL='…'— the real script ✅Tests
warmStartInstanceunit test: it now asserts the SDK receives the raw script bytes (Buffer/Uint8Array), not a pre-encoded string. The previous assertion base64-decoded the value, which only passed because the value was wrongly pre-encoded — it now fails against the bug.tests/warmpool-userdata-encoding.test.js: runs a realEC2Clientthrough a capturing request handler and asserts raw bytes round-trip to the script verbatim, while a pre-base64'd string double-encodes and loses it. This pins the SDK wire contract independently of our code.npm run lint,npm test(239 passing), andnpm run package(dist rebuilt) all clean.