Skip to content

Commit

Permalink
Allow terraform init when only test files are present in directory (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
dsa0x authored Feb 6, 2025
1 parent 86295f5 commit 7f29df9
Show file tree
Hide file tree
Showing 15 changed files with 172 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changes/unreleased/ENHANCEMENTS-20250205-104144.yaml
Original file line number Diff line number Diff line change
@@ -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"
4 changes: 2 additions & 2 deletions internal/command/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
57 changes: 57 additions & 0 deletions internal/command/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"log"
"os"
"path/filepath"
"regexp"
"strings"
"testing"

Expand All @@ -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"
Expand All @@ -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()
Expand All @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion internal/command/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions internal/command/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
variable "sample" {
type = bool
default = true
}

output "name" {
value = var.sample
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
run "foo" {
module {
source = "./fixtures"
}
assert {
condition = output.name == true
error_message = "foo"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
variable "sample" {
type = bool
default = true
}

output "name" {
value = var.sample
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
run "foo" {
module {
source = "./fixtures"
}
assert {
condition = output.name == true
error_message = "foo"
}
}
8 changes: 4 additions & 4 deletions internal/configs/parser_config_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
31 changes: 27 additions & 4 deletions internal/configs/parser_config_dir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -344,12 +344,35 @@ 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)
}
if !val {
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")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
variable "sample" {
type = bool
default = true
}

output "name" {
value = var.sample
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
run "foo" {
module {
source = "./fixtures"
}
assert {
condition = output.name == true
error_message = "foo"
}
}
8 changes: 8 additions & 0 deletions internal/configs/testdata/only-test-files/fixtures/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
variable "sample" {
type = bool
default = true
}

output "name" {
value = var.sample
}
9 changes: 9 additions & 0 deletions internal/configs/testdata/only-test-files/main.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
run "foo" {
module {
source = "./fixtures"
}
assert {
condition = output.name == true
error_message = "foo"
}
}

0 comments on commit 7f29df9

Please sign in to comment.