Skip to main content

Deploy AWS resources using AWS CloudFormation

This example demonstrates how to deploy an AWS resource using an AWS CloudFormation template, via Port Actions.

We will use an AWS managed GitHub Action called aws-actions/aws-cloudformation-github-deploy.

Steps​

  1. Create the following GitHub action secrets:

    1. PORT_CLIENT_ID - Port Client ID learn more.
    2. PORT_CLIENT_SECRET - Port Client Secret learn more.
    3. AWS_ACCESS_KEY_ID - AWS credentials.
    4. AWS_SECRET_ACCESS_KEY - AWS credentials.
    5. AWS_REGION - AWS region name to deploy your resources to.
  2. Install Port's GitHub app by clicking here.

  3. Create a Port blueprint with the following JSON definition (choose your desired resource):

Port EC2 Instance Blueprint
{
"identifier": "ec2Instance",
"description": "AWS EC2 Instance",
"title": "EC2 Instance",
"icon": "EC2",
"schema": {
"properties": {
"instance_name": {
"title": "Instance Name",
"type": "string"
},
"instance_type": {
"title": "Instance Type",
"type": "string"
},
"image_id": {
"title": "Image ID",
"type": "string"
},
"key_pair_name": {
"title": "Key Pair Name",
"type": "string"
},
"security_group_ids": {
"title": "Security Group IDs",
"type": "string"
}
},
"required": [
"instance_name",
"instance_type",
"image_id",
"key_pair_name",
"security_group_ids"
]
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {}
}

  1. Create Port Action using the following JSON definition:
note

Please make sure to modify GITHUB_ORG, GITHUB_REPO and GITHUB_WORKFLOW_FILE placeholders to match your environment.

Port Action
{
"identifier": "ec2Instance_deploy_ec2_instance",
"title": "Deploy EC2 Instance",
"icon": "EC2",
"trigger": {
"type": "self-service",
"operation": "CREATE",
"userInputs": {
"properties": {
"instance_name": {
"title": "Instance Name",
"type": "string"
},
"instance_type": {
"title": "Instance Type",
"type": "string",
"default": "t2.micro",
"enum": [
"t2.micro",
"t2.small"
],
"enumColors": {
"t2.micro": "lightGray",
"t2.small": "lightGray"
}
},
"image_id": {
"title": "Image ID",
"type": "string"
},
"key_pair_name": {
"title": "Key Pair Name",
"type": "string"
},
"security_group_ids": {
"title": "Security Group IDs",
"icon": "DefaultProperty",
"type": "string",
"description": "Use comma delimited values for multiple SGs"
}
},
"required": [
"instance_name",
"instance_type",
"image_id",
"key_pair_name",
"security_group_ids"
],
"order": [
"instance_name",
"instance_type",
"image_id",
"key_pair_name",
"security_group_ids"
]
},
"blueprintIdentifier": "ec2Instance"
},
"invocationMethod": {
"type": "GITHUB",
"org": "<GITHUB_ORG>",
"repo": "<GITHUB_REPO>",
"workflow": "<GITHUB_WORKFLOW_FILE>",
"workflowInputs": {
"{{if (.inputs | has(\"ref\")) then \"ref\" else null end}}": "{{.inputs.\"ref\"}}",
"{{if (.inputs | has(\"instance_name\")) then \"instance_name\" else null end}}": "{{.inputs.\"instance_name\"}}",
"{{if (.inputs | has(\"instance_type\")) then \"instance_type\" else null end}}": "{{.inputs.\"instance_type\"}}",
"{{if (.inputs | has(\"image_id\")) then \"image_id\" else null end}}": "{{.inputs.\"image_id\"}}",
"{{if (.inputs | has(\"key_pair_name\")) then \"key_pair_name\" else null end}}": "{{.inputs.\"key_pair_name\"}}",
"{{if (.inputs | has(\"security_group_ids\")) then \"security_group_ids\" else null end}}": "{{.inputs.\"security_group_ids\"}}",
"port_payload": {
"action": "{{ .action.identifier[(\"ec2Instance_\" | length):] }}",
"resourceType": "run",
"status": "TRIGGERED",
"trigger": "{{ .trigger | {by, origin, at} }}",
"context": {
"entity": "{{.entity.identifier}}",
"blueprint": "{{.action.blueprint}}",
"runId": "{{.run.id}}"
},
"payload": {
"entity": "{{ (if .entity == {} then null else .entity end) }}",
"action": {
"invocationMethod": {
"type": "GITHUB",
"omitPayload": false,
"omitUserInputs": false,
"reportWorkflowStatus": true,
"org": "<GITHUB_ORG>",
"repo": "<GITHUB_REPO>",
"workflow": "<GITHUB_WORKFLOW_FILE>"
},
"trigger": "{{.trigger.operation}}"
},
"properties": {
"{{if (.inputs | has(\"instance_name\")) then \"instance_name\" else null end}}": "{{.inputs.\"instance_name\"}}",
"{{if (.inputs | has(\"instance_type\")) then \"instance_type\" else null end}}": "{{.inputs.\"instance_type\"}}",
"{{if (.inputs | has(\"image_id\")) then \"image_id\" else null end}}": "{{.inputs.\"image_id\"}}",
"{{if (.inputs | has(\"key_pair_name\")) then \"key_pair_name\" else null end}}": "{{.inputs.\"key_pair_name\"}}",
"{{if (.inputs | has(\"security_group_ids\")) then \"security_group_ids\" else null end}}": "{{.inputs.\"security_group_ids\"}}"
},
"censoredProperties": "{{.action.encryptedProperties}}"
}
}
},
"reportWorkflowStatus": true
},
"requiredApproval": false,
"publish": true
}

  1. Create a CloudFormation template file in your GitHub repository:
AWS CloudFormation Template
AWSTemplateFormatVersion: "2010-09-09"
Description: CloudFormation Template to Deploy an EC2 Instance

Parameters:
InstanceName:
Description: Name for the EC2 instance
Type: String
MinLength: 1
MaxLength: 255
Default: MyEC2InstanceName
ConstraintDescription: Instance name must not be empty

InstanceType:
Description: EC2 instance type
Type: String
Default: t2.micro
AllowedValues:
- t2.micro
- t2.small
- t2.medium
# Add more instance types as needed
ConstraintDescription: Must be a valid EC2 instance type

ImageId:
Description: ID of the Amazon Machine Image (AMI) to use
Type: AWS::EC2::Image::Id
ConstraintDescription: Must be a valid AMI ID

KeyPairName:
Description: Name of the key pair for SSH access
Type: String
MinLength: 1
MaxLength: 255
ConstraintDescription: Key pair name must not be empty

SecurityGroupIds:
Description: List of Security Group IDs for the EC2 instance
Type: List<AWS::EC2::SecurityGroup::Id>
ConstraintDescription: Must be a list of valid Security Group IDs

Resources:
EC2Instance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref ImageId
KeyName: !Ref KeyPairName
SecurityGroupIds: !Ref SecurityGroupIds
Tags:
- Key: Name
Value: !Ref InstanceName

Outputs:
InstanceId:
Description: ID of the created EC2 instance
Value: !Ref EC2Instance

  1. Create a workflow file under .github/workflows/deploy-cloudformation-template.yml with the following content:
note

Please make sure to modify CF_TEMPLATE_FILE placeholder to match the CloudFormation template file path.

GitHub workflow
name: Deploy CloudFormation - EC2 Instance

on:
workflow_dispatch:
inputs:
instance_name:
required: true
type: string
description: instance name
instance_type:
required: true
type: string
description: instance type
image_id:
required: true
type: string
description: image id
key_pair_name:
required: true
type: string
description: key pair name
security_group_ids:
required: true
type: string
description: security group ids
port_payload:
required: true
description:
Port's payload, including details for who triggered the action and
general context (blueprint, run id, etc...)
type: string

jobs:
deploy-cloudformation-template:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Configure AWS Credentials 🔒
id: aws-credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}

- name: Deploy to AWS CloudFormation
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
name: ${{ inputs.instance_name }}
template: <CF_TEMPLATE_FILE>
parameter-overrides: >-
InstanceName=${{ inputs.instance_name }},
InstanceType=${{ inputs.instance_type }},
ImageId=${{ inputs.image_id }},
KeyPairName=${{ inputs.key_pair_name }},
SecurityGroupIds="${{ inputs.security_group_ids }}"

- name: UPSERT EC2 Instance Entity in Port
uses: port-labs/port-github-action@v1
with:
identifier: ${{ inputs.instance_name }}
title: ${{ inputs.instance_name }}
team: "[]"
icon: EC2
blueprint: ec2Instance
properties: |-
{
"instance_name": "${{ inputs.instance_name }}",
"instance_type": "${{ inputs.instance_type }}",
"image_id": "${{ inputs.image_id }}",
"key_pair_name": "${{ inputs.key_pair_name }}",
"security_group_ids": "${{ inputs.security_group_ids }}"
}
relations: "{}"
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: UPSERT
runId: ${{fromJson(inputs.port_payload).context.runId}}

  1. Trigger the action from the Self-service tab of your Port application.

What's next?​

  • Connect Port's AWS exporter to make sure all of the properties and entities are automatically ingested from AWS.
    • You can learn how to setup Port's AWS exporter here.
    • You can see example configurations and use cases here.