diff --git a/.changes/unreleased/NOTES-20250506-121618.yaml b/.changes/unreleased/NOTES-20250506-121618.yaml new file mode 100644 index 000000000..028c3fa58 --- /dev/null +++ b/.changes/unreleased/NOTES-20250506-121618.yaml @@ -0,0 +1,5 @@ +kind: NOTES +body: 'ImportState: extend auto-generated import blocks to work with `ConfigFile` and `ConfigDirectory`; adds `ConfigExact` flag to opt out' +time: 2025-05-06T12:16:18.375025-04:00 +custom: + Issue: "494" diff --git a/helper/resource/importstate/import_block_as_first_step_test.go b/helper/resource/importstate/import_block_as_first_step_test.go index 915c61b3c..e7dacef9f 100644 --- a/helper/resource/importstate/import_block_as_first_step_test.go +++ b/helper/resource/importstate/import_block_as_first_step_test.go @@ -48,6 +48,7 @@ func TestImportBlock_AsFirstStep(t *testing.T) { id = "westeurope/somevalue" } `, + ImportStateConfigExact: true, ImportPlanChecks: r.ImportPlanChecks{ PreApply: []plancheck.PlanCheck{ plancheck.ExpectResourceAction("examplecloud_container.test", plancheck.ResourceActionNoop), diff --git a/helper/resource/importstate/import_block_in_config_directory_test.go b/helper/resource/importstate/import_block_in_config_directory_test.go index df6feecba..cf24a0240 100644 --- a/helper/resource/importstate/import_block_in_config_directory_test.go +++ b/helper/resource/importstate/import_block_in_config_directory_test.go @@ -43,3 +43,35 @@ func TestImportBlock_InConfigDirectory(t *testing.T) { }, }) } + +func TestImportBlock_InConfigDirectory_ConfigExactTrue(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithID requires Terraform 1.5.0 or later + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "examplecloud_container": examplecloudResource(), + }, + }), + }, + Steps: []r.TestStep{ + { + ConfigDirectory: config.StaticDirectory(`testdata/1`), + }, + { + ResourceName: "examplecloud_container.test", + ImportState: true, + ImportStateKind: r.ImportBlockWithID, + + // This content includes an import block with an ID so we will + // use the exact content + ConfigDirectory: config.StaticDirectory(`testdata/2_with_exact_import_config`), + ImportStateConfigExact: true, + }, + }, + }) +} diff --git a/helper/resource/importstate/import_block_in_config_file_test.go b/helper/resource/importstate/import_block_in_config_file_test.go index 2d5a97a29..db762f2d3 100644 --- a/helper/resource/importstate/import_block_in_config_file_test.go +++ b/helper/resource/importstate/import_block_in_config_file_test.go @@ -38,7 +38,7 @@ func TestImportBlock_InConfigFile(t *testing.T) { ResourceName: "examplecloud_container.test", ImportState: true, ImportStateKind: r.ImportBlockWithID, - ConfigFile: config.StaticFile(`testdata/2/examplecloud_container_import.tf`), + ConfigFile: config.StaticFile(`testdata/2/examplecloud_container.tf`), }, }, }) @@ -66,7 +66,39 @@ func TestImportBlock_WithResourceIdentity_InConfigFile(t *testing.T) { ResourceName: "examplecloud_container.test", ImportState: true, ImportStateKind: r.ImportBlockWithResourceIdentity, - ConfigFile: config.StaticFile(`testdata/examplecloud_container_import_with_identity.tf`), + ConfigFile: config.StaticFile(`testdata/2/examplecloud_container.tf`), + }, + }, + }) +} + +func TestImportBlock_InConfigFile_ConfigExactTrue(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithID requires Terraform 1.5.0 or later + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "examplecloud_container": examplecloudResource(), + }, + }), + }, + Steps: []r.TestStep{ + { + ConfigFile: config.StaticFile(`testdata/1/examplecloud_container.tf`), + }, + { + ResourceName: "examplecloud_container.test", + ImportState: true, + ImportStateKind: r.ImportBlockWithID, + + // This content includes an import block with an ID so we will + // use the exact content + ConfigFile: config.StaticFile(`testdata/examplecloud_container_with_exact_import_config_with_id.tf`), + ImportStateConfigExact: true, }, }, }) diff --git a/helper/resource/importstate/import_block_with_id_test.go b/helper/resource/importstate/import_block_with_id_test.go index ef9661503..c11754a6d 100644 --- a/helper/resource/importstate/import_block_with_id_test.go +++ b/helper/resource/importstate/import_block_with_id_test.go @@ -89,10 +89,11 @@ func TestImportBlock_WithID_ExpectError(t *testing.T) { id = "westeurope/somevalue" } `, - ResourceName: "examplecloud_container.test", - ImportState: true, - ImportStateKind: r.ImportBlockWithID, - ExpectError: regexp.MustCompile(`importing resource examplecloud_container.test: expected a no-op import operation, got.*\["update"\] action with plan(.?)`), + ResourceName: "examplecloud_container.test", + ImportState: true, + ImportStateKind: r.ImportBlockWithID, + ImportStateConfigExact: true, + ExpectError: regexp.MustCompile(`importing resource examplecloud_container.test: expected a no-op import operation, got.*\["update"\] action with plan(.?)`), }, }, }) @@ -295,9 +296,10 @@ func TestImportBlock_WithID_WithBlankOptionalAttribute_GeneratesCorrectPlan(t *t id = "sometestid" }`, - ResourceName: "examplecloud_container.test", - ImportState: true, - ImportStateKind: r.ImportBlockWithID, + ResourceName: "examplecloud_container.test", + ImportState: true, + ImportStateKind: r.ImportBlockWithID, + ImportStateConfigExact: true, }, }, }) @@ -416,10 +418,11 @@ import { Config: config, }, { - ImportState: true, - ImportStateKind: r.ImportBlockWithID, - Config: configWithImportBlock, - ResourceName: "random_string.mystery_message", + ImportState: true, + ImportStateKind: r.ImportBlockWithID, + ImportStateConfigExact: true, + Config: configWithImportBlock, + ResourceName: "random_string.mystery_message", ImportPlanChecks: r.ImportPlanChecks{ PreApply: []plancheck.PlanCheck{ plancheck.ExpectKnownValue( diff --git a/helper/resource/importstate/testdata/2/examplecloud_container.tf b/helper/resource/importstate/testdata/2/examplecloud_container.tf new file mode 100644 index 000000000..ccfb698e6 --- /dev/null +++ b/helper/resource/importstate/testdata/2/examplecloud_container.tf @@ -0,0 +1,7 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +resource "examplecloud_container" "test" { + name = "somevalue" + location = "westeurope" +} diff --git a/helper/resource/importstate/testdata/2/examplecloud_container_import.tf b/helper/resource/importstate/testdata/2_with_exact_import_config/examplecloud_container.tf similarity index 100% rename from helper/resource/importstate/testdata/2/examplecloud_container_import.tf rename to helper/resource/importstate/testdata/2_with_exact_import_config/examplecloud_container.tf diff --git a/helper/resource/importstate/testdata/examplecloud_container_import_with_identity.tf b/helper/resource/importstate/testdata/examplecloud_container_with_exact_import_config_with_id.tf similarity index 78% rename from helper/resource/importstate/testdata/examplecloud_container_import_with_identity.tf rename to helper/resource/importstate/testdata/examplecloud_container_with_exact_import_config_with_id.tf index 9412afb97..f7e9411f9 100644 --- a/helper/resource/importstate/testdata/examplecloud_container_import_with_identity.tf +++ b/helper/resource/importstate/testdata/examplecloud_container_with_exact_import_config_with_id.tf @@ -8,7 +8,5 @@ resource "examplecloud_container" "test" { import { to = examplecloud_container.test - identity = { - id = "examplecloud_container.test" - } + id = "examplecloud_container.test" } diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 0cfba4991..fab58baa0 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -576,6 +576,16 @@ type TestStep struct { // otherwise an error will be returned. ConfigFile config.TestStepConfigFunc + // ImportStateConfigExact indicates that the test framework should use the exact + // content of the Config, ConfigFile, or ConfigDirectory inputs and should + // not modify it at test run time. + // + // The default is false. At test run time, the test framework will generate + // specific kinds of configuration, such as import blocks, and append them + // to the given Config, ConfigFile, or ConfigDirectory inputs. Using this + // default improves test readability and removes duplication of setup. + ImportStateConfigExact bool + // ConfigVariables is a map defining variables for use in conjunction // with Terraform configuration. If this map is populated then it // will be used to assemble an *.auto.tfvars.json which will be diff --git a/helper/resource/testing_new_import_state.go b/helper/resource/testing_new_import_state.go index 887707180..dae85825e 100644 --- a/helper/resource/testing_new_import_state.go +++ b/helper/resource/testing_new_import_state.go @@ -105,6 +105,12 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest } } + var inlineConfig string + if step.Config != "" { + inlineConfig = step.Config + } else { + inlineConfig = cfgRaw + } testStepConfigRequest := config.TestStepConfigRequest{ StepNumber: stepNumber, TestName: t.Name(), @@ -112,27 +118,23 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest testStepConfig := teststep.Configuration(teststep.PrepareConfigurationRequest{ Directory: step.ConfigDirectory, File: step.ConfigFile, - Raw: step.Config, + Raw: inlineConfig, TestStepConfigRequest: testStepConfigRequest, }.Exec()) - if testStepConfig == nil { - logging.HelperResourceTrace(ctx, "Using prior TestStep Config for import") - importConfig := cfgRaw + switch { + case step.ImportStateConfigExact: + break - if kind.plannable() && kind.resourceIdentity() { - importConfig = appendImportBlockWithIdentity(importConfig, resourceName, priorIdentityValues) - } else if kind.plannable() { - importConfig = appendImportBlock(importConfig, resourceName, importId) - } + case kind.plannable() && kind.resourceIdentity(): + testStepConfig = appendImportBlockWithIdentity(testStepConfig, resourceName, priorIdentityValues) - testStepConfig = teststep.Configuration(teststep.PrepareConfigurationRequest{ - Raw: importConfig, - TestStepConfigRequest: testStepConfigRequest, - }.Exec()) - if testStepConfig == nil { - t.Fatal("Cannot import state with no specified config") - } + case kind.plannable(): + testStepConfig = appendImportBlock(testStepConfig, resourceName, importId) + } + + if testStepConfig == nil { + t.Fatal("Cannot import state with no specified config") } var workingDir *plugintest.WorkingDir @@ -424,51 +426,51 @@ func testImportCommand(ctx context.Context, t testing.T, workingDir *plugintest. return nil } -func appendImportBlock(config string, resourceName string, importID string) string { - return config + fmt.Sprintf(``+"\n"+ - `import {`+"\n"+ - ` to = %s`+"\n"+ - ` id = %q`+"\n"+ - `}`, - resourceName, importID) +func appendImportBlock(config teststep.Config, resourceName string, importID string) teststep.Config { + return config.Append( + fmt.Sprintf(``+"\n"+ + `import {`+"\n"+ + ` to = %s`+"\n"+ + ` id = %q`+"\n"+ + `}`, + resourceName, importID)) } -func appendImportBlockWithIdentity(config string, resourceName string, identityValues map[string]any) string { - configBuilder := config - configBuilder += fmt.Sprintf(``+"\n"+ +func appendImportBlockWithIdentity(config teststep.Config, resourceName string, identityValues map[string]any) teststep.Config { + configBuilder := strings.Builder{} + configBuilder.WriteString(fmt.Sprintf(``+"\n"+ `import {`+"\n"+ ` to = %s`+"\n"+ ` identity = {`+"\n", - resourceName) + resourceName)) for k, v := range identityValues { switch v := v.(type) { case bool: - configBuilder += fmt.Sprintf(` %q = %t`+"\n", k, v) + configBuilder.WriteString(fmt.Sprintf(` %q = %t`+"\n", k, v)) case []any: var quotedV []string for _, v := range v { quotedV = append(quotedV, fmt.Sprintf(`%q`, v)) } - configBuilder += fmt.Sprintf(` %q = [%s]`+"\n", k, strings.Join(quotedV, ", ")) + configBuilder.WriteString(fmt.Sprintf(` %q = [%s]`+"\n", k, strings.Join(quotedV, ", "))) case json.Number: - configBuilder += fmt.Sprintf(` %q = %s`+"\n", k, v) + configBuilder.WriteString(fmt.Sprintf(` %q = %s`+"\n", k, v)) case string: - configBuilder += fmt.Sprintf(` %q = %q`+"\n", k, v) + configBuilder.WriteString(fmt.Sprintf(` %q = %q`+"\n", k, v)) default: panic(fmt.Sprintf("unexpected type %T for identity value %q", v, k)) } } - configBuilder += `` + - ` }` + "\n" + - `}` + "\n" + configBuilder.WriteString(` }` + "\n") + configBuilder.WriteString(`}` + "\n") - return configBuilder + return config.Append(configBuilder.String()) } func importStatePreconditions(t testing.T, helper *plugintest.Helper, step TestStep) error { diff --git a/internal/teststep/config.go b/internal/teststep/config.go index b81c264d9..91a708e26 100644 --- a/internal/teststep/config.go +++ b/internal/teststep/config.go @@ -45,6 +45,7 @@ type Config interface { HasProviderBlock(context.Context) (bool, error) HasTerraformBlock(context.Context) (bool, error) Write(context.Context, string) error + Append(string) Config } // PrepareConfigurationRequest is used to simplify the generation of @@ -151,7 +152,7 @@ func copyFiles(path string, dstPath string) error { if info.IsDir() { continue } else { - err = copyFile(srcPath, dstPath) + _, err = copyFile(srcPath, dstPath) if err != nil { return err @@ -164,11 +165,11 @@ func copyFiles(path string, dstPath string) error { // copyFile accepts a path to a file and a destination, // copying the file from path to destination. -func copyFile(path string, dstPath string) error { +func copyFile(path string, dstPath string) (string, error) { srcF, err := os.Open(path) if err != nil { - return err + return "", err } defer srcF.Close() @@ -176,7 +177,7 @@ func copyFile(path string, dstPath string) error { di, err := os.Stat(dstPath) if err != nil { - return err + return "", err } if di.IsDir() { @@ -187,12 +188,28 @@ func copyFile(path string, dstPath string) error { dstF, err := os.Create(dstPath) if err != nil { - return err + return "", err } defer dstF.Close() if _, err := io.Copy(dstF, srcF); err != nil { + return "", err + } + + return dstPath, nil +} + +// appendToFile accepts a path to a file and a string, +// appending the file from path to destination. +func appendToFile(path string, content string) error { + f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, os.ModeAppend) + if err != nil { + return err + } + defer f.Close() + + if _, err := io.WriteString(f, content); err != nil { return err } diff --git a/internal/teststep/directory.go b/internal/teststep/directory.go index 0126e82aa..67ecc5ccd 100644 --- a/internal/teststep/directory.go +++ b/internal/teststep/directory.go @@ -5,6 +5,8 @@ package teststep import ( "context" + "fmt" + "hash/crc32" "os" "path/filepath" ) @@ -13,6 +15,9 @@ var _ Config = configurationDirectory{} type configurationDirectory struct { directory string + + // appendedConfig is a map of filenames to content + appendedConfig map[string]string } // HasConfigurationFiles is used during validation to ensure that @@ -85,10 +90,39 @@ func (c configurationDirectory) Write(ctx context.Context, dest string) error { } err := copyFiles(configDirectory, dest) + if err != nil { + return err + } + err = c.writeAppendedConfig(dest) if err != nil { return err } return nil } + +func (c configurationDirectory) Append(config string) Config { + if c.appendedConfig == nil { + c.appendedConfig = make(map[string]string) + } + + checksum := crc32.Checksum([]byte(config), crc32.IEEETable) + filename := fmt.Sprintf("terraform_plugin_test_%d.tf", checksum) + + c.appendedConfig[filename] = config + return c +} + +func (c configurationDirectory) writeAppendedConfig(dstPath string) error { + for filename, config := range c.appendedConfig { + outFilename := filepath.Join(dstPath, filename) + + err := os.WriteFile(outFilename, []byte(config), 0700) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/teststep/directory_test.go b/internal/teststep/directory_test.go index 0118b91d5..cb88d214f 100644 --- a/internal/teststep/directory_test.go +++ b/internal/teststep/directory_test.go @@ -432,17 +432,17 @@ func TestConfigurationDirectory_Write(t *testing.T) { }, "no-config": { configDirectory: configurationDirectory{ - "testdata/empty_dir", + directory: "testdata/empty_dir", }, }, "dir-single-file": { configDirectory: configurationDirectory{ - "testdata/random", + directory: "testdata/random", }, }, "dir-multiple-files": { configDirectory: configurationDirectory{ - "testdata/random_multiple_files", + directory: "testdata/random_multiple_files", }, }, } @@ -523,17 +523,17 @@ func TestConfigurationDirectory_Write_AbsolutePath(t *testing.T) { }, "no-config": { configDirectory: configurationDirectory{ - "testdata/empty_dir", + directory: "testdata/empty_dir", }, }, "dir-single-file": { configDirectory: configurationDirectory{ - "testdata/random", + directory: "testdata/random", }, }, "dir-multiple-files": { configDirectory: configurationDirectory{ - "testdata/random_multiple_files", + directory: "testdata/random_multiple_files", }, }, } @@ -607,6 +607,81 @@ func TestConfigurationDirectory_Write_AbsolutePath(t *testing.T) { } } +func TestConfigurationDirectory_Write_WithAppendedConfig(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + configDirectory configurationDirectory + expectedError *regexp.Regexp + }{ + "dir-single-file": { + configDirectory: configurationDirectory{ + directory: "testdata/random", + appendedConfig: map[string]string{ + "import.tf": `terraform {\nimport\n{\nto = satellite.the_moon\nid = "moon"\n}\n}\n`, + }, + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + tempDir := t.TempDir() + + err := testCase.configDirectory.Write(context.Background(), tempDir) + if err != nil { + t.Errorf("unexpected error %s", err) + } + + dirEntries, err := os.ReadDir(testCase.configDirectory.directory) + if err != nil { + t.Errorf("error reading directory: %s", err) + } + + tempDirEntries, err := os.ReadDir(tempDir) + + if err != nil { + t.Errorf("error reading temp directory: %s", err) + } + + if len(tempDirEntries)-len(dirEntries) != 1 { + t.Errorf("expected %d dir entries, got %d dir entries", len(dirEntries)+1, tempDirEntries) + } + + for _, entry := range dirEntries { + filename := entry.Name() + expectedContent, err := os.ReadFile(filepath.Join(testCase.configDirectory.directory, filename)) + if err != nil { + t.Errorf("error reading file from config directory %s: %s", filename, err) + } + + content, err := os.ReadFile(filepath.Join(tempDir, filename)) + if err != nil { + t.Errorf("error reading generated file %s: %s", filename, err) + } + + if diff := cmp.Diff(expectedContent, content); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + } + + appendedConfigFiles := testCase.configDirectory.appendedConfig + for filename, expectedContent := range appendedConfigFiles { + content, err := os.ReadFile(filepath.Join(tempDir, filename)) + if err != nil { + t.Errorf("error reading appended config file %s: %s", filename, err) + } + + if diff := cmp.Diff([]byte(expectedContent), content); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + } + }) + } +} + var fileInfoComparer = cmp.Comparer(func(x, y os.FileInfo) bool { if x.Name() != y.Name() { return false diff --git a/internal/teststep/file.go b/internal/teststep/file.go index 6de3f0752..75ee6f7d6 100644 --- a/internal/teststep/file.go +++ b/internal/teststep/file.go @@ -12,7 +12,8 @@ import ( var _ Config = configurationFile{} type configurationFile struct { - file string + file string + appendedConfig string } // HasConfigurationFiles is used during validation to ensure that @@ -84,11 +85,24 @@ func (c configurationFile) Write(ctx context.Context, dest string) error { configFile = filepath.Join(pwd, configFile) } - err := copyFile(configFile, dest) - + destPath, err := copyFile(configFile, dest) if err != nil { return err } + if len(c.appendedConfig) > 0 { + err := appendToFile(destPath, c.appendedConfig) + if err != nil { + return err + } + } + return nil } + +func (c configurationFile) Append(config string) Config { + return configurationFile{ + file: c.file, + appendedConfig: config, + } +} diff --git a/internal/teststep/file_test.go b/internal/teststep/file_test.go index 5d0ebb73f..b6f89bed6 100644 --- a/internal/teststep/file_test.go +++ b/internal/teststep/file_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-testing/config" ) func TestConfigurationFile_HasProviderBlock(t *testing.T) { @@ -432,7 +433,7 @@ func TestConfigurationFile_Write(t *testing.T) { }, "file": { configFile: configurationFile{ - "testdata/random/random.tf", + file: "testdata/random/random.tf", }, }, } @@ -495,7 +496,7 @@ func TestConfigurationFile_Write_AbsolutePath(t *testing.T) { }, "file": { configFile: configurationFile{ - "testdata/random/random.tf", + file: "testdata/random/random.tf", }, }, } @@ -550,3 +551,49 @@ func TestConfigurationFile_Write_AbsolutePath(t *testing.T) { }) } } + +func TestConfigFile_Append(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + filename string + appendContent string + expectedContent string + }{ + "append content to a ConfigFile": { + filename: `testdata/main.tf`, // Contains `// Hello world` + appendContent: `terraform {}`, + expectedContent: "# Copyright (c) HashiCorp, Inc.\n# SPDX-License-Identifier: MPL-2.0\n\n// Hello world" + "\n" + "terraform {}", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + prepareConfigRequest := PrepareConfigurationRequest{ + File: func(config.TestStepConfigRequest) string { + return testCase.filename + }, + } + + teststepConfig := Configuration(prepareConfigRequest.Exec()) + teststepConfig = teststepConfig.Append(testCase.appendContent) + + tempdir := t.TempDir() + if err := teststepConfig.Write(context.Background(), tempdir); err != nil { + t.Fatalf("failed to write file: %s", err) + } + + got, err := os.ReadFile(filepath.Join(tempdir, filepath.Base(testCase.filename))) + if err != nil { + t.Fatalf("failed to read file: %s", err) + } + + gotS := string(got[:]) + if diff := cmp.Diff(testCase.expectedContent, gotS); diff != "" { + t.Errorf("expected %+v, got %+v", testCase.expectedContent, gotS) + } + }) + } +} diff --git a/internal/teststep/string.go b/internal/teststep/string.go index 4143b484d..39028682a 100644 --- a/internal/teststep/string.go +++ b/internal/teststep/string.go @@ -9,6 +9,7 @@ import ( "fmt" "os" "path/filepath" + "strings" ) var _ Config = configurationString{} @@ -59,3 +60,9 @@ func (c configurationString) Write(ctx context.Context, dest string) error { return nil } + +func (c configurationString) Append(config string) Config { + return configurationString{ + raw: strings.Join([]string{c.raw, config}, "\n"), + } +} diff --git a/internal/teststep/testdata/main.tf b/internal/teststep/testdata/main.tf new file mode 100644 index 000000000..ba356816b --- /dev/null +++ b/internal/teststep/testdata/main.tf @@ -0,0 +1,4 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +// Hello world