mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-19 02:39:17 -05:00
* Add forked version of `run` logic that's only used if experiments are enabled * Reorder actions in experimental init - load in full config before configuring the backend. * Add getProvidersFromConfig method, initially as an exact copy of getProviders * Make getProvidersFromConfig not use state to get providers * Add `appendLockedDependencies` method to `Meta` to allow multi-phase saving to the dep locks file * Update experimental init to use new getProvidersFromConfig method * Add new getProvidersFromState method that only accepts state information as input for getting providers. Use in experimental init and append values to existing deps lock file * Update messages sent to view about provider download phases * Change init to save updates to the deps lock file only once * Make Terraform output report that a lock file _will_ be made after providers are determined from config * Remove use of `ProviderDownloadOutcome`s * Move repeated code into separate method * Change provider download approach: determine if locks changed at point of attempting to update the lockfile, keep record of incomplete providers inside init command struct * Refactor `mergeLockedDependencies` and update test * Add comments to provider download methods * Fix issue where incorrect message ouput to view when downloading providers * Update `mergeLockedDependencies` method to be more generic * Update `getProvidersFromState` method to receive in-progress config locks and merge those with any locks on file. This allows re-use of providers downloaded by `getProvidersFromConfig` in the same init command * Fix config for `TestInit_stateStoreBlockIsExperimental` * Improve testing of mergeLockedDependencies; state locks are always missing version constraints * Add tests for 2 phase provider download * Add test case to cover use of the `-upgrade` flag * Change the message shown when a provider is reused during the second provider download step. When downloading providers described only in the state then the provider may already be downloaded from a previous init (i.e. is recorded in the deps lock file) or downloaded during step 1 of provider download. The message here needs to cover both potential scenarios. * Update mergeLockedDependencies comment * fix: completely remove use of upgrade flag in getProvidersFromState * Fix: avoid nil pointer errors by returning an empty collection of locks when there is no state * Fix: use state store data only in diagnostic * Change how we make PSS experimental - avoid relying on a package level variable that causes tests to interact. * Remove full-stop in view message, update tests * Update span names to be unique * Re-add lost early returns * Remove unused view messages * Add comments to new view messages
151 lines
5.7 KiB
Go
151 lines
5.7 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package command
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/apparentlymart/go-versions/versions"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/hashicorp/cli"
|
|
tfaddr "github.com/hashicorp/terraform-registry-address"
|
|
"github.com/hashicorp/terraform/internal/depsfile"
|
|
"github.com/hashicorp/terraform/internal/getproviders/providerreqs"
|
|
)
|
|
|
|
// This tests combining locks from config and state. Locks derived from state are always unconstrained, i.e. no version constraint data,
|
|
// so this test
|
|
func Test_mergeLockedDependencies_config_and_state(t *testing.T) {
|
|
|
|
providerA := tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "my-org", "providerA")
|
|
providerB := tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "my-org", "providerB")
|
|
v1_0_0 := providerreqs.MustParseVersion("1.0.0")
|
|
versionConstraintv1, _ := providerreqs.ParseVersionConstraints("1.0.0")
|
|
hashesProviderA := []providerreqs.Hash{providerreqs.MustParseHash("providerA:this-is-providerA")}
|
|
hashesProviderB := []providerreqs.Hash{providerreqs.MustParseHash("providerB:this-is-providerB")}
|
|
|
|
var versionUnconstrained providerreqs.VersionConstraints = nil
|
|
noVersion := versions.Version{}
|
|
|
|
cases := map[string]struct {
|
|
configLocks *depsfile.Locks
|
|
stateLocks *depsfile.Locks
|
|
expectedLocks *depsfile.Locks
|
|
}{
|
|
"no locks when all inputs empty": {
|
|
configLocks: depsfile.NewLocks(),
|
|
stateLocks: depsfile.NewLocks(),
|
|
expectedLocks: depsfile.NewLocks(),
|
|
},
|
|
"when provider only described in config, output locks have matching constraints": {
|
|
configLocks: func() *depsfile.Locks {
|
|
configLocks := depsfile.NewLocks()
|
|
configLocks.SetProvider(providerA, v1_0_0, versionConstraintv1, hashesProviderA)
|
|
return configLocks
|
|
}(),
|
|
stateLocks: depsfile.NewLocks(),
|
|
expectedLocks: func() *depsfile.Locks {
|
|
combinedLocks := depsfile.NewLocks()
|
|
combinedLocks.SetProvider(providerA, v1_0_0, versionConstraintv1, hashesProviderA)
|
|
return combinedLocks
|
|
}(),
|
|
},
|
|
"when provider only described in state, output locks are unconstrained": {
|
|
configLocks: depsfile.NewLocks(),
|
|
stateLocks: func() *depsfile.Locks {
|
|
stateLocks := depsfile.NewLocks()
|
|
stateLocks.SetProvider(providerA, noVersion, versionUnconstrained, hashesProviderA)
|
|
return stateLocks
|
|
}(),
|
|
expectedLocks: func() *depsfile.Locks {
|
|
combinedLocks := depsfile.NewLocks()
|
|
combinedLocks.SetProvider(providerA, noVersion, versionUnconstrained, hashesProviderA)
|
|
return combinedLocks
|
|
}(),
|
|
},
|
|
"different providers present in state and config are combined, with version constraints kept on config providers": {
|
|
configLocks: func() *depsfile.Locks {
|
|
configLocks := depsfile.NewLocks()
|
|
configLocks.SetProvider(providerA, v1_0_0, versionConstraintv1, hashesProviderA)
|
|
return configLocks
|
|
}(),
|
|
stateLocks: func() *depsfile.Locks {
|
|
stateLocks := depsfile.NewLocks()
|
|
|
|
// Imagine that the state locks contain:
|
|
// 1) provider for resources in the config
|
|
stateLocks.SetProvider(providerA, noVersion, versionUnconstrained, hashesProviderA)
|
|
|
|
// 2) also, a provider that's deleted from the config and only present in state
|
|
stateLocks.SetProvider(providerB, noVersion, versionUnconstrained, hashesProviderB)
|
|
|
|
return stateLocks
|
|
}(),
|
|
expectedLocks: func() *depsfile.Locks {
|
|
combinedLocks := depsfile.NewLocks()
|
|
combinedLocks.SetProvider(providerA, v1_0_0, versionConstraintv1, hashesProviderA) // version constraint preserved
|
|
combinedLocks.SetProvider(providerB, noVersion, versionUnconstrained, hashesProviderB) // sourced from state only
|
|
return combinedLocks
|
|
}(),
|
|
},
|
|
}
|
|
|
|
for tn, tc := range cases {
|
|
t.Run(tn, func(t *testing.T) {
|
|
// Use tmp dir as we're creating lock files in the test
|
|
td := t.TempDir()
|
|
t.Chdir(td)
|
|
|
|
ui := new(cli.MockUi)
|
|
view, _ := testView(t)
|
|
|
|
m := Meta{
|
|
Ui: ui,
|
|
View: view,
|
|
}
|
|
|
|
// Code under test - combine deps from state with prior deps from config
|
|
// mergedLocks := m.mergeLockedDependencies(tc.configLocks, tc.stateLocks)
|
|
mergedLocks := m.mergeLockedDependencies(tc.configLocks, tc.stateLocks)
|
|
|
|
// We cannot use (l *depsfile.Locks) Equal here as it doesn't compare version constraints
|
|
// Instead, inspect entries directly
|
|
if len(mergedLocks.AllProviders()) != len(tc.expectedLocks.AllProviders()) {
|
|
t.Fatalf("expected merged dependencies to include %d providers, but got %d:\n %#v",
|
|
len(tc.expectedLocks.AllProviders()),
|
|
len(mergedLocks.AllProviders()),
|
|
mergedLocks.AllProviders(),
|
|
)
|
|
}
|
|
for _, lock := range tc.expectedLocks.AllProviders() {
|
|
match := mergedLocks.Provider(lock.Provider())
|
|
if match == nil {
|
|
t.Fatalf("expected merged dependencies to include provider %s, but it's missing", lock.Provider())
|
|
}
|
|
if len(match.VersionConstraints()) != len(lock.VersionConstraints()) {
|
|
t.Fatalf("detected a problem with version constraints for provider %s, got: %d, want %d",
|
|
lock.Provider(),
|
|
len(match.VersionConstraints()),
|
|
len(lock.VersionConstraints()),
|
|
)
|
|
}
|
|
if len(match.VersionConstraints()) > 0 && len(lock.VersionConstraints()) > 0 {
|
|
gotConstraints := match.VersionConstraints()[0]
|
|
wantConstraints := lock.VersionConstraints()[0]
|
|
|
|
if gotConstraints.Boundary.String() != wantConstraints.Boundary.String() {
|
|
t.Fatalf("expected merged dependencies to include provider %s with version constraint %v, but instead got %v",
|
|
lock.Provider(),
|
|
gotConstraints.Boundary.String(),
|
|
wantConstraints.Boundary.String(),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
if diff := cmp.Diff(tc.expectedLocks, mergedLocks); diff != "" {
|
|
t.Errorf("difference in file contents detected\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|