使用GitHub动作管理Terraform云
#go #githubactions #terraform #hashicorp

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 }}

namedescriptionauthor零件是自称的。 inputs部分指定六个参数:

  • organizationproject用于识别该工作空间应放置在地Terraform云中。 Terraform云中的工作区分为项目,所有项目都是组织的一部分。通常,您有一个组织,几个项目,甚至还有更多工作区。
  • workspace用于配置将创建的工作空间的所需名称。
  • repositorybranchdirectory都用于确定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)
}

我使用我搜索并希望找到的输入参数和projectgitHubApplication配置工作空间。我将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最重要的是要做以下一项(或两者):

  1. 为Terraform Cloud创建CLI
  2. 创建用于管理Terraform Cloud的官方GitHub动作

我们将看到未来带来什么!


  1. 也就是说,如果,您希望Terraform Cloud这样做!您仍然可以使用想要使用的任何远程后端。但是,我个人发现状态存储是Terraform Cloud的最佳功能之一。无需配置远程后端并将其保留为后端的凭据。除非您有一个需要不同状态后端的特定用例,我建议您信任Terraform Cloud保留状态文件。

  2. 我怀疑某个时候会有一个CLI,甚至很快就会出现。记住:您首先在这里听到它!

  3. 此认证可用于GitHub Partners。

  4. 我在弄清楚如何为我的跑步Docker图像提供的旗帜参数时遇到了严重的问题。我以为我可以将每个标志写为这样的参数列表中的一个项目,但是事实证明,如果您这样做,那么Doc​​ker会将整个内容解释为标志(不仅仅是-organization Part)。