-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #533 from guardian/aa/event-forwarder
feat(event-forwarder): Forward CFN events and ASG activity to Central ELK
- Loading branch information
Showing
14 changed files
with
2,643 additions
and
184 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,16 @@ | ||
import 'source-map-support/register'; | ||
import { GuRoot } from '@guardian/cdk/lib/constructs/root'; | ||
import { CdkPlayground } from '../lib/cdk-playground'; | ||
import { EventForwarder } from '../lib/event-forwarder'; | ||
|
||
const app = new GuRoot(); | ||
new CdkPlayground(app, 'CdkPlayground', { | ||
|
||
const eventForwarder = new EventForwarder(app); | ||
|
||
const applicationStack = new CdkPlayground(app, 'CdkPlayground', { | ||
cloudFormationStackName: 'playground-PROD-cdk-playground', | ||
buildIdentifier: process.env.GITHUB_RUN_NUMBER ?? 'DEV', | ||
}); | ||
|
||
// Configure Riff-Raff to deploy the application stack after the EventForwarder stack has finished. | ||
applicationStack.addDependency(eventForwarder); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
STACK=playground | ||
STAGE=DEV | ||
APP=event-forwarder |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { env } from 'process'; | ||
|
||
export interface Config { | ||
stack: string; | ||
stage: string; | ||
app: string; | ||
} | ||
|
||
function getEnvOrThrow(key: string): string { | ||
const value = env[key]; | ||
if (value === undefined) { | ||
throw new Error(`Environment variable ${key} is not set`); | ||
} | ||
return value; | ||
} | ||
|
||
export function getConfig(): Config { | ||
return { | ||
stack: getEnvOrThrow('STACK'), | ||
stage: getEnvOrThrow('STAGE'), | ||
app: getEnvOrThrow('APP'), | ||
}; | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import { | ||
AutoScalingClient, | ||
DescribeAutoScalingGroupsCommand, | ||
} from '@aws-sdk/client-auto-scaling'; | ||
import { fromIni } from '@aws-sdk/credential-providers'; | ||
import type { AwsCredentialIdentityProvider } from '@smithy/types'; | ||
import { getConfig } from './config'; | ||
import type { | ||
AutoscalingEvent, | ||
CloudformationEvent, | ||
HydratedEvent, | ||
} from './types'; | ||
|
||
interface AwsClientConfig { | ||
region: string; | ||
credentials?: AwsCredentialIdentityProvider; | ||
} | ||
|
||
export async function main(event: CloudformationEvent | AutoscalingEvent) { | ||
const { stage } = getConfig(); | ||
|
||
try { | ||
const hydrated: HydratedEvent = { | ||
...event, | ||
cloudformationStackName: await getCloudformationStackName(event, stage), | ||
}; | ||
console.log(JSON.stringify(hydrated)); | ||
} catch (err) { | ||
if (err instanceof Error) { | ||
console.error(err.message); | ||
} | ||
console.log(JSON.stringify(event)); | ||
} | ||
} | ||
|
||
async function getCloudformationStackName( | ||
event: CloudformationEvent | AutoscalingEvent, | ||
stage: string, | ||
) { | ||
switch (event.source) { | ||
case 'aws.autoscaling': { | ||
return await getCloudformationStackNameForAsg( | ||
event as AutoscalingEvent, | ||
stage, | ||
); | ||
} | ||
case 'aws.cloudformation': { | ||
return getCloudformationStackNameForStack(event as CloudformationEvent); | ||
} | ||
default: { | ||
throw new Error('Unrecognised event source'); | ||
} | ||
} | ||
} | ||
|
||
async function getCloudformationStackNameForAsg( | ||
event: AutoscalingEvent, | ||
stage: string, | ||
): Promise<string> { | ||
const awsConfig: AwsClientConfig = { | ||
region: 'eu-west-1', | ||
...(stage === 'DEV' && { | ||
credentials: fromIni({ profile: 'developerPlayground' }), | ||
}), | ||
}; | ||
|
||
const { AutoScalingGroupName } = event.detail; | ||
|
||
const client = new AutoScalingClient(awsConfig); | ||
const command = new DescribeAutoScalingGroupsCommand({ | ||
AutoScalingGroupNames: [AutoScalingGroupName], | ||
}); | ||
const { AutoScalingGroups = [] } = await client.send(command); | ||
|
||
if (AutoScalingGroups.length !== 1) { | ||
throw new Error('Unable to locate unique autoscaling group'); | ||
} | ||
|
||
const [cfnStackName] = AutoScalingGroups.flatMap((_) => _.Tags ?? []) | ||
.filter((_) => _.Key === 'aws:cloudformation:stack-name') | ||
.map((_) => _.Value); | ||
|
||
if (!cfnStackName) { | ||
throw new Error( | ||
'Unable to locate unique tag: aws:cloudformation:stack-name', | ||
); | ||
} | ||
|
||
return cfnStackName; | ||
} | ||
|
||
async function getCloudformationStackNameForStack( | ||
event: CloudformationEvent, | ||
): Promise<string> { | ||
const stackId = event.detail['stack-id']; | ||
const [, stackName] = stackId.split('/'); | ||
|
||
if (!stackName) { | ||
throw new Error(`Unable to parse stack-id: ${stackId}`); | ||
} | ||
|
||
return Promise.resolve(stackName); | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
This is a little helper to run a lambda locally. | ||
It purposefully does only one thing to keep it simple. | ||
*/ | ||
|
||
import type { CloudformationEvent } from './types'; | ||
import { main } from './index'; | ||
|
||
export const sampleCloudformationEvent: CloudformationEvent = { | ||
version: '0', | ||
id: 'unknown', | ||
'detail-type': 'CloudFormation Resource Status Change', | ||
source: 'aws.cloudformation', | ||
account: '000000000000', | ||
time: '2024-08-24T11:38:34Z', | ||
region: 'eu-west-1', | ||
resources: [ | ||
'arn:aws:cloudformation:eu-west-1:000000000000:stack/playground-CODE-basic-asg-rolling-update/000000-000000', | ||
], | ||
detail: { | ||
'stack-id': | ||
'arn:aws:cloudformation:eu-west-1:000000000000:stack/playground-CODE-basic-asg-rolling-update/000000-000000', | ||
'logical-resource-id': 'AutoScalingGroupBasicASG123', | ||
'physical-resource-id': | ||
'playground-CODE-basic-asg-rolling-update-AutoScalingGroupBasicASG123-ABC', | ||
'resource-type': 'AWS::AutoScaling::AutoScalingGroup', | ||
'status-details': { | ||
status: 'UPDATE_IN_PROGRESS', | ||
'detailed-status': '', | ||
'status-reason': | ||
'New instance(s) added to autoscaling group - Waiting on 5 resource signal(s) with a timeout of PT5M.', | ||
}, | ||
}, | ||
}; | ||
|
||
void main(sampleCloudformationEvent); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import type { EventBridgeEvent } from 'aws-lambda'; | ||
|
||
export type CloudformationEvent = EventBridgeEvent< | ||
string, | ||
{ | ||
'stack-id': string; | ||
'logical-resource-id': string; | ||
'physical-resource-id': string; | ||
'resource-type': string; | ||
'status-details': { | ||
status: string; | ||
'detailed-status': string; | ||
'status-reason': string; | ||
[additional: string]: string; | ||
}; | ||
} | ||
>; | ||
|
||
export type AutoscalingEvent = EventBridgeEvent< | ||
string, | ||
{ | ||
StatusCode: string; | ||
AutoScalingGroupName: string; | ||
ActivityId: string; | ||
Details: Record<string, unknown>; | ||
RequestId: string; | ||
EndTime: string; | ||
EC2InstanceId: string; | ||
StartTime: string; | ||
Cause: string; | ||
} | ||
>; | ||
|
||
export type HydratedEvent = (CloudformationEvent | AutoscalingEvent) & { | ||
cloudformationStackName: string; | ||
}; |
Oops, something went wrong.