合并请求中的 Terraform 集成

围绕基础架构即代码 (IaC) 更改进行协作,需要检查和批准代码更改和预期的基础架构更改。极狐GitLab 提供了一个解决方案来帮助围绕 Terraform 代码更改及其使用合并请求页面的预期效果进行协作。这样用户就不必构建自定义工具或依赖第三方解决方案来简化他们的 IaC 工作流程。

将 Terraform 计划信息输出到合并请求中

使用极狐GitLab Terraform 报告产物,您可以将 terraform plan 运行的详细信息直接公开到合并请求部件中,使您能够查看关于 Terraform 创建、修改或销毁的资源的统计信息。

caution 与任何其他作业产物一样,任何具有仓库访客角色的人都可以查看 Terraform 计划数据。默认情况下,Terraform 和极狐GitLab 都不加密计划文件。如果您的 Terraform 计划包含密码、访问令牌或证书等敏感数据,我们强烈建议您加密计划输出或修改项目可见性设置。

配置 Terraform 报告产物

极狐GitLab 与 Terraform 集成通过使用极狐GitLab 管理的 Terraform 状态,在合并请求上显示 Terraform 更改的 CI/CD 模板。我们建议自定义预构建的镜像并依靠其中提供的 gitlab-terraform helper 进行快速设置。

要手动配置极狐GitLab Terraform 报告产物:

  1. 为简单起见,让我们定义一些可重用的变量以允许我们多次引用这些文件:

    variables:
      PLAN: plan.cache
      PLAN_JSON: plan.json
    
  2. 安装 jq,一个轻量级且灵活的命令行 JSON 处理器
  3. 为特定的 jq 命令创建一个别名,该命令解析出我们要从 terraform plan 输出中提取的信息:

    before_script:
      - apk --no-cache add jq
      - alias convert_report="jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create\":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'"
    
    note 在使用 Bash 的发行版(例如 Ubuntu)中,alias 语句不会在非交互模式下展开。如果您的流水线因错误 convert_report: command not found 而失败,则可以通过在脚本中添加 shopt 命令来显式激活别名扩展:
    before_script:
      - shopt -s expand_aliases
      - alias convert_report="jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create\":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'"
    
  4. 定义一个运行 terraform planterraform showscript。这些命令通过流水线输出并将相关位转换为存储变量 PLAN_JSON。此 JSON 用于创建极狐GitLab Terraform 报告产物。Terraform 报告获取 Terraform tfplan.json 文件。收集的 Terraform 计划报告作为产物上传到极狐GitLab,并显示在合并请求中。

    plan:
      stage: build
      script:
        - terraform plan -out=$PLAN
        - terraform show --json $PLAN | convert_report > $PLAN_JSON
      artifacts:
        reports:
          terraform: $PLAN_JSON
    

    有关使用预构建镜像的完整示例,请参阅示例 .gitlab-ci.yml 文件

    有关显示多个报告的示例,请参阅 .gitlab-ci.yml 多个报告文件

  5. 运行流水线会在合并请求中显示部件,如下所示:

    merge request Terraform widget

  6. 单击小部件中的 查看完整日志 按钮可直接进入流水线日志中的计划输出:

    Terraform plan logs

示例 .gitlab-ci.yml 文件

default:
  image: registry.gitlab.com/gitlab-org/terraform-images/stable:latest

  cache:
    key: example-production
    paths:
      - ${TF_ROOT}/.terraform

variables:
  TF_ROOT: ${CI_PROJECT_DIR}/environments/example/production
  TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/example-production

before_script:
  - cd ${TF_ROOT}

stages:
  - prepare
  - validate
  - build
  - deploy

init:
  stage: prepare
  script:
    - gitlab-terraform init

validate:
  stage: validate
  script:
    - gitlab-terraform validate

plan:
  stage: build
  script:
    - gitlab-terraform plan
    - gitlab-terraform plan-json
  artifacts:
    name: plan
    paths:
      - ${TF_ROOT}/plan.cache
    reports:
      terraform: ${TF_ROOT}/plan.json

apply:
  stage: deploy
  environment:
    name: production
  script:
    - gitlab-terraform apply
  dependencies:
    - plan
  when: manual
  only:
    - master

多个 Terraform 计划报告

从 13.2 版本开始,您可以在合并请求页面上显示多个报告。报告还显示 artifacts: name:。有关建议的设置,请参见下面的示例。

default:
  image:
    name: registry.gitlab.com/gitlab-org/gitlab-build-images:terraform
    entrypoint:
      - '/usr/bin/env'
      - 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'

  cache:
    paths:
      - .terraform

stages:
  - build

.terraform-plan-generation:
  stage: build
  variables:
    PLAN: plan.tfplan
    JSON_PLAN_FILE: tfplan.json
  before_script:
    - cd ${TERRAFORM_DIRECTORY}
    - terraform --version
    - terraform init
    - apk --no-cache add jq
  script:
    - terraform validate
    - terraform plan -out=${PLAN}
    - terraform show --json ${PLAN} | jq -r '([.resource_changes[]?.change.actions?]|flatten)|{"create":(map(select(.=="create"))|length),"update":(map(select(.=="update"))|length),"delete":(map(select(.=="delete"))|length)}' > ${JSON_PLAN_FILE}
  artifacts:
    reports:
      terraform: ${TERRAFORM_DIRECTORY}/${JSON_PLAN_FILE}

review_plan:
  extends: .terraform-plan-generation
  variables:
    TERRAFORM_DIRECTORY: "review/"
  # Review will not include an artifact name

staging_plan:
  extends: .terraform-plan-generation
  variables:
    TERRAFORM_DIRECTORY: "staging/"
  artifacts:
    name: Staging

production_plan:
  extends: .terraform-plan-generation
  variables:
    TERRAFORM_DIRECTORY: "production/"
  artifacts:
    name: Production