2018-06-20 01:30:59 +02:00
|
|
|
package planfile
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io/ioutil"
|
|
|
|
"path/filepath"
|
|
|
|
"testing"
|
|
|
|
|
2021-05-05 00:59:58 +02:00
|
|
|
"github.com/google/go-cmp/cmp"
|
2018-06-20 01:30:59 +02:00
|
|
|
|
2021-05-17 21:17:09 +02:00
|
|
|
"github.com/hashicorp/terraform/internal/configs/configload"
|
terraform: Ugly huge change to weave in new State and Plan types
Due to how often the state and plan types are referenced throughout
Terraform, there isn't a great way to switch them out gradually. As a
consequence, this huge commit gets us from the old world to a _compilable_
new world, but still has a large number of known test failures due to
key functionality being stubbed out.
The stubs here are for anything that interacts with providers, since we
now need to do the follow-up work to similarly replace the old
terraform.ResourceProvider interface with its replacement in the new
"providers" package. That work, along with work to fix the remaining
failing tests, will follow in subsequent commits.
The aim here was to replace all references to terraform.State and its
downstream types with states.State, terraform.Plan with plans.Plan,
state.State with statemgr.State, and switch to the new implementations of
the state and plan file formats. However, due to the number of times those
types are used, this also ended up affecting numerous other parts of core
such as terraform.Hook, the backend.Backend interface, and most of the CLI
commands.
Just as with 5861dbf3fc49b19587a31816eb06f511ab861bb4 before, I apologize
in advance to the person who inevitably just found this huge commit while
spelunking through the commit history.
2018-08-14 23:24:45 +02:00
|
|
|
"github.com/hashicorp/terraform/plans"
|
2018-06-20 01:30:59 +02:00
|
|
|
"github.com/hashicorp/terraform/states"
|
|
|
|
"github.com/hashicorp/terraform/states/statefile"
|
2018-11-17 03:16:06 +01:00
|
|
|
tfversion "github.com/hashicorp/terraform/version"
|
2018-06-20 01:30:59 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestRoundtrip(t *testing.T) {
|
|
|
|
fixtureDir := filepath.Join("testdata", "test-config")
|
|
|
|
loader, err := configload.NewLoader(&configload.Config{
|
|
|
|
ModulesDir: filepath.Join(fixtureDir, ".terraform", "modules"),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, snapIn, diags := loader.LoadConfigWithSnapshot(fixtureDir)
|
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Fatal(diags.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Just a minimal state file so we can test that it comes out again at all.
|
|
|
|
// We don't need to test the entire thing because the state file
|
|
|
|
// serialization is already tested in its own package.
|
|
|
|
stateFileIn := &statefile.File{
|
2021-05-05 00:59:58 +02:00
|
|
|
TerraformVersion: tfversion.SemVer,
|
|
|
|
Serial: 2,
|
|
|
|
Lineage: "abc123",
|
|
|
|
State: states.NewState(),
|
|
|
|
}
|
|
|
|
prevStateFileIn := &statefile.File{
|
2018-11-17 03:16:06 +01:00
|
|
|
TerraformVersion: tfversion.SemVer,
|
2018-06-20 01:30:59 +02:00
|
|
|
Serial: 1,
|
|
|
|
Lineage: "abc123",
|
|
|
|
State: states.NewState(),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Minimal plan too, since the serialization of the tfplan portion of the
|
|
|
|
// file is tested more fully in tfplan_test.go .
|
|
|
|
planIn := &plans.Plan{
|
|
|
|
Changes: &plans.Changes{
|
2018-09-11 01:26:55 +02:00
|
|
|
Resources: []*plans.ResourceInstanceChangeSrc{},
|
|
|
|
Outputs: []*plans.OutputChangeSrc{},
|
2018-06-20 01:30:59 +02:00
|
|
|
},
|
|
|
|
ProviderSHA256s: map[string][]byte{},
|
|
|
|
VariableValues: map[string]plans.DynamicValue{
|
|
|
|
"foo": plans.DynamicValue([]byte("foo placeholder")),
|
|
|
|
},
|
2018-07-05 22:21:38 +02:00
|
|
|
Backend: plans.Backend{
|
|
|
|
Type: "local",
|
|
|
|
Config: plans.DynamicValue([]byte("config placeholder")),
|
|
|
|
Workspace: "default",
|
|
|
|
},
|
2021-05-07 19:18:59 +02:00
|
|
|
|
|
|
|
// Due to some historical oddities in how we've changed modelling over
|
|
|
|
// time, we also include the states (without the corresponding file
|
|
|
|
// headers) in the plans.Plan object. This is currently ignored by
|
|
|
|
// Create but will be returned by ReadPlan and so we need to include
|
|
|
|
// it here so that we'll get a match when we compare input and output
|
|
|
|
// below.
|
|
|
|
PrevRunState: prevStateFileIn.State,
|
|
|
|
PriorState: stateFileIn.State,
|
2018-06-20 01:30:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
workDir, err := ioutil.TempDir("", "tf-planfile")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
planFn := filepath.Join(workDir, "tfplan")
|
|
|
|
|
2021-05-05 00:59:58 +02:00
|
|
|
err = Create(planFn, snapIn, prevStateFileIn, stateFileIn, planIn)
|
2018-06-20 01:30:59 +02:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to create plan file: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
pr, err := Open(planFn)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to open plan file for reading: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run("ReadPlan", func(t *testing.T) {
|
|
|
|
planOut, err := pr.ReadPlan()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to read plan: %s", err)
|
|
|
|
}
|
2021-05-05 00:59:58 +02:00
|
|
|
if diff := cmp.Diff(planIn, planOut); diff != "" {
|
|
|
|
t.Errorf("plan did not survive round-trip\n%s", diff)
|
2018-06-20 01:30:59 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("ReadStateFile", func(t *testing.T) {
|
|
|
|
stateFileOut, err := pr.ReadStateFile()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to read state: %s", err)
|
|
|
|
}
|
2021-05-05 00:59:58 +02:00
|
|
|
if diff := cmp.Diff(stateFileIn, stateFileOut); diff != "" {
|
|
|
|
t.Errorf("state file did not survive round-trip\n%s", diff)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("ReadPrevStateFile", func(t *testing.T) {
|
|
|
|
prevStateFileOut, err := pr.ReadPrevStateFile()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to read state: %s", err)
|
|
|
|
}
|
|
|
|
if diff := cmp.Diff(prevStateFileIn, prevStateFileOut); diff != "" {
|
|
|
|
t.Errorf("state file did not survive round-trip\n%s", diff)
|
2018-06-20 01:30:59 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("ReadConfigSnapshot", func(t *testing.T) {
|
|
|
|
snapOut, err := pr.ReadConfigSnapshot()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to read config snapshot: %s", err)
|
|
|
|
}
|
2021-05-05 00:59:58 +02:00
|
|
|
if diff := cmp.Diff(snapIn, snapOut); diff != "" {
|
|
|
|
t.Errorf("config snapshot did not survive round-trip\n%s", diff)
|
2018-06-20 01:30:59 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("ReadConfig", func(t *testing.T) {
|
|
|
|
// Reading from snapshots is tested in the configload package, so
|
|
|
|
// here we'll just test that we can successfully do it, to see if the
|
|
|
|
// glue code in _this_ package is correct.
|
|
|
|
_, diags := pr.ReadConfig()
|
|
|
|
if diags.HasErrors() {
|
|
|
|
t.Errorf("when reading config: %s", diags.Err())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|