Skip to content

Commit

Permalink
add support for registry modules without vcs
Browse files Browse the repository at this point in the history
  • Loading branch information
Uk1288 committed Jul 18, 2022
1 parent 921da93 commit 408d380
Show file tree
Hide file tree
Showing 4 changed files with 652 additions and 53 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
FEATURES:
* **New Resource**: `tfe_workspace_variable_set` ([#537](https://github.com/hashicorp/terraform-provider-tfe/pull/537)) adds the ability to assign a variable set to a workspace in a single, flexible resource.

DEPRECATION NOTICE: The `workspace_ids` argument on `tfe_variable_set` has been labelled as deprecated and should not be used in conjunction with `tfe_workspace_variable_set`.
ENHANCEMENTS:
* r/tfe_registry_module: Add ability to create both public and private `registry_modules` without VCS. ([#546](https://github.com/hashicorp/terraform-provider-tfe/pull/546))

DEPRECATION NOTICE:
* The `workspace_ids` argument on `tfe_variable_set` has been labelled as deprecated and should not be used in conjunction with `tfe_workspace_variable_set`.
* The `registry_modules` import format `<ORGANIZATION>/<REGISTRY MODULE NAME>/<REGISTRY MODULE PROVIDER>/<REGISTRY MODULE ID>` has been deprecated in favour of `<ORGANIZATION>/<REGISTRY_NAME>/<NAMESPACE>/<REGISTRY MODULE NAME>/<REGISTRY MODULE PROVIDER>/<REGISTRY MODULE ID>` to support public and private `registry_modules`.

## 0.32.1 (June 21st, 2022)

Expand Down
154 changes: 125 additions & 29 deletions tfe/resource_tfe_registry_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func resourceTFERegistryModule() *schema.Resource {
Expand All @@ -24,18 +25,25 @@ func resourceTFERegistryModule() *schema.Resource {
"organization": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"module_provider": {
Type: schema.TypeString,
Computed: true,
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
ExactlyOneOf: []string{"vcs_repo"},
ConflictsWith: []string{"vcs_repo"},
RequiredWith: []string{"organization", "name"},
},
"name": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"vcs_repo": {
Type: schema.TypeList,
Required: true,
Optional: true,
ForceNew: true,
MinItems: 1,
MaxItems: 1,
Expand All @@ -59,39 +67,109 @@ func resourceTFERegistryModule() *schema.Resource {
},
},
},
"namespace": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
RequiredWith: []string{"registry_name"},
},
"registry_name": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
RequiredWith: []string{"module_provider"},
ValidateFunc: validation.StringInSlice(
[]string{"private", "public"},
true,
),
},
},
}
}

func resourceTFERegistryModuleCreate(d *schema.ResourceData, meta interface{}) error {
func resourceTFERegistryModuleCreateWithVCS(v interface{}, meta interface{}) (*tfe.RegistryModule, error) {
tfeClient := meta.(*tfe.Client)

// Create a new options struct.
// Create module with VCS repo configuration block.
options := tfe.RegistryModuleCreateWithVCSConnectionOptions{}
vcsRepo := v.([]interface{})[0].(map[string]interface{})

// Get and assert the VCS repo configuration block.
if v, ok := d.GetOk("vcs_repo"); ok {
vcsRepo := v.([]interface{})[0].(map[string]interface{})

options.VCSRepo = &tfe.RegistryModuleVCSRepoOptions{
Identifier: tfe.String(vcsRepo["identifier"].(string)),
OAuthTokenID: tfe.String(vcsRepo["oauth_token_id"].(string)),
DisplayIdentifier: tfe.String(vcsRepo["display_identifier"].(string)),
}
options.VCSRepo = &tfe.RegistryModuleVCSRepoOptions{
Identifier: tfe.String(vcsRepo["identifier"].(string)),
OAuthTokenID: tfe.String(vcsRepo["oauth_token_id"].(string)),
DisplayIdentifier: tfe.String(vcsRepo["display_identifier"].(string)),
}

log.Printf("[DEBUG] Create registry module from repository %s", *options.VCSRepo.Identifier)
registryModule, err := tfeClient.RegistryModules.CreateWithVCSConnection(ctx, options)
if err != nil {
return fmt.Errorf(
return nil, fmt.Errorf(
"Error creating registry module from repository %s: %w", *options.VCSRepo.Identifier, err)
}
return registryModule, nil
}

func resourceTFERegistryModuleCreateWithoutVCS(meta interface{}, d *schema.ResourceData) (*tfe.RegistryModule, error) {
tfeClient := meta.(*tfe.Client)
// Create module without VCS.
options := tfe.RegistryModuleCreateOptions{
Name: tfe.String(d.Get("name").(string)),
Provider: tfe.String(d.Get("module_provider").(string)),
}

if registryName, ok := d.GetOk("registry_name"); ok {
options.RegistryName = tfe.RegistryName(registryName.(string))

namespace, ok := d.GetOk("namespace")
if registryName.(string) == "private" && ok {
// Namespace is not allowed for private registry name
return nil, fmt.Errorf("Namespace is not allowed for %s registry name", registryName)
}

if registryName.(string) == "public" {
if !ok { // Namespace is required for public registry name
return nil, fmt.Errorf("Namespace is required for %s registry name", registryName)
}
options.Namespace = namespace.(string)
}
}

orgName := d.Get("organization").(string)

log.Printf("[DEBUG] Create registry module named %s", *options.Name)
registryModule, err := tfeClient.RegistryModules.Create(ctx, orgName, options)

if err != nil {
return nil, fmt.Errorf("Error creating registry module %s: %w", *options.Name, err)
}

return registryModule, nil
}

func resourceTFERegistryModuleCreate(d *schema.ResourceData, meta interface{}) error {
tfeClient := meta.(*tfe.Client)

var registryModule *tfe.RegistryModule
var err error

if v, ok := d.GetOk("vcs_repo"); ok {
registryModule, err = resourceTFERegistryModuleCreateWithVCS(v, meta)
} else {
registryModule, err = resourceTFERegistryModuleCreateWithoutVCS(meta, d)
}

if err != nil {
return err
}

err = resource.Retry(time.Duration(5)*time.Minute, func() *resource.RetryError {
rmID := tfe.RegistryModuleID{
Organization: registryModule.Organization.Name,
Name: registryModule.Name,
Provider: registryModule.Provider,
Namespace: registryModule.Namespace,
RegistryName: registryModule.RegistryName,
}
_, err := tfeClient.RegistryModules.Read(ctx, rmID)
if err != nil {
Expand All @@ -113,6 +191,8 @@ func resourceTFERegistryModuleCreate(d *schema.ResourceData, meta interface{}) e
d.Set("name", registryModule.Name)
d.Set("module_provider", registryModule.Provider)
d.Set("organization", registryModule.Organization.Name)
d.Set("namespace", registryModule.Namespace)
d.Set("registry_name", registryModule.RegistryName)

return resourceTFERegistryModuleRead(d, meta)
}
Expand All @@ -127,6 +207,8 @@ func resourceTFERegistryModuleRead(d *schema.ResourceData, meta interface{}) err
Organization: d.Get("organization").(string),
Name: d.Get("name").(string),
Provider: d.Get("module_provider").(string),
Namespace: d.Get("namespace").(string),
RegistryName: tfe.RegistryName(d.Get("registry_name").(string)),
}

registryModule, err := tfeClient.RegistryModules.Read(ctx, rmID)
Expand All @@ -144,6 +226,8 @@ func resourceTFERegistryModuleRead(d *schema.ResourceData, meta interface{}) err
d.Set("name", registryModule.Name)
d.Set("module_provider", registryModule.Provider)
d.Set("organization", registryModule.Organization.Name)
d.Set("namespace", registryModule.Namespace)
d.Set("registry_name", registryModule.RegistryName)

// Set VCS repo options.
var vcsRepo []interface{}
Expand Down Expand Up @@ -179,19 +263,31 @@ func resourceTFERegistryModuleDelete(d *schema.ResourceData, meta interface{}) e
}

func resourceTFERegistryModuleImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
registryModuleInfo := strings.SplitN(d.Id(), "/", 4)
if len(registryModuleInfo) != 4 {
return nil, fmt.Errorf(
"invalid registry module import format: %s (expected <ORGANIZATION>/<REGISTRY MODULE NAME>/<REGISTRY MODULE PROVIDER>/<REGISTRY MODULE ID>)",
d.Id(),
)
}
registryModuleInfo := strings.SplitN(d.Id(), "/", 6)
if len(registryModuleInfo) == 4 {
// for format: <ORGANIZATION>/<REGISTRY MODULE NAME>/<REGISTRY MODULE PROVIDER>/<REGISTRY MODULE ID>
log.Printf("[WARN] The import format <ORGANIZATION>/<REGISTRY MODULE NAME>/<REGISTRY MODULE PROVIDER>/<REGISTRY MODULE ID> is deprecated as of release 0.33.0 and may be removed in a future version. The preferred format is <ORGANIZATION>/<REGISTRY_NAME>/<NAMESPACE>/<REGISTRY MODULE NAME>/<REGISTRY MODULE PROVIDER>/<REGISTRY MODULE ID>.")
d.Set("organization", registryModuleInfo[0])
d.Set("name", registryModuleInfo[1])
d.Set("module_provider", registryModuleInfo[2])
d.SetId(registryModuleInfo[3])

// Set the fields that are part of the import ID.
d.Set("name", registryModuleInfo[1])
d.Set("module_provider", registryModuleInfo[2])
d.Set("organization", registryModuleInfo[0])
d.SetId(registryModuleInfo[3])
return []*schema.ResourceData{d}, nil
} else if len(registryModuleInfo) == 6 {
// for format: <ORGANIZATION>/<REGISTRY_NAME>/<NAMESPACE>/<REGISTRY MODULE NAME>/<REGISTRY MODULE PROVIDER>/<REGISTRY MODULE ID>
// see https://www.terraform.io/cloud-docs/api-docs/private-registry/modules#get-a-module
d.Set("organization", registryModuleInfo[0])
d.Set("registry_name", registryModuleInfo[1])
d.Set("namespace", registryModuleInfo[2])
d.Set("name", registryModuleInfo[3])
d.Set("module_provider", registryModuleInfo[4])
d.SetId(registryModuleInfo[5])

return []*schema.ResourceData{d}, nil
}

return []*schema.ResourceData{d}, nil
return nil, fmt.Errorf(
"invalid registry module import format: %s (expected <ORGANIZATION>/<REGISTRY_NAME>/<NAMESPACE>/<REGISTRY MODULE NAME>/<REGISTRY MODULE PROVIDER>/<REGISTRY MODULE ID>)",
d.Id(),
)
}
Loading

0 comments on commit 408d380

Please sign in to comment.