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 }