How to create multiple resources (just different name) with one entry in CloudFormation

Viewed 316

This is part of my CloudFormation template:

resources:
  DynamoDBTablePoints:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      TableName: points
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: '5'
        WriteCapacityUnits: '5'

  DynamoDBTableScore:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      TableName: score
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: '5'
        WriteCapacityUnits: '5'

  DynamoDBTableName:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      TableName: name
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: '5'
        WriteCapacityUnits: '5'

It creates a 3 DynamoDB resources. This is just an example, do not concentrate on the names or if the tables created are correct. I want to know how to apply this tactic to any resource.

As you can see, there are 3 pretty much the same resource they just differ in name. How can I have one entry like this:

resources:
  DynamoDBTable[Point, Score, Name]:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      TableName: [point, score, name]
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: '5'
        WriteCapacityUnits: '5'
'''
So I get the same result as with the original template. 
2 Answers

You could refactor your template to use nested stacks. Using nested stack you would define your dynamodb template table in a separate template and upload it to S3.

Then in your main template you would use AWS::CloudFormation::Stack to reference the dynamodb template.

You would still have three AWS::CloudFormation::Stack instances in your main template, they would be shorter and would follow good practices when developing with CloudFormation.

I found a solution that works for alarm ressources:

I create php script to generate resources alarms cloudformation and save theses resources into a yml file:

<?php

use Aws\CloudWatch\CloudWatchClient;
use Aws\Credentials\Credentials;
use Symfony\Component\Yaml\Yaml;

require 'vendor/autoload.php';

/**
 * Your AWS account credentials
 */
const AWS_REGION = 'eu-west-3';
const AWS_ACCESS_KEY_ID = '';
const AWS_SECRET_ACCESS_KEY = '';
const PROJECTS = [
    'project01',
    'project02',
    'project03'
];

$credentials = new Credentials(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY);

$clientValues = [
    'version' => 'latest',
    'region' => AWS_REGION,
    'credentials' => $credentials
];

$cloudWatchClient = new CloudWatchClient($clientValues);

// Va permerttre de récupérer les métriques : Namespace Lambda, MetricName Errors
$args = [
    'Namespace' => 'AWS/Lambda',
    'MetricName' => 'Errors',
];

$metrics = [];

do {
    $listMetrics = $cloudWatchClient->listMetrics($args);

    // Ajoute NextToken à la requête listMetrics pour get les prochains résultats (limit 500/page)
    $args['NextToken'] = $listMetrics->hasKey('NextToken') ? $listMetrics->get('NextToken') : null;

    // Rempli un tableau avec les metrics récupérés; on prend FunctionName au lieu de RessourceName (même chose)
    foreach ($listMetrics->get('Metrics') as $metric) {
        if (isset($metric['Dimensions'])) {
            foreach ($metric['Dimensions'] as $dimension) {
                if ($dimension['Name'] === 'FunctionName') {
                    $metrics[] = $dimension['Value'];
                }
            }
        }
    }
} while ($listMetrics->hasKey('NextToken'));

asort($metrics);

$yaml = [];
$alarms = [];
$noAlarm = [];
$okAlarm = [];

foreach ($metrics as $item) {
    preg_match('/(?<=-)[a-zA-Z]*/', $item, $matches);

    if (empty($matches)) {
        $noAlarm[] = $item;
        continue;
    }

    // on check si $matches appartient à la liste des projets sinon on passe l'itération
    if (!in_array($matches[0], PROJECTS)) {
        $noAlarm[] = $item;
        continue;
    }

    $okAlarm[] = $item;

    $alarms['Resources'][sprintf('Alarm%s', str_replace('-', '', $item))] = [
        'Type' => 'AWS::CloudWatch::Alarm',
        'Properties' => [
            'AlarmActions' => [
                0 => '${self:custom.snsTopic}',
            ],
            'AlarmDescription' => sprintf('Lambda error %s', $item),
            'AlarmName' => sprintf('Alarm-%s', $item),
            'ComparisonOperator' => 'GreaterThanOrEqualToThreshold',
            'EvaluationPeriods' => 1,
            'MetricName' => 'Errors',
            'Dimensions' => [
                0 => [
                    'Name' => 'FunctionName',
                    'Value' => $item,
                ],
            ],
            'Namespace' => 'AWS/Lambda',
            'Period' => '${self:custom.period}',
            'Statistic' => 'Sum',
            'Threshold' => 1,
        ],
    ];
}

dump('No alarme = ' . count($noAlarm), 'Ok alarme = ' . count($okAlarm));
dump($noAlarm);

$yaml = Yaml::dump($alarms, 6, 2);

file_put_contents('/var/www/html/resources/resources.yml', $yaml);

Then, in my serverless:

# Usage
# --------------------------------
# serverless deploy
#   --aws-profile <AwsProfile>              [optional] AWS Profile name. Default profile if not set.
#   --stage [dev|sbx|prd]                   [required] Deployment stage : 'dev', 'sbx' or 'prd'.
#   --client <ClientName>                   [required] Client Name. Ex: 'Connect'.

service: Monitoring

provider:
  name: aws
  region: eu-west-3
  runtime: provided
  endpointType: regional
  deploymentBucket:
    name: ${lower:${opt:stage}}-${lower:${opt:client}}-slsdeploy
    blockPublicAccess: true
  deploymentPrefix: Monitoring

plugins:
  - ./sls

custom:
  period: 900
  snsTopic: "arn:aws:sns:${aws:region}:${aws:accountId}:monitoring-metrics"

resources:
  - ${file(resources/resources.yml)}

I hope this will help some people:)

Related