diff --git a/go.mod b/go.mod index 42eeb73b4..645a30198 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,8 @@ require ( github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect - github.com/hashicorp/go-slug v0.15.2 - github.com/hashicorp/go-tfe v1.67.0 + github.com/hashicorp/go-slug v0.16.0 + github.com/hashicorp/go-tfe v1.68.1-0.20241022063844-fc80091931b7 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/hcl v1.0.0 github.com/hashicorp/hcl/v2 v2.19.1 // indirect @@ -28,7 +28,7 @@ require ( golang.org/x/crypto v0.21.0 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.17.0 // indirect - golang.org/x/sys v0.24.0 // indirect + golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.6.0 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/go.sum b/go.sum index f0a247ed9..22daacee9 100644 --- a/go.sum +++ b/go.sum @@ -64,10 +64,10 @@ github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDm github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= -github.com/hashicorp/go-slug v0.15.2 h1:/ioIpE4bWVN/d7pG2qMrax0a7xe9vOA66S+fz7fZmGY= -github.com/hashicorp/go-slug v0.15.2/go.mod h1:THWVTAXwJEinbsp4/bBRcmbaO5EYNLTqxbG4tZ3gCYQ= -github.com/hashicorp/go-tfe v1.67.0 h1:wj8IY9PYYf6E6J3qnkET6ZMyMdVBPMmZPH4q2/jYX6g= -github.com/hashicorp/go-tfe v1.67.0/go.mod h1:JIgzD8EKkwAqFJdtmo0X2k1NUTrozyniKijL1nVkJgE= +github.com/hashicorp/go-slug v0.16.0 h1:S/ko9fms1gf6305ktJNUKGxFmscZ+yWvAtsas0SYUyA= +github.com/hashicorp/go-slug v0.16.0/go.mod h1:THWVTAXwJEinbsp4/bBRcmbaO5EYNLTqxbG4tZ3gCYQ= +github.com/hashicorp/go-tfe v1.68.1-0.20241022063844-fc80091931b7 h1:ARqeMINL15i9d3DBUGD5DCcI84lQdDXIEPx2gGybOW0= +github.com/hashicorp/go-tfe v1.68.1-0.20241022063844-fc80091931b7/go.mod h1:2rOcdTxXwbWm0W7dCKjC3Ec8KQ+HhW165GiurXNshc4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -199,8 +199,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/provider/resource_tfe_no_code_module.go b/internal/provider/resource_tfe_no_code_module.go index 92a39253f..01073b5c6 100644 --- a/internal/provider/resource_tfe_no_code_module.go +++ b/internal/provider/resource_tfe_no_code_module.go @@ -11,6 +11,7 @@ package provider import ( "context" "errors" + "fmt" "log" "time" @@ -60,6 +61,8 @@ func resourceTFENoCodeModule() *schema.Resource { Type: schema.TypeList, Optional: true, ForceNew: false, + // The version_pin needs to be set when variable_options are set + RequiredWith: []string{"version_pin"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -98,10 +101,9 @@ func resourceTFENoCodeModuleCreate(ctx context.Context, d *schema.ResourceData, if enabled, ok := d.GetOk("enabled"); ok { options.Enabled = tfe.Bool(enabled.(bool)) } - if variableOptions, ok := d.GetOk("variable_options"); ok { - options.VariableOptions = variableOptionsMaptoStruct(variableOptions.([]interface{})) - } - if versionPin, ok := d.GetOk("version_pin"); ok { + + versionPin, ok := d.GetOk("version_pin") + if ok { options.VersionPin = versionPin.(string) } @@ -110,6 +112,23 @@ func resourceTFENoCodeModuleCreate(ctx context.Context, d *schema.ResourceData, return diag.FromErr(err) } + // If variable_options are set, we need to ensure the pinned version is available + // before creating the no-code module with the variable options. + // We check for the version_pin and wait for the pinned version to be available. + if variableOptions, ok := d.GetOk("variable_options"); ok { + if options.VersionPin == "" { + return diag.Errorf("version_pin must be set when variable_options are set") + } + moduleID, err := getFullModuleID(ctx, config.Client, orgName, options.RegistryModule.ID) + if err != nil { + return diag.Errorf("Error getting full module ID for registry module %s: %s", options.RegistryModule.ID, err) + } + if err := waitForModuleVersion(ctx, config.Client, moduleID, options.VersionPin); err != nil { + return diag.Errorf("Error reading registry module version %s: %s", options.VersionPin, err) + } + options.VariableOptions = variableOptionsMaptoStruct(variableOptions.([]interface{})) + } + log.Printf("[DEBUG] Create no-code module for registry module %s", options.RegistryModule.ID) noCodeModule, err := config.Client.RegistryNoCodeModules.Create(ctx, orgName, options) @@ -139,6 +158,34 @@ func variableOptionsMaptoStruct(variableOptions []interface{}) []*tfe.NoCodeVari return variableOptionsRes } +func getFullModuleID(ctx context.Context, client *tfe.Client, orgName, ID string) (tfe.RegistryModuleID, error) { + module, err := client.RegistryModules.Read(ctx, tfe.RegistryModuleID{ID: ID}) + if err != nil { + return tfe.RegistryModuleID{}, err + } + return tfe.RegistryModuleID{ + Organization: orgName, + Namespace: module.Namespace, + Name: module.Name, + Provider: module.Provider, + RegistryName: module.RegistryName, + }, nil +} + +func waitForModuleVersion(ctx context.Context, client *tfe.Client, moduleID tfe.RegistryModuleID, versionPin string) error { + timeout := time.Duration(5) * time.Minute + return retry.RetryContext(ctx, timeout, func() *retry.RetryError { + _, err := client.RegistryModules.ReadVersion(ctx, moduleID, versionPin) + if err == tfe.ErrResourceNotFound { + return retry.RetryableError(fmt.Errorf("Version %s not found for module %s", versionPin, moduleID)) + } + if err != nil { + return retry.NonRetryableError(err) + } + return nil + }) +} + func resourceTFENoCodeModuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { config := meta.(ConfiguredClient)