Terraform Cloud是在Hashicorp自己的云环境中远程运行Terraform的平台。它简化了与Terraform在团队和组织中的合作。 Terraform Cloud为您存储您的状态文件 1 。您可以将Terraform Cloud Workspace 连接到Git存储库,并使Terraform Cloud自动将任何更新应用于您的存储库。
当您在Terraform Cloud中设置新的工作区时,您可以选择一个存储库,您的Terraform配置所在的存储库中的目录,以及Workspace应该从哪个GIT分支中创建资源。一个工作空间只能连接到单个GIT存储库和分支。如果要创建基础架构的几个实例,则必须在Terraform Cloud中创建多个工作区。这一切都很好,但是有没有一种方法可以自动创建新的临时工作空间,以用于您只能在短时间内存活的开发分支?为此,您必须使用Terraform Cloud API。没有CLI可用,这有点令人惊讶,因为Hashicorp为其各种产品具有大量不同的CLI工具 2 。 Hashicorp友好地为Terraform云/企业开发了GO客户,因此我们没有被迫自己完成所有工作。
让我们使用GO客户端开发一些有用的东西!我徒劳地搜索的一件事是一种使用GitHub动作与Terraform Cloud合作的方法。我梦想能够自动为开发分支创建Terraform云工作区,当我将更改合并到主分支时,这些开发分支也会自动删除。因此,在这篇文章中,我将浏览我的做法!
这不是引入Terraform云的帖子。如果您想使用我在这篇文章中提出的不同的github操作,则需要拥有一个工作的Terraform云组织,并熟悉如何创建一个令牌以对Terraform Cloud进行身份验证。如果您想了解更多信息并开始开始,建议您去developer.hashicorp.com/terraform/cloud-docs
在GO中编写自定义动作
我最近在GitHub Action中获得了认证 3 ,并且作为其中的一部分,我学会了创建自己的自定义操作。我使用这些技能来创建许多在Terraform Cloud中执行小任务的动作。我所有的动作都可以在这里找到:
- Set up environment variables for Terraform Cloud:此操作需要许多输入,用于指定Terraform Cloud的API令牌,您拥有的组织名称,项目名称(工作空间集合)以及您想要使用的工作空间的名称。然后,它使这些不同的设置可作为以下操作的环境变量可用。
- Create a new workspace:此动作创建了一个新的工作区。此操作的版本1(当前版本)对工作区的外观非常有用。
- Delete an existing workspace:此操作删除了现有的工作区。它首先启动A 销毁运行,以便删除Terraform创建的所有基础架构,然后继续删除工作区本身。
- Apply a variable set to a workspace:此操作将现有变量集应用于工作区。变量集是您要在许多工作区中重复使用的变量集合。可变集的良好候选人包括对云提供商或任何其他类型的凭据的凭据。
- Start a new run in a workspace:此操作触发A plan-apply 在选定的工作区中运行。
在这篇文章中,我将详细介绍如何创建用于创建新工作区的动作。
首先,如果要创建自定义操作,则有三个不同的选项可供选择:
- JavaScript动作:在JavaScript中写下动作。
- Docker Action :用您梦dream以求的任何框架的动作编写动作,并将其作为Docker Image包装。
- 复合操作:编写一个由执行Shell命令或脚本的许多步骤组成的动作。
由于HashiCorp拥有Terraform Cloud的GO客户端,因此我的选择是在GO中编写我的动作代码,因此选择 Docker Action 类型的动作。
元数据采取行动
自定义操作的元数据指定诸如其名称,描述,所需的输入,产生的输出以及有关该动作所组成的属性之类的内容。此数据是在称为action.yaml
(或action.yml
)的文件中配置的。对于我的自定义操作,action.yaml
文件看起来像这样:
name: Create Terraform Cloud workspace
author: Mattias Fjellström (mattias.fjellstrom [at] gmail.com)
description: Create a new workspace in Terraform Cloud
inputs:
organization:
description: Organization name
project:
description: Project name
workspace:
description: Desired workspace name
repository:
description: GitHub repository name
default: ${{ github.repository }}
branch:
description: Git branch name to trigger runs from
directory:
description: Repository directory name containing Terraform configuration
runs:
using: docker
image: Dockerfile
args:
- -organization
- ${{ inputs.organization }}
- -project
- ${{ inputs.project }}
- -workspace
- ${{ inputs.workspace }}
- -repository
- ${{ inputs.repository }}
- -working_directory
- ${{ inputs.directory }}
- -branch
- ${{ inputs.branch }}
name
,description
和author
零件是自称的。 inputs
部分指定六个参数:
-
organization
和project
用于识别该工作空间应放置在地Terraform云中。 Terraform云中的工作区分为项目,所有项目都是组织的一部分。通常,您有一个组织,几个项目,甚至还有更多工作区。 -
workspace
用于配置将创建的工作空间的所需名称。 -
repository
,branch
和directory
都用于确定Terraform配置的位置。
最后一节是runs
。这是我配置此操作是using: docker
的地方,我提供了一条(通往我的Dockerfile的路径(偶然性只是Dockerfile
),我将输入作为参数提供给我的Docker Image 4 。
停靠我的行动
我的动作的Dockerfile
是用于简单的GO应用程序,而没有任何精美的功能:
FROM golang:1.20.2-alpine
WORKDIR /app
COPY ./ ./
RUN go build -o /bin/app main.go
ENTRYPOINT ["app"]
GO代码
动作本身的肉位于main.go
中。我将逐步浏览代码的相关部分。要解析输入参数,我使用flag
软件包:
var organizationName string
var projectName string
var workspaceName string
var repositoryName string
var workingDirectory string
var branchName string
func init() {
flag.StringVar(&organizationName, "organization", "", "Organization name")
flag.StringVar(&projectName, "project", "", "Project name")
flag.StringVar(&workspaceName, "workspace", "", "Desired workspace name")
flag.StringVar(&repositoryName, "repository", "", "Git repository")
flag.StringVar(&workingDirectory, "working_directory", "", "Directory containing Terraform configuration")
flag.StringVar(&branchName, "branch", "", "Git branch name")
}
func main() {
flag.Parse()
// ...
}
我在main()
函数之前运行的init()
函数中定义了各种标志。 main()
功能的第一步是解析标志以获得提供的任何值(如果有)。之后,我浏览每个标志以检查是否提供了一个值,如果没有,则可以使用环境变量定义。组织名称的一个示例如下:
if organizationName == "" {
log.Println("No organization name provided, will fall back to environment variable")
_, ok := os.LookupEnv(ENV_TERRAFORM_CLOUD_ORGANIZATION)
if !ok {
log.Fatal("Organization name must be provided as input or as environment variable")
}
organizationName = os.Getenv(ENV_TERRAFORM_CLOUD_ORGANIZATION)
log.Println("Organization name read from environment variable")
}
环境变量的实际名称存储为const
名为ENV_TERRAFORM_CLOUD_ORGANIZATION
。要读取环境变量,我使用os
软件包。
一旦解析了输入,我会寻找我需要将其设置为环境变量的Terraform Cloud API令牌,如果发现它,我继续初始化Terraform Cloud Go Client:
token, ok := os.LookupEnv(ENV_TERRAFORM_CLOUD_TOKEN)
if !ok || token == "" {
log.Fatalf("%s environment variable must be set with a valid token", ENV_TERRAFORM_CLOUD_TOKEN)
}
config := &tfe.Config{
Token: token,
RetryServerErrors: true,
}
client, err := tfe.NewClient(config)
if err != nil {
log.Fatal(err)
}
我以import tfe "github.com/hashicorp/go-tfe"
的形式导入了go客户端,并将其与tfe
相称。
接下来有两个部分我搜索各种内容:
- 我需要找到正确使用的项目。我必须访问项目ID,而不是其名称。 GO客户端不允许我按名称查找一个项目,因此我必须列出所有项目,并选择具有匹配名称的项目。这有点乏味,我不会在这里显示代码。有关详细信息,请参见GitHub repository。
- 我还需要找到与GitHub的正确连接。这是我行动的要求,必须有一个GitHub应用程序安装将我的Terraform Cloud组织与我的GitHub组织联系起来。对此行动的未来改进将是允许其他GIT连接。但是,与项目一样,我必须列出可用的GitHub应用程序,并找到当前正在运行该操作的GitHub组织的应用程序。我也不会在此处包含该代码,但是详细信息可在GitHub repository中找到。
所有这些都以代码的最后一部分创建工作空间的方式:
_, err = client.Workspaces.Create(ctx, organizationName, tfe.WorkspaceCreateOptions{
Type: "workspaces",
Name: tfe.String(workspaceName),
AutoApply: tfe.Bool(true),
WorkingDirectory: tfe.String(workingDirectory),
VCSRepo: &tfe.VCSRepoOptions{
Branch: tfe.String(branchName),
Identifier: tfe.String(repositoryName),
GHAInstallationID: gitHubApplication.ID,
},
Project: project,
})
if err != nil {
log.Fatal(err)
}
我使用我搜索并希望找到的输入参数和project
和gitHubApplication
配置工作空间。我将AutoApply
设置为true
,因为我希望在没有手动批准的情况下自动创建基础架构。
这是代码的简短演练,可以在GitHub中获得完整源。
编写创建和删除Terraform云工作空间的GitHub工作流程
用我的所有自定义动作编写和出版了他们自己的github存储库,我可以继续使用它们。
我想到要创建一个github工作流程,如果我创建一个新的plupquest,将自动创建新的Terraform云工作空间,并触发初始运行。然后,当套管关闭时,应删除工作区和相应的基础架构。
工作流程完全做到这一点可能看起来如下:
name: Sample Terraform Cloud administration for pull requests
on:
# trigger when pull requests are opened or closed
pull_request:
types:
- opened
- closed
# set some convenience environment variables
env:
ORGANIZATION: my-terraform-cloud-organization
PROJECT: my-terraform-cloud-project
jobs:
# job for creating a workspace for new pull requests
create-workspace:
if: ${{ github.event.action == 'opened' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# set up environment variables for terraform cloud
- uses: mattias-fjellstrom/tfc-setup@v1
with:
token: ${{ secrets.TERRAFORM_CLOUD_TOKEN }}
organization: ${{ env.ORGANIZATION }}
project: ${{ env.PROJECT }}
workspace: my-application-${{ github.head_ref }}
# create the workspace (the action I went through above!)
- uses: mattias-fjellstrom/tfc-create-workspace@v1
with:
directory: terraform
branch: ${{ github.head_ref }}
# apply a variable set with azure credentials to the workspace
- uses: mattias-fjellstrom/tfc-apply-variable-set@v1
with:
variable_set: azure-credentials
# trigger an initial run
- uses: mattias-fjellstrom/tfc-start-run@v1
# job for deleting a workspace for closed pull requests
delete-workspace:
if: ${{ github.event.action == 'closed' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# set up environment variables for terraform cloud
- uses: mattias-fjellstrom/tfc-setup@v1
with:
token: ${{ secrets.TERRAFORM_CLOUD_TOKEN }}
organization: ${{ env.ORGANIZATION }}
project: ${{ env.PROJECT }}
workspace: my-application-${{ github.head_ref }}
# delete infrastructure and terraform cloud workspace
- uses: mattias-fjellstrom/tfc-delete-workspace@v1
我已经在工作流程中添加了一些评论,以解释不同的动作在做什么。我希望突出显示一些细节:
- 我在名为
TERRAFORM_CLOUD_TOKEN
的GitHub动作中存储了一个API令牌。目前,您必须为mattias-fjellstrom/tfc-setup@v1
动作提供代币,以便其他操作进行其他操作。这类似于azure/login@v1
动作的工作原理。 - 我将工作空间名称设置为
my-application-${{ github.head_ref }}
。如果我创建了一个名为feature-1
的新分支,并打开了一个拉动请求,将此分支合并到我的主分支中,我将获得一个名为my-application-feature-1
的新Terraform Cloud Workspace。 - 在
mattias-fjellstrom/tfc-create-workspace@v1
操作中我指定了directory: terraform
。这是因为在我的虚构存储库中,我将所有Terraform文件(.tf
)放在一个名为terraform
的目录中。如果我不为目录提供值,那么我的存储库的根目录将被使用。 - 在
mattias-fjellstrom/tfc-apply-variable-set@v1
操作中,我指定的是,应将名为azure-credentials
的变量集应用于我的工作区。此变量集必须从之前存在,它不是由此操作自动创建的。 - 要记住的一件重要的事情是,
mattias-fjellstrom/tfc-delete-workspace@v1
的动作可能需要一些时间。作为第一步,它将开始销毁运行,删除其创建的所有基础架构。根据此基础架构的大小,将需要相应的时间来删除。
总的来说,我现在对这些行动的状态感到满意。如果我需要其他功能,我稍后可能会添加它们。但是,我希望Hashicorp最重要的是要做以下一项(或两者):
- 为Terraform Cloud创建CLI
- 创建用于管理Terraform Cloud的官方GitHub动作
我们将看到未来带来什么!
-
也就是说,如果,您希望Terraform Cloud这样做!您仍然可以使用想要使用的任何远程后端。但是,我个人发现状态存储是Terraform Cloud的最佳功能之一。无需配置远程后端并将其保留为后端的凭据。除非您有一个需要不同状态后端的特定用例,我建议您信任Terraform Cloud保留状态文件。
-
我怀疑某个时候会有一个CLI,甚至很快就会出现。记住:您首先在这里听到它!↩
-
此认证可用于GitHub Partners。↩
-
我在弄清楚如何为我的跑步Docker图像提供的旗帜参数时遇到了严重的问题。我以为我可以将每个标志写为这样的参数列表中的一个项目,但是事实证明,如果您这样做,那么Docker会将整个内容解释为标志(不仅仅是
-organization
Part)。↩