使用Terraform创建资源前先检查其是否存在

14
在Terraform中,有没有一种方法可以在尝试创建资源之前检查Google Cloud中是否存在该资源?我想要检查以下资源在我的CircleCI CI/CD管道中的作业期间是否存在。我可以使用终端命令、bash和gcloud命令。如果这些资源存在,我希望使用它们。如果不存在,我希望创建它们。我在CircleCI的config.yml文件中使用步骤进行这种逻辑,并且可以访问终端命令和bash。我的目标是在需要时在GCP中创建所需的基础设施(资源),否则在创建了它们之后使用它们,而不会在CI/CD构建中出现Terraform错误。如果我尝试创建已经存在的资源,Terraform apply将导致错误,例如“您已经拥有此资源”,现在我的CI/CD作业失败。以下是描述我尝试获取的资源的伪代码。
resource "google_artifact_registry_repository" "main" {
  # this is the repo for hosting my Docker images
  # it does not have a data source afaik because it is beta
}

对于我的 google_artifact_registry_repository 资源,我的一个方法是使用数据源块来执行 Terraform apply 并查看是否返回值。但问题在于 google_artifact_registry_repository 没有数据源块。因此,我必须使用资源块创建此资源,并且每个 CI/CD 构建都可以依赖于它的存在。是否有一种解决方法可以读取其存在?

resource "google_storage_bucket" "bucket" {
  # bucket containing the folder below
}

resource "google_storage_bucket_object" "content_folder" {
  # folder containing Terraform default.tfstate for my Cloud Run Service
}

对于我的google_storage_bucketgoogle_storage_bucket_object资源,如果我使用数据源块执行Terraform应用程序以查看这些资源是否存在,则遇到的一个问题是当找不到资源时,Terraform需要很长时间才能返回状态。如果我能够在10-15秒内确定资源是否存在,那就太好了,如果不存在,就假定这些资源不存在。

data "google_storage_bucket" "bucket" {
  # bucket containing the folder below
}

output bucket {
  value = data.google_storage_bucket.bucket
}

当资源存在时,我可以使用 Terraform 输出桶来获取该值。如果资源不存在,Terraform 返回响应的时间太长了。对此有什么建议吗?


1
在这里的理想路径是将这些资源导入状态,并使用TF进行管理。 - Matt Schuchard
我试过了。我遇到的问题是导入一个不存在的资源(云存储桶)。Terraform导入会挂起,或者花费的时间太长,我没有等待它完成。这比我想要此脚本等待的时间长。如果资源不存在且我知道,那么我可以创建该资源。如果资源存在,则可以导入它。我需要先确定资源是否存在才能确定是否需要导入。 - Mule
5个回答

6
感谢Marcin的建议,我有了一个可行的例子,使用Terraform的外部数据源来解决检查GCP资源是否存在的问题。这是一种可行的方法,当然也可能有其他方式。
我有一个CircleCI的config.yml文件,其中包含一个使用run命令和bash的作业。从bash中,我将初始化/应用一个Terraform脚本,以以下方式检查我的资源是否存在:
data "external" "get_bucket" {
  program = ["bash","gcp.sh"]
  query = {
    bucket_name = var.bucket_name
  }
}

output "bucket" {
  value = data.external.get_bucket.result.name
}

然后在我的gcp.sh文件中,如果存在的话,我使用gsutil获取我的存储桶。

#!/bin/bash

eval "$(jq -r '@sh "BUCKET_NAME=\(.bucket_name)"')"
bucket=$(gsutil ls gs://$BUCKET_NAME)

if [[ ${#bucket} -gt 0 ]]; then
  jq -n --arg name "" '{name:"'$BUCKET_NAME'"}'
else
  jq -n --arg name "" '{name:""}'
fi

然后,在我的 CircleCI 的 config.yml 文件中,我将所有内容整合在一起。

terraform init
terraform apply -auto-approve -var bucket_name=my-bucket
bucket=$(terraform output bucket)

此时,我会检查返回的存储桶名称,并根据结果决定如何继续。


那么实际上你可以不使用terraform来完成相同的工作吗?你可以在yaml文件级别调用你的bash脚本,而不是调用terraform。我理解你在这种情况下调用了两次terraform?第一次是为了知道是否存在,然后第二次使用前一个输出作为第二次调用的输入。 - HoLengZai

6

TensorFlow(TF)没有任何内置工具用于检查是否存在预先存在的资源,因为这不是TF的目的。但是,您可以创建自己的自定义数据源

使用自定义数据源,您可以编写任何逻辑,包括检查预先存在的资源并将该信息返回给TF以供将来使用。


谢谢你分享这个。我之前不知道 Terraform 中有外部数据源的功能。我看到了使用 bash 脚本、Python 和 JavaScript 的例子。我写了一个快速的 bash 脚本来调用 gsutil 获取我的 GCP Cloud 存储桶。我认为这会起作用。谢谢! - Mule

4

在创建资源之前,有一种方法可以检查资源是否已经存在。但是您应该知道它是否存在。使用这种方法,您需要知道资源是否存在。如果资源不存在,它将给您一个错误。

我将通过创建/读取Azure资源组中的数据来演示它。首先,创建一个布尔变量azurerm_create_resource_group。如果您需要创建资源,则可以将值设置为true;否则,如果您只想从现有资源中读取数据,则可以将其设置为false

variable "azurerm_create_resource_group" {
    type = bool
}

接下来,使用三元运算符获取有关资源的数据,并将其提供给 count,接下来为创建资源执行相同的操作:
data "azurerm_resource_group" "rg" {
    count = var.azurerm_create_resource_group == false ? 1 : 0
    name = var.azurerm_resource_group
}

resource "azurerm_resource_group" "rg" {
    count         = var.azurerm_create_resource_group ? 1 : 0
    name          = var.azurerm_resource_group
    location      = var.azurerm_location
}

该代码将根据var.azurerm_resource_group的值创建或读取资源组中的数据。接下来,将dataresource部分的数据合并到locals中。请注意保留HTML标签。
locals {
        resource_group_name = element(coalescelist(data.azurerm_resource_group.rg.*.name, azurerm_resource_group.rg.*.name, [""]), 0) 
        location            = element(coalescelist(data.azurerm_resource_group.rg.*.location, azurerm_resource_group.rg.*.location, [""]), 0)
    }

另一种方法可能是使用 terraformer 导入基础设施代码。

希望这可以帮助到您。


2
这不是检查资源是否存在的方式;它断言资源是否存在,然后根据情况调整行为。为了检查资源是否存在,Terraform需要支持非故障/条件数据资源,以便调用模块可以检查数据资源是否返回有效结果,但是这种功能目前不存在。 - Paul Gear
如果我需要删除实际创建资源组的模块实例呢?那么资源组也会被删除。我担心现在没有一个好的解决方案,方法需要改变。 - undefined

0
这非常有用。
1. 创建一个 PowerShell 或 shell 脚本,对资源进行查找,如果存在则返回 true,如果不存在则返回 false。
...
$schedules = (Get-AzAutomationSchedule -AutomationAccountName $automationAccountName -ResourceGroupName $resourceGroupName -Name $lookupName -ErrorAction SilentlyContinue)
if ($schedules.Count -gt 0) {
    Write-output '{ "exists": "1" }'
} else {
    Write-Output '{ "exists": "0" }'
}

添加一个数据元素
data "external" "getSchedule" {
  program = ["pwsh", "${path.module}/getSchedule.ps1", "new-schedule"]
}

使用count返回的变量,如果为真则添加资源,如果为假则不添加。
resource "azurerm_automation_schedule" "new-schedule" {
  count = data.external.getSchedule.result.exists ? 0 : 1
...
}

-1

这对我有用:

  1. 创建数据
data "gitlab_user" "user" {
  for_each = local.users
  username = each.value.user_name 
}
  • 创建资源
  • resource "gitlab_user" "user" {
      for_each       = local.users
      name           = each.key
      username       = data.gitlab_user.user[each.key].username != null ? data.gitlab_user.user[each.key].username : split("@", each.value.user_email)[0]
      email          = each.value.user_email
      reset_password = data.gitlab_user.user[each.key].username != null ? false : true
    }
    

    P.S.

    Variable

    variable "users_info" {
      type = list(
        object(
          {
            name         = string
            user_name    = string
            user_email   = string
            access_level = string
            expires_at   = string
            group_name   = string
          }
        )
      )
      description = "List of users and their access to team's groups for newcommers"
    }
    

    本地变量

    locals {
      users  = { for user in var.users_info : user.name => user }
    }
    

    不,它不起作用,我已经尝试过使用aws_iam_role。 - Yougeshwar Khatri

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接