All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.lambadaframework.aws.Cloudformation Maven / Gradle / Ivy

The newest version!
package org.lambadaframework.aws;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.cloudformation.AmazonCloudFormation;
import com.amazonaws.services.cloudformation.AmazonCloudFormationClient;
import com.amazonaws.services.cloudformation.model.*;
import org.lambadaframework.deployer.Deployment;

import java.util.List;

public class Cloudformation extends AWSTools {

    private final static String CLOUDFORMATION_TEMPLATE = "{\n" +
            "  \"AWSTemplateFormatVersion\": \"2010-09-09\",\n" +
            "  \"Description\": \"\",\n" +
            "  \"Parameters\": {\n" +
            "    \"LambdaMemorySize\": {\n" +
            "      \"Type\": \"Number\",\n" +
            "      \"Default\": \"128\",\n" +
            "      \"Description\": \"AWS Lambda Function Maximum Allowed Memory.\"\n" +
            "    },\n" +
            "    \"LambdaHandler\": {\n" +
            "      \"Type\": \"String\",\n" +
            "      \"Default\": \"org.lambadaframework.runtime.Handler\",\n" +
            "      \"Description\": \"AWS Lambda Function entry point.\"\n" +
            "    },\n" +
            "    \"LambdaMaximumExecutionTime\": {\n" +
            "      \"Type\": \"Number\",\n" +
            "      \"Default\": \"3\",\n" +
            "      \"Description\": \"AWS Lambda Function Maximum Execution Time (seconds).\"\n" +
            "    },\n" +
            "    \"DeploymentS3Bucket\": {\n" +
            "      \"Description\": \"Deployment S3 Bucket is where project is deployed after mvn deploy command.\",\n" +
            "      \"Type\": \"String\",\n" +
            "      \"MinLength\": \"3\",\n" +
            "      \"MaxLength\": \"63\"\n" +
            "    },\n" +
            "    \"DeploymentS3Key\": {\n" +
            "      \"Description\": \"Deployment S3 Key is the S3 Path where project is deployed after mvn deploy command.\",\n" +
            "      \"Type\": \"String\",\n" +
            "      \"MinLength\": \"1\"\n" +
            "    },\n" +
            "    \"LambdaDescription\": {\n" +
            "      \"Description\": \"Lambda Description\",\n" +
            "      \"Type\": \"String\",\n" +
            "      \"MinLength\": \"1\"\n" +
            "    },\n" +
            "    \"LambdaExecutionRoleManagedPolicyARNs\": {\n" +
            "      \"Description\": \"Managed Policy ARNs for Lambda Execution IAM Role\",\n" +
            "      \"Type\": \"CommaDelimitedList\",\n" +
            "      \"Default\": \"arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole\"\n" +
            "    },\n" +
            "    \"SecurityGroupIds\": {\n" +
            "      \"Description\": \"Lambda VPC Security Group Ids\",\n" +
            "      \"Type\": \"CommaDelimitedList\",\n" +
            "      \"Default\": \"\"\n" +
            "    },\n" +
            "    \"SubnetIds\": {\n" +
            "      \"Description\": \"Lambda VPC Subnet Ids\",\n" +
            "      \"Type\": \"CommaDelimitedList\",\n" +
            "      \"Default\": \"\"\n" +
            "    }\n" +
            "  },\n" +
            "  \"Conditions\": {\n" +
            "    \"UseVpcForLambda\": {\n" +
            "      \"Fn::Not\": [\n" +
            "        {\n" +
            "          \"Fn::And\": [\n" +
            "            {\n" +
            "              \"Fn::Equals\": [\n" +
            "                {\n" +
            "                  \"Fn::Join\": [\n" +
            "                    \",\",\n" +
            "                    {\n" +
            "                      \"Ref\": \"SubnetIds\"\n" +
            "                    }\n" +
            "                  ]\n" +
            "                },\n" +
            "                \"\"\n" +
            "              ]\n" +
            "            },\n" +
            "            {\n" +
            "              \"Fn::Equals\": [\n" +
            "                {\n" +
            "                  \"Fn::Join\": [\n" +
            "                    \",\",\n" +
            "                    {\n" +
            "                      \"Ref\": \"SecurityGroupIds\"\n" +
            "                    }\n" +
            "                  ]\n" +
            "                },\n" +
            "                \"\"\n" +
            "              ]\n" +
            "            }\n" +
            "          ]\n" +
            "        }\n" +
            "      ]\n" +
            "    }\n" +
            "  },\n" +
            "  \"Mappings\": {\n" +
            "  },\n" +
            "  \"Resources\": {\n" +
            "    \"LambadaExecutionRole\": {\n" +
            "      \"Type\": \"AWS::IAM::Role\",\n" +
            "      \"Properties\": {\n" +
            "        \"AssumeRolePolicyDocument\": {\n" +
            "          \"Version\": \"2012-10-17\",\n" +
            "          \"Statement\": [\n" +
            "            {\n" +
            "              \"Effect\": \"Allow\",\n" +
            "              \"Principal\": {\n" +
            "                \"Service\": [\n" +
            "                  \"lambda.amazonaws.com\",\n" +
            "                  \"apigateway.amazonaws.com\"\n" +
            "                ]\n" +
            "              },\n" +
            "              \"Action\": [\n" +
            "                \"sts:AssumeRole\"\n" +
            "              ]\n" +
            "            }\n" +
            "          ]\n" +
            "        },\n" +
            "        \"ManagedPolicyArns\": {\n" +
            "          \"Ref\": \"LambdaExecutionRoleManagedPolicyARNs\"\n" +
            "        }\n" +
            "      }\n" +
            "    },\n" +
            "    \"LambadaExecutionPolicy\": {\n" +
            "      \"Type\": \"AWS::IAM::Policy\",\n" +
            "      \"Properties\": {\n" +
            "        \"PolicyName\": \"${stage}-${project}-lambda\",\n" +
            "        \"PolicyDocument\": {\n" +
            "          \"Version\": \"2012-10-17\",\n" +
            "          \"Statement\": [\n" +
            "            {\n" +
            "              \"Effect\": \"Allow\",\n" +
            "              \"Action\": [\n" +
            "                \"ec2:CreateNetworkInterface\",\n" +
            "                \"ec2:DescribeNetworkInterfaces\",\n" +
            "                \"ec2:DeleteNetworkInterface\"\n" +
            "              ],\n" +
            "              \"Resource\": \"*\"\n" +
            "            },\n" +
            "            {\n" +
            "              \"Action\": [\n" +
            "                \"logs:CreateLogGroup\",\n" +
            "                \"logs:CreateLogStream\",\n" +
            "                \"logs:PutLogEvents\"\n" +
            "              ],\n" +
            "              \"Effect\": \"Allow\",\n" +
            "              \"Resource\": \"arn:aws:logs:*\"\n" +
            "            },\n" +
            "            {\n" +
            "              \"Effect\": \"Allow\",\n" +
            "              \"Action\": [\n" +
            "                \"lambda:InvokeFunction\"\n" +
            "              ],\n" +
            "              \"Resource\": [\n" +
            "                \"*\"\n" +
            "              ]\n" +
            "            },\n" +
            "            {\n" +
            "              \"Effect\": \"Allow\",\n" +
            "              \"Action\": [\n" +
            "                \"apigateway:*\",\n" +
            "                \"iam:PassRole\"\n" +
            "              ],\n" +
            "              \"Resource\": [\n" +
            "                \"*\"\n" +
            "              ]\n" +
            "            }\n" +
            "          ]\n" +
            "        },\n" +
            "        \"Roles\": [\n" +
            "          {\n" +
            "            \"Ref\": \"LambadaExecutionRole\"\n" +
            "          }\n" +
            "        ]\n" +
            "      }\n" +
            "    },\n" +
            "    \"LambdaPermissionForApiGateway\": {\n" +
            "      \"Type\": \"AWS::Lambda::Permission\",\n" +
            "      \"Properties\": {\n" +
            "        \"Action\": \"lambda:InvokeFunction\",\n" +
            "        \"FunctionName\": {\n" +
            "          \"Fn::GetAtt\": [\n" +
            "            \"LambdaFunction\",\n" +
            "            \"Arn\"\n" +
            "          ]\n" +
            "        },\n" +
            "        \"Principal\": \"apigateway.amazonaws.com\",\n" +
            "        \"SourceArn\": {\n" +
            "          \"Fn::Join\": [\n" +
            "            \"\",\n" +
            "            [\n" +
            "              \"arn:aws:execute-api:\",\n" +
            "              {\n" +
            "                \"Ref\": \"AWS::Region\"\n" +
            "              },\n" +
            "              \":\",\n" +
            "              {\n" +
            "                \"Ref\": \"AWS::AccountId\"\n" +
            "              },\n" +
            "              \":*\"\n" +
            "            ]\n" +
            "          ]\n" +
            "        }\n" +
            "      }\n" +
            "    },\n" +
            "    \"LambdaFunction\": {\n" +
            "      \"Type\": \"AWS::Lambda::Function\",\n" +
            "      \"Properties\": {\n" +
            "        \"Handler\": {\n" +
            "          \"Ref\": \"LambdaHandler\"\n" +
            "        },\n" +
            "        \"Role\": {\n" +
            "          \"Fn::GetAtt\": [\n" +
            "            \"LambadaExecutionRole\",\n" +
            "            \"Arn\"\n" +
            "          ]\n" +
            "        },\n" +
            "        \"Code\": {\n" +
            "          \"S3Bucket\": {\n" +
            "            \"Ref\": \"DeploymentS3Bucket\"\n" +
            "          },\n" +
            "          \"S3Key\": {\n" +
            "            \"Ref\": \"DeploymentS3Key\"\n" +
            "          }\n" +
            "        },\n" +
            "        \"Runtime\": \"java8\",\n" +
            "        \"Timeout\": {\n" +
            "          \"Ref\": \"LambdaMaximumExecutionTime\"\n" +
            "        },\n" +
            "        \"MemorySize\": {\n" +
            "          \"Ref\": \"LambdaMemorySize\"\n" +
            "        },\n" +
            "        \"Description\": {\n" +
            "          \"Ref\": \"LambdaDescription\"\n" +
            "        },\n" +
            "        \"VpcConfig\": {\n" +
            "          \"Fn::If\": [\n" +
            "            \"UseVpcForLambda\",\n" +
            "            {\n" +
            "              \"SecurityGroupIds\": {\n" +
            "                \"Fn::If\": [\n" +
            "                  \"UseVpcForLambda\",\n" +
            "                  {\n" +
            "                    \"Ref\": \"SecurityGroupIds\"\n" +
            "                  },\n" +
            "                  {\n" +
            "                    \"Ref\": \"AWS::NoValue\"\n" +
            "                  }\n" +
            "                ]\n" +
            "              },\n" +
            "              \"SubnetIds\": {\n" +
            "                \"Fn::If\": [\n" +
            "                  \"UseVpcForLambda\",\n" +
            "                  {\n" +
            "                    \"Ref\": \"SubnetIds\"\n" +
            "                  },\n" +
            "                  {\n" +
            "                    \"Ref\": \"AWS::NoValue\"\n" +
            "                  }\n" +
            "                ]\n" +
            "              }\n" +
            "            },\n" +
            "            {\n" +
            "              \"Ref\": \"AWS::NoValue\"\n" +
            "            }\n" +
            "          ]\n" +
            "        }\n" +
            "      }\n" +
            "    }\n" +
            "  },\n" +
            "  \"Outputs\": {\n" +
            "    \"LambdaExecutionRoleArn\": {\n" +
            "      \"Description\": \"Lambada Execution Role ARN\",\n" +
            "      \"Value\": {\n" +
            "        \"Fn::GetAtt\": [\n" +
            "          \"LambadaExecutionRole\",\n" +
            "          \"Arn\"\n" +
            "        ]\n" +
            "      }\n" +
            "    },\n" +
            "    \"LambdaFunctionArn\": {\n" +
            "      \"Description\": \"Lambada Function ARN\",\n" +
            "      \"Value\": {\n" +
            "        \"Fn::GetAtt\": [\n" +
            "          \"LambdaFunction\",\n" +
            "          \"Arn\"\n" +
            "        ]\n" +
            "      }\n" +
            "    }\n" +
            "  }\n" +
            "}";


    private final static String LAMBDA_EXECUTION_IAM_RESOURCE_NAME = "LambdaExecutionRoleArn";
    private final static String LAMBDA_EXECUTION_NAME = "LambdaFunctionArn";

    private AmazonCloudFormationClient cloudformationClient;

    protected Deployment deployment;

    public Cloudformation(Deployment deployment) {
        this.deployment = deployment;
    }

    protected AmazonCloudFormationClient getCloudFormationClient() {
        if (null != cloudformationClient) {
            return cloudformationClient;
        }

        return cloudformationClient = new AmazonCloudFormationClient(getAWSCredentialsProvideChain()).withRegion(Region.getRegion(Regions.fromName(deployment.getRegion())));
    }

    public String getCloudformationTemplate() {
        return CLOUDFORMATION_TEMPLATE
                .replace("${project}", deployment.getProjectName())
                .replace("${stage}", deployment.getStage());

    }


    public static class CloudFormationOutput {

        protected String lambdaExecutionRole;

        protected String lambdaFunctionArn;

        public String getLambdaExecutionRole() {
            return lambdaExecutionRole;
        }

        public CloudFormationOutput setLambdaExecutionRole(String lambdaExecutionRole) {
            this.lambdaExecutionRole = lambdaExecutionRole;
            return this;
        }

        public String getLambdaFunctionArn() {
            return lambdaFunctionArn;
        }

        public CloudFormationOutput setLambdaFunctionArn(String lambdaFunctionArn) {
            this.lambdaFunctionArn = lambdaFunctionArn;
            return this;
        }
    }

    public String waitForCompletion() throws Exception {

        DescribeStacksRequest wait = new DescribeStacksRequest();
        wait.setStackName(deployment.getCloudFormationStackName());
        Boolean completed = false;
        String stackStatus = "Unknown";
        String stackReason = "";

        log.info("Waiting");

        int iteration = 0;
        while (!completed) {
            List stacks = getCloudFormationClient().describeStacks(wait).getStacks();
            if (stacks.isEmpty()) {
                completed = true;
                stackStatus = "NO_SUCH_STACK";
                stackReason = "Stack has been deleted";
            } else {
                for (Stack stack : stacks) {

                    if (stack.getStackStatus().contains("FAILED")
                            || stack.getStackStatus().equals(StackStatus.UPDATE_ROLLBACK_COMPLETE.toString())
                            || stack.getStackStatus().equals(StackStatus.ROLLBACK_COMPLETE.toString())
                            ) {
                        throw new Exception("Cloudformation failed. Please check AWS Console for details");
                    }


                    if (stack.getStackStatus().equals(StackStatus.UPDATE_COMPLETE.toString())
                            || stack.getStackStatus().equals(StackStatus.CREATE_COMPLETE.toString())
                            ) {
                        completed = true;
                        stackStatus = stack.getStackStatus();
                        stackReason = stack.getStackStatusReason();
                    }
                }
            }

            // Show we are waiting
            log.info("Please wait (" + ++iteration + ")...");

            // Not done yet so sleep for 2 seconds.
            if (!completed) Thread.sleep(1000);
        }

        // Show we are done
        log.info("Cloudformation update completed.");

        return stackStatus + (stackReason != null ? " (" + stackReason + ")" : "");
    }

    public CloudFormationOutput getStackOutputs(AmazonCloudFormation stackbuilder,
                                                String stackName) {
        DescribeStacksRequest wait = new DescribeStacksRequest();
        wait.setStackName(stackName);
        List stacks = getCloudFormationClient().describeStacks(wait).getStacks();

        CloudFormationOutput cloudFormationOutput = new CloudFormationOutput();

        for (Stack stack : stacks) {
            if (stack.getStackName().equals(stackName)) {
                stack.getOutputs().forEach(output -> {
                    if (output.getOutputKey().equals(LAMBDA_EXECUTION_IAM_RESOURCE_NAME)) {
                        cloudFormationOutput.setLambdaExecutionRole(output.getOutputValue());
                    }

                    if (output.getOutputKey().equals(LAMBDA_EXECUTION_NAME)) {
                        cloudFormationOutput.setLambdaFunctionArn(output.getOutputValue());
                    }
                });
                return cloudFormationOutput;
            }
        }
        throw new RuntimeException("Unknown Cloudformation error. Try deploying.");

    }


    public CloudFormationOutput createOrUpdateStack() throws Exception {
        log.info("Creating or updating Cloudformation stack");
        try {
            createStack(deployment, getCloudformationTemplate());
        } catch (AlreadyExistsException alreadyExistsException) {
            log.info("Stack already exists. Trying to update.");
            try {
                updateStack(deployment, getCloudformationTemplate());
            } catch (AmazonServiceException noUpdateNeededException) {
                log.info("No updates needed for Cloudformation. Resuming deployment.");
            }
        }

        return getStackOutputs(getCloudFormationClient(), deployment.getCloudFormationStackName());
    }

    protected void createStack(Deployment deployment,
                               String templateBody) throws Exception {

        String templateName = deployment.getCloudFormationStackName();
        CreateStackRequest createRequest = new CreateStackRequest();
        createRequest.setStackName(templateName);
        createRequest.setTemplateBody(templateBody);
        createRequest.setParameters(deployment.getCloudFormationParameters());
        createRequest.withCapabilities(Capability.CAPABILITY_IAM);
        getCloudFormationClient().createStack(createRequest);
        log.info("Stack creation completed, the stack " + templateName + " completed with " + waitForCompletion());
    }

    protected void updateStack(Deployment deployment,
                               String templateBody) throws Exception {
        String templateName = deployment.getCloudFormationStackName();
        UpdateStackRequest updateStackRequest = new UpdateStackRequest();
        updateStackRequest.setStackName(templateName);
        updateStackRequest.setTemplateBody(templateBody);
        updateStackRequest.setParameters(deployment.getCloudFormationParameters());
        updateStackRequest.withCapabilities(Capability.CAPABILITY_IAM);
        getCloudFormationClient().updateStack(updateStackRequest);
        log.info("Stack update completed, the stack " + templateName + " completed with " + waitForCompletion());
    }

}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy