@@ -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+
522598func 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