Skip to content

Commit 2d52319

Browse files
committed
feat(riff-raff.yaml)!: Support generation of a subset of GuStacks
Add support for the scenario where we create a singleton stack for shared infrastructure alongside application stacks. For example, a singleton stack that provisions an Elastic Container Registry, and a CODE and PROD application stack that pulls images from the registry. Previously, we had to create work-arounds with multiple `App`s. BREAKING CHANGE: `RiffRaffYamlFile` can no longer be directly instantiated Instead of directly instantiating `RiffRaffYamlFile`, please use `RiffRaffYamlFile.fromApp`: ```ts // Before const app = new App(); new RiffRaffYamlFile(app); // After const app = new App(); RiffRaffYamlFile.fromApp(app); ```
1 parent 91f9ecf commit 2d52319

5 files changed

Lines changed: 215 additions & 28 deletions

File tree

.changeset/young-pets-fetch.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
"@guardian/cdk": major
3+
---
4+
5+
Add support for the scenario where we create a singleton stack for shared infrastructure
6+
alongside application stacks.
7+
For example, a singleton stack that provisions an Elastic Container Registry,
8+
and a CODE and PROD application stack that pulls images from the registry.
9+
10+
Previously, we had to create work-arounds with multiple `App`s.
11+
12+
BREAKING CHANGE: `RiffRaffYamlFile` can no longer be directly instantiated
13+
Instead of directly instantiating `RiffRaffYamlFile`, please use `RiffRaffYamlFile.fromApp`:
14+
15+
```ts
16+
// Before
17+
const app = new App();
18+
new RiffRaffYamlFile(app);
19+
20+
// After
21+
const app = new App();
22+
RiffRaffYamlFile.fromApp(app);
23+
```

src/constructs/root.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { RiffRaffYamlFile } from "../riff-raff-yaml-file";
3030
*/
3131
export class GuRoot extends App {
3232
override synth(options?: StageSynthesisOptions): CloudAssembly {
33-
new RiffRaffYamlFile(this).synth();
33+
RiffRaffYamlFile.fromApp(this).synth();
3434
return super.synth(options);
3535
}
3636
}

src/riff-raff-yaml-file/README.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ new MyStack(app, "my-stack-PROD", {});
3838
```
3939

4040
### Advanced usage
41+
#### Additional deployment types
4142
As noted above, only specific deployment types are currently supported.
4243

4344
If you want to add additional deployment types, you can do so by instantiating `RiffRaffYamlFile` directly:
@@ -54,7 +55,7 @@ const { stack, region } = new MyStack(app, "my-stack", {
5455
env: { region: "eu-west-1" },
5556
});
5657

57-
const riffRaff = new RiffRaffYamlFile(app);
58+
const riffRaff = RiffRaffYamlFile.fromApp(app);
5859
const { riffRaffYaml: { deployments } } = riffRaff;
5960

6061
deployments.set("upload-my-static-files", {
@@ -79,6 +80,36 @@ riffRaff.synth();
7980

8081
When the CDK stack is synthesized, a `riff-raff.yaml` file will be created in the output directory, typically `/<repo-root>/cdk/cdk.out`.
8182

83+
#### Multiple Riff-Raff projects in a single repository
84+
If your repository deploys multiple Riff-Raff projects, use `RiffRaffYamlFile.fromStacks` to generate a `riff-raff.yaml` for a selection of `GuStack`s:
85+
86+
```ts
87+
import { App } from "aws-cdk-lib";
88+
import { RiffRaffYamlFile } from "@guardian/cdk/lib/riff-raff-yaml-file";
89+
90+
const app = new App();
91+
92+
const myInfraStack = new MyInfraStack(app, "my-infra-stack", {
93+
stack: "playground",
94+
stage: "INFRA",
95+
env: { region: "eu-west-1" },
96+
});
97+
98+
const myAppStackCODE = new MyAppStack(app, "my-stack-CODE", {
99+
stack: "playground",
100+
stage: "CODE",
101+
env: { region: "eu-west-1" },
102+
});
103+
const myAppStackPROD = new MyAppStack(app, "my-stack-PROD", {
104+
stack: "playground",
105+
stage: "CODE",
106+
env: { region: "eu-west-1" },
107+
});
108+
109+
RiffRaffYamlFile.fromStacks([myInfraStack], "playground::core-infra"); // Generates a file to `cdk.out/playground::core-infra/riff-raff.yaml`
110+
RiffRaffYamlFile.fromStacks([myAppStackCODE, myAppStackPROD], "playground::my-app"); // Generates a file to `cdk.out/playground::my-app/riff-raff.yaml`
111+
```
112+
82113
## Package layout
83114
`RiffRaffYamlFile` assumes CI has uploaded files in the following structure:
84115

src/riff-raff-yaml-file/index.test.ts

Lines changed: 131 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe("The RiffRaffYamlFile class", () => {
4343
stage: "CODE",
4444
});
4545

46-
const actual = new RiffRaffYamlFile(app).toYAML();
46+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
4747

4848
expect(actual).toMatchInlineSnapshot(`
4949
"allowedStages:
@@ -114,7 +114,7 @@ describe("The RiffRaffYamlFile class", () => {
114114
stage: "CODE",
115115
});
116116

117-
const actual = new RiffRaffYamlFile(app).toYAML();
117+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
118118

119119
expect(actual).toMatchInlineSnapshot(`
120120
"allowedStages:
@@ -180,7 +180,7 @@ describe("The RiffRaffYamlFile class", () => {
180180
new MyDatabaseStack(app, "Database-CODE-deploy", { ...region, stack: "deploy", stage: "PROD" });
181181

182182
expect(() => {
183-
new RiffRaffYamlFile(app);
183+
RiffRaffYamlFile.fromApp(app);
184184
}).toThrowError("Unable to produce a working riff-raff.yaml file; missing 1 definitions"); // Stack of media-service has no CODE stage
185185
});
186186

@@ -190,7 +190,7 @@ describe("The RiffRaffYamlFile class", () => {
190190
new MyApplicationStack(app, "App-CODE-deploy", { stack: "deploy", stage: "CODE" });
191191

192192
expect(() => {
193-
new RiffRaffYamlFile(app);
193+
RiffRaffYamlFile.fromApp(app);
194194
}).toThrowError("Unable to produce a working riff-raff.yaml file; all stacks must have an explicit region set");
195195
});
196196

@@ -199,7 +199,7 @@ describe("The RiffRaffYamlFile class", () => {
199199
class MyApplicationStack extends GuStack {}
200200
new MyApplicationStack(app, "App-PROD-deploy", { stack: "deploy", stage: "PROD", env: { region: "eu-west-1" } });
201201

202-
const actual = new RiffRaffYamlFile(app).toYAML();
202+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
203203

204204
// Not sure why we have the extra `"` characters...they don't appear in the resulting file on disk...
205205
expect(actual).toMatchInlineSnapshot(`
@@ -227,7 +227,7 @@ describe("The RiffRaffYamlFile class", () => {
227227
new MyApplicationStack(app, "App-PROD-deploy", { stack: "deploy", stage: "PROD", env: { region: "eu-west-1" } });
228228
new MyApplicationStack(app, "App-CODE-deploy", { stack: "deploy", stage: "CODE", env: { region: "eu-west-1" } });
229229

230-
const actual = new RiffRaffYamlFile(app).toYAML();
230+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
231231

232232
expect(actual).toMatchInlineSnapshot(`
233233
"allowedStages:
@@ -275,7 +275,7 @@ describe("The RiffRaffYamlFile class", () => {
275275
env: { region: "us-east-1" },
276276
});
277277

278-
const actual = new RiffRaffYamlFile(app).toYAML();
278+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
279279

280280
expect(actual).toMatchInlineSnapshot(`
281281
"allowedStages:
@@ -332,7 +332,7 @@ describe("The RiffRaffYamlFile class", () => {
332332

333333
new MyApplicationStack(app, "test-stack", { stack: "test", stage: "TEST", env: { region: "eu-west-1" } });
334334

335-
const actual = new RiffRaffYamlFile(app).toYAML();
335+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
336336

337337
expect(actual).toMatchInlineSnapshot(`
338338
"allowedStages:
@@ -408,7 +408,7 @@ describe("The RiffRaffYamlFile class", () => {
408408

409409
new MyApplicationStack(app, "test-stack", { stack: "test", stage: "TEST", env: { region: "eu-west-1" } });
410410

411-
const actual = new RiffRaffYamlFile(app).toYAML();
411+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
412412

413413
expect(actual).toMatchInlineSnapshot(`
414414
"allowedStages:
@@ -488,7 +488,7 @@ describe("The RiffRaffYamlFile class", () => {
488488

489489
new MyApplicationStack(app, "test-stack", { stack: "test", stage: "TEST", env: { region: "eu-west-1" } });
490490

491-
const actual = new RiffRaffYamlFile(app).toYAML();
491+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
492492

493493
expect(actual).toMatchInlineSnapshot(`
494494
"allowedStages:
@@ -548,7 +548,7 @@ describe("The RiffRaffYamlFile class", () => {
548548

549549
new MyApplicationStack(app, "test-stack", { stack: "test", stage: "TEST", env: { region: "eu-west-1" } });
550550

551-
const actual = new RiffRaffYamlFile(app).toYAML();
551+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
552552

553553
expect(actual).toMatchInlineSnapshot(`
554554
"allowedStages:
@@ -621,7 +621,7 @@ describe("The RiffRaffYamlFile class", () => {
621621

622622
new MyApplicationStack(app, "test-stack", { stack: "test", stage: "TEST", env: { region: "eu-west-1" } });
623623

624-
const actual = new RiffRaffYamlFile(app).toYAML();
624+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
625625

626626
expect(actual).toMatchInlineSnapshot(`
627627
"allowedStages:
@@ -725,7 +725,7 @@ describe("The RiffRaffYamlFile class", () => {
725725
new MyApplicationStack(app, "test-stack-eu-PROD", { stack: "test", stage: "PROD", env: { region: "eu-west-1" } });
726726
new MyApplicationStack(app, "test-stack-us-PROD", { stack: "test", stage: "PROD", env: { region: "us-east-1" } });
727727

728-
const actual = new RiffRaffYamlFile(app).toYAML();
728+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
729729

730730
expect(actual).toMatchInlineSnapshot(`
731731
"allowedStages:
@@ -954,7 +954,7 @@ describe("The RiffRaffYamlFile class", () => {
954954

955955
new MyApplicationStack(app, "test-stack", { stack: "test", stage: "CODE", env: { region: "eu-west-1" } });
956956

957-
const actual = new RiffRaffYamlFile(app).toYAML();
957+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
958958

959959
expect(actual).toMatchInlineSnapshot(`
960960
"allowedStages:
@@ -1081,7 +1081,7 @@ describe("The RiffRaffYamlFile class", () => {
10811081

10821082
new MyApplicationStack(app, "test-stack", { stack: "test", stage: "CODE", env: { region: "eu-west-1" } });
10831083

1084-
const actual = new RiffRaffYamlFile(app).toYAML();
1084+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
10851085

10861086
expect(actual).toMatchInlineSnapshot(`
10871087
"allowedStages:
@@ -1147,7 +1147,7 @@ describe("The RiffRaffYamlFile class", () => {
11471147
env: { region: "eu-west-1" },
11481148
});
11491149

1150-
const riffraff = new RiffRaffYamlFile(app);
1150+
const riffraff = RiffRaffYamlFile.fromApp(app);
11511151

11521152
riffraff.riffRaffYaml.deployments.set("upload-my-static-files", {
11531153
app: "my-static-site",
@@ -1228,7 +1228,7 @@ describe("The RiffRaffYamlFile class", () => {
12281228
codeStack.addDependency(sharedResources);
12291229
prodStack.addDependency(sharedResources);
12301230

1231-
const actual = new RiffRaffYamlFile(app).toYAML();
1231+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
12321232

12331233
expect(actual).toMatchInlineSnapshot(`
12341234
"allowedStages:
@@ -1302,7 +1302,7 @@ describe("The RiffRaffYamlFile class", () => {
13021302

13031303
new MyApplicationStack(app, "test-stack", { stack: "test", stage: "TEST", env: { region: "eu-west-1" } });
13041304

1305-
const actual = new RiffRaffYamlFile(app).toYAML();
1305+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
13061306

13071307
expect(actual).toMatchInlineSnapshot(`
13081308
"allowedStages:
@@ -1381,7 +1381,7 @@ describe("The RiffRaffYamlFile class", () => {
13811381

13821382
new MyApplicationStack(app, "test-stack", { stack: "test", stage: "TEST", env: { region: "eu-west-1" } });
13831383

1384-
const actual = new RiffRaffYamlFile(app).toYAML();
1384+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
13851385

13861386
expect(actual).toMatchInlineSnapshot(`
13871387
"allowedStages:
@@ -1479,7 +1479,7 @@ describe("The RiffRaffYamlFile class", () => {
14791479
getTemplateAfterAspectInvocation(guStack);
14801480

14811481
// ...so that the CFN Parameters are added to the template, to then be processed by the `RiffRaffYamlFile`
1482-
const actual = new RiffRaffYamlFile(app).toYAML();
1482+
const actual = RiffRaffYamlFile.fromApp(app).toYAML();
14831483

14841484
const cfnParameterName = getAsgRollingUpdateCfnParameterName(guStack.asg);
14851485

@@ -1525,4 +1525,115 @@ describe("The RiffRaffYamlFile class", () => {
15251525
"
15261526
`);
15271527
});
1528+
1529+
it("Should support instantiation with a subset of GuStacks from an App", () => {
1530+
const app = new App();
1531+
1532+
class MyCoreInfrastructureStack extends GuStack {}
1533+
class MyApplicationStack extends GuStack {}
1534+
1535+
const props = {
1536+
env: {
1537+
region: "eu-west-1",
1538+
},
1539+
stack: "deploy",
1540+
};
1541+
1542+
const infraStack = new MyCoreInfrastructureStack(app, "MyCoreInfrastructure-INFRA", {
1543+
...props,
1544+
stage: "INFRA",
1545+
});
1546+
const applicationStacks = [
1547+
new MyApplicationStack(app, "MyApp-CODE", {
1548+
...props,
1549+
stage: "CODE",
1550+
}),
1551+
new MyApplicationStack(app, "MyApp-PROD", {
1552+
...props,
1553+
stage: "PROD",
1554+
}),
1555+
];
1556+
1557+
expect(RiffRaffYamlFile.fromStacks([infraStack], "tools:core-infra").toYAML()).toMatchInlineSnapshot(`
1558+
"allowedStages:
1559+
- INFRA
1560+
deployments:
1561+
cfn-eu-west-1-deploy-my-core-infrastructure-stack:
1562+
type: cloud-formation
1563+
regions:
1564+
- eu-west-1
1565+
stacks:
1566+
- deploy
1567+
app: my-core-infrastructure-stack
1568+
contentDirectory: tools:core-infra
1569+
parameters:
1570+
templateStagePaths:
1571+
INFRA: MyCoreInfrastructure-INFRA.template.json
1572+
"
1573+
`);
1574+
1575+
expect(RiffRaffYamlFile.fromStacks(applicationStacks, "tools:my-app").toYAML()).toMatchInlineSnapshot(`
1576+
"allowedStages:
1577+
- CODE
1578+
- PROD
1579+
deployments:
1580+
cfn-eu-west-1-deploy-my-application-stack:
1581+
type: cloud-formation
1582+
regions:
1583+
- eu-west-1
1584+
stacks:
1585+
- deploy
1586+
app: my-application-stack
1587+
contentDirectory: tools:my-app
1588+
parameters:
1589+
templateStagePaths:
1590+
CODE: MyApp-CODE.template.json
1591+
PROD: MyApp-PROD.template.json
1592+
"
1593+
`);
1594+
});
1595+
1596+
it("Should throw when instantiated from a subset of GuStacks and there are missing stack definitions", () => {
1597+
const app = new App();
1598+
1599+
class MyCoreInfrastructureStack extends GuStack {}
1600+
class MyApplicationStack extends GuStack {}
1601+
1602+
const region = {
1603+
env: {
1604+
region: "eu-west-1",
1605+
},
1606+
};
1607+
1608+
const infraStacks = [
1609+
new MyCoreInfrastructureStack(app, "MyCoreInfrastructure-INFRA-deploy", {
1610+
...region,
1611+
stack: "deploy",
1612+
stage: "INFRA",
1613+
}),
1614+
new MyCoreInfrastructureStack(app, "MyCoreInfrastructure-INFRA-media-service", {
1615+
...region,
1616+
stack: "media-service",
1617+
stage: "INFRA",
1618+
}),
1619+
];
1620+
1621+
const applicationStacks = [
1622+
new MyApplicationStack(app, "App-CODE-deploy", { ...region, stack: "deploy", stage: "CODE" }),
1623+
new MyApplicationStack(app, "App-PROD-media-service", {
1624+
...region,
1625+
stack: "media-service",
1626+
stage: "PROD",
1627+
}),
1628+
new MyApplicationStack(app, "App-PROD-deploy", { ...region, stack: "deploy", stage: "PROD" }),
1629+
];
1630+
1631+
// No error as all stacks with INFRA stage are included
1632+
expect(() => RiffRaffYamlFile.fromStacks(infraStacks, "misc:core-infra")).not.toThrow();
1633+
1634+
// Missing an instance of `MyApplication` for stack=media-service stage=CODE
1635+
expect(() => {
1636+
RiffRaffYamlFile.fromStacks(applicationStacks, "misc:my-app");
1637+
}).toThrow("Unable to produce a working riff-raff.yaml file; missing 1 definitions");
1638+
});
15281639
});

0 commit comments

Comments
 (0)