github.com/opentofu/opentofu@v1.7.1/internal/command/providers_lock_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 command
     7  
     8  import (
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/mitchellh/cli"
    17  
    18  	"github.com/opentofu/opentofu/internal/addrs"
    19  	"github.com/opentofu/opentofu/internal/depsfile"
    20  	"github.com/opentofu/opentofu/internal/getproviders"
    21  )
    22  
    23  func TestProvidersLock(t *testing.T) {
    24  	t.Run("noop", func(t *testing.T) {
    25  		// in the most basic case, running providers lock in a directory with no configuration at all should succeed.
    26  		// create an empty working directory
    27  		td := t.TempDir()
    28  		os.MkdirAll(td, 0755)
    29  		defer testChdir(t, td)()
    30  
    31  		ui := new(cli.MockUi)
    32  		c := &ProvidersLockCommand{
    33  			Meta: Meta{
    34  				Ui: ui,
    35  			},
    36  		}
    37  		code := c.Run([]string{})
    38  		if code != 0 {
    39  			t.Fatalf("wrong exit code; expected 0, got %d", code)
    40  		}
    41  	})
    42  
    43  	// This test depends on the -fs-mirror argument, so we always know what results to expect
    44  	t.Run("basic", func(t *testing.T) {
    45  		testDirectory := "providers-lock/basic"
    46  		expected := `# This file is maintained automatically by "tofu init".
    47  # Manual edits may be lost in future updates.
    48  
    49  provider "registry.opentofu.org/hashicorp/test" {
    50    version = "1.0.0"
    51    hashes = [
    52      "h1:7MjN4eFisdTv4tlhXH5hL4QQd39Jy4baPhFxwAd/EFE=",
    53    ]
    54  }
    55  `
    56  		runProviderLockGenericTest(t, testDirectory, expected)
    57  	})
    58  
    59  	// This test depends on the -fs-mirror argument, so we always know what results to expect
    60  	t.Run("append", func(t *testing.T) {
    61  		testDirectory := "providers-lock/append"
    62  		expected := `# This file is maintained automatically by "tofu init".
    63  # Manual edits may be lost in future updates.
    64  
    65  provider "registry.opentofu.org/hashicorp/test" {
    66    version = "1.0.0"
    67    hashes = [
    68      "h1:7MjN4eFisdTv4tlhXH5hL4QQd39Jy4baPhFxwAd/EFE=",
    69      "h1:invalid",
    70    ]
    71  }
    72  `
    73  		runProviderLockGenericTest(t, testDirectory, expected)
    74  	})
    75  }
    76  
    77  func runProviderLockGenericTest(t *testing.T, testDirectory, expected string) {
    78  	td := t.TempDir()
    79  	testCopyDir(t, testFixturePath(testDirectory), td)
    80  	defer testChdir(t, td)()
    81  
    82  	// Our fixture dir has a generic os_arch dir, which we need to customize
    83  	// to the actual OS/arch where this test is running in order to get the
    84  	// desired result.
    85  	fixtMachineDir := filepath.Join(td, "fs-mirror/registry.opentofu.org/hashicorp/test/1.0.0/os_arch")
    86  	wantMachineDir := filepath.Join(td, "fs-mirror/registry.opentofu.org/hashicorp/test/1.0.0/", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH))
    87  	err := os.Rename(fixtMachineDir, wantMachineDir)
    88  	if err != nil {
    89  		t.Fatalf("unexpected error: %s", err)
    90  	}
    91  
    92  	p := testProvider()
    93  	ui := new(cli.MockUi)
    94  	c := &ProvidersLockCommand{
    95  		Meta: Meta{
    96  			Ui:               ui,
    97  			testingOverrides: metaOverridesForProvider(p),
    98  		},
    99  	}
   100  
   101  	args := []string{"-fs-mirror=fs-mirror"}
   102  	code := c.Run(args)
   103  	if code != 0 {
   104  		t.Fatalf("wrong exit code; expected 0, got %d", code)
   105  	}
   106  
   107  	lockfile, err := os.ReadFile(".terraform.lock.hcl")
   108  	if err != nil {
   109  		t.Fatal("error reading lockfile")
   110  	}
   111  
   112  	if string(lockfile) != expected {
   113  		t.Fatalf("wrong lockfile content")
   114  	}
   115  }
   116  
   117  func TestProvidersLock_args(t *testing.T) {
   118  
   119  	t.Run("mirror collision", func(t *testing.T) {
   120  		ui := new(cli.MockUi)
   121  		c := &ProvidersLockCommand{
   122  			Meta: Meta{
   123  				Ui: ui,
   124  			},
   125  		}
   126  
   127  		// only one of these arguments can be used at a time
   128  		args := []string{
   129  			"-fs-mirror=/foo/",
   130  			"-net-mirror=www.foo.com",
   131  		}
   132  		code := c.Run(args)
   133  
   134  		if code != 1 {
   135  			t.Fatalf("wrong exit code; expected 1, got %d", code)
   136  		}
   137  		output := ui.ErrorWriter.String()
   138  		if !strings.Contains(output, "The -fs-mirror and -net-mirror command line options are mutually-exclusive.") {
   139  			t.Fatalf("missing expected error message: %s", output)
   140  		}
   141  	})
   142  
   143  	t.Run("invalid platform", func(t *testing.T) {
   144  		ui := new(cli.MockUi)
   145  		c := &ProvidersLockCommand{
   146  			Meta: Meta{
   147  				Ui: ui,
   148  			},
   149  		}
   150  
   151  		// not a valid platform
   152  		args := []string{"-platform=arbitrary_nonsense_that_isnt_valid"}
   153  		code := c.Run(args)
   154  
   155  		if code != 1 {
   156  			t.Fatalf("wrong exit code; expected 1, got %d", code)
   157  		}
   158  		output := ui.ErrorWriter.String()
   159  		if !strings.Contains(output, "must be two words separated by an underscore.") {
   160  			t.Fatalf("missing expected error message: %s", output)
   161  		}
   162  	})
   163  
   164  	t.Run("invalid provider argument", func(t *testing.T) {
   165  		ui := new(cli.MockUi)
   166  		c := &ProvidersLockCommand{
   167  			Meta: Meta{
   168  				Ui: ui,
   169  			},
   170  		}
   171  
   172  		// There is no configuration, so it's not valid to use any provider argument
   173  		args := []string{"hashicorp/random"}
   174  		code := c.Run(args)
   175  
   176  		if code != 1 {
   177  			t.Fatalf("wrong exit code; expected 1, got %d", code)
   178  		}
   179  		output := ui.ErrorWriter.String()
   180  		if !strings.Contains(output, "The provider registry.opentofu.org/hashicorp/random is not required by the\ncurrent configuration.") {
   181  			t.Fatalf("missing expected error message: %s", output)
   182  		}
   183  	})
   184  }
   185  
   186  func TestProvidersLockCalculateChangeType(t *testing.T) {
   187  	provider := addrs.NewDefaultProvider("provider")
   188  	v2 := getproviders.MustParseVersion("2.0.0")
   189  	v2EqConstraints := getproviders.MustParseVersionConstraints("2.0.0")
   190  
   191  	t.Run("oldLock == nil", func(t *testing.T) {
   192  		platformLock := depsfile.NewProviderLock(provider, v2, v2EqConstraints, []getproviders.Hash{
   193  			"9r3i9a9QmASqMnQM",
   194  			"K43RHM2klOoywtyW",
   195  			"swJPXfuCNhJsTM5c",
   196  		})
   197  
   198  		if ct := providersLockCalculateChangeType(nil, platformLock); ct != providersLockChangeTypeNewProvider {
   199  			t.Fatalf("output was %s but should be %s", ct, providersLockChangeTypeNewProvider)
   200  		}
   201  	})
   202  
   203  	t.Run("oldLock == platformLock", func(t *testing.T) {
   204  		platformLock := depsfile.NewProviderLock(provider, v2, v2EqConstraints, []getproviders.Hash{
   205  			"9r3i9a9QmASqMnQM",
   206  			"K43RHM2klOoywtyW",
   207  			"swJPXfuCNhJsTM5c",
   208  		})
   209  
   210  		oldLock := depsfile.NewProviderLock(provider, v2, v2EqConstraints, []getproviders.Hash{
   211  			"9r3i9a9QmASqMnQM",
   212  			"K43RHM2klOoywtyW",
   213  			"swJPXfuCNhJsTM5c",
   214  		})
   215  
   216  		if ct := providersLockCalculateChangeType(oldLock, platformLock); ct != providersLockChangeTypeNoChange {
   217  			t.Fatalf("output was %s but should be %s", ct, providersLockChangeTypeNoChange)
   218  		}
   219  	})
   220  
   221  	t.Run("oldLock > platformLock", func(t *testing.T) {
   222  		platformLock := depsfile.NewProviderLock(provider, v2, v2EqConstraints, []getproviders.Hash{
   223  			"9r3i9a9QmASqMnQM",
   224  			"K43RHM2klOoywtyW",
   225  			"swJPXfuCNhJsTM5c",
   226  		})
   227  
   228  		oldLock := depsfile.NewProviderLock(provider, v2, v2EqConstraints, []getproviders.Hash{
   229  			"9r3i9a9QmASqMnQM",
   230  			"1ZAChGWUMWn4zmIk",
   231  			"K43RHM2klOoywtyW",
   232  			"HWjRvIuWZ1LVatnc",
   233  			"swJPXfuCNhJsTM5c",
   234  			"KwhJK4p/U2dqbKhI",
   235  		})
   236  
   237  		if ct := providersLockCalculateChangeType(oldLock, platformLock); ct != providersLockChangeTypeNoChange {
   238  			t.Fatalf("output was %s but should be %s", ct, providersLockChangeTypeNoChange)
   239  		}
   240  	})
   241  
   242  	t.Run("oldLock < platformLock", func(t *testing.T) {
   243  		platformLock := depsfile.NewProviderLock(provider, v2, v2EqConstraints, []getproviders.Hash{
   244  			"9r3i9a9QmASqMnQM",
   245  			"1ZAChGWUMWn4zmIk",
   246  			"K43RHM2klOoywtyW",
   247  			"HWjRvIuWZ1LVatnc",
   248  			"swJPXfuCNhJsTM5c",
   249  			"KwhJK4p/U2dqbKhI",
   250  		})
   251  
   252  		oldLock := depsfile.NewProviderLock(provider, v2, v2EqConstraints, []getproviders.Hash{
   253  			"9r3i9a9QmASqMnQM",
   254  			"K43RHM2klOoywtyW",
   255  			"swJPXfuCNhJsTM5c",
   256  		})
   257  
   258  		if ct := providersLockCalculateChangeType(oldLock, platformLock); ct != providersLockChangeTypeNewHashes {
   259  			t.Fatalf("output was %s but should be %s", ct, providersLockChangeTypeNoChange)
   260  		}
   261  	})
   262  }