Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for registry modules without vcs #546

Merged
merged 4 commits into from Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,10 @@ BUG FIXES:
FEATURES:
* d/agent_pool: Improve efficiency of reading agent pool data when the target organization has more than 20 agent pools ([#508](https://github.com/hashicorp/terraform-provider-tfe/pull/508))
* Added warning logs for 404 error responses ([#538](https://github.com/hashicorp/terraform-provider-tfe/pull/538))
* 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 `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.33.0 (July 8th, 2022)

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Expand Up @@ -13,7 +13,7 @@ require (
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
github.com/hashicorp/go-slug v0.9.1
github.com/hashicorp/go-tfe v1.5.0
github.com/hashicorp/go-tfe v1.6.0
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce
github.com/hashicorp/hcl/v2 v2.10.0 // indirect
Expand All @@ -30,7 +30,7 @@ require (
golang.org/x/oauth2 v0.0.0-20210622215436-a8dc77f794b6 // indirect
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
google.golang.org/api v0.44.0-impersonate-preview // indirect
google.golang.org/protobuf v1.27.1 // indirect
)
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Expand Up @@ -220,6 +220,8 @@ github.com/hashicorp/go-tfe v1.4.0 h1:rMoQ2r1QppaglYsBdmdgFphipNTCkjTaO1oOLVj04E
github.com/hashicorp/go-tfe v1.4.0/go.mod h1:E8a90lC4kjU5Lc2c0D+SnWhUuyuoCIVm4Ewzv3jCD3A=
github.com/hashicorp/go-tfe v1.5.0 h1:MtABkqH2s6lRFl8HaGt0qESLGAyrmMAFfecsEm+13K8=
github.com/hashicorp/go-tfe v1.5.0/go.mod h1:E8a90lC4kjU5Lc2c0D+SnWhUuyuoCIVm4Ewzv3jCD3A=
github.com/hashicorp/go-tfe v1.6.0 h1:lRfyTVLBP1njo2wShE9FimALzVZBfOqMGNuBdsor38w=
github.com/hashicorp/go-tfe v1.6.0/go.mod h1:E8a90lC4kjU5Lc2c0D+SnWhUuyuoCIVm4Ewzv3jCD3A=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
Expand Down Expand Up @@ -569,6 +571,8 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
Expand Down
145 changes: 114 additions & 31 deletions tfe/resource_tfe_registry_module.go
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 @@ -23,19 +24,24 @@ func resourceTFERegistryModule() *schema.Resource {
Schema: map[string]*schema.Schema{
"organization": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"module_provider": {
Type: schema.TypeString,
Computed: true,
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ExactlyOneOf: []string{"vcs_repo"},
RequiredWith: []string{"organization", "name"},
},
"name": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"vcs_repo": {
Type: schema.TypeList,
Required: true,
Optional: true,
ForceNew: true,
MinItems: 1,
MaxItems: 1,
Expand All @@ -59,39 +65,98 @@ func resourceTFERegistryModule() *schema.Resource {
},
},
},
"namespace": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pointing out ForceNew attribute for reviewing

RequiredWith: []string{"registry_name"},
},
"registry_name": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pointing out ForceNew attribute for reviewing

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)

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))

if registryName.(string) == "public" {
options.Namespace = d.Get("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)
}
Comment on lines +143 to +147
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this.


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 +178,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 +194,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 +213,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 +250,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(),
)
}