terraform: Diff!
This commit is contained in:
parent
0d9fb53a5a
commit
e904fca3da
2
Makefile
2
Makefile
|
@ -19,7 +19,7 @@ default: test
|
|||
libucl: vendor/libucl/$(LIBUCL_NAME)
|
||||
|
||||
test: libucl
|
||||
go test $(TEST)
|
||||
go test $(TEST) -timeout=5s
|
||||
|
||||
vendor/libucl/libucl.a: vendor/libucl
|
||||
cd vendor/libucl && \
|
||||
|
|
|
@ -1,15 +1,26 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Diff tracks the differences between resources to apply.
|
||||
type Diff struct {
|
||||
resources map[string]map[string]*resourceDiff
|
||||
Resources map[string]map[string]*ResourceAttrDiff
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// resourceDiff is the diff of a single attribute of a resource.
|
||||
func (d *Diff) init() {
|
||||
d.once.Do(func() {
|
||||
d.Resources = make(map[string]map[string]*ResourceAttrDiff)
|
||||
})
|
||||
}
|
||||
|
||||
// ResourceAttrDiff is the diff of a single attribute of a resource.
|
||||
//
|
||||
// This tracks the old value, the new value, and whether the change of this
|
||||
// value actually requires an entirely new resource.
|
||||
type resourceDiff struct {
|
||||
type ResourceAttrDiff struct {
|
||||
Old string
|
||||
New string
|
||||
RequiresNew bool
|
||||
|
|
|
@ -30,14 +30,7 @@ type ResourceProvider interface {
|
|||
|
||||
// ResourceDiff is the diff of a resource from some state to another.
|
||||
type ResourceDiff struct {
|
||||
Attributes map[string]ResourceDiffAttribute
|
||||
}
|
||||
|
||||
// ResourceDiffAttribute is the diff of a single attribute of a resource.
|
||||
type ResourceDiffAttribute struct {
|
||||
Old string
|
||||
New string
|
||||
RequiresNew bool
|
||||
Attributes map[string]*ResourceAttrDiff
|
||||
}
|
||||
|
||||
// ResourceState holds the state of a resource that is used so that
|
||||
|
|
|
@ -10,11 +10,11 @@ type MockResourceProvider struct {
|
|||
ConfigureConfig map[string]interface{}
|
||||
ConfigureReturnWarnings []string
|
||||
ConfigureReturnError error
|
||||
ResourceDiffCalled bool
|
||||
ResourceDiffState ResourceState
|
||||
ResourceDiffDesired map[string]interface{}
|
||||
ResourceDiffReturn ResourceDiff
|
||||
ResourceDiffReturnError error
|
||||
DiffCalled bool
|
||||
DiffState ResourceState
|
||||
DiffDesired map[string]interface{}
|
||||
DiffReturn ResourceDiff
|
||||
DiffReturnError error
|
||||
ResourcesCalled bool
|
||||
ResourcesReturn []ResourceType
|
||||
}
|
||||
|
@ -28,10 +28,10 @@ func (p *MockResourceProvider) Configure(c map[string]interface{}) ([]string, er
|
|||
func (p *MockResourceProvider) Diff(
|
||||
state ResourceState,
|
||||
desired map[string]interface{}) (ResourceDiff, error) {
|
||||
p.ResourceDiffCalled = true
|
||||
p.ResourceDiffState = state
|
||||
p.ResourceDiffDesired = desired
|
||||
return p.ResourceDiffReturn, p.ResourceDiffReturnError
|
||||
p.DiffCalled = true
|
||||
p.DiffState = state
|
||||
p.DiffDesired = desired
|
||||
return p.DiffReturn, p.DiffReturnError
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Resources() []ResourceType {
|
||||
|
|
|
@ -3,6 +3,7 @@ package terraform
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/depgraph"
|
||||
|
@ -124,14 +125,57 @@ func (t *Terraform) Apply(*State, *Diff) (*State, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (t *Terraform) Diff(*State) (*Diff, error) {
|
||||
return nil, nil
|
||||
func (t *Terraform) Diff(s *State) (*Diff, error) {
|
||||
result := new(Diff)
|
||||
err := t.graph.Walk(t.diffWalkFn(s, result))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (t *Terraform) Refresh(*State) (*State, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (t *Terraform) diffWalkFn(
|
||||
state *State, result *Diff) depgraph.WalkFunc {
|
||||
var resultLock sync.Mutex
|
||||
|
||||
return func(n *depgraph.Noun) error {
|
||||
// If it is the root node, ignore
|
||||
if n.Name == config.ResourceGraphRoot {
|
||||
return nil
|
||||
}
|
||||
|
||||
r := n.Meta.(*config.Resource)
|
||||
p := t.mapping[r]
|
||||
if p == nil {
|
||||
panic(fmt.Sprintf("No provider for resource: %s", r.Id()))
|
||||
}
|
||||
|
||||
var rs ResourceState
|
||||
diff, err := p.Diff(rs, r.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If there were no diff items, return right away
|
||||
if len(diff.Attributes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Acquire a lock and modify the resulting diff
|
||||
resultLock.Lock()
|
||||
defer resultLock.Unlock()
|
||||
result.init()
|
||||
result.Resources[r.Id()] = diff.Attributes
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// matchingPrefixes takes a resource type and a set of resource
|
||||
// providers we know about by prefix and returns a list of prefixes
|
||||
// that might be valid for that resource.
|
||||
|
|
|
@ -10,46 +10,6 @@ import (
|
|||
// This is the directory where our test fixtures are.
|
||||
const fixtureDir = "./test-fixtures"
|
||||
|
||||
func testConfig(t *testing.T, name string) *config.Config {
|
||||
c, err := config.Load(filepath.Join(fixtureDir, name, "main.tf"))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func testProviderFunc(n string, rs []string) ResourceProviderFactory {
|
||||
resources := make([]ResourceType, len(rs))
|
||||
for i, v := range rs {
|
||||
resources[i] = ResourceType{
|
||||
Name: v,
|
||||
}
|
||||
}
|
||||
|
||||
return func() (ResourceProvider, error) {
|
||||
result := &MockResourceProvider{
|
||||
Meta: n,
|
||||
ResourcesReturn: resources,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
func testProviderName(p ResourceProvider) string {
|
||||
return p.(*MockResourceProvider).Meta.(string)
|
||||
}
|
||||
|
||||
func testResourceMapping(tf *Terraform) map[string]ResourceProvider {
|
||||
result := make(map[string]ResourceProvider)
|
||||
for resource, provider := range tf.mapping {
|
||||
result[resource.Id()] = provider
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
config := testConfig(t, "new-good")
|
||||
tfConfig := &Config{
|
||||
|
@ -141,3 +101,86 @@ func TestNew_variables(t *testing.T) {
|
|||
t.Fatal("tf should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformDiff(t *testing.T) {
|
||||
tf := testTerraform(t, "diff-good")
|
||||
|
||||
diff, err := tf.Diff(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(diff.Resources) < 2 {
|
||||
t.Fatalf("bad: %#v", diff.Resources)
|
||||
}
|
||||
}
|
||||
|
||||
func testConfig(t *testing.T, name string) *config.Config {
|
||||
c, err := config.Load(filepath.Join(fixtureDir, name, "main.tf"))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func testProviderFunc(n string, rs []string) ResourceProviderFactory {
|
||||
resources := make([]ResourceType, len(rs))
|
||||
for i, v := range rs {
|
||||
resources[i] = ResourceType{
|
||||
Name: v,
|
||||
}
|
||||
}
|
||||
|
||||
return func() (ResourceProvider, error) {
|
||||
var diff ResourceDiff
|
||||
diff.Attributes = map[string]*ResourceAttrDiff{
|
||||
n: &ResourceAttrDiff{
|
||||
Old: "foo",
|
||||
New: "bar",
|
||||
},
|
||||
}
|
||||
|
||||
result := &MockResourceProvider{
|
||||
Meta: n,
|
||||
DiffReturn: diff,
|
||||
ResourcesReturn: resources,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
func testProviderName(p ResourceProvider) string {
|
||||
return p.(*MockResourceProvider).Meta.(string)
|
||||
}
|
||||
|
||||
func testResourceMapping(tf *Terraform) map[string]ResourceProvider {
|
||||
result := make(map[string]ResourceProvider)
|
||||
for resource, provider := range tf.mapping {
|
||||
result[resource.Id()] = provider
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func testTerraform(t *testing.T, name string) *Terraform {
|
||||
config := testConfig(t, name)
|
||||
tfConfig := &Config{
|
||||
Config: config,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFunc("aws", []string{"aws_instance"}),
|
||||
"do": testProviderFunc("do", []string{"do_droplet"}),
|
||||
},
|
||||
}
|
||||
|
||||
tf, err := New(tfConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if tf == nil {
|
||||
t.Fatal("tf should not be nil")
|
||||
}
|
||||
|
||||
return tf
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
resource "aws_instance" "foo" {
|
||||
num = 2
|
||||
}
|
||||
|
||||
resource "aws_instance" "bar" {
|
||||
foo = "${aws_instance.foo.num}"
|
||||
}
|
Loading…
Reference in New Issue