Skip to content

Commit d94fc03

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

File tree

5 files changed

+454
-7
lines changed

5 files changed

+454
-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: 85 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,14 @@ func resourceTFERegistryModuleCreateWithVCS(v interface{}, meta interface{}, d *
193194
log.Printf("[WARN] Error getting organization name: %s", err)
194195
}
195196

197+
if name, ok := d.GetOk("name"); ok {
198+
options.Name = tfe.String(name.(string))
199+
}
200+
201+
if provider, ok := d.GetOk("module_provider"); ok {
202+
options.Provider = tfe.String(provider.(string))
203+
}
204+
196205
options.VCSRepo = &tfe.RegistryModuleVCSRepoOptions{
197206
Identifier: tfe.String(vcsRepo["identifier"].(string)),
198207
GHAInstallationID: tfe.String(vcsRepo["github_app_installation_id"].(string)),
@@ -519,6 +528,76 @@ func resourceTFERegistryModuleDelete(d *schema.ResourceData, meta interface{}) e
519528
return nil
520529
}
521530

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

0 commit comments

Comments
 (0)