Skip to content

tfe_registry_module: Allow module_provider with vcs_repo when using source_directory for monorepos #1956

@Mark-Broadhurst

Description

@Mark-Broadhurst

When publishing modules from a monorepo to the HCP Terraform private registry using source_directory and tag_prefix, there is no way to specify the module provider name. The
module_provider attribute has an ExactlyOneOf constraint with vcs_repo, preventing their combined use.

However, the HCP Terraform UI allows specifying the provider name when creating VCS-backed modules with source directories, indicating the underlying API supports this combination.

Monorepo Directory Structure

Our repository is organized by provider, with modules nested underneath:

terraform-modules/                    # Repository root
├── random/                           # Provider: random
│   └── random-string/                # Module: random-string
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
├── aws/                              # Provider: aws
│   └── vpc/                          # Module: vpc
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
├── azurerm/                          # Provider: azurerm
│   └── resource-group/               # Module: resource-group
│       └── main.tf
└── registry/                         # Registry automation
    └── main.tf                       # Publishes modules to HCP Terraform

We use tag-based versioning with prefixes to version each module independently:

  • random-random-string-v1.0.0 → publishes random/random-string as v1.0.0
  • aws-vpc-v2.1.0 → publishes aws/vpc as v2.1.0

Desired Configuration

locals {
  module_files = fileset("${path.module}/..", "*/*/main.tf")

  modules = {
    for path in local.module_files :
    "${dirname(dirname(path))}-${basename(dirname(path))}" => {
      provider   = dirname(dirname(path))      # e.g., "random"
      name       = basename(dirname(path))     # e.g., "random-string"
      source_dir = dirname(path)               # e.g., "random/random-string"
      tag_prefix = "${dirname(dirname(path))}-${basename(dirname(path))}-"
    }
    if dirname(dirname(path)) != "registry"
  }
}

resource "tfe_registry_module" "modules" {
  for_each = local.modules

  organization    = "My-Org"
  name            = each.value.name
  module_provider = each.value.provider  # ❌ Blocked by provider validation

  vcs_repo {
    identifier       = "my-org/terraform-modules"
    oauth_token_id   = "ot-xxx"
    source_directory = each.value.source_dir
    tag_prefix       = each.value.tag_prefix
    tags             = true
  }
}

Current Behavior

The provider rejects this configuration:

Error: Invalid combination of arguments

  with tfe_registry_module.modules["random-random-string"],
  on main.tf line 25, in resource "tfe_registry_module" "modules":
  25:   module_provider = each.value.provider

"module_provider": only one of `module_provider,vcs_repo` can be specified,
but `module_provider,vcs_repo` were specified.

Testing Without module_provider

When omitting module_provider (and optionally name), the API returns:

Error: Error creating registry module from repository my-org/terraform-modules: unprocessable entity

Validation failed: Name is invalid

This confirms the API requires explicit name/provider values for repositories that don't follow the terraform-- naming convention.

Root Cause

The provider schema defines:

"module_provider": {
    ExactlyOneOf: []string{"vcs_repo"},
    ...
}

This constraint was appropriate before monorepo support (source_directory/tag_prefix) was added in v0.69.0, but it now prevents valid use cases.

Expected Behavior

Allow module_provider (and name) to be specified alongside vcs_repo when source_directory is set, since:

  1. The HCP Terraform UI supports this workflow
  2. The API requires these values for non-standard repo names
  3. Monorepos with source_directory inherently don't follow terraform-- naming

Proposed Solution

Modify the schema validation to conditionally allow module_provider when vcs_repo.source_directory is set:

"module_provider": {
    Type:     schema.TypeString,
    Optional: true,
    Computed: true,
    ForceNew: true,
    // Remove ExactlyOneOf, or make it conditional on source_directory
},

Alternatively, add provider as an attribute within the vcs_repo block for monorepo configurations.

Workarounds (All Suboptimal)

  1. Create via UI, import to Terraform - Doesn't scale for new modules
  2. Use API directly via provisioner - No state management
  3. Restructure repo to terraform-- directories - Breaking change

Environment

  • Terraform: >= 1.14.0
  • Provider version: 0.73.0
  • HCP Terraform (cloud)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions