Skip to content

Commit 9a8fedb

Browse files
committed
Support name and provider when display identifier unexpected
1 parent a499496 commit 9a8fedb

File tree

3 files changed

+448
-7
lines changed

3 files changed

+448
-7
lines changed

internal/provider/resource_tfe_registry_module.go

Lines changed: 82 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,69 @@ 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+
vcsRepoProvided := hasVcsRepo && !vcsRepoValue.IsNull()
542+
nameProvided := hasName && !nameValue.IsNull()
543+
providerProvided := hasProvider && !providerValue.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+
// When using vcs_repo, check if we can parse terraform-<provider>-<name> from display_identifier
551+
if vcsRepoProvided {
552+
// Check if the repository name follows terraform-<provider>-<name> convention
553+
canInferFromRepo := false
554+
if vcsRepoValue.LengthInt() > 0 {
555+
vcsRepoBlock := vcsRepoValue.AsValueSlice()[0]
556+
displayIdentifier := vcsRepoBlock.GetAttr("display_identifier")
557+
if !displayIdentifier.IsNull() && displayIdentifier.IsKnown() {
558+
repoName := displayIdentifier.AsString()
559+
// Extract just the repo name from "org/repo" format
560+
parts := strings.Split(repoName, "/")
561+
if len(parts) > 0 {
562+
lastPart := parts[len(parts)-1]
563+
// Check if it follows terraform-<provider>-<name> format
564+
nameParts := strings.Split(lastPart, "-")
565+
if len(nameParts) == 3 {
566+
canInferFromRepo = true
567+
}
568+
}
569+
}
570+
}
571+
572+
// If we can't infer from repo name, require both name and module_provider together
573+
if !canInferFromRepo {
574+
if nameProvided != providerProvided {
575+
if nameProvided {
576+
return fmt.Errorf("module_provider must be provided when name is specified with a non-standard repository name")
577+
}
578+
return fmt.Errorf("name must be provided when module_provider is specified with a non-standard repository name")
579+
}
580+
// If neither is provided with a non-standard repo name, that's also an error
581+
if !nameProvided && !providerProvided {
582+
return fmt.Errorf("name and module_provider are required when the repository name does not follow the terraform-<provider>-<name> convention")
583+
}
584+
}
585+
} else {
586+
// When not using vcs_repo, both name and module_provider are required
587+
if !providerProvided {
588+
return fmt.Errorf("module_provider is required when not using vcs_repo")
589+
}
590+
if !nameProvided {
591+
return fmt.Errorf("name must be set if module_provider is used")
592+
}
593+
}
594+
595+
return nil
596+
}
597+
522598
func resourceTFERegistryModuleImporter(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
523599
registryModuleInfo := strings.SplitN(d.Id(), "/", 6)
524600
if len(registryModuleInfo) == 4 {

0 commit comments

Comments
 (0)