【问题标题】:Specify depends_on for dynamic resources created by third party module为第三方模块创建的动态资源指定depends_on
【发布时间】:2021-02-23 23:37:57
【问题描述】:

我正在使用 Google Cloud Platform 发布的方便的 Terraform module 来构建一个包含多个表和多个视图的 BigQuery 数据集。这非常有效,除了当视图依赖于表时,我必须重试我的apply,因为没有什么告诉 Terraform 在另一个之前做一个。我根据 assets 子目录的内容分配了 tablesviews 属性,这为我提供了我想要的所有架构和模板化查询。

除了放弃漂亮的模块重用模式之外,有没有一种受支持的方法可以做到这一点?我对自己的模式感到非常满意。

为了完整起见,我调用模块的代码是

locals {
  #
  # Tables
  #

  # Description attribute for each table. If absent, no description is set (null).
  table_to_description = {
# ...
  }

  # Values that don't ever change set for this dataset.
  TABLE_CONSTANTS = {
    time_partitioning = null
    expiration_time   = null
    clustering        = []
    labels = {
      terraform_managed = "true"
    }
  }

  TABLE_SCHEMA_SUFFIX = ".json"


  table_schema_filenames = fileset(pathexpand("${path.module}/assets/schemas"), "*.json")
  // fileset() doesn't have an option to output full paths, so we need to re-expand them
  table_schema_paths = [for file_name in local.table_schema_filenames : pathexpand("${path.module}/assets/schemas/${file_name}")]

  # Build a vector of objects, one for each table
  table_inputs = [for full_path in local.table_schema_paths : {
    schema = full_path
    # TODO(jaycarlton) I do not yet see a way around doing the replacement twice, as it's not possible
    #   to refer to other values in the same object when defining it.
    table_id    = replace(basename(full_path), local.TABLE_SCHEMA_SUFFIX, "")
    description = lookup(local.table_to_description, replace(basename(full_path), local.TABLE_SCHEMA_SUFFIX, ""), null)
  }]

  # Merge calculated inputs with the ones we use every time.
  tables = [for table_input in local.table_inputs :
    merge(table_input, local.TABLE_CONSTANTS)
  ]

  #
  # Views
  #
  VIEW_CONSTANTS = {
    # Reporting Subsystem always uses Standard SQL Syntax
    use_legacy_sql = false,
    labels = {
      terraform_managed = "true"
    }
  }
  QUERY_TEMPLATE_SUFFIX = ".sql"
  # Local filenames for view templates. Returns something like ["latest_users.sql", "users_by_id.sql"]
  view_query_template_filenames = fileset("${path.module}/assets/views", "*.sql")
  # expanded to fully qualified path, e.g. ["/repos/workbench/terraform/modules/reporting/views/latest_users.sql", ...]
  //  view_query_template_paths = [for file_name in local.view_query_template_filenames : pathexpand("./reporting/views/${file_name}")]
  view_query_template_paths = [for file_name in local.view_query_template_filenames : pathexpand("${path.module}/assets/views/${file_name}")]

  # Create views for each .sql file in the views directory. There is no Terraform
  # dependency from the view to the table(s) it queries, and I  don't believe the SQL is even checked
  # for accuracy prior to creation on the BQ side.
  views = [for view_query_template_path in local.view_query_template_paths :
    merge({
      view_id = replace(basename(view_query_template_path), local.QUERY_TEMPLATE_SUFFIX, ""),
      query = templatefile(view_query_template_path, {
        project = var.project_id
        dataset = var.reporting_dataset_id
      })
  }, local.VIEW_CONSTANTS)]

}

# All BigQuery assets for Reporting subsystem
module "main" {
  source     = "terraform-google-modules/bigquery/google"
  version    = "~> 4.3"
  dataset_id = var.reporting_dataset_id
  project_id = var.project_id
  location   = "US"

  # Note: friendly_name is discovered in plan and apply steps, but can't be
  # entered here. Maybe they're just not exposed by the dataset module but the resources are looking
  # for them?
  dataset_name = "Workbench ${title(var.aou_env)} Environment Reporting Data" # exposed as friendly_name in plan
  description  = "Daily output of relational tables and time series views for analysis. Views are provided for general ad-hoc analysis."

  tables = local.tables

  # Note that, when creating this module fom the ground up, it's common to see an error like
  # `Error: googleapi: Error 404: Not found: Table my-project:my_dataset.my_table, notFound`. It seems
  # to be a momentary issue due to the dataset's existence not yet being observable to the table/view
  # create API. So far, it's always worked on a re-run.
  # TODO(jaycarlton) see if there's a way to put a retry on this. I'm not convinced that will work
  #   outside of a resource context (and inside a third-party module).
  views = local.views

}

错误看起来像:

Error: googleapi: Error 404: Not found: Table <MY_PROJECT>:<MY_DATASET>.user, notFound

  on .terraform/modules/workbench.reporting.main/main.tf line 76, in resource "google_bigquery_table" "view":
  76: resource "google_bigquery_table" "view" {

Terraform 接受视图但被 BigQuery 拒绝,因为它们引用的表尚未创建或尚不可用。看起来depends_on 进入了一个资源块,但据我所知,在这种情况下,这些是抽象出来的。重试器也可以解决我的问题(但不太优雅),因为在所有情况下重新运行 terraform apply 都可以。

【问题讨论】:

    标签: google-bigquery terraform hcl


    【解决方案1】:

    如果我在这里了解必要的数据流,我认为完成这项工作的一种方法是安排local.views 依赖于模块中与表相关的输出值之一。由于您的 local.views 表达式包含一个 templatefile 调用,因此您可以通过将表名传递到模板中来实现:

      views = [for view_query_template_path in local.view_query_template_paths :
        merge({
          view_id = replace(basename(view_query_template_path), local.QUERY_TEMPLATE_SUFFIX, ""),
          query = templatefile(view_query_template_path, {
            project     = var.project_id
            dataset     = var.reporting_dataset_id
            table_names = module.main.table_names
          })
      }, local.VIEW_CONSTANTS)]
    

    因为table_names输出值依赖于模块中的表资源,所以可以用它来声明对表的间接依赖。然后,模块的 views 参数本身将依赖于 local.views,因此间接(现在已删除级别)依赖于表。

    以上假设模块内的表的配置不依赖于视图。如果存在这样的依赖关系,那么这将创建一个依赖关系循环,但从快速阅读the module source code 来看,这似乎不是问题。

    这个答案所依赖的一个关键是 Terraform 模块的输入变量和输出变量是 每个 依赖图中的一个单独节点,而不是整个模块都是一个节点。因此,一个模块的一个输入变量可能依赖于同一模块的输出值,只要该模块内部的依赖关系不会导致它创建一个依赖循环。

    【讨论】:

    • 我们最初使用隐式依赖来实现这种方法,然后我们意识到定义我在回答中提到的策略对于我们的整体基础架构来说是一种更简洁的实现。
    • 我们发现在某些用例中,我们面临与 OP 在本主题中表达的类似问题,但原因是同一数据集下的多个视图之间的依赖关系,而不是视图和表之间的依赖关系.如果可能的话,我想征求你的意见,马丁。我的第一个想法是在模块内以某种方式定义动态依赖关系(请记住,谷歌模块的行为是您通过模块的单个实例定义数据集下的所有视图,they usefor_each
    • 尽管动态依赖听起来太老套(如果可行的话),但无论如何我们都需要为该用例扩展 google 的模块。
    • 我不再有关于我们在这里讨论的内容的心理背景,听起来你的情况至少与这个问题的内容略有不同,所以我建议开始一个新的顶部Stack Overflow 上的级别问题,您可以在其中分享有关您正在尝试实现的目标以及您迄今为止尝试过的更完整的详细信息。
    【解决方案2】:

    我们使用类似的方法实现了我们的框架,但为了缓解这种用例,我们决定定义一个策略,其中 bq-dataset 永远不会同时托管表和视图,而只托管其中一个。这样,我们的 CI 将始终首先运行/应用 ds/tables 的代码,然后是 ds/views 的代码。

    【讨论】:

    • 在我的情况下,我绝对希望视图与表在同一个数据集中,否则对于我的用户来说这会让故事变得复杂。
    猜你喜欢
    • 1970-01-01
    • 2020-06-02
    • 1970-01-01
    • 2019-08-12
    • 1970-01-01
    • 1970-01-01
    • 2012-03-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多