Skip to main content

Manage a Developer Environment Lifecycle

In this example you are going to create a developer environment in AWS then report its information to Port as a developer environment entity.

Our developer environment will include:

  • An SQS queue which receives messages;
  • A Lambda function that is triggered by messages sent to the queue;
  • An S3 bucket that stores the artifacts generated by the S3 function.

Prerequisites​

Since we are creating a lambda function, we need to provide its source code. You can download a basic Nodejs Lambda template by clicking here (be sure to save the .zip file next to the main.tf file).

You will need to create a developer environment blueprint to follow this example:

{
"identifier": "developerEnvironment",
"description": "",
"title": "Developer Environment",
"icon": "Environment",
"schema": {
"properties": {
"bucketUrl": {
"type": "string",
"title": "Bucket URL",
"format": "url"
},
"lambdaUrl": {
"type": "string",
"title": "Lambda URL",
"format": "url"
},
"queueUrl": {
"type": "string",
"title": "Queue URL",
"format": "url"
},
"memorySize": {
"type": "number",
"title": "Memory Size"
}
},
"required": []
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {}
}

Here is the complete main.tf file:

Complete Terraform definition file
terraform {
required_providers {
port = {
source = "port-labs/port-labs"
version = "~> 1.0.0"
}
}
}

provider "aws" {
access_key = "YOUR_ACCESS_KEY_ID"
secret_key = "YOUR_SECRET_ACCESS_KEY"
region = "eu-west-1"
}

provider "port" {
client_id = "YOUR_CLIENT_ID" # or set the environment variable PORT_CLIENT_ID
secret = "YOUR_CLIENT_SECRET" # or set the environment variable PORT_CLIENT_SECRET
}

resource "aws_s3_bucket" "port_terraform_example_dev_env_bucket" {
bucket = "my-port-terraform-example-dev-env-bucket"
}

resource "aws_s3_bucket_acl" "port_terraform_example_dev_env_bucket-acl" {
bucket = aws_s3_bucket.port_terraform_example_dev_env_bucket.id
acl = "private"
}

resource "aws_sqs_queue" "port_terraform_example_dev_env_queue" {
name = "terraform-example-queue.fifo"
fifo_queue = true
content_based_deduplication = true
}

resource "aws_iam_role" "iam_for_lambda" {
name = "iam_for_lambda"

assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}

resource "aws_iam_role_policy_attachment" "attach_sqs_policy_for_lambda_role" {
role = aws_iam_role.iam_for_lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole"
}

resource "aws_lambda_function" "port_terraform_example_dev_env_lambda" {
depends_on = [
aws_sqs_queue.port_terraform_example_dev_env_queue
]

filename = "lambda_function_payload.zip"
function_name = "dev-env-function"
role = aws_iam_role.iam_for_lambda.arn
handler = "index.test"

runtime = "nodejs16.x"
}

resource "aws_lambda_function_url" "example_function_url" {
function_name = aws_lambda_function.port_terraform_example_dev_env_lambda.function_name
authorization_type = "NONE"
}

resource "aws_lambda_event_source_mapping" "event_source_mapping" {
event_source_arn = aws_sqs_queue.port_terraform_example_dev_env_queue.arn
enabled = true
function_name = aws_lambda_function.port_terraform_example_dev_env_lambda.arn
batch_size = 1
}

resource "port_entity" "dev_env" {
depends_on = [
aws_s3_bucket.port_terraform_example_dev_env_bucket,
aws_lambda_function.port_terraform_example_dev_env_lambda,
aws_sqs_queue.port_terraform_example_dev_env_queue,
aws_lambda_function_url.example_function_url
]

identifier = aws_lambda_function.port_terraform_example_dev_env_lambda.function_name
title = aws_lambda_function.port_terraform_example_dev_env_lambda.function_name
blueprint = "developerEnvironment"

properties = {
string_props = {
"bucketUrl" = "https://${aws_s3_bucket.port_terraform_example_dev_env_bucket.bucket_domain_name}"
"queueUrl" = aws_sqs_queue.port_terraform_example_dev_env_queue.id
"lambdaUrl" = aws_lambda_function_url.example_function_url.function_url
}
number_props = {
"memorySize" = aws_lambda_function.port_terraform_example_dev_env_lambda.memory_size
}
}
}

To use this example yourself, simply replace the placeholders for access_key, secret_key, client_id and secret and then run the following commands to setup your new backend, create the new infrastructure and update the software catalog:

# install modules and create an initial state
terraform init
# To view Terraform's planned changes based on your .tf definition file:
terraform plan
# To apply the changes and update the catalog
terraform apply

Let's break down the definition file and understand the different parts:

Module imports​

This part includes importing and setting up the required Terraform providers and modules:

terraform {
required_providers {
port = {
source = "port-labs/port-labs"
version = "~> 1.0.0"
}
}
}

provider "aws" {
access_key = "YOUR_ACCESS_KEY_ID"
secret_key = "YOUR_SECRET_ACCESS_KEY"
region = "eu-west-1"
}

provider "port" {
client_id = "YOUR_CLIENT_ID" # or set the environment variable PORT_CLIENT_ID
secret = "YOUR_CLIENT_SECRET" # or set the environment variable PORT_CLIENT_SECRET
}

Defining the AWS resources​

This part includes defining the following AWS resources:

  • An S3 bucket;
  • A SQS FIFO queue;
  • An IAM policy for the Lambda function, with access to the new SQS queue;
  • A Lambda function with a function URL and event source mapping to the SQS queue.
resource "aws_s3_bucket" "port_terraform_example_dev_env_bucket" {
bucket = "my-port-terraform-example-dev-env-bucket"
}

resource "aws_s3_bucket_acl" "port_terraform_example_dev_env_bucket-acl" {
bucket = aws_s3_bucket.port_terraform_example_dev_env_bucket.id
acl = "private"
}

resource "aws_sqs_queue" "port_terraform_example_dev_env_queue" {
name = "terraform-example-queue.fifo"
fifo_queue = true
content_based_deduplication = true
}

resource "aws_iam_role" "iam_for_lambda" {
name = "iam_for_lambda"

assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}

resource "aws_iam_role_policy_attachment" "attach_sqs_policy_for_lambda_role" {
role = aws_iam_role.iam_for_lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole"
}

resource "aws_lambda_function" "port_terraform_example_dev_env_lambda" {
depends_on = [
aws_sqs_queue.port_terraform_example_dev_env_queue
]

filename = "lambda_function_payload.zip"
function_name = "dev-env-function"
role = aws_iam_role.iam_for_lambda.arn
handler = "index.test"

runtime = "nodejs16.x"
}

resource "aws_lambda_function_url" "example_function_url" {
function_name = aws_lambda_function.port_terraform_example_dev_env_lambda.function_name
authorization_type = "NONE"
}

resource "aws_lambda_event_source_mapping" "event_source_mapping" {
event_source_arn = aws_sqs_queue.port_terraform_example_dev_env_queue.arn
enabled = true
function_name = aws_lambda_function.port_terraform_example_dev_env_lambda.arn
batch_size = 1
}

Creating the developer env entity matching the new environment​

This part includes configuring the developerEnvironment blueprint and creating an entity for our new bucket:

resource "port_entity" "dev_env" {
depends_on = [
aws_s3_bucket.port_terraform_example_dev_env_bucket,
aws_lambda_function.port_terraform_example_dev_env_lambda,
aws_sqs_queue.port_terraform_example_dev_env_queue,
aws_lambda_function_url.example_function_url
]

identifier = aws_lambda_function.port_terraform_example_dev_env_lambda.function_name
title = aws_lambda_function.port_terraform_example_dev_env_lambda.function_name
blueprint = "developerEnvironment"

properties = {
string_props = {
"bucketName" = aws_s3_bucket.port_terraform_example_dev_env_bucket.id
"queueUrl" = aws_sqs_queue.port_terraform_example_dev_env_queue.id
"lambdaUrl" = aws_lambda_function_url.example_function_url.function_url
}
}
}
Terraform dependencies

Note how we use a depends_on block on the new developer environment entity, this is required because the developerEnvironment blueprint has to exist before the entity can be created. In addition, the entity relies on values that will only be available after the AWS resources are created.

Result​

After running terraform apply you will see the resources in AWS, and the matching developerEnvironment entity in Port.

Continue reading to learn how to make updates and how to cleanup.

Updating the developer environment and the matching entity​

Since the new developer environment entity maps dynamic properties directly from the AWS resources defined in Terraform. It will also update when one of the developer environment resources change.

For example, let's increase the memory size of the Lambda function:

resource "aws_lambda_function" "port_terraform_example_dev_env_lambda" {
depends_on = [
aws_sqs_queue.port_terraform_example_dev_env_queue
]

filename = "lambda_function_payload.zip"
function_name = "dev-env-function"
role = aws_iam_role.iam_for_lambda.arn
handler = "index.handler"

runtime = "nodejs16.x"

memory_size = 256 # was 128
}

Now by running terraform apply, both the Lambda resources will change, as well as the memorySize property of the matching entity.

Cleanup​

To cleanup your environment, you can run the command terraform destroy, which will delete all of the resources you created in this example (including the AWS infrastructure and matching Port entity).