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