使用 Terraform 在 Azure 云中部署资源
本示例演示了如何使用 Terraform 模板通过 Port Actions 在 Azure 中部署storage account 。
工作流程通过 Jenkins 管道执行。
先决条件
- 在 Jenkins 中安装以下插件:
- Azure Credentials - 该插件提供 Jenkins Credentials 中的 "Azure Service Principal "类型。
- Terraform - 此插件提供了一个构建包装器,可简化应用、计划和销毁等 Terraform 命令的执行。
- Generic Webhook Trigger - 该插件使 Jenkins 能够根据传入的 HTTP 请求接收和触发作业,从 JSON 或 XML 有效载荷中提取数据并将其作为变量提供。
示例 - 创建存储账户
请按照以下步骤开始操作:
- 创建以下 Jenkins 证书:
1.使用
Username with password
类型和 idport-credentials
创建Port凭据。PORT_CLIENT_ID
- Port客户端 IDlearn more 。PORT_CLIENT_SECRET
- Port客户端secretlearn more 。 2.使用Azure 服务 Principal
类型和 idazure
创建 Azure 凭据。 提示 请按照此guide 创建服务委托以获取 Azure 凭据。 :::ARM_CLIENT_ID
- 应用程序的 Azure 客户 ID(APP ID)。ARM_CLIENT_SECRET
- 应用程序的 Azure 客户端secret(密码)。ARM_SUBSCRIPTION_ID
- 应用程序的 Azure 订阅 ID。ARM_TENANT_ID
- AzureTenant ID 。WEBHOOK_TOKEN
- Webhook 令牌,只有提供该令牌才能触发作业。 2.创建具有以下属性的 Port蓝图:
Port Azure Storage Account Blueprint
Keep in mind that this can be any blueprint you require; the provided example is just for reference.
{
"identifier": "azureStorage",
"title": "Azure Storage Account",
"icon": "Azure",
"schema": {
"properties": {
"storage_name": {
"title": "Account Name",
"type": "string",
"minLength": 3,
"maxLength": 63,
"icon": "DefaultProperty"
},
"storage_location": {
"icon": "DefaultProperty",
"title": "Location",
"type": "string"
},
"url": {
"title": "URL",
"format": "url",
"type": "string",
"icon": "DefaultProperty"
}
},
"required": [
"storage_name",
"storage_location"
]
},
"mirrorProperties": {},
"calculationProperties": {},
"relations": {}
}
3.使用以下 JSON 定义在self-service hub 中创建 Port 操作:
Port Action
Make sure to replace the placeholders for JENKINS_URL
and JOB_TOKEN
.
{
"identifier": "create_azure_storage",
"title": "Create Azure Storage",
"icon": "Azure",
"userInputs": {
"properties": {
"storage_name": {
"title": "Storage Name",
"type": "string",
"minLength": 3,
"maxLength": 63
},
"storage_location": {
"icon": "DefaultProperty",
"title": "Storage Location",
"description": "storage account geo region",
"type": "string"
}
},
"required": [
"storage_name"
],
"order": [
"storage_name"
]
},
"invocationMethod": {
"type": "WEBHOOK",
"agent": false,
"url": "https://<JENKINS_HOST>/generic-webhook-trigger/invoke?token=<JOB_TOKEN>",
"synchronized": false,
"method": "POST"
},
"trigger": "CREATE",
"requiredApproval": false
}
4.在 GitHub 仓库根目录下的 terraform
文件夹中创建以下 Terraform 模板:
main.tf
- 该文件将包含资源块,用于定义要在 Azure 云中创建的存储帐户和要在 Port 中创建的实体。variables.tf
- 此文件将包含在资源块中被用于的变量声明式,例如 Port 凭据和 Port 运行 ID。output.tf
- 该文件将包含 "应用 "操作成功完成后生成的存储帐户的 URL。该 URL 将在创建 Port 实体时被用于到endpoint
属性中。
Terraform main.tf
template
# Configure the Azure provider
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0.2"
}
port = {
source = "port-labs/port-labs"
version = "~> 1.0.0"
}
}
required_version = ">= 1.1.0"
}
provider "azurerm" {
features {}
}
provider "port" {
client_id = var.port_client_id
secret = var.port_client_secret
}
resource "azurerm_storage_account" "storage_account" {
name = var.storage_account_name
resource_group_name = var.resource_group_name
location = var.location
account_tier = "Standard"
account_replication_type = "LRS"
account_kind = "StorageV2"
}
resource "port_entity" "azure_storage_account" {
count = length(azurerm_storage_account.storage_account) > 0 ? 1 : 0
identifier = var.storage_account_name
title = var.storage_account_name
blueprint = "azureStorage"
run_id = var.port_run_id
properties = {
string_props = {
"storage_name" = var.storage_account_name,
"storage_location" = var.location,
"endpoint" = azurerm_storage_account.storage_account.primary_web_endpoint
}
}
depends_on = [azurerm_storage_account.storage_account]
}
Terraform variables.tf
template
Replace the default resource_group_name
with a resource group from your Azure account. Check this guide to find your resource groups. You may also wish to set the default values of other variables.
variable "resource_group_name" {
type = string
default = "myTFResourceGroup"
description = "RG name in Azure"
}
variable "location" {
type = string
default = "westus2"
description = "RG location in Azure"
}
variable "storage_account_name" {
type = string
description = "Storage Account name in Azure"
default = "demo"
}
variable "port_run_id" {
type = string
description = "The runID of the action run that created the entity"
}
variable "port_client_id" {
type = string
description = "The Port client ID"
}
variable "port_client_secret" {
type = string
description = "The Port client secret"
}
Terraform output.tf
template
output "endpoint_url" {
value = azurerm_storage_account.storage_account.primary_web_endpoint
}
5.创建 Jenkins Pipelines:
1.Enable webhook trigger for a pipeline
2.Define variables for a pipeline 定义 STORAGE_NAME、STORAGE_LOCATION、PORT_RUN_ID 和 BLUEPRINT_ID 变量。
3.Token Setup 定义令牌,使其与 Port Action 中配置的 JOB_TOKEN
匹配。
Jenkins Pipeline Script
Please make sure to modify the YOUR_USERNAME
and YOUR_REPO
placeholders in the URL of the git repository in the Checkout
stage. Alternatively you can use our example repository.
import groovy.json.JsonSlurper
pipeline {
agent any
tools {
"org.jenkinsci.plugins.terraform.TerraformInstallation" "terraform"
}
environment {
TF_HOME = tool('terraform')
TF_IN_AUTOMATION = "true"
PATH = "$TF_HOME:$PATH"
PORT_ACCESS_TOKEN = ""
endpoint_url = ""
}
triggers {
GenericTrigger(
genericVariables: [
[key: 'STORAGE_NAME', value: '$.payload.properties.storage_name'],
[key: 'STORAGE_LOCATION', value: '$.payload.properties.storage_location'],
[key: 'PORT_RUN_ID', value: '$.context.runId'],
[key: 'BLUEPRINT_ID', value: '$.context.blueprint']
],
causeString: 'Triggered by Port',
allowSeveralTriggersPerBuild: true,
tokenCredentialId: "WEBHOOK_TOKEN",
regexpFilterExpression: '',
regexpFilterText: '',
printContributedVariables: true,
printPostContent: true
)
}
stages {
stage('Checkout') {
steps {
// example repo: [email protected]:port-labs/pipelines-terraform-azure.git
git branch: 'main', credentialsId: 'github', url: '[email protected]:<YOUR_USERNAME>/<YOUR_REPO>.git'
}
}
stage('Get access token') {
steps {
withCredentials([usernamePassword(
credentialsId: 'port-credentials',
usernameVariable: 'PORT_CLIENT_ID',
passwordVariable: 'PORT_CLIENT_SECRET')]) {
script {
// Execute the curl command and capture the output
def result = sh(returnStdout: true, script: """
accessTokenPayload=\$(curl -X POST \
-H "Content-Type: application/json" \
-d '{"clientId": "${PORT_CLIENT_ID}", "clientSecret": "${PORT_CLIENT_SECRET}"}' \
-s "https://api.getport.io/v1/auth/access_token")
echo \$accessTokenPayload
""")
// Parse the JSON response using JsonSlurper
def jsonSlurper = new JsonSlurper()
def payloadJson = jsonSlurper.parseText(result.trim())
// Access the desired data from the payload
PORT_ACCESS_TOKEN = payloadJson.accessToken
}
}
}
}
stage('Terraform Azure') {
steps {
withCredentials([azureServicePrincipal(
credentialsId: 'azure',
subscriptionIdVariable: 'ARM_SUBSCRIPTION_ID',
clientIdVariable: 'ARM_CLIENT_ID',
clientSecretVariable: 'ARM_CLIENT_SECRET',
tenantIdVariable: 'ARM_TENANT_ID'
), usernamePassword(credentialsId: 'port-credentials', usernameVariable: 'TF_VAR_port_client_id', passwordVariable: 'TF_VAR_port_client_secret')]) {
dir('terraform') {
script {
echo 'Initializing Terraform'
sh 'terraform init'
echo 'Validating Terraform configuration'
sh 'terraform validate'
echo 'Creating Terraform Plan for Azure changes'
sh """
terraform plan -out=tfazure -var storage_account_name=$STORAGE_NAME -var location=$STORAGE_LOCATION -var port_run_id=$PORT_RUN_ID -target=azurerm_storage_account.storage_account
"""
echo 'Applying Terraform changes to Azure'
sh 'terraform apply -auto-approve -input=false tfazure'
echo 'Creating Terraform Plan for Port changes'
sh """
terraform plan -out=tfport -var storage_account_name=$STORAGE_NAME -var location=$STORAGE_LOCATION -var port_run_id=$PORT_RUN_ID
"""
echo 'Applying Terraform changes to Port'
sh 'terraform apply -auto-approve -input=false tfport'
}
}
}
}
}
stage('Notify Port') {
steps {
script {
def logs_report_response = sh(script: """
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${PORT_ACCESS_TOKEN}" \
-d '{"message": "Created port entity"}' \
"https://api.getport.io/v1/actions/runs/$PORT_RUN_ID/logs"
""", returnStdout: true)
println(logs_report_response)
}
}
}
stage('Update Run Status') {
steps {
script {
def status_report_response = sh(script: """
curl -X PATCH \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${PORT_ACCESS_TOKEN}" \
-d '{"status":"SUCCESS", "message": {"run_status": "Jenkins CI/CD Run completed successfully!"}}' \
"https://api.getport.io/v1/actions/runs/${PORT_RUN_ID}"
""", returnStdout: true)
println(status_report_response)
}
}
}
}
post {
failure {
// Update Port Run failed.
script {
def status_report_response = sh(script: """
curl -X PATCH \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${PORT_ACCESS_TOKEN}" \
-d '{"status":"FAILURE", "message": {"run_status": "Failed to create azure resource ${STORAGE_NAME}"}}' \
"https://api.getport.io/v1/actions/runs/${PORT_RUN_ID}"
""", returnStdout: true)
println(status_report_response)
}
}
// Clean after build
always {
cleanWs(cleanWhenNotBuilt: false,
deleteDirs: true,
disableDeferredWipeout: false,
notFailBuild: true,
patterns: [[pattern: '.gitignore', type: 'INCLUDE'],
[pattern: '.propsfile', type: 'EXCLUDE']])
}
}
}
6.从 Port 应用程序的self-service 标签触发操作。