It can be worth adding some extra work to get the benefits of automation and reproducibility.
I use AWS::CloudFormation::CustomResource with a Lambda Function to do the work.
Add this to your CloudFormation definition.
"MyPlatformApplication": {
Type: "AWS::CloudFormation::CustomResource",
DependsOn: [ "PlatformApplicationProvisionLambdaFunction" ],
Properties: {
ServiceToken: { "Fn::GetAtt", "PlatformApplicationProvisionLambdaFunction.Arn" },
Name: "MyPlatformApplication",
Platform: "GCM",
PlatformCredential: "<Your FCM ServerKey>",
},
},
And define the Lambda Function PlatformApplicationProvisionLambdaFunction using your preferred strategy. For me, I use Serverless Framework so I can put the above custom resource together with the Lambda Function in the same repository and deploy all of them in one go.
Here is the Lambda code in Typescript
import {
CreatePlatformApplicationCommand,
DeletePlatformApplicationCommand,
SetPlatformApplicationAttributesCommand,
SNSClient,
} from '@aws-sdk/client-sns';
import {
CloudFormationCustomResourceCreateEvent,
CloudFormationCustomResourceDeleteEvent,
CloudFormationCustomResourceEvent,
CloudFormationCustomResourceUpdateEvent,
Context,
} from 'aws-lambda';
import response from 'cfn-response';
import middy from '@middy/core';
import middyJsonBodyParser from '@middy/http-json-body-parser';
const snsClient = new SNSClient({});
const parseInput = (event: CloudFormationCustomResourceEvent) => {
const input = {
Name: event.ResourceProperties.Name,
Platform: event.ResourceProperties.Platform,
PlatformCredential: event.ResourceProperties.PlatformCredential,
};
if (input.Name === undefined) {
throw new Error('Missing parameter: Name');
}
if (input.Platform === undefined) {
throw new Error('Missing parameter: Platform');
}
if (input.PlatformCredential === undefined) {
throw new Error('Missing parameter: PlatformCredential');
}
return input;
};
const processCreate = async (event: CloudFormationCustomResourceCreateEvent, context: Context) => {
const input = parseInput(event);
const result = await snsClient.send(
new CreatePlatformApplicationCommand({
Name: input.Name,
Platform: input.Platform,
Attributes: {
PlatformCredential: input.PlatformCredential,
},
}),
);
response.send(event, context, 'SUCCESS', {
Arn: result.PlatformApplicationArn,
});
};
const processUpdate = async (event: CloudFormationCustomResourceUpdateEvent, context: Context) => {
const input = parseInput(event);
const arn = `arn:aws:sns:${process.env.REGION}:${process.env.ACCOUNT_ID}:app/${input.Platform}/${input.Name}`;
await snsClient.send(
new SetPlatformApplicationAttributesCommand({
PlatformApplicationArn: arn,
Attributes: {
PlatformCredential: input.PlatformCredential,
},
}),
);
response.send(event, context, 'SUCCESS', {
Arn: arn,
});
};
const processDelete = async (event: CloudFormationCustomResourceDeleteEvent, context: Context) => {
const input = parseInput(event);
const arn = `arn:aws:sns:${process.env.REGION}:${process.env.ACCOUNT_ID}:app/${input.Platform}/${input.Name}`;
await snsClient.send(
new DeletePlatformApplicationCommand({
PlatformApplicationArn: arn,
}),
);
response.send(event, context, 'SUCCESS', {
Arn: arn,
});
};
const handler = async (event: CloudFormationCustomResourceEvent, context: Context) => {
try {
if (event.RequestType === 'Create') {
await processCreate(event, context);
} else if (event.RequestType === 'Update') {
await processUpdate(event, context);
} else {
await processDelete(event, context);
}
} catch (err) {
if (err instanceof Error) {
response.send(event, context, 'FAILED', { message: err.message });
} else {
response.send(event, context, 'FAILED', err);
}
}
};
export const main = middy(handler).use(middyJsonBodyParser());