How to work with deployments

In this guide we will demonstrate how to work with Attini deployments.

Introduction

An Attini deployment is executed when you upload a distribution into an environment. After the upload, Attini will run the deployment on your behalf using serverless resources within your AWS account.

In this guide, we will first learn how to create an Attini environment and how to upload the distribution to it.

Then we will look at the deployment plan (pipeline) to learn how to reconfigure it according to our needs.

Create an Environment

Before you can deploy a distribution to your AWS account you need to create an environment. This is done by running the attini environment create command. For example:

attini environment create stage

The above example will create an environment in your account called “stage”. Attini has two types of environments, “test” and “production”. The only difference between the two is that you will be prompted for an extra confirmation when deploying to a production environment. Production is the default type, but you can create a test environment by using --type option when creating the environment.

Deploy a distribution

We also need a distribution to deploy, and we can use the Attini CLI to create one for us. Let’s first create a folder for our distribution:

mkdir attini-hello-world
cd attini-hello-world

Then run the following command to create a basic hello world project:

attini init-project hello-world

Our project should now have the following file structure:

├── attini-config.yaml
├── deployment-plan.yaml
└── lambda-endpoint
    └── lambda.yaml

The project contains an attini-config file, a deployment plan, and a folder with a CloudFormation template for a lambda. This example uses CloudFormation for creating the Lambda function but Attini has other types for different tasks. See the additional guides for more information.

We can now deploy the distribution by running the command:

attini deploy run .

The first argument for the attini deploy run command is the path to the distribution to deploy. If the path point towards a directory, the CLI will perform the attini package command automatically. This means that all options available for the attini package command is also available for the attini deploy run command. However, they will only take effect if the distribution is not already packaged.

Let’s run the command 🚂 !

image of deployment plan result

Success! As you noticed we were prompted to confirm the deployment because our environment is of the “production” type. If we dont want to be prompted when deploying we can add the --force option to the command. Because we only have one environment in the account we did not have to specify it. If we had more then one environment in the account the CLI would have asked us to choose one, We can also specify it with the --environment option (-e for short). For example:

attini deploy run . -e stage --force

We have now successfully deployed a distribution. The following sections will take a closer look at the deployment plan and the Attini configuration file.

The deployment plan

The deployment plan is a pipeline that will be executed within your AWS Account by a combination of serverless resources.

It’s defined in a CloudFormation template, which makes it easy to combine with any cloud resources you need. This also allows you to define it using CloudFormation abstractions like the CDK.

When you run a deployment, Attini will first create/update a dedicated deployment plan for your distribution in your environment. The deployment plan is defined in code and packaged with your distribution, making it easy to maintain at scale. This makes it possible to reuse your distribution for multiple environments while keeping the environments completely separated.

The deployment plan uses AWS StepFunction to execute the different steps in the deployment plan. It supports all StepFunction features, as well as a lot of Attini-specific ones. The deployment plan can be written using normal AWS State Language, but it also supports the Attini simplified syntax that eliminates a lot of clutter and makes it much easier to manage. Read more about the deployment plan in the documentation.

The deployment-plan.yaml file in our example project looks like this:

AWSTemplateFormatVersion: 2010-09-09
Transform:
  - AttiniDeploymentPlan
  - AWS::Serverless-2016-10-31

Parameters:
  AttiniEnvironmentName:
    Type: String

Resources:

  HelloWorldDeploymentPlan:
    Type: Attini::Deploy::DeploymentPlan
    Properties:
      DeploymentPlan:
        - Name: Deploy_HelloWorldLambda
          Type: AttiniCfn
          Properties:
            StackName: !Sub ${AttiniEnvironmentName}-hello-world-lambda
            Template: /lambda-endpoint/lambda.yaml
        - Name: Invoke_HelloWorldLambda
          Type: AttiniLambdaInvoke
          Parameters:
            FunctionName.$: $.output.Deploy_HelloWorldLambda.FunctionName

If you are familiar with CloudFormation you might see alot of thing you recognize. The deployment-plan.yaml is a CloudFormation template, meaning we can use any CloudFormation features we need. But the “Attini::Deploy::DeploymentPlan” is a special Attini resource that needs the AttiniFramework to function.

As you can see the deployment plan contains a list of two steps with two different types. The “AttiniCfn” type will deploy a CloudFormation stack containing a lambda, and the “AttiniLambdaInvoke” will invoke the lambda after it is deployed. All steps, no matter the type, must have a unique name.

You can read more about the types used in these guides:

  1. How to deploy CloudFormation
  2. How to invoke a Lambda

Or in the documentation.

Other useful guides about how to use work with deployment plans are:

  1. How to run scripts
  2. How to run steps in parallel

The Attini configuration file

Every distribution has to contain an Attini configuration file Attini configuration file. The Attini configuration file has to be located in the root of the project and can be either in JSON or YAML format. The file has to have one of the following names:

  1. attini-config.yaml
  2. attini-config.yml
  3. attini-config.json

The configuration file in our example project looks like this:

distributionName: hello-world
initDeployConfig:
  template: deployment-plan.yaml
  stackName: ${environment}-${distributionName}-deployment-plan

package:
  prePackage:
    commands:
      - attini configure set-dist-id --random

Because the deployment plan is created using CloudFormation, we need to provide a “template” and “stackName”. The stack name has to be unique within our AWS Account and region, so in the example above example we use the distribution name and the current environment name to create an appropriate name for the stack.

The package section is more extensively covered in the package a distribution guide.

Environment specific configuration

When deploying to different environments you often need different configuration depending on what environment you are deploying to. Configuration for the deployment plan stack can be written in Attini configuration file and can be specified per environment. A simple configuration could look like this:

distributionName: attini-deployment-demo
initDeployConfig:
  template: deployment-plan.yaml
  stackName: ${environment}-${distributionName}-deployment-plan
  parameters:
    default:
      Message: "We are testing"
    production:
      Message: "It's showtime!"

In the above example we specify a parameter called “Message” for the deployment plan stack. If the environment is called “production” the parameter value will be “It’s showtime!”, otherwise it will be “We are testing”. You can have more environment names depending on your needs. If the environment you are deploying to doesn’t match any of the names the “default” value will be used. The deployment plan template for the configuration above could look like this:

AWSTemplateFormatVersion: 2010-09-09
Transform:
  - AttiniDeploymentPlan
  - AWS::Serverless-2016-10-31

Parameters:
  Message:
    Type: String

  RunnerDemo:
    Type: Attini::Deploy::DeploymentPlan
    Properties:
      DeploymentPlan:
        - Name: EchoWorld
          Type: AttiniRunnerJob
          Properties:
            Commands:
              - !Sub echo ${Message}

Because the “Message” is passed as a Parameter we can use normal CloudFormation to read it. You can also find more information about the AttiniRunnerJob here.

Example files

deployment-plan.yaml
AWSTemplateFormatVersion: 2010-09-09
Transform: # These macros is needed for the Attini::Deploy::DeploymentPlan to work. You can add additional macros if you need it.
  - AttiniDeploymentPlan
  - AWS::Serverless-2016-10-31

Parameters:
  AttiniEnvironmentName: # This is automatically configured by the Attini Framework, find more info here https://docs.attini.io/api-reference/cloudformation-configuration.html#framework-parameters
    Type: String

Resources:

  HelloWorldDeploymentPlan:
    Type: Attini::Deploy::DeploymentPlan # https://docs.attini.io/api-reference/deployment-plan/
    Properties:
      DeploymentPlan:
        - Name: Deploy_HelloWorldLambda
          Type: AttiniCfn
          Properties:
            StackName: !Sub ${AttiniEnvironmentName}-hello-world-lambda
            Template: /lambda-endpoint/lambda.yaml
        - Name: Invoke_HelloWorldLambda
          Type: AttiniLambdaInvoke
          Parameters:
            FunctionName.$: $.output.Deploy_HelloWorldLambda.FunctionName
attini-config.yaml
distributionName: hello-world
initDeployConfig:
  template: deployment-plan.yaml # The path to the deployment plan template, find more info here: https://docs.attini.io/getting-started/create-your-first-deployment-plan.html
  stackName: ${environment}-${distributionName}-deployment-plan # The stack name for the init deployment, this stack will for example be called "dev-hello-world-deployment-plan"

package: # When you use the Attini CLI to "package" a distribution, these instructions will be used, find more info here https://docs.attini.io/api-reference/attini-configuration/
  prePackage:
    commands: # These shell commands will be executed on a temporary copy of your files, so they will not affect your source files
      - attini configure set-dist-id --random
lambda.yaml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Parameters:
  AttiniEnvironmentName:
    Type: String

Resources:

  HelloWorldLambda:
    Type: AWS::Serverless::Function
    Properties:
      Description: !Sub Lambda that returns hello ${AttiniEnvironmentName} world
      FunctionName: !Sub ${AttiniEnvironmentName}-hello-world-get-parameter
      InlineCode: |
          import os

          def lambda_handler(event, context):

            return f"Hello {os.environ['Env']} world"          

      Environment:
        Variables:
          Env: !Ref AttiniEnvironmentName
      Handler: index.lambda_handler
      Runtime: python3.9
      FunctionUrlConfig:
        AuthType: NONE


  HelloWorldLambdaLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/lambda/${HelloWorldLambda}
      RetentionInDays: 30


Outputs:
  FunctionName:
    Value: !Ref HelloWorldLambda
  Url:
    Value: !GetAtt HelloWorldLambdaUrl.FunctionUrl