blob: af3615cfcf90ec5211af143d0fd75786390c4252 [file] [log] [blame]
package planfile
import (
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configload"
"github.com/hashicorp/terraform/internal/depsfile"
"github.com/hashicorp/terraform/internal/getproviders"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/states/statefile"
tfversion "github.com/hashicorp/terraform/version"
)
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{
TerraformVersion: tfversion.SemVer,
Serial: 2,
Lineage: "abc123",
State: states.NewState(),
}
prevStateFileIn := &statefile.File{
TerraformVersion: tfversion.SemVer,
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{
Resources: []*plans.ResourceInstanceChangeSrc{},
Outputs: []*plans.OutputChangeSrc{},
},
DriftedResources: []*plans.ResourceInstanceChangeSrc{},
VariableValues: map[string]plans.DynamicValue{
"foo": plans.DynamicValue([]byte("foo placeholder")),
},
Backend: plans.Backend{
Type: "local",
Config: plans.DynamicValue([]byte("config placeholder")),
Workspace: "default",
},
Checks: &states.CheckResults{},
// 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,
}
locksIn := depsfile.NewLocks()
locksIn.SetProvider(
addrs.NewDefaultProvider("boop"),
getproviders.MustParseVersion("1.0.0"),
getproviders.MustParseVersionConstraints(">= 1.0.0"),
[]getproviders.Hash{
getproviders.MustParseHash("fake:hello"),
},
)
planFn := filepath.Join(t.TempDir(), "tfplan")
err = Create(planFn, CreateArgs{
ConfigSnapshot: snapIn,
PreviousRunStateFile: prevStateFileIn,
StateFile: stateFileIn,
Plan: planIn,
DependencyLocks: locksIn,
})
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)
}
if diff := cmp.Diff(planIn, planOut); diff != "" {
t.Errorf("plan did not survive round-trip\n%s", diff)
}
})
t.Run("ReadStateFile", func(t *testing.T) {
stateFileOut, err := pr.ReadStateFile()
if err != nil {
t.Fatalf("failed to read state: %s", err)
}
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)
}
})
t.Run("ReadConfigSnapshot", func(t *testing.T) {
snapOut, err := pr.ReadConfigSnapshot()
if err != nil {
t.Fatalf("failed to read config snapshot: %s", err)
}
if diff := cmp.Diff(snapIn, snapOut); diff != "" {
t.Errorf("config snapshot did not survive round-trip\n%s", diff)
}
})
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())
}
})
t.Run("ReadDependencyLocks", func(t *testing.T) {
locksOut, diags := pr.ReadDependencyLocks()
if diags.HasErrors() {
t.Fatalf("when reading config: %s", diags.Err())
}
got := locksOut.AllProviders()
want := locksIn.AllProviders()
if diff := cmp.Diff(want, got, cmp.AllowUnexported(depsfile.ProviderLock{})); diff != "" {
t.Errorf("provider locks did not survive round-trip\n%s", diff)
}
})
}