使用 AWS CloudFormation 部署 AWS 资源
此示例演示了如何使用AWS CloudFormation 模板,通过 Port Actions 部署 AWS 资源。
我们将使用一个名为aws-actions/aws-cloudformation-github-deploy 的 AWS 管理的 GitHub Action。
步骤
- 创建以下 GitHub 操作secret:
PORT_CLIENT_ID
- Port 客户端 IDlearn more.PORT_CLIENT_SECRET
- Port客户端secretlearn more.AWS_ACCESS_KEY_ID
- AWS 凭据。AWS_SECRET_ACCESS_KEY
- AWS 凭据。 5.AWS_REGION
- 要将资源部署到的 AWS 区域名称。 2.点击here 安装 Port 的 GitHub 应用程序。 3.使用以下 JSON 定义创建 Port 蓝图(选择所需资源):
- EC2 Instance
- S3 Bucket
- RDS Instance
Port EC2 Instance Blueprint
{
"identifier": "ec2_instance",
"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": {}
}
Port S3 Bucket Blueprint
{
"identifier": "s3_bucket",
"title": "S3 Bucket",
"icon": "S3",
"schema": {
"properties": {
"bucket_name": {
"title": "Bucket Name",
"type": "string",
"minLength": 3,
"maxLength": 63,
"icon": "DefaultProperty"
},
"bucket_acl": {
"icon": "DefaultProperty",
"title": "Bucket ACL",
"type": "string",
"default": "Private"
}
},
"required": ["bucket_name", "bucket_acl"]
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {}
}
Port RDS Instance Blueprint
{
"identifier": "rds_instance",
"title": "RDS Instance",
"icon": "AmazonRDS",
"schema": {
"properties": {
"db_instance_identifier": {
"title": "DB Instance Identifier",
"type": "string",
"minLength": 1,
"maxLength": 63,
"icon": "DefaultProperty"
},
"db_master_password": {
"icon": "DefaultProperty",
"title": "DB Master Password",
"type": "string"
},
"db_master_username": {
"title": "DB Master Username",
"type": "string",
"minLength": 1,
"maxLength": 63,
"icon": "DefaultProperty"
},
"db_engine": {
"title": "DB Engine",
"type": "string",
"icon": "DefaultProperty"
},
"allocated_storage": {
"title": "Allocated Storage",
"type": "number",
"default": 20,
"minimum": 5,
"maximum": 1000,
"icon": "DefaultProperty"
},
"db_instance_class": {
"title": "DB Instance Class",
"type": "string",
"icon": "DefaultProperty"
}
},
"required": [
"db_instance_identifier",
"db_master_password",
"db_master_username",
"db_engine",
"allocated_storage",
"db_instance_class"
]
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {}
}
- 使用以下 JSON 定义创建 Port Action:
请确保修改 GITHUB_ORG、GITHUB_REPO 和 GITHUB_WORKFLOW_FILE 占位符,使其与您的环境相匹配。
- EC2 Instance
- S3 Bucket
- RDS Instance
Port Action
{
"identifier": "deploy_ec2_instance",
"title": "Deploy EC2 Instance",
"icon": "EC2",
"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"
]
},
"invocationMethod": {
"type": "GITHUB",
"omitPayload": false,
"omitUserInputs": false,
"reportWorkflowStatus": true,
"org": "<GITHUB_ORG>",
"repo": "<GITHUB_REPO>",
"workflow": "<GITHUB_WORKFLOW_FILE>"
},
"trigger": "CREATE",
"requiredApproval": false
}
Port Action
{
"identifier": "create_s3_bucket",
"title": "Create S3 Bucket",
"icon": "S3",
"userInputs": {
"properties": {
"bucket_name": {
"title": "Bucket Name",
"type": "string",
"minLength": 3,
"maxLength": 63
},
"bucket_acl": {
"icon": "DefaultProperty",
"title": "Bucket ACL",
"description": "bucket access control list",
"type": "string",
"default": "Private",
"enum": [
"Private",
"PublicRead",
"PublicReadWrite",
"AuthenticatedRead"
],
"enumColors": {
"Private": "lightGray",
"PublicRead": "lightGray",
"PublicReadWrite": "lightGray",
"AuthenticatedRead": "lightGray"
}
}
},
"required": ["bucket_name", "bucket_acl"],
"order": ["bucket_name", "bucket_acl"]
},
"invocationMethod": {
"type": "GITHUB",
"omitPayload": false,
"omitUserInputs": false,
"reportWorkflowStatus": true,
"org": "<GITHUB_ORG>",
"repo": "<GITHUB_REPO>",
"workflow": "<GITHUB_WORKFLOW_FILE>"
},
"trigger": "CREATE",
"requiredApproval": false
}
Port Action
{
"identifier": "deploy_rds_instance",
"title": "Deploy RDS",
"icon": "AmazonRDS",
"userInputs": {
"properties": {
"db_instance_identifier": {
"title": "DB Instance Identifier",
"type": "string",
"minLength": 1,
"maxLength": 63
},
"db_master_password": {
"title": "DB Master Password",
"type": "string",
"encryption": "aes256-gcm"
},
"db_master_username": {
"title": "DB Master Username",
"type": "string"
},
"db_engine": {
"title": "DB Engine",
"type": "string",
"default": "mysql",
"enum": ["mysql", "postgres", "sqlserver", "oracle"],
"enumColors": {
"mysql": "lightGray",
"postgres": "lightGray",
"sqlserver": "lightGray",
"oracle": "lightGray"
}
},
"allocated_storage": {
"title": "Allocated Storage",
"type": "number",
"default": 20,
"minimum": 5,
"maximum": 1000
},
"db_instance_class": {
"title": "DB Instance Class",
"type": "string",
"default": "db.t2.micro",
"enum": ["db.t2.micro", "db.t2.small", "db.m4.large"],
"enumColors": {
"db.t2.micro": "lightGray",
"db.t2.small": "lightGray",
"db.m4.large": "lightGray"
}
}
},
"required": [
"db_instance_identifier",
"db_master_password",
"db_master_username",
"db_engine",
"allocated_storage",
"db_instance_class"
],
"order": [
"db_instance_identifier",
"db_master_username",
"db_master_password",
"db_engine",
"db_instance_class",
"allocated_storage"
]
},
"invocationMethod": {
"type": "GITHUB",
"omitPayload": false,
"omitUserInputs": false,
"reportWorkflowStatus": true,
"org": "<GITHUB_ORG>",
"repo": "<GITHUB_REPO>",
"workflow": "<GITHUB_WORKFLOW_FILE>"
},
"trigger": "CREATE",
"requiredApproval": false
}
5.在 GitHub 仓库中创建 CloudFormation 模板文件:
- EC2 Instance
- S3 Bucket
- RDS Instance
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
AWS CloudFormation Template
AWSTemplateFormatVersion: "2010-09-09"
Description: CloudFormation Template for an S3 Bucket
Parameters:
BucketName:
Description: Name for the S3 bucket
Type: String
MinLength: 3
MaxLength: 63
ConstraintDescription: The bucket name must be between 3 and 63 characters.
BucketAcl:
Description: Access control for the S3 bucket
Type: String
Default: Private
AllowedValues:
- Private
- PublicRead
- PublicReadWrite
- AuthenticatedRead
ConstraintDescription: Choose a valid access control option.
Resources:
S3Bucket:
Type: "AWS::S3::Bucket"
Properties:
BucketName: !Ref BucketName
AccessControl: !Ref BucketAcl
Outputs:
S3BucketName:
Description: Name of the created S3 bucket
Value: !Ref S3Bucket
AWS CloudFormation Template
AWSTemplateFormatVersion: "2010-09-09"
Description: CloudFormation Template for an Amazon RDS Instance
Parameters:
DBInstanceIdentifier:
Description: Identifier for the RDS instance
Type: String
MinLength: 1
MaxLength: 63
Default: myrdsinstance
ConstraintDescription: The DB instance identifier must be between 1 and 63 characters.
DBMasterUsername:
Description: Master username for the RDS instance
Type: String
MinLength: 1
MaxLength: 63
Default: admin
ConstraintDescription: The master username must be between 1 and 63 characters.
DBMasterPassword:
Description: Master password for the RDS instance
Type: String
NoEcho: true
MinLength: 8
MaxLength: 41
Default: MySecurePassword
ConstraintDescription: The master password must be between 8 and 41 characters.
DBEngine:
Description: Database engine for the RDS instance
Type: String
Default: mysql
AllowedValues:
- mysql
- postgres
- sqlserver
- oracle
ConstraintDescription: Choose a valid database engine.
AllocatedStorage:
Description: Allocated storage for the RDS instance (in GB)
Type: Number
Default: 20
MinValue: 5
MaxValue: 6144
ConstraintDescription: Allocated storage must be between 5 and 6144 GB.
DBInstanceClass:
Description: DB instance class for the RDS instance
Type: String
Default: db.t2.micro
AllowedValues:
- db.t2.micro
- db.t2.small
- db.m4.large
# Add more instance types as needed
ConstraintDescription: Choose a valid DB instance class.
Resources:
RDSInstance:
Type: "AWS::RDS::DBInstance"
Properties:
DBInstanceIdentifier: !Ref DBInstanceIdentifier
AllocatedStorage: !Ref AllocatedStorage
DBInstanceClass: !Ref DBInstanceClass
Engine: !Ref DBEngine
MasterUsername: !Ref DBMasterUsername
MasterUserPassword: !Ref DBMasterPassword
Outputs:
RDSInstanceEndpoint:
Description: Endpoint for the created RDS instance
Value: !GetAtt RDSInstance.Endpoint.Address
6.在.github/workflows/deploy-cloudformation-template.yml
下创建 一个工作流文件,内容如下:
请确保修改 CF_TEMPLATE_FILE 占位符以匹配 CloudFormation 模板文件路径。
- EC2 Instance
- S3 Bucket
- RDS Instance
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: ec2_instance
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}}
GitHub workflow
name: Deploy CloudFormation - S3 Bucket
on:
workflow_dispatch:
inputs:
bucket_name:
required: true
type: string
description: bucket name
bucket_acl:
required: true
type: string
description: bucket acl
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.bucket_name }}
template: <CF_TEMPLATE_FILE>
parameter-overrides: >-
BucketName=${{ inputs.bucket_name }},
BucketAcl=${{ inputs.bucket_acl }}
- name: UPSERT S3 Bucket Entity in Port
uses: port-labs/port-github-action@v1
with:
identifier: ${{ inputs.bucket_name }}
title: ${{ inputs.bucket_name }}
team: "[]"
icon: S3
blueprint: s3_bucket
properties: |-
{
"bucket_name": "${{ inputs.bucket_name }}",
"bucket_acl": "${{ inputs.bucket_acl }}"
}
relations: "{}"
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: UPSERT
runId: ${{fromJson(inputs.port_payload).context.runId}}
GitHub workflow
name: Deploy CloudFormation - RDS Instance
on:
workflow_dispatch:
inputs:
db_instance_identifier:
required: true
type: string
description: db_instance_identifier
db_master_username:
required: true
type: string
description: db_master_username
db_master_password:
required: true
type: string
description: db_master_password
db_engine:
required: true
type: string
description: db_engine
db_instance_class:
required: true
type: string
description: db_instance_class
allocated_storage:
required: true
type: number
description: allocated_storage
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: Set Up Python
uses: actions/setup-python@v2
with:
python-version: 3.x
- 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: Decrypt aes256-gcm String
id: decrypt_password
run: |
pip install --upgrade pip
pip install pycryptodome
python decrypt_password.py
env:
PORT_CLIENT_SECRET: ${{ secrets.PORT_CLIENT_SECRET }}
PASSWORD: ${{ inputs.db_master_password }}
- name: Deploy to AWS CloudFormation
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
name: ${{ inputs.db_instance_identifier }}
template: <CF_TEMPLATE_FILE>
parameter-overrides: >-
DBInstanceIdentifier=${{ inputs.db_instance_identifier }},
DBMasterUsername=${{ inputs.db_master_username }},
DBMasterPassword=${{ steps.decrypt_password.outputs.decrypted_value }},
DBEngine=${{ inputs.db_engine }},
DBInstanceClass=${{ inputs.db_instance_class}},
AllocatedStorage=${{ inputs.allocated_storage }}
- name: UPSERT RDS Instance Entity in Port
uses: port-labs/port-github-action@v1
with:
identifier: ${{ inputs.db_instance_identifier }}
title: ${{ inputs.db_instance_identifier }}
team: "[]"
icon: RDS
blueprint: rds_instance
properties: |-
{
"db_instance_identifier": "${{ inputs.db_instance_identifier }}",
"db_master_username": "${{ inputs.db_master_username }}",
"db_master_password": "${{ inputs.db_master_password }}",
"db_engine": "${{ inputs.db_engine }}",
"db_instance_class": "${{ inputs.db_instance_class }}",
"allocated_storage": ${{ inputs.allocated_storage }}
}
relations: "{}"
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
operation: UPSERT
runId: ${{fromJson(inputs.port_payload).context.runId}}
Create decrypt_password.py file with the following content to decrypt the password values:
Decrypt Password Script
import base64
import os
from Crypto.Cipher import AES
key = os.getenv('PORT_CLIENT_SECRET')[:32].encode()
encrypted_property_value = base64.b64decode(os.getenv('PASSWORD'))
iv = encrypted_property_value[:16]
ciphertext = encrypted_property_value[16:-16]
mac = encrypted_property_value[-16:]
cipher = AES.new(key, AES.MODE_GCM, iv)
# decrypt the property
decrypted_property_value = cipher.decrypt_and_verify(ciphertext, mac)
print(f"::set-output name=decrypted_value::{decrypted_property_value}")
7.从 Port 应用程序的Self-service 标签触发操作。
#What's next?
- Connect Port's AWS exporter 以确保从 AWS 自动摄取所有属性和实体。