Skip to content

Commit

Permalink
Merge pull request #8728 from guardian/front-code-stack
Browse files Browse the repository at this point in the history
feat: add front rendering app
  • Loading branch information
jamesgorrie authored Oct 12, 2023
2 parents cebb395 + d9490ef commit cf12c19
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 76 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ jobs:
- frontend-cfn
rendering:
- rendering
front-web-cfn:
- front-web-cfn
front-web:
- front-web
frontend-static:
- frontend-static
22 changes: 21 additions & 1 deletion dotcom-rendering/cdk/bin/cdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { DotcomRendering } from '../lib/dotcom-rendering';
const app = new App();

const sharedProps = {
app: 'rendering',
stack: 'frontend',
region: 'eu-west-1',
};

new DotcomRendering(app, 'DotcomRendering-PROD', {
...sharedProps,
app: 'rendering',
stage: 'PROD',
minCapacity: 30,
maxCapacity: 120,
Expand All @@ -20,8 +20,28 @@ new DotcomRendering(app, 'DotcomRendering-PROD', {

new DotcomRendering(app, 'DotcomRendering-CODE', {
...sharedProps,
app: 'rendering',
stage: 'CODE',
minCapacity: 1,
maxCapacity: 4,
instanceType: 't4g.micro',
});

new DotcomRendering(app, 'DotcomRendering-front-web-CODE', {
...sharedProps,
app: 'front-web',
stage: 'CODE',
minCapacity: 1,
maxCapacity: 4,
instanceType: 't4g.micro',
});

new DotcomRendering(app, 'DotcomRendering-front-web-PROD', {
...sharedProps,
app: 'front-web',
stage: 'PROD',
// TODO: up this once we have code working
minCapacity: 1,
maxCapacity: 4,
instanceType: 't4g.micro',
});
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,25 @@ sudo NODE_ENV=$NODE_ENV GU_STAGE=$GU_STAGE -u dotcom-rendering -g frontend make
},
"Type": "AWS::IAM::InstanceProfile",
},
"loadBalancerDnsName0B1DEBAD": {
"Properties": {
"Name": "/frontend/TEST/rendering.loadBalancerDnsName",
"Tags": {
"Stack": "frontend",
"Stage": "TEST",
"gu:cdk:version": "TEST",
"gu:repo": "guardian/dotcom-rendering",
},
"Type": "String",
"Value": {
"Fn::GetAtt": [
"InternalLoadBalancer",
"DNSName",
],
},
},
"Type": "AWS::SSM::Parameter",
},
},
}
`;
14 changes: 13 additions & 1 deletion dotcom-rendering/cdk/lib/dotcom-rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { CfnAlarm } from 'aws-cdk-lib/aws-cloudwatch';
import { InstanceType, Peer } from 'aws-cdk-lib/aws-ec2';
import { LoadBalancingProtocol } from 'aws-cdk-lib/aws-elasticloadbalancing';
import { StringParameter } from 'aws-cdk-lib/aws-ssm';
import type { DCRAlarmConfig, DCRProps } from './types';
import { getUserData } from './userData';

Expand Down Expand Up @@ -88,7 +89,11 @@ export class DotcomRendering extends GuStack {
/**
* TODO - migrate this ELB (classic load balancer) to an ALB (application load balancer)
* @see https://github.com/guardian/cdk/blob/512536bd590b26d9fcac5d39329e8217103d7859/src/constructs/loadbalancing/elb.ts#L24-L46
*
* GOTCHA: The load balancer name appends `-ELB` when the `app = "rendering"` for backwards compatibility
* We removed this to avoid the `LoadBalancerName.length > 32`. This will be fixable once we migrate to ALBs.
*/
const loadBalancerName = app === 'rendering' ? `${stack}-${stage}-${app}-ELB` : `${stack}-${stage}-${app}`;
const loadBalancer = new GuClassicLoadBalancer(
this,
'InternalLoadBalancer',
Expand Down Expand Up @@ -129,7 +134,7 @@ export class DotcomRendering extends GuStack {
],
subnetSelection: { subnets: publicSubnets },
propertiesToOverride: {
LoadBalancerName: `${stack}-${stage}-${app}-ELB`,
LoadBalancerName: loadBalancerName,
// Note: this does not prevent the GuClassicLoadBalancer
// from creating a default security group, though it does
// override which one is used/associated with the load balancer
Expand All @@ -145,6 +150,13 @@ export class DotcomRendering extends GuStack {
new CfnOutput(this, 'LoadBalancerUrl', {
value: loadBalancer.loadBalancerDnsName,
});

new StringParameter(this, 'loadBalancerDnsName', {
// Annoyingly this doesn't follow the same pattern as the other SSM parameters
parameterName: `/${stack}/${stage}/${app}.loadBalancerDnsName`,
stringValue: loadBalancer.loadBalancerDnsName,
});

// ------------------------------------

// ------------------------------------
Expand Down
163 changes: 95 additions & 68 deletions dotcom-rendering/scripts/deploy/build-riffraff-bundle.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,89 +8,120 @@ import { log, warn } from '../env/log.js';
const dirname = url.fileURLToPath(new URL('.', import.meta.url));
const target = path.resolve(dirname, '../..', 'target');

// This task generates the riff-raff bundle. It creates the following
// directory layout under target/
//
// target
// ├── build.json
// ├── riff-raff.yaml
// ├── frontend-cfn
// │ ├── DotcomRendering-CODE.template.json
// │ ├── DotcomRendering-PROD.template.json
// ├── frontend-static
// │ ├── assets
// │ │ └── **
// │ │ └── *
// │ └── static
// │ ├── frontend
// │ │ └── **
// │ │ └── *
// │ └── etc
// └── rendering
// └── dist
// └── rendering.zip

const copyCfn = () => {
log(' - copying cloudformation config');
return cpy(
/** This task generates the riff-raff bundle. It creates the following
* directory layout under target/
* target
* ├── build.json
* ├── riff-raff.yaml
* ├── ${copyFrontendStatic()}
* ├── ${copyApp('rendering')}
* └── ${copyApp('renderi-front')}
*/

/**
* This method creates a bundle needed to run an app including:
* - CloudFormation files
* - .zip artefact comprised of the JS app
*
* It generates a folder like this:
* ├── ${appName}-cfn
* │ ├── DotcomRendering-${appName}-CODE.template.json
* │ └── DotcomRendering-${appName}-PROD.template.json
* └── ${appName}
* └── dist
* └── ${appName}.zip
*
* Except for the instance where appName === 'rendering' due to backwards compatibility
*
* @param appName {string}
**/
const copyApp = (appName) => {
// GOTCHA: This is a little hack to be backwards compatible with the naming for when this was a single stack app
const cfnTemplateName = appName === 'rendering' ? '' : `-${appName}`;
const cfnFolder =
appName === 'rendering' ? 'frontend-cfn' : `${appName}-cfn`;

log(` - copying app: ${appName}`);

log(` - ${appName}: copying cloudformation config`);
const cfnJob = cpy(
[
'cdk.out/DotcomRendering-CODE.template.json',
'cdk.out/DotcomRendering-PROD.template.json',
`cdk.out/DotcomRendering${cfnTemplateName}-CODE.template.json`,
`cdk.out/DotcomRendering${cfnTemplateName}-PROD.template.json`,
],
path.resolve(target, 'frontend-cfn'),
path.resolve(target, cfnFolder),
);

log(` - ${appName}: copying makefile`);
const makefileJob = cpy(['makefile'], path.resolve(target, appName));

log(` - ${appName}: copying server dist`);
const serverDistJob = cpy(
path.resolve(dirname, '../../dist/**'),
path.resolve(target, appName, 'dist'),
{
nodir: true,
},
);

log(`' - ${appName}: copying scripts`);
const scriptsJob = cpy(
path.resolve(dirname, '../../scripts/**'),
path.resolve(target, appName, 'scripts'),
{
nodir: true,
},
);

return [cfnJob, makefileJob, serverDistJob, scriptsJob];
};

const copyStatic = () => {
/**
* This method copies the static files over the frontend-static folder, which is then deployed to S3.
*
* It generates a folder like this:
* ├── frontend-static
* ├── assets
* │ └── **
* │ └── *
* └── static
* ├── frontend
* │ └── **
* │ └── *
* └── etc
*/
const copyFrontendStatic = () => {
log(' - copying static');
return cpy(
const staticJob = cpy(
path.resolve(dirname, '../../src/static/**'),
path.resolve(target, 'frontend-static', 'static', 'frontend'),
{
nodir: true,
},
);
};

const copyDist = () => {
log(' - copying dist');
const source = path.resolve(dirname, '../../dist');
const dest = path.resolve(target, 'frontend-static', 'assets');
return Promise.all([
cpy(path.resolve(source, '**/*.!(html|json)'), dest, {
nodir: true,
}),
cpy(path.resolve(source, 'stats'), path.resolve(dest, 'stats'), {
nodir: true,
}),
]);
};

const copyScripts = () => {
log(' - copying scripts');
return cpy(
path.resolve(dirname, '../../scripts/**'),
path.resolve(target, 'rendering', 'scripts'),
log(' - copying dist => assets');
const distToAssetsJob = cpy(
path.resolve(source, '**/*.!(html|json)'),
dest,
{
nodir: true,
},
);
};

const copyDistServer = () => {
log(' - copying server dist');
return cpy(
path.resolve(dirname, '../../dist/**'),
path.resolve(target, 'rendering', 'dist'),
log(' - copying stats => assets');
const statsToAssetsJob = cpy(
path.resolve(source, 'stats'),
path.resolve(dest, 'stats'),
{
nodir: true,
},
);
};

const copyMakefile = () => {
log(' - copying makefile');
return cpy(['makefile'], path.resolve(target, 'rendering'));
return [staticJob, distToAssetsJob, statsToAssetsJob];
};

const copyRiffRaff = () => {
Expand All @@ -99,15 +130,11 @@ const copyRiffRaff = () => {
};

Promise.all([
copyCfn(),
copyMakefile(),
copyStatic(),
copyDist(),
copyDistServer(),
copyScripts(),
...copyApp('rendering'),
...copyApp('front-web'),
...copyFrontendStatic(),
copyRiffRaff(),
])
.catch((err) => {
warn(err.stack);
process.exit(1);
});
]).catch((err) => {
warn(err.stack);
process.exit(1);
});
32 changes: 26 additions & 6 deletions dotcom-rendering/scripts/deploy/riff-raff.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,48 @@ regions: [eu-west-1]
allowedStages:
- CODE
- PROD
templates:
cloudformation:
type: cloud-formation
parameters:
amiEncrypted: true
amiTags:
# Keep the Node version in sync with `.nvmrc`
Recipe: dotcom-rendering-ARM-jammy-node-18.17.0
AmigoStage: PROD
deployments:
frontend-cfn:
type: cloud-formation
template: cloudformation
parameters:
templateStagePaths:
CODE: DotcomRendering-CODE.template.json
PROD: DotcomRendering-PROD.template.json
cloudFormationStackByTags: false
cloudFormationStackName: rendering
amiParameter: AMIRendering
amiEncrypted: true
amiTags:
# Keep the Node version in sync with `.nvmrc`
Recipe: dotcom-rendering-ARM-jammy-node-18.17.0
AmigoStage: PROD
rendering:
type: autoscaling
parameters:
bucketSsmKey: /account/services/dotcom-artifact.bucket
dependencies:
- frontend-static
- frontend-cfn
front-web-cfn:
template: cloudformation
parameters:
templateStagePaths:
CODE: DotcomRendering-front-web-CODE.template.json
PROD: DotcomRendering-front-web-PROD.template.json
cloudFormationStackByTags: false
cloudFormationStackName: front-web
amiParameter: AMIFrontweb
front-web:
type: autoscaling
parameters:
bucketSsmKey: /account/services/dotcom-artifact.bucket
dependencies:
- frontend-static
- frontend-cfn
frontend-static:
type: aws-s3
parameters:
Expand Down

0 comments on commit cf12c19

Please sign in to comment.