Skip to content

Commit 72e137a

Browse files
committed
Support name and provider when display identifier unconventional
1 parent a499496 commit 72e137a

File tree

5 files changed

+458
-7
lines changed

5 files changed

+458
-7
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## Unreleased
22

3+
ENHANCEMENTS:
4+
* `r/tfe_registry_module`: Adds support for `name` and `module_provider` alongside `vcs_repo` with `source_directory`, by @jillirami [#1959](https://github.com/hashicorp/terraform-provider-tfe/pull/1959)
5+
36
BUG FIXES:
47
* `r/tfe_variable`: Fixed a bug where value_wo was ignored in tfe_variable resources when using variable_set_id. By @Maed223 [#1950](https://github.com/hashicorp/terraform-provider-tfe/pull/1950)
58

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,5 @@ require (
8383
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
8484
gopkg.in/yaml.v3 v3.0.1 // indirect
8585
)
86+
87+
replace github.com/hashicorp/go-tfe => /Users/jillianne.ramirez/Desktop/folder/go-tfe

internal/provider/resource_tfe_registry_module.go

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ func resourceTFERegistryModule() *schema.Resource {
3333
},
3434

3535
CustomizeDiff: func(c context.Context, d *schema.ResourceDiff, meta interface{}) error {
36+
if err := validateNameAndProvider(d); err != nil {
37+
return err
38+
}
3639
if err := validateVcsRepo(d); err != nil {
3740
return err
3841
}
@@ -46,12 +49,10 @@ func resourceTFERegistryModule() *schema.Resource {
4649
ForceNew: true,
4750
},
4851
"module_provider": {
49-
Type: schema.TypeString,
50-
Optional: true,
51-
Computed: true,
52-
ForceNew: true,
53-
ExactlyOneOf: []string{"vcs_repo"},
54-
RequiredWith: []string{"organization", "name"},
52+
Type: schema.TypeString,
53+
Optional: true,
54+
Computed: true,
55+
ForceNew: true,
5556
},
5657
"name": {
5758
Type: schema.TypeString,
@@ -193,6 +194,18 @@ func resourceTFERegistryModuleCreateWithVCS(v interface{}, meta interface{}, d *
193194
log.Printf("[WARN] Error getting organization name: %s", err)
194195
}
195196

197+
// Support for explicitly specifying Name and Provider with VCS repos.
198+
// This is particularly useful for monorepos with source_directory where the repository name
199+
// doesn't follow the terraform-<provider>-<name> convention (e.g., "private-modules", "monorepo").
200+
// When these fields are not provided, the API will infer them from the repository identifier.
201+
if name, ok := d.GetOk("name"); ok {
202+
options.Name = tfe.String(name.(string))
203+
}
204+
205+
if provider, ok := d.GetOk("module_provider"); ok {
206+
options.Provider = tfe.String(provider.(string))
207+
}
208+
196209
options.VCSRepo = &tfe.RegistryModuleVCSRepoOptions{
197210
Identifier: tfe.String(vcsRepo["identifier"].(string)),
198211
GHAInstallationID: tfe.String(vcsRepo["github_app_installation_id"].(string)),
@@ -519,6 +532,76 @@ func resourceTFERegistryModuleDelete(d *schema.ResourceData, meta interface{}) e
519532
return nil
520533
}
521534

535+
func validateNameAndProvider(d *schema.ResourceDiff) error {
536+
configMap := d.GetRawConfig().AsValueMap()
537+
nameValue, hasName := configMap["name"]
538+
providerValue, hasProvider := configMap["module_provider"]
539+
vcsRepoValue, hasVcsRepo := configMap["vcs_repo"]
540+
541+
nameProvided := hasName && !nameValue.IsNull()
542+
providerProvided := hasProvider && !providerValue.IsNull()
543+
vcsRepoProvided := hasVcsRepo && !vcsRepoValue.IsNull()
544+
545+
// Either vcs_repo OR module_provider must be provided
546+
if !vcsRepoProvided && !providerProvided {
547+
return fmt.Errorf("one of vcs_repo or module_provider is required")
548+
}
549+
550+
// Without vcs_repo, both name and module_provider are required
551+
if !vcsRepoProvided {
552+
if !nameProvided || !providerProvided {
553+
return fmt.Errorf("name and module_provider are required when not using vcs_repo")
554+
}
555+
return nil
556+
}
557+
558+
// With vcs_repo: check source_directory and repo naming convention
559+
if vcsRepoValue.LengthInt() == 0 {
560+
return nil
561+
}
562+
563+
vcsRepoBlock := vcsRepoValue.AsValueSlice()[0]
564+
565+
// When using source_directory, both fields are required
566+
sourceDirectory := vcsRepoBlock.GetAttr("source_directory")
567+
if !sourceDirectory.IsNull() && sourceDirectory.AsString() != "" {
568+
if !nameProvided || !providerProvided {
569+
return fmt.Errorf("name and module_provider are required when using source_directory")
570+
}
571+
return nil
572+
}
573+
574+
// Check if repo follows terraform-<provider>-<name> convention
575+
displayIdentifier := vcsRepoBlock.GetAttr("display_identifier")
576+
if displayIdentifier.IsNull() || !displayIdentifier.IsKnown() {
577+
return nil
578+
}
579+
580+
// Extract repo name from "org/repo" format and check convention
581+
repoName := displayIdentifier.AsString()
582+
if idx := strings.LastIndex(repoName, "/"); idx >= 0 {
583+
repoName = repoName[idx+1:]
584+
}
585+
586+
nameParts := strings.Split(repoName, "-")
587+
followsConvention := len(nameParts) == 3
588+
589+
// Standard repos allow both provided or both omitted, but not partial
590+
if followsConvention && nameProvided != providerProvided {
591+
if nameProvided {
592+
return fmt.Errorf("module_provider must be provided when name is specified")
593+
}
594+
return fmt.Errorf("name must be provided when module_provider is specified")
595+
}
596+
597+
// Non-standard repos require both fields
598+
if !followsConvention && (!nameProvided || !providerProvided) {
599+
return fmt.Errorf("name and module_provider are required when the repository name does not follow the terraform-<provider>-<name> convention")
600+
}
601+
602+
return nil
603+
}
604+
522605
func resourceTFERegistryModuleImporter(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
523606
registryModuleInfo := strings.SplitN(d.Id(), "/", 6)
524607
if len(registryModuleInfo) == 4 {

0 commit comments

Comments
 (0)