Skip to main content

Create An AWS EC2 Instance

Overview

In the following guide, you are going to create a self-service action in Port that executes a GitHub workflow to create an EC2 Instance in AWS using Terraform templates.

Prerequisites

  1. A GitHub repository to contain your action resources i.e. the github workflow file.

  2. An AWS Account or IAM user with permission to create access keys. Learn more

  3. An SSH Key Pair to connect with the provisioned instance. Learn more

  4. Install the Ports GitHub app from here.

  5. In your GitHub repository, go to Settings > Secrets and add the following secrets:

    • PORT_CLIENT_ID - Port Client ID learn more
    • PORT_CLIENT_SECRET - Port Client Secret learn more
    • TF_USER_AWS_KEY - An aws access key with the right iam permission to create an ec2 instance learn more
    • TF_USER_AWS_SECRET - An aws access key secret with permission to create an ec2 instance learn more
    • TF_USER_AWS_REGION - The aws region where you would like to provision your ec2 instance.
  6. Create a blueprint in Port for the EC2 Instance.

EC2 Instance Blueprint
{
"identifier": "ec2Instance",
"description": "This blueprint represents an AWS EC2 instance in our software catalog.",
"title": "EC2 Instance",
"icon": "EC2",
"schema": {
"properties": {
"instance_state": {
"type": "string",
"title": "Instance State",
"description": "The state of the EC2 instance (e.g., running, stopped).",
"enum": [
"pending",
"running",
"shutting-down",
"terminated",
"stopping",
"stopped"
],
"enumColors": {
"pending": "yellow",
"running": "green",
"shutting-down": "pink",
"stopped": "purple",
"stopping": "orange",
"terminated": "red"
}
},
"instance_type": {
"type": "string",
"title": "Instance Type",
"description": "The type of EC2 instance (e.g., t2.micro, m5.large)."
},
"availability_zone": {
"type": "string",
"title": "Availability Zone",
"description": "The Availability Zone where the EC2 instance is deployed."
},
"public_dns": {
"type": "string",
"title": "Public DNS",
"description": "The public DNS name assigned to the EC2 instance."
},
"public_ip": {
"type": "string",
"title": "Public IP Address",
"description": "The public IP address assigned to the EC2 instance."
},
"private_dns": {
"type": "string",
"title": "Private DNS",
"description": "The private DNS name assigned to the EC2 instance within its VPC."
},
"private_ip": {
"type": "string",
"title": "Private IP Address",
"description": "The private IP address assigned to the EC2 instance within its VPC."
},
"monitoring": {
"type": "boolean",
"title": "Monitoring",
"description": "Indicates if detailed monitoring is enabled for the EC2 instance."
},
"security_group_ids": {
"type": "array",
"title": "Security Group IDs",
"description": "The list of security group IDs assigned to the EC2 instance."
},
"key_name": {
"type": "string",
"title": "Key Name",
"description": "The name of the key pair associated with the EC2 instance."
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"aggregationProperties": {},
"relations": {}
}

GitHub Workflow

  1. Create a folder in a directory of your choice within your github repository to host the terraform template files.

  2. Create the following terraform templates ( main.tf, variables.tf and outputs.tf ) within the created folder.

tip

We recommend creating a dedicated repository for the workflows that are used by Port actions.

main.tf
main.tf
data "aws_ami" "ubuntu" {
most_recent = true

filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/*20.04-amd64-server-*"]
}

filter {
name = "virtualization-type"
values = ["hvm"]
}

owners = ["099720109477"] # Canonical
}

provider "aws" {
region = var.aws_region
}

resource "aws_instance" "app_server" {
ami = data.aws_ami.ubuntu.id
instance_type = var.ec2_instance_type
key_name = var.pem_key_name

tags = {
Name = var.ec2_name
}
}

variables.tf
variable "ec2_name" {
type = string
}

variable "pem_key_name" {
type = string
}

variable "aws_region" {
type = string
}

variable "ec2_instance_type" {
type = string
}
outputs.tf
outputs.tf

output "instance_id" {
description = "The unique identifier for the provisioned EC2 instance."
value = aws_instance.app_server.id
}

output "instance_state" {
description = "The state of the EC2 instance (e.g., running, stopped)."
value = aws_instance.app_server.instance_state
}

output "instance_type" {
description = "The type of EC2 instance (e.g., t2.micro, m5.large)."
value = aws_instance.app_server.instance_type
}

output "availability_zone" {
description = "The Availability Zone where the EC2 instance is deployed."
value = aws_instance.app_server.availability_zone
}

output "public_dns" {
description = "The public DNS name assigned to the EC2 instance."
value = aws_instance.app_server.public_dns
}

output "public_ip" {
description = "The public IP address assigned to the EC2 instance."
value = aws_instance.app_server.public_ip
}

output "private_dns" {
description = "The private DNS name assigned to the EC2 instance within its VPC."
value = aws_instance.app_server.private_dns
}

output "private_ip" {
description = "The private IP address assigned to the EC2 instance within its VPC."
value = aws_instance.app_server.private_ip
}

output "monitoring" {
description = "Indicates if detailed monitoring is enabled for the EC2 instance."
value = aws_instance.app_server.monitoring
}

output "security_group_ids" {
description = "The list of security group IDs assigned to the EC2 instance."
value = aws_instance.app_server.vpc_security_group_ids
}

output "key_name" {
description = "The name of the key pair associated with the EC2 instance."
value = aws_instance.app_server.key_name
}

output "subnet_id" {
description = "The ID of the subnet to which the instance is attached."
value = aws_instance.app_server.subnet_id
}

output "tags" {
description = "A map of tags assigned to the resource."
value = aws_instance.app_server.tags
}
  1. Create a Github Workflow file under .github/workflows/create-an-ec2-instance.yaml with the following content:
GitHub workflow
tip

Replace <TERRAFORM-TEMPLATE-DIR> with the directory created to host your terraform templates.

create-an-ec2-instance.yaml
name: Provision AN EC2 Instance

on:
workflow_dispatch:
inputs:
ec2_name:
description: EC2 name
required: true
default: 'App Server'
type: string
ec2_instance_type:
description: EC2 instance type
required: false
default: "t3.micro"
type: string
pem_key_name:
description: EC2 pem key
required: true
type: string
port_payload:
required: true
type: string
jobs:
provision-ec2:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '14'

- name: Log starting of EC2 Instance creation
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_payload).context.runId }}
logMessage: |
About to create ec2 instance ${{ github.event.inputs.ec2_name }} .. ⛴️

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: '${{ secrets.TF_USER_AWS_KEY }}'
aws-secret-access-key: '${{ secrets.TF_USER_AWS_SECRET }}'
aws-region: '${{ secrets.TF_USER_AWS_REGION }}'

- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_wrapper: false

- name: Terraform Apply
id: apply
env:
TF_VAR_ec2_name: "${{ github.event.inputs.ec2_name }}"
TF_VAR_pem_key_name: "${{ github.event.inputs.pem_key_name}}"
TF_VAR_aws_region: "${{ secrets.TF_USER_AWS_REGION }}"
TF_VAR_ec2_instance_type: "${{ github.event.inputs.ec2_instance_type}}"
run: |
cd <TERRAFORM-TEMPLATE-DIR>
terraform init
terraform validate
terraform plan
terraform apply -auto-approve

- name: Set Outputs
id: set_outputs
run: |
cd <TERRAFORM-TEMPLATE-DIR>
echo "instance_id=$(terraform output -raw instance_id)" >> $GITHUB_ENV
echo "instance_state=$(terraform output -raw instance_state)" >> $GITHUB_ENV
echo "instance_type=$(terraform output -raw instance_type)" >> $GITHUB_ENV
echo "availability_zone=$(terraform output -raw availability_zone)" >> $GITHUB_ENV
echo "public_dns=$(terraform output -raw public_dns)" >> $GITHUB_ENV
echo "public_ip=$(terraform output -raw public_ip)" >> $GITHUB_ENV
echo "private_dns=$(terraform output -raw private_dns)" >> $GITHUB_ENV
echo "private_ip=$(terraform output -raw private_ip)" >> $GITHUB_ENV
echo "monitoring=$(terraform output -raw monitoring)" >> $GITHUB_ENV
security_group_ids_json=$(terraform output -json security_group_ids | jq -c .)
echo "security_group_ids=$security_group_ids_json" >> $GITHUB_ENV
echo "key_name=$(terraform output -raw key_name)" >> $GITHUB_ENV
echo "subnet_id=$(terraform output -raw subnet_id)" >> $GITHUB_ENV
tags=$(terraform output -json tags | jq -c .)
echo "tags=$tags" >> $GITHUB_ENV

- name: Create a log message
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: PATCH_RUN
runId: ${{ fromJson(inputs.port_payload).context.runId }}
logMessage: |
EC2 Instance created successfully ✅

- name: Report Created Instance to Port
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: PATCH_RUN
runId: ${{fromJson(github.event.inputs.port_payload).context.runId}}
logMessage: "Upserting created EC2 Instance to Port ... "

- name: UPSERT EC2 Instance Entity
uses: port-labs/port-github-action@v1
with:
identifier: "${{ steps.display_outputs.outputs.instance_id }}"
title: "${{ inputs.ec2_name }}"
blueprint: ec2Instance
properties: |-
{
"instance_state": "${{ env.instance_state }}",
"instance_type": "${{ env.instance_type }}",
"availability_zone": "${{ env.availability_zone }}",
"public_dns": "${{ env.public_dns }}",
"public_ip": "${{ env.public_ip }}",
"private_dns": "${{ env.private_dns }}",
"private_ip": "${{ env.private_ip }}",
"monitoring": ${{ env.monitoring }},
"security_group_ids": ${{ env.security_group_ids }},
"key_name": "${{ env.key_name }}",
"subnet_id": "${{ env.subnet_id }}",
"tags": ${{ env.tags }}
}
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: UPSERT
runId: ${{ fromJson(inputs.port_payload).context.runId }}


- name: Log After Upserting Entity
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: PATCH_RUN
runId: ${{fromJson(github.event.inputs.port_payload).context.runId}}
logMessage: "Entity upserting was successful ✅"

Port Configuration

  1. Create the Port action on the ec2Instance blueprint:
    • Head to the self-service page.
    • Click on the + New Action button.
    • Click on the {...} Edit JSON button.
    • Copy and paste the following JSON configuration into the editor:
Port Action: Create An EC2 Instance
Modification Required

Make sure to replace <GITHUB_ORG> and <GITHUB_REPO> with your GitHub organization and repository names respectively.

{
"identifier": "ec2Instance_create_instance",
"title": "Create Instance",
"icon": "EC2",
"description": "Create An EC2 Instance from Port",
"trigger": {
"type": "self-service",
"operation": "CREATE",
"userInputs": {
"properties": {
"pem_key_name": {
"title": "Pem Key Name",
"description": "EC2 .pem key pair name",
"icon": "EC2",
"type": "string"
},
"ec2_name": {
"icon": "EC2",
"title": "EC2_Name",
"description": "Name of the instance",
"type": "string"
},
"ec2_instance_type": {
"title": "EC2 Instance Type",
"description": "EC2 instance type",
"icon": "EC2",
"type": "string",
"default": "t2.micro",
"enum": [
"t2.micro",
"t2.medium",
"t2.large",
"t2.xlarge",
"t2.2xlarge"
]
}
},
"required": [
"ec2_name",
"pem_key_name"
],
"order": [
"ec2_name",
"ec2_instance_type",
"pem_key_name"
]
},
"blueprintIdentifier": "ec2Instance"
},
"invocationMethod": {
"type": "GITHUB",
"org": "<GITHUB_ORG>",
"repo": "<GITHUB_REPO>",
"workflow": "create-ec2-instance.yaml",
"workflowInputs": {
"{{if (.inputs | has(\"ref\")) then \"ref\" else null end}}": "{{.inputs.\"ref\"}}",
"{{if (.inputs | has(\"pem_key_name\")) then \"pem_key_name\" else null end}}": "{{.inputs.\"pem_key_name\"}}",
"{{if (.inputs | has(\"ec2_name\")) then \"ec2_name\" else null end}}": "{{.inputs.\"ec2_name\"}}",
"{{if (.inputs | has(\"ec2_instance_type\")) then \"ec2_instance_type\" else null end}}": "{{.inputs.\"ec2_instance_type\"}}",
"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",
"org": "<GITHUB-ORG>",
"repo": "<GITHUB-REPO-NAME>",
"workflow": "create-ec2-instance.yaml",
"omitUserInputs": false,
"omitPayload": false,
"reportWorkflowStatus": true
},
"trigger": "{{.trigger.operation}}"
},
"properties": {
"{{if (.inputs | has(\"pem_key_name\")) then \"pem_key_name\" else null end}}": "{{.inputs.\"pem_key_name\"}}",
"{{if (.inputs | has(\"ec2_name\")) then \"ec2_name\" else null end}}": "{{.inputs.\"ec2_name\"}}",
"{{if (.inputs | has(\"ec2_instance_type\")) then \"ec2_instance_type\" else null end}}": "{{.inputs.\"ec2_instance_type\"}}"
},
"censoredProperties": "{{.action.encryptedProperties}}"
}
}
},
"reportWorkflowStatus": true
},
"requiredApproval": false,
"publish": true
}

Let's test it!

  1. Head to the Self Service hub
  2. Click on the Create An EC2 Instance action
  3. Fill the pop-up form with details of the EC2 Instance you wish to create
  4. Click on Execute
  5. Wait for the EC2 Instance to be created in AWS

Congrats 🎉 You've created an EC2 Instance in Port 🔥