github.com/opentofu/opentofu@v1.7.1/internal/cloud/e2e/main_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package main
     7  
     8  import (
     9  	"flag"
    10  	"fmt"
    11  	"log"
    12  	"os"
    13  	"os/exec"
    14  	"strings"
    15  	"testing"
    16  
    17  	expect "github.com/Netflix/go-expect"
    18  	tfe "github.com/hashicorp/go-tfe"
    19  	"github.com/opentofu/opentofu/internal/e2e"
    20  	tfversion "github.com/opentofu/opentofu/version"
    21  )
    22  
    23  var tofuBin string
    24  var cliConfigFileEnv string
    25  
    26  var tfeClient *tfe.Client
    27  var tfeHostname string
    28  var tfeToken string
    29  var verboseMode bool
    30  
    31  func TestMain(m *testing.M) {
    32  	teardown := setup()
    33  	code := m.Run()
    34  	teardown()
    35  
    36  	os.Exit(code)
    37  }
    38  
    39  func accTest() bool {
    40  	// TF_ACC is set when we want to run acceptance tests, meaning it relies on
    41  	// network access.
    42  	return os.Getenv("TF_ACC") != ""
    43  }
    44  
    45  func hasHostname() bool {
    46  	return os.Getenv("TFE_HOSTNAME") != ""
    47  }
    48  
    49  func hasToken() bool {
    50  	return os.Getenv("TFE_TOKEN") != ""
    51  }
    52  
    53  func hasRequiredEnvVars() bool {
    54  	return accTest() && hasHostname() && hasToken()
    55  }
    56  
    57  func skipIfMissingEnvVar(t *testing.T) {
    58  	if !hasRequiredEnvVars() {
    59  		t.Skip("Skipping test, required environment variables missing. Use `TF_ACC`, `TFE_HOSTNAME`, `TFE_TOKEN`")
    60  	}
    61  }
    62  
    63  func setup() func() {
    64  	tfOutput := flag.Bool("tfoutput", false, "This flag produces the terraform output from tests.")
    65  	flag.Parse()
    66  	verboseMode = *tfOutput
    67  
    68  	setTfeClient()
    69  	teardown := setupBinary()
    70  
    71  	return func() {
    72  		teardown()
    73  	}
    74  }
    75  func testRunner(t *testing.T, cases testCases, orgCount int, tfEnvFlags ...string) {
    76  	for name, tc := range cases {
    77  		tc := tc // rebind tc into this lexical scope
    78  		t.Run(name, func(subtest *testing.T) {
    79  			subtest.Parallel()
    80  
    81  			orgNames := []string{}
    82  			for i := 0; i < orgCount; i++ {
    83  				organization, cleanup := createOrganization(t)
    84  				t.Cleanup(cleanup)
    85  				orgNames = append(orgNames, organization.Name)
    86  			}
    87  
    88  			exp, err := expect.NewConsole(defaultOpts()...)
    89  			if err != nil {
    90  				subtest.Fatal(err)
    91  			}
    92  			defer exp.Close()
    93  
    94  			tmpDir := t.TempDir()
    95  
    96  			tf := e2e.NewBinary(t, tofuBin, tmpDir)
    97  			tfEnvFlags = append(tfEnvFlags, "TF_LOG=INFO")
    98  			tfEnvFlags = append(tfEnvFlags, cliConfigFileEnv)
    99  			for _, env := range tfEnvFlags {
   100  				tf.AddEnv(env)
   101  			}
   102  
   103  			var orgName string
   104  			for index, op := range tc.operations {
   105  				switch orgCount {
   106  				case 0:
   107  					orgName = ""
   108  				case 1:
   109  					orgName = orgNames[0]
   110  				default:
   111  					orgName = orgNames[index]
   112  				}
   113  
   114  				op.prep(t, orgName, tf.WorkDir())
   115  				for _, tfCmd := range op.commands {
   116  					cmd := tf.Cmd(tfCmd.command...)
   117  					cmd.Stdin = exp.Tty()
   118  					cmd.Stdout = exp.Tty()
   119  					cmd.Stderr = exp.Tty()
   120  
   121  					err = cmd.Start()
   122  					if err != nil {
   123  						subtest.Fatal(err)
   124  					}
   125  
   126  					if tfCmd.expectedCmdOutput != "" {
   127  						got, err := exp.ExpectString(tfCmd.expectedCmdOutput)
   128  						if err != nil {
   129  							subtest.Fatalf("error while waiting for output\nwant: %s\nerror: %s\noutput\n%s", tfCmd.expectedCmdOutput, err, got)
   130  						}
   131  					}
   132  
   133  					lenInput := len(tfCmd.userInput)
   134  					lenInputOutput := len(tfCmd.postInputOutput)
   135  					if lenInput > 0 {
   136  						for i := 0; i < lenInput; i++ {
   137  							input := tfCmd.userInput[i]
   138  							exp.SendLine(input)
   139  							// use the index to find the corresponding
   140  							// output that matches the input.
   141  							if lenInputOutput-1 >= i {
   142  								output := tfCmd.postInputOutput[i]
   143  								_, err := exp.ExpectString(output)
   144  								if err != nil {
   145  									subtest.Fatal(err)
   146  								}
   147  							}
   148  						}
   149  					}
   150  
   151  					err = cmd.Wait()
   152  					if err != nil && !tfCmd.expectError {
   153  						subtest.Fatal(err)
   154  					}
   155  				}
   156  			}
   157  
   158  			if tc.validations != nil {
   159  				tc.validations(t, orgName)
   160  			}
   161  		})
   162  	}
   163  }
   164  
   165  func setTfeClient() {
   166  	tfeHostname = os.Getenv("TFE_HOSTNAME")
   167  	tfeToken = os.Getenv("TFE_TOKEN")
   168  
   169  	cfg := &tfe.Config{
   170  		Address: fmt.Sprintf("https://%s", tfeHostname),
   171  		Token:   tfeToken,
   172  	}
   173  
   174  	if tfeHostname != "" && tfeToken != "" {
   175  		// Create a new TFE client.
   176  		client, err := tfe.NewClient(cfg)
   177  		if err != nil {
   178  			fmt.Printf("Could not create new tfe client: %v\n", err)
   179  			os.Exit(1)
   180  		}
   181  		tfeClient = client
   182  	}
   183  }
   184  
   185  func setupBinary() func() {
   186  	log.Println("Setting up terraform binary")
   187  	tmpTerraformBinaryDir, err := os.MkdirTemp("", "terraform-test")
   188  	if err != nil {
   189  		fmt.Printf("Could not create temp directory: %v\n", err)
   190  		os.Exit(1)
   191  	}
   192  	log.Println(tmpTerraformBinaryDir)
   193  	currentDir, err := os.Getwd()
   194  	defer os.Chdir(currentDir)
   195  	if err != nil {
   196  		fmt.Printf("Could not change directories: %v\n", err)
   197  		os.Exit(1)
   198  	}
   199  	// Getting top level dir
   200  	dirPaths := strings.Split(currentDir, "/")
   201  	log.Println(currentDir)
   202  	topLevel := len(dirPaths) - 3
   203  	topDir := strings.Join(dirPaths[0:topLevel], "/")
   204  
   205  	if err := os.Chdir(topDir); err != nil {
   206  		fmt.Printf("Could not change directories: %v\n", err)
   207  		os.Exit(1)
   208  	}
   209  
   210  	cmd := exec.Command(
   211  		"go",
   212  		"build",
   213  		"-o", tmpTerraformBinaryDir,
   214  		"-ldflags", fmt.Sprintf("-X \"github.com/opentofu/opentofu/version.Prerelease=%s\"", tfversion.Prerelease),
   215  		"./cmd/tofu",
   216  	)
   217  	err = cmd.Run()
   218  	if err != nil {
   219  		fmt.Printf("Could not run exec command: %v\n", err)
   220  		os.Exit(1)
   221  	}
   222  
   223  	credFile := fmt.Sprintf("%s/dev.tfrc", tmpTerraformBinaryDir)
   224  	writeCredRC(credFile)
   225  
   226  	tofuBin = fmt.Sprintf("%s/terraform", tmpTerraformBinaryDir)
   227  	cliConfigFileEnv = fmt.Sprintf("TF_CLI_CONFIG_FILE=%s", credFile)
   228  
   229  	return func() {
   230  		os.RemoveAll(tmpTerraformBinaryDir)
   231  	}
   232  }
   233  
   234  func writeCredRC(file string) {
   235  	creds := credentialBlock()
   236  	f, err := os.Create(file)
   237  	if err != nil {
   238  		fmt.Printf("Could not create file: %v\n", err)
   239  		os.Exit(1)
   240  	}
   241  	_, err = f.WriteString(creds)
   242  	if err != nil {
   243  		fmt.Printf("Could not write credentials: %v\n", err)
   244  		os.Exit(1)
   245  	}
   246  	f.Close()
   247  }
   248  
   249  func credentialBlock() string {
   250  	return fmt.Sprintf(`
   251  credentials "%s" {
   252    token = "%s"
   253  }`, tfeHostname, tfeToken)
   254  }