Skip to content

Commit

Permalink
helper/resource: Prevent Inconsistent dependency lock file errors w…
Browse files Browse the repository at this point in the history
…hen using `ExternalProviders` outside the `hashicorp` namespace (#1057)

By default, Terraform CLI will use the `registry.terraform.io` hostname and `hashicorp` namespace for provider addresses if there is not a `terraform` configuration block with an explicit configuration. Previously, the testing framework would automatically generate a temporary configuration with the `terraform` configuration block for running `terraform init`, but it did not include this extra configuration along with a given `TestStep` `Config`. When working with `hashicorp` namespace providers, this isn't an issue because of the Terraform CLI defaults.

External provider developers could manually workaround this issue by including the `terraform` configuration block as part of their `TestStep` `Config`, however this is certainly confusing and error prone.

Previously before code adjustments:

```
=== CONT  TestTest_TestCase_ExternalProviders_NonHashiCorpNamespace
    testcase_providers_test.go:236: Step 1/1 error: Error running pre-apply refresh:
        Error: Inconsistent dependency lock file

        The following dependency selections recorded in the lock file are
        inconsistent with the current configuration:
          - provider registry.terraform.io/hashicorp/scaffoldingtest: required by this configuration but no version is selected

        To update the locked dependency selections to match a changed configuration,
        run:
          terraform init -upgrade
--- FAIL: TestTest_TestCase_ExternalProviders_NonHashiCorpNamespace (1.91s)

=== CONT  TestTest_TestCase_ExternalProvidersAndProviderFactories_NonHashiCorpNamespace
    testcase_providers_test.go:258: Step 1/1 error: Error running pre-apply refresh:
        Error: Inconsistent dependency lock file

        The following dependency selections recorded in the lock file are
        inconsistent with the current configuration:
          - provider registry.terraform.io/hashicorp/scaffoldingtest: required by this configuration but no version is selected

        To update the locked dependency selections to match a changed configuration,
        run:
          terraform init -upgrade
--- FAIL: TestTest_TestCase_ExternalProvidersAndProviderFactories_NonHashiCorpNamespace (0.46s)

=== CONT  TestTest_TestStep_ExternalProviders_NonHashiCorpNamespace
    teststep_providers_test.go:304: Step 1/1 error: Error running pre-apply refresh:
        Error: Inconsistent dependency lock file

        The following dependency selections recorded in the lock file are
        inconsistent with the current configuration:
          - provider registry.terraform.io/hashicorp/scaffoldingtest: required by this configuration but no version is selected

        To update the locked dependency selections to match a changed configuration,
        run:
          terraform init -upgrade
--- FAIL: TestTest_TestStep_ExternalProviders_NonHashiCorpNamespace (0.32s)

=== CONT  TestTest_TestStep_ExternalProvidersAndProviderFactories_NonHashiCorpNamespace
    teststep_providers_test.go:325: Step 1/1 error: Error running pre-apply refresh:
        Error: Inconsistent dependency lock file

        The following dependency selections recorded in the lock file are
        inconsistent with the current configuration:
          - provider registry.terraform.io/hashicorp/scaffoldingtest: required by this configuration but no version is selected

        To update the locked dependency selections to match a changed configuration,
        run:
          terraform init -upgrade
--- FAIL: TestTest_TestStep_ExternalProvidersAndProviderFactories_NonHashiCorpNamespace (0.35s)
```
  • Loading branch information
bflad committed Sep 14, 2022
1 parent 44ccfdc commit ef65fde
Show file tree
Hide file tree
Showing 6 changed files with 860 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .changelog/1057.txt
@@ -0,0 +1,3 @@
```release-note:bug
helper/resource: Prevented `Inconsistent dependency lock file` errors when using `ExternalProviders` outside the `hashicorp` namespace
```
149 changes: 149 additions & 0 deletions helper/resource/testcase_providers_test.go
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

Expand All @@ -19,6 +20,81 @@ func TestTestCaseProviderConfig(t *testing.T) {
testCase TestCase
expected string
}{
"externalproviders-and-protov5providerfactories": {
testCase: TestCase{
ExternalProviders: map[string]ExternalProvider{
"externaltest": {
Source: "registry.terraform.io/hashicorp/externaltest",
VersionConstraint: "1.2.3",
},
},
ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){
"localtest": nil,
},
},
expected: `
terraform {
required_providers {
externaltest = {
source = "registry.terraform.io/hashicorp/externaltest"
version = "1.2.3"
}
}
}
provider "externaltest" {}
`,
},
"externalproviders-and-protov6providerfactories": {
testCase: TestCase{
ExternalProviders: map[string]ExternalProvider{
"externaltest": {
Source: "registry.terraform.io/hashicorp/externaltest",
VersionConstraint: "1.2.3",
},
},
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"localtest": nil,
},
},
expected: `
terraform {
required_providers {
externaltest = {
source = "registry.terraform.io/hashicorp/externaltest"
version = "1.2.3"
}
}
}
provider "externaltest" {}
`,
},
"externalproviders-and-providerfactories": {
testCase: TestCase{
ExternalProviders: map[string]ExternalProvider{
"externaltest": {
Source: "registry.terraform.io/hashicorp/externaltest",
VersionConstraint: "1.2.3",
},
},
ProviderFactories: map[string]func() (*schema.Provider, error){
"localtest": nil,
},
},
expected: `
terraform {
required_providers {
externaltest = {
source = "registry.terraform.io/hashicorp/externaltest"
version = "1.2.3"
}
}
}
provider "externaltest" {}
`,
},
"externalproviders-missing-source-and-versionconstraint": {
testCase: TestCase{
ExternalProviders: map[string]ExternalProvider{
Expand Down Expand Up @@ -155,6 +231,79 @@ func TestTest_TestCase_ExternalProviders(t *testing.T) {
})
}

func TestTest_TestCase_ExternalProviders_NonHashiCorpNamespace(t *testing.T) {
t.Parallel()

Test(t, TestCase{
ExternalProviders: map[string]ExternalProvider{
// This can be set to any provider outside the hashicorp namespace.
// bflad/scaffoldingtest happens to be a published version of
// terraform-provider-scaffolding-framework.
"scaffoldingtest": {
Source: "registry.terraform.io/bflad/scaffoldingtest",
VersionConstraint: "0.1.0",
},
},
Steps: []TestStep{
{
Config: `resource "scaffoldingtest_example" "test" {}`,
},
},
})
}

func TestTest_TestCase_ExternalProvidersAndProviderFactories_NonHashiCorpNamespace(t *testing.T) {
t.Parallel()

Test(t, TestCase{
ExternalProviders: map[string]ExternalProvider{
// This can be set to any provider outside the hashicorp namespace.
// bflad/scaffoldingtest happens to be a published version of
// terraform-provider-scaffolding-framework.
"scaffoldingtest": {
Source: "registry.terraform.io/bflad/scaffoldingtest",
VersionConstraint: "0.1.0",
},
},
ProviderFactories: map[string]func() (*schema.Provider, error){
"null": func() (*schema.Provider, error) { //nolint:unparam // required signature
return &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"null_resource": {
CreateContext: func(_ context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics {
d.SetId("test")
return nil
},
DeleteContext: func(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
return nil
},
ReadContext: func(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics {
return nil
},
Schema: map[string]*schema.Schema{
"triggers": {
Elem: &schema.Schema{Type: schema.TypeString},
ForceNew: true,
Optional: true,
Type: schema.TypeMap,
},
},
},
},
}, nil
},
},
Steps: []TestStep{
{
Config: `
resource "null_resource" "test" {}
resource "scaffoldingtest_example" "test" {}
`,
},
},
})
}

func TestTest_TestCase_ExternalProviders_Error(t *testing.T) {
t.Parallel()

Expand Down
2 changes: 1 addition & 1 deletion helper/resource/testing_new.go
Expand Up @@ -278,7 +278,7 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest
}
}

appliedCfg = step.Config
appliedCfg = step.mergedConfig(ctx, c)

logging.HelperResourceDebug(ctx, "Finished TestStep")

Expand Down
2 changes: 1 addition & 1 deletion helper/resource/testing_new_config.go
Expand Up @@ -16,7 +16,7 @@ import (
func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugintest.WorkingDir, step TestStep, providers *providerFactories) error {
t.Helper()

err := wd.SetConfig(ctx, step.Config)
err := wd.SetConfig(ctx, step.mergedConfig(ctx, c))
if err != nil {
return fmt.Errorf("Error setting config: %w", err)
}
Expand Down
25 changes: 25 additions & 0 deletions helper/resource/teststep_providers.go
Expand Up @@ -6,6 +6,31 @@ import (
"strings"
)

// mergedConfig prepends any necessary terraform configuration blocks to the
// TestStep Config.
//
// If there are ExternalProviders configurations in either the TestCase or
// TestStep, the terraform configuration block should be included with the
// step configuration to prevent errors with providers outside the
// registry.terraform.io hostname or outside the hashicorp namespace.
func (s TestStep) mergedConfig(ctx context.Context, testCase TestCase) string {
var config strings.Builder

// Prevent issues with existing configurations containing the terraform
// configuration block.
if !strings.Contains(s.Config, "terraform {") {
if testCase.hasProviders(ctx) {
config.WriteString(testCase.providerConfig(ctx))
} else {
config.WriteString(s.providerConfig(ctx))
}
}

config.WriteString(s.Config)

return config.String()
}

// providerConfig takes the list of providers in a TestStep and returns a
// config with only empty provider blocks. This is useful for Import, where no
// config is provided, but the providers must be defined.
Expand Down

0 comments on commit ef65fde

Please sign in to comment.