mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 08:03:15 +00:00
The systemd unit booted with a literal `@@OPENSWE_ENV@@` (fetch-config.sh got the token, not "dev" -> exit 2 -> crash-loop) because user-data.sh is double-templated: CDK substitutes @@tokens@@ AND user-data seds @@tokens@@ into the baked systemd/nginx files. CDK's `.replace(/@@OPENSWE_ENV@@/g, "dev")` clobbered the sed PATTERN (`s|@@OPENSWE_ENV@@|...|` -> `s|dev|...|`, a no-op), so the unit's token never got replaced. Same collision hit @@SERVER_NAME@@ (masked by nginx default_server). Fix: CDK tokens move to a DISTINCT delimiter %%...%% (rendered in app-service.ts); the @@...@@ tokens stay for the baked-template seds. No AMI rebuild (templates unchanged). Add a guard test asserting no unresolved %%CDK%% token survives in the synthesized user-data. Also add .github/scripts/** to build-artifacts paths so script-only changes trigger a publish. jest 20/20; tsc + shellcheck clean; rendered user-data: OPENSWE_ENV="dev", SERVER_NAME="openswe-dev.seahaven.com", @@ sed patterns preserved, 16872 B.
87 lines
4.1 KiB
TypeScript
87 lines
4.1 KiB
TypeScript
import * as cdk from "aws-cdk-lib";
|
|
import { Template } from "aws-cdk-lib/assertions";
|
|
import { OpenSweStack } from "../lib/open-swe-stack";
|
|
|
|
const ENV = { account: "328440206208", region: "us-east-1" };
|
|
|
|
/**
|
|
* Several AWS APIs reject non-ASCII in fields that `cdk synth` happily emits and
|
|
* `tsc` happily compiles — so a stray em-dash/arrow only blows up at DEPLOY time
|
|
* (e.g. EC2 SecurityGroup GroupDescription: "Character sets beyond ASCII are not
|
|
* supported"). This has bitten us twice (the AMI Description, then the instance-SG
|
|
* description). This test fails the build at synth time instead.
|
|
*
|
|
* Scope: the EC2 fields with a documented ASCII/restricted-charset constraint —
|
|
* SecurityGroup GroupDescription and ingress/egress rule descriptions. (CloudFormation
|
|
* Output descriptions + Route53 comments accept UTF-8, so they are not asserted.)
|
|
*
|
|
* EC2 rule descriptions are stricter than ASCII: the allowed set is
|
|
* `a-zA-Z0-9. _-:/()#,@[]+=&;{}!$*` — note it EXCLUDES `<` and `>`, which is why a
|
|
* naive em-dash -> "->" replacement still fails at deploy. We assert that exact set.
|
|
*/
|
|
// Characters NOT in the EC2 description allowed set.
|
|
const DISALLOWED = /[^a-zA-Z0-9. _:/()#,@[\]+=&;{}!$*-]/;
|
|
|
|
function synthDev(): Record<string, { Type: string; Properties?: Record<string, unknown> }> {
|
|
const app = new cdk.App();
|
|
const dev = new OpenSweStack(app, "OpenSweDevStack", {
|
|
stackName: "open-swe-dev",
|
|
env: ENV,
|
|
envName: "dev",
|
|
});
|
|
return Template.fromStack(dev).toJSON().Resources;
|
|
}
|
|
|
|
describe("ASCII-only EC2 description fields", () => {
|
|
const resources = synthDev();
|
|
|
|
it("SecurityGroup GroupDescription is ASCII", () => {
|
|
for (const [id, r] of Object.entries(resources)) {
|
|
if (r.Type !== "AWS::EC2::SecurityGroup") continue;
|
|
const desc = (r.Properties?.GroupDescription as string) ?? "";
|
|
expect(DISALLOWED.test(desc) ? `${id}: ${desc}` : "ascii").toBe("ascii");
|
|
}
|
|
});
|
|
|
|
it("SecurityGroup ingress/egress rule descriptions are ASCII", () => {
|
|
for (const [id, r] of Object.entries(resources)) {
|
|
const props = r.Properties ?? {};
|
|
const groups: Array<{ Description?: string }> = [];
|
|
if (Array.isArray(props.SecurityGroupIngress)) groups.push(...props.SecurityGroupIngress);
|
|
if (Array.isArray(props.SecurityGroupEgress)) groups.push(...props.SecurityGroupEgress);
|
|
// Standalone AWS::EC2::SecurityGroupEgress / ...Ingress resources.
|
|
if (r.Type === "AWS::EC2::SecurityGroupEgress" || r.Type === "AWS::EC2::SecurityGroupIngress") {
|
|
groups.push(props as { Description?: string });
|
|
}
|
|
for (const rule of groups) {
|
|
const desc = rule.Description ?? "";
|
|
expect(DISALLOWED.test(desc) ? `${id}: ${desc}` : "ascii").toBe("ascii");
|
|
}
|
|
}
|
|
});
|
|
|
|
// EC2 caps base64-encoded user-data at 25600 bytes; CDK + tsc don't check it, so
|
|
// an oversized boot script (e.g. an embedded deploy.sh) only fails at deploy.
|
|
it("EC2 user-data fits the 25600-byte encoded limit", () => {
|
|
for (const [id, r] of Object.entries(resources)) {
|
|
if (r.Type !== "AWS::EC2::Instance") continue;
|
|
const ud = (r.Properties?.UserData as { "Fn::Base64"?: string }) ?? {};
|
|
const script = typeof ud["Fn::Base64"] === "string" ? ud["Fn::Base64"] : "";
|
|
const encoded = Buffer.from(script, "utf8").toString("base64").length;
|
|
expect(`${id}: ${encoded} bytes`).toBe(encoded < 25600 ? `${id}: ${encoded} bytes` : "OVER 25600");
|
|
}
|
|
});
|
|
|
|
// CDK substitutes %%...%% tokens in user-data at synth. Any %%TOKEN%% left in the
|
|
// rendered script means a token wasn't wired in app-service.ts (the @@...@@ tokens
|
|
// are intentional — user-data seds those into the baked templates at boot).
|
|
it("user-data has no unresolved %%CDK%% tokens", () => {
|
|
for (const [id, r] of Object.entries(resources)) {
|
|
if (r.Type !== "AWS::EC2::Instance") continue;
|
|
const ud = (r.Properties?.UserData as { "Fn::Base64"?: string }) ?? {};
|
|
const script = typeof ud["Fn::Base64"] === "string" ? ud["Fn::Base64"] : "";
|
|
const leftover = script.match(/%%[A-Z0-9_]+%%/g) ?? [];
|
|
expect(`${id}: ${leftover.join(",")}`).toBe(`${id}: `);
|
|
}
|
|
});
|
|
});
|