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)
|
libucl: vendor/libucl/$(LIBUCL_NAME)
|
||||||
|
|
||||||
test: libucl
|
test: libucl
|
||||||
go test $(TEST)
|
go test $(TEST) -timeout=5s
|
||||||
|
|
||||||
vendor/libucl/libucl.a: vendor/libucl
|
vendor/libucl/libucl.a: vendor/libucl
|
||||||
cd vendor/libucl && \
|
cd vendor/libucl && \
|
||||||
|
|
|
@ -1,15 +1,26 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
// Diff tracks the differences between resources to apply.
|
// Diff tracks the differences between resources to apply.
|
||||||
type Diff struct {
|
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
|
// This tracks the old value, the new value, and whether the change of this
|
||||||
// value actually requires an entirely new resource.
|
// value actually requires an entirely new resource.
|
||||||
type resourceDiff struct {
|
type ResourceAttrDiff struct {
|
||||||
Old string
|
Old string
|
||||||
New string
|
New string
|
||||||
RequiresNew bool
|
RequiresNew bool
|
||||||
|
|
|
@ -30,14 +30,7 @@ type ResourceProvider interface {
|
||||||
|
|
||||||
// ResourceDiff is the diff of a resource from some state to another.
|
// ResourceDiff is the diff of a resource from some state to another.
|
||||||
type ResourceDiff struct {
|
type ResourceDiff struct {
|
||||||
Attributes map[string]ResourceDiffAttribute
|
Attributes map[string]*ResourceAttrDiff
|
||||||
}
|
|
||||||
|
|
||||||
// ResourceDiffAttribute is the diff of a single attribute of a resource.
|
|
||||||
type ResourceDiffAttribute struct {
|
|
||||||
Old string
|
|
||||||
New string
|
|
||||||
RequiresNew bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResourceState holds the state of a resource that is used so that
|
// ResourceState holds the state of a resource that is used so that
|
||||||
|
|
|
@ -10,11 +10,11 @@ type MockResourceProvider struct {
|
||||||
ConfigureConfig map[string]interface{}
|
ConfigureConfig map[string]interface{}
|
||||||
ConfigureReturnWarnings []string
|
ConfigureReturnWarnings []string
|
||||||
ConfigureReturnError error
|
ConfigureReturnError error
|
||||||
ResourceDiffCalled bool
|
DiffCalled bool
|
||||||
ResourceDiffState ResourceState
|
DiffState ResourceState
|
||||||
ResourceDiffDesired map[string]interface{}
|
DiffDesired map[string]interface{}
|
||||||
ResourceDiffReturn ResourceDiff
|
DiffReturn ResourceDiff
|
||||||
ResourceDiffReturnError error
|
DiffReturnError error
|
||||||
ResourcesCalled bool
|
ResourcesCalled bool
|
||||||
ResourcesReturn []ResourceType
|
ResourcesReturn []ResourceType
|
||||||
}
|
}
|
||||||
|
@ -28,10 +28,10 @@ func (p *MockResourceProvider) Configure(c map[string]interface{}) ([]string, er
|
||||||
func (p *MockResourceProvider) Diff(
|
func (p *MockResourceProvider) Diff(
|
||||||
state ResourceState,
|
state ResourceState,
|
||||||
desired map[string]interface{}) (ResourceDiff, error) {
|
desired map[string]interface{}) (ResourceDiff, error) {
|
||||||
p.ResourceDiffCalled = true
|
p.DiffCalled = true
|
||||||
p.ResourceDiffState = state
|
p.DiffState = state
|
||||||
p.ResourceDiffDesired = desired
|
p.DiffDesired = desired
|
||||||
return p.ResourceDiffReturn, p.ResourceDiffReturnError
|
return p.DiffReturn, p.DiffReturnError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *MockResourceProvider) Resources() []ResourceType {
|
func (p *MockResourceProvider) Resources() []ResourceType {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package terraform
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/depgraph"
|
"github.com/hashicorp/terraform/depgraph"
|
||||||
|
@ -124,14 +125,57 @@ func (t *Terraform) Apply(*State, *Diff) (*State, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terraform) Diff(*State) (*Diff, error) {
|
func (t *Terraform) Diff(s *State) (*Diff, error) {
|
||||||
return nil, nil
|
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) {
|
func (t *Terraform) Refresh(*State) (*State, error) {
|
||||||
return nil, nil
|
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
|
// matchingPrefixes takes a resource type and a set of resource
|
||||||
// providers we know about by prefix and returns a list of prefixes
|
// providers we know about by prefix and returns a list of prefixes
|
||||||
// that might be valid for that resource.
|
// that might be valid for that resource.
|
||||||
|
|
|
@ -10,46 +10,6 @@ import (
|
||||||
// This is the directory where our test fixtures are.
|
// This is the directory where our test fixtures are.
|
||||||
const fixtureDir = "./test-fixtures"
|
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) {
|
func TestNew(t *testing.T) {
|
||||||
config := testConfig(t, "new-good")
|
config := testConfig(t, "new-good")
|
||||||
tfConfig := &Config{
|
tfConfig := &Config{
|
||||||
|
@ -141,3 +101,86 @@ func TestNew_variables(t *testing.T) {
|
||||||
t.Fatal("tf should not be nil")
|
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