mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 08:03:15 +00:00
Create the per-env config surface the EC2 box reads at boot via
fetch-config.sh / seed_store.sh:
- ConfigStore construct (infra/lib/constructs/config-store.ts):
- 28 value-LESS Secrets Manager shells open-swe-<env>/<VAR>
(RemovalPolicy.RETAIN, no SecretString/generateSecretString — real
values are set out-of-band by put-config.sh, never in IaC/state).
- 8 IaC-managed SSM params /open-swe-<env>/<VAR> with real,
stable/derivable values (SANDBOX_TYPE, DEFAULT_REPO_OWNER/NAME,
ALLOWED_GITHUB_ORGS, DASHBOARD_*_URL/ORIGINS, LLM_MODEL_ID).
- OUT_OF_BAND_SSM documents the ~30 params CDK intentionally does NOT
own (operationally-variable / env-specific-unknown).
- Wire ConfigStore into OpenSwe<Env>Stack.
- KebabNamingAspect: exempt Secrets Manager + SSM names, which carry the
literal UPPER_SNAKE env-var segment (open-swe-dev/DASHBOARD_JWT_SECRET).
- deploy/seahaven/put-config.sh: out-of-band populator (placeholders only,
OPENSWE_PUT_<VAR> env indirection; no real values committed).
Synth-only; not deployed. Instance-role read grants on open-swe-<env>/*
already exist from T6 — no IAM/trust changes here.
109 lines
3.7 KiB
TypeScript
109 lines
3.7 KiB
TypeScript
import * as cdk from "aws-cdk-lib";
|
|
import { Annotations, Match, Template } from "aws-cdk-lib/assertions";
|
|
import * as iam from "aws-cdk-lib/aws-iam";
|
|
import { KebabNamingAspect, isKebabCase } from "../lib/aspects/kebab-naming-aspect";
|
|
import { OpenSweIamStack } from "../lib/open-swe-iam-stack";
|
|
import { OpenSweStack } from "../lib/open-swe-stack";
|
|
|
|
const ENV = { account: "328440206208", region: "us-east-1" };
|
|
|
|
describe("isKebabCase", () => {
|
|
it.each([
|
|
"open-swe-dev",
|
|
"open-swe-prod-instance-role",
|
|
"githubdeploy-open-swe-infra",
|
|
"open-swe-dev/slack-signing", // Secrets Manager path
|
|
"/open-swe-dev/feature-flag", // SSM param path
|
|
"/open-swe/dev/agent", // log group path
|
|
"abc123",
|
|
])("accepts conforming name %s", (name) => {
|
|
expect(isKebabCase(name)).toBe(true);
|
|
});
|
|
|
|
it.each([
|
|
"OpenSweDev",
|
|
"open_swe_dev",
|
|
"openSweDev",
|
|
"Open-Swe-Dev",
|
|
"open-swe-dev/SlackSigning",
|
|
])("rejects non-conforming name %s", (name) => {
|
|
expect(isKebabCase(name)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("KebabNamingAspect", () => {
|
|
it("passes the real app stacks (no errors)", () => {
|
|
const app = new cdk.App();
|
|
cdk.Aspects.of(app).add(new KebabNamingAspect());
|
|
|
|
new OpenSweIamStack(app, "OpenSweIamStack", { stackName: "open-swe-iam", env: ENV });
|
|
const dev = new OpenSweStack(app, "OpenSweDevStack", {
|
|
stackName: "open-swe-dev",
|
|
env: ENV,
|
|
envName: "dev",
|
|
});
|
|
const prod = new OpenSweStack(app, "OpenSweProdStack", {
|
|
stackName: "open-swe-prod",
|
|
env: ENV,
|
|
envName: "prod",
|
|
});
|
|
|
|
for (const s of [dev, prod]) {
|
|
Annotations.fromStack(s).hasNoError("*", Match.anyValue());
|
|
}
|
|
});
|
|
|
|
it("exempts Secrets Manager + SSM names that carry the literal env-var segment", () => {
|
|
// The config store names a secret open-swe-dev/ANTHROPIC_API_KEY and a param
|
|
// /open-swe-dev/SANDBOX_TYPE — the UPPER_SNAKE last segment is a REQUIRED
|
|
// deviation from kebab (fetch-config naming contract). Must NOT be flagged.
|
|
const app = new cdk.App();
|
|
cdk.Aspects.of(app).add(new KebabNamingAspect());
|
|
|
|
const dev = new OpenSweStack(app, "OpenSweDevStack", {
|
|
stackName: "open-swe-dev",
|
|
env: ENV,
|
|
envName: "dev",
|
|
});
|
|
|
|
const tpl = Template.fromStack(dev);
|
|
// Sanity: the shells actually render with the literal env-var names.
|
|
tpl.hasResourceProperties("AWS::SecretsManager::Secret", {
|
|
Name: "open-swe-dev/ANTHROPIC_API_KEY",
|
|
});
|
|
tpl.hasResourceProperties("AWS::SSM::Parameter", {
|
|
Name: "/open-swe-dev/SANDBOX_TYPE",
|
|
Value: "langsmith",
|
|
});
|
|
Annotations.fromStack(dev).hasNoError("*", Match.anyValue());
|
|
});
|
|
|
|
it("flags a deliberately non-kebab-case resource name", () => {
|
|
const app = new cdk.App();
|
|
const stack = new cdk.Stack(app, "ConformingStackId", { stackName: "open-swe-test", env: ENV });
|
|
cdk.Aspects.of(stack).add(new KebabNamingAspect());
|
|
|
|
// Deliberately bad physical name — must be flagged.
|
|
new iam.Role(stack, "BadlyNamedRole", {
|
|
roleName: "OpenSweBadRole",
|
|
assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"),
|
|
});
|
|
|
|
Annotations.fromStack(stack).hasError(
|
|
"*",
|
|
Match.stringLikeRegexp("not kebab-case"),
|
|
);
|
|
});
|
|
|
|
it("flags a deliberately non-kebab-case stack name", () => {
|
|
const app = new cdk.App();
|
|
// PascalCase stackName — the convention CDK defaults to and that we forbid.
|
|
const stack = new cdk.Stack(app, "BadStack", { stackName: "OpenSweBadStack", env: ENV });
|
|
cdk.Aspects.of(stack).add(new KebabNamingAspect());
|
|
|
|
Annotations.fromStack(stack).hasError(
|
|
"*",
|
|
Match.stringLikeRegexp("Stack name .* is not kebab-case"),
|
|
);
|
|
});
|
|
});
|