9
votes

Is it possible to create SNS platform application with Cloudformation template?

There is support for aws-cli, http://docs.aws.amazon.com/cli/latest/reference/sns/create-platform-application.html. But there is no information about doing the same with Cloudformation, is it supported at all (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sns-topic.html)?

2
Did you ever figure out how to do this? - Craigt
Seems like the feature is not officially documented and I didn't find any examples at online. I ended up doing this as manual step through aws-cli. - Risto Novik
Thanks for getting back to me. I will have to do something similar. Its a shame. - Craigt

2 Answers

10
votes

NO. It is not possible to create an SNS Platform Application using CloudFormation in AWS.

I am facing the same problem. In AWS Could Formation docs there are only AWS::SNS::Subscription, AWS::SNS::Topic and AWS::SNS::TopicPolicy today. None of them allows to define a Platform Application.

Unless it is possible to declare it under another AWS:: service, it is not possible today. Hope to have this feature soon available.

Kind of solution:

• since creating/updating the Application Endpoint is a rare "event", once a year, I will manually :( create them.

awscli and aws-sdk seem to support the creation of platform application, but it will add extra dependency or development

0
votes

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());