mirror of
https://github.com/hashicorp/terraform.git
synced 2026-05-28 04:03:27 -04:00
Allow terraform init when only test files are present in directory (#36429)
This commit is contained in:
parent
86295f518c
commit
7f29df96a0
15 changed files with 172 additions and 11 deletions
5
.changes/unreleased/ENHANCEMENTS-20250205-104144.yaml
Normal file
5
.changes/unreleased/ENHANCEMENTS-20250205-104144.yaml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
kind: ENHANCEMENTS
|
||||
body: Allow terraform init when tests are present but no configuration files are directly inside the current directory
|
||||
time: 2025-02-05T10:41:44.663251+01:00
|
||||
custom:
|
||||
Issue: "35040"
|
||||
|
|
@ -108,7 +108,7 @@ func (c *InitCommand) Run(args []string) int {
|
|||
if initArgs.FromModule != "" {
|
||||
src := initArgs.FromModule
|
||||
|
||||
empty, err := configs.IsEmptyDir(path)
|
||||
empty, err := configs.IsEmptyDir(path, initArgs.TestsDirectory)
|
||||
if err != nil {
|
||||
diags = diags.Append(fmt.Errorf("Error validating destination directory: %s", err))
|
||||
view.Diagnostics(diags)
|
||||
|
|
@ -148,7 +148,7 @@ func (c *InitCommand) Run(args []string) int {
|
|||
|
||||
// If our directory is empty, then we're done. We can't get or set up
|
||||
// the backend with an empty directory.
|
||||
empty, err := configs.IsEmptyDir(path)
|
||||
empty, err := configs.IsEmptyDir(path, initArgs.TestsDirectory)
|
||||
if err != nil {
|
||||
diags = diags.Append(fmt.Errorf("Error checking configuration: %s", err))
|
||||
view.Diagnostics(diags)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -20,6 +21,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||
"github.com/hashicorp/terraform/internal/command/views"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/depsfile"
|
||||
|
|
@ -32,6 +34,25 @@ import (
|
|||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
)
|
||||
|
||||
// cleanString removes newlines, and redundant spaces.
|
||||
func cleanString(s string) string {
|
||||
// Replace newlines with a single space.
|
||||
s = strings.ReplaceAll(s, "\n", " ")
|
||||
|
||||
// Remove other special characters like \r, \t
|
||||
s = strings.ReplaceAll(s, "\r", "")
|
||||
s = strings.ReplaceAll(s, "\t", "")
|
||||
|
||||
// Replace multiple spaces with a single space.
|
||||
spaceRegex := regexp.MustCompile(`\s+`)
|
||||
s = spaceRegex.ReplaceAllString(s, " ")
|
||||
|
||||
// Trim any leading or trailing spaces.
|
||||
s = strings.TrimSpace(s)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func TestInit_empty(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := t.TempDir()
|
||||
|
|
@ -52,6 +73,42 @@ func TestInit_empty(t *testing.T) {
|
|||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: \n%s", done(t).All())
|
||||
}
|
||||
exp := views.MessageRegistry[views.OutputInitEmptyMessage].JSONValue
|
||||
actual := cleanString(done(t).All())
|
||||
if !strings.Contains(actual, cleanString(exp)) {
|
||||
t.Fatalf("expected output to be %q\n, got %q", exp, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit_only_test_files(t *testing.T) {
|
||||
// Create a temporary working directory that has only test files and no tf configuration
|
||||
td := t.TempDir()
|
||||
os.MkdirAll(td, 0755)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
if _, err := os.Create("main.tftest.hcl"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
view, done := testView(t)
|
||||
c := &InitCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: \n%s", done(t).All())
|
||||
}
|
||||
exp := views.MessageRegistry[views.OutputInitSuccessCLIMessage].JSONValue
|
||||
actual := cleanString(done(t).All())
|
||||
if !strings.Contains(actual, cleanString(exp)) {
|
||||
t.Fatalf("expected output to be %q\n, got %q", exp, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit_multipleArgs(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func (c *ProvidersCommand) Run(args []string) int {
|
|||
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
empty, err := configs.IsEmptyDir(configPath)
|
||||
empty, err := configs.IsEmptyDir(configPath, testsDirectory)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,14 @@ func TestTest_Runs(t *testing.T) {
|
|||
expectedOut: []string{"1 passed, 0 failed."},
|
||||
code: 0,
|
||||
},
|
||||
"top-dir-only-test-files": {
|
||||
expectedOut: []string{"1 passed, 0 failed."},
|
||||
code: 0,
|
||||
},
|
||||
"top-dir-only-nested-test-files": {
|
||||
expectedOut: []string{"1 passed, 0 failed."},
|
||||
code: 0,
|
||||
},
|
||||
"simple_pass_nested": {
|
||||
expectedOut: []string{"1 passed, 0 failed."},
|
||||
code: 0,
|
||||
|
|
|
|||
8
internal/command/testdata/test/top-dir-only-nested-test-files/fixtures/main.tf
vendored
Normal file
8
internal/command/testdata/test/top-dir-only-nested-test-files/fixtures/main.tf
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
variable "sample" {
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
output "name" {
|
||||
value = var.sample
|
||||
}
|
||||
9
internal/command/testdata/test/top-dir-only-nested-test-files/tests/main.tftest.hcl
vendored
Normal file
9
internal/command/testdata/test/top-dir-only-nested-test-files/tests/main.tftest.hcl
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
run "foo" {
|
||||
module {
|
||||
source = "./fixtures"
|
||||
}
|
||||
assert {
|
||||
condition = output.name == true
|
||||
error_message = "foo"
|
||||
}
|
||||
}
|
||||
8
internal/command/testdata/test/top-dir-only-test-files/fixtures/main.tf
vendored
Normal file
8
internal/command/testdata/test/top-dir-only-test-files/fixtures/main.tf
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
variable "sample" {
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
output "name" {
|
||||
value = var.sample
|
||||
}
|
||||
9
internal/command/testdata/test/top-dir-only-test-files/main.tftest.hcl
vendored
Normal file
9
internal/command/testdata/test/top-dir-only-test-files/main.tftest.hcl
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
run "foo" {
|
||||
module {
|
||||
source = "./fixtures"
|
||||
}
|
||||
assert {
|
||||
condition = output.name == true
|
||||
error_message = "foo"
|
||||
}
|
||||
}
|
||||
|
|
@ -324,21 +324,21 @@ func IsIgnoredFile(name string) bool {
|
|||
}
|
||||
|
||||
// IsEmptyDir returns true if the given filesystem path contains no Terraform
|
||||
// configuration files.
|
||||
// configuration or test files.
|
||||
//
|
||||
// Unlike the methods of the Parser type, this function always consults the
|
||||
// real filesystem, and thus it isn't appropriate to use when working with
|
||||
// configuration loaded from a plan file.
|
||||
func IsEmptyDir(path string) (bool, error) {
|
||||
func IsEmptyDir(path, testDir string) (bool, error) {
|
||||
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
p := NewParser(nil)
|
||||
fs, os, _, diags := p.dirFiles(path, "")
|
||||
fs, os, tests, diags := p.dirFiles(path, testDir)
|
||||
if diags.HasErrors() {
|
||||
return false, diags
|
||||
}
|
||||
|
||||
return len(fs) == 0 && len(os) == 0, nil
|
||||
return len(fs) == 0 && len(os) == 0 && len(tests) == 0, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ func TestParserLoadConfigDirFailure(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIsEmptyDir(t *testing.T) {
|
||||
val, err := IsEmptyDir(filepath.Join("testdata", "valid-files"))
|
||||
val, err := IsEmptyDir(filepath.Join("testdata", "valid-files"), "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
@ -335,7 +335,7 @@ func TestIsEmptyDir(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIsEmptyDir_noExist(t *testing.T) {
|
||||
val, err := IsEmptyDir(filepath.Join("testdata", "nopenopenope"))
|
||||
val, err := IsEmptyDir(filepath.Join("testdata", "nopenopenope"), "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
@ -344,8 +344,8 @@ func TestIsEmptyDir_noExist(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestIsEmptyDir_noConfigs(t *testing.T) {
|
||||
val, err := IsEmptyDir(filepath.Join("testdata", "dir-empty"))
|
||||
func TestIsEmptyDir_noConfigsAndTests(t *testing.T) {
|
||||
val, err := IsEmptyDir(filepath.Join("testdata", "dir-empty"), "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
|
@ -353,3 +353,26 @@ func TestIsEmptyDir_noConfigs(t *testing.T) {
|
|||
t.Fatal("should be empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmptyDir_noConfigsButHasTests(t *testing.T) {
|
||||
// The top directory has no configs, but it contains test files
|
||||
val, err := IsEmptyDir(filepath.Join("testdata", "only-test-files"), "tests")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if val {
|
||||
t.Fatal("should not be empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmptyDir_nestedTestsOnly(t *testing.T) {
|
||||
// The top directory has no configs and no test files, but the nested
|
||||
// directory has test files
|
||||
val, err := IsEmptyDir(filepath.Join("testdata", "only-nested-test-files"), "tests")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if val {
|
||||
t.Fatal("should not be empty")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8
internal/configs/testdata/only-nested-test-files/fixtures/main.tf
vendored
Normal file
8
internal/configs/testdata/only-nested-test-files/fixtures/main.tf
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
variable "sample" {
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
output "name" {
|
||||
value = var.sample
|
||||
}
|
||||
9
internal/configs/testdata/only-nested-test-files/tests/main.tftest.hcl
vendored
Normal file
9
internal/configs/testdata/only-nested-test-files/tests/main.tftest.hcl
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
run "foo" {
|
||||
module {
|
||||
source = "./fixtures"
|
||||
}
|
||||
assert {
|
||||
condition = output.name == true
|
||||
error_message = "foo"
|
||||
}
|
||||
}
|
||||
8
internal/configs/testdata/only-test-files/fixtures/main.tf
vendored
Normal file
8
internal/configs/testdata/only-test-files/fixtures/main.tf
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
variable "sample" {
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
output "name" {
|
||||
value = var.sample
|
||||
}
|
||||
9
internal/configs/testdata/only-test-files/main.tftest.hcl
vendored
Normal file
9
internal/configs/testdata/only-test-files/main.tftest.hcl
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
run "foo" {
|
||||
module {
|
||||
source = "./fixtures"
|
||||
}
|
||||
assert {
|
||||
condition = output.name == true
|
||||
error_message = "foo"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue