terraform: Context.Refresh
This commit is contained in:
parent
947fa4e669
commit
2e10ddb878
|
@ -1,7 +1,13 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/depgraph"
|
||||||
"github.com/hashicorp/terraform/helper/multierror"
|
"github.com/hashicorp/terraform/helper/multierror"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -46,6 +52,28 @@ func NewContext(opts *ContextOpts) *Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh goes through all the resources in the state and refreshes them
|
||||||
|
// to their latest state. This will update the state that this context
|
||||||
|
// works with, along with returning it.
|
||||||
|
//
|
||||||
|
// Even in the case an error is returned, the state will be returned and
|
||||||
|
// will potentially be partially updated.
|
||||||
|
func (c *Context) Refresh() (*State, error) {
|
||||||
|
g, err := Graph(&GraphOpts{
|
||||||
|
Config: c.config,
|
||||||
|
Providers: c.providers,
|
||||||
|
State: c.state,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return c.state, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := new(State)
|
||||||
|
s.init()
|
||||||
|
err = g.Walk(c.refreshWalkFn(s))
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
// Validate validates the configuration and returns any warnings or errors.
|
// Validate validates the configuration and returns any warnings or errors.
|
||||||
func (c *Context) Validate() ([]string, []error) {
|
func (c *Context) Validate() ([]string, []error) {
|
||||||
var rerr *multierror.Error
|
var rerr *multierror.Error
|
||||||
|
@ -67,3 +95,142 @@ func (c *Context) Validate() ([]string, []error) {
|
||||||
|
|
||||||
return nil, errs
|
return nil, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context) refreshWalkFn(result *State) depgraph.WalkFunc {
|
||||||
|
var l sync.Mutex
|
||||||
|
|
||||||
|
cb := func(r *Resource) (map[string]string, error) {
|
||||||
|
for _, h := range c.hooks {
|
||||||
|
handleHook(h.PreRefresh(r.Id, r.State))
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, err := r.Provider.Refresh(r.State)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rs == nil {
|
||||||
|
rs = new(ResourceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix the type to be the type we have
|
||||||
|
rs.Type = r.State.Type
|
||||||
|
|
||||||
|
l.Lock()
|
||||||
|
result.Resources[r.Id] = rs
|
||||||
|
l.Unlock()
|
||||||
|
|
||||||
|
for _, h := range c.hooks {
|
||||||
|
handleHook(h.PostRefresh(r.Id, rs))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.genericWalkFn(c.variables, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) genericWalkFn(
|
||||||
|
invars map[string]string,
|
||||||
|
cb genericWalkFunc) depgraph.WalkFunc {
|
||||||
|
var l sync.RWMutex
|
||||||
|
|
||||||
|
// Initialize the variables for application
|
||||||
|
vars := make(map[string]string)
|
||||||
|
for k, v := range invars {
|
||||||
|
vars[fmt.Sprintf("var.%s", k)] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will keep track of whether we're stopped or not
|
||||||
|
var stop uint32 = 0
|
||||||
|
|
||||||
|
return func(n *depgraph.Noun) error {
|
||||||
|
// If it is the root node, ignore
|
||||||
|
if n.Name == GraphRootNode {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're stopped, return right away
|
||||||
|
if atomic.LoadUint32(&stop) != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch m := n.Meta.(type) {
|
||||||
|
case *GraphNodeResource:
|
||||||
|
case *GraphNodeResourceProvider:
|
||||||
|
var rc *ResourceConfig
|
||||||
|
if m.Config != nil {
|
||||||
|
if err := m.Config.RawConfig.Interpolate(vars); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
rc = NewResourceConfig(m.Config.RawConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, p := range m.Providers {
|
||||||
|
log.Printf("[INFO] Configuring provider: %s", k)
|
||||||
|
err := p.Configure(rc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rn := n.Meta.(*GraphNodeResource)
|
||||||
|
|
||||||
|
l.RLock()
|
||||||
|
if len(vars) > 0 && rn.Config != nil {
|
||||||
|
if err := rn.Config.RawConfig.Interpolate(vars); err != nil {
|
||||||
|
panic(fmt.Sprintf("Interpolate error: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force the config to be set later
|
||||||
|
rn.Resource.Config = nil
|
||||||
|
}
|
||||||
|
l.RUnlock()
|
||||||
|
|
||||||
|
// Make sure that at least some resource configuration is set
|
||||||
|
if !rn.Orphan {
|
||||||
|
if rn.Resource.Config == nil {
|
||||||
|
if rn.Config == nil {
|
||||||
|
rn.Resource.Config = new(ResourceConfig)
|
||||||
|
} else {
|
||||||
|
rn.Resource.Config = NewResourceConfig(rn.Config.RawConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rn.Resource.Config = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle recovery of special panic scenarios
|
||||||
|
defer func() {
|
||||||
|
if v := recover(); v != nil {
|
||||||
|
if v == HookActionHalt {
|
||||||
|
atomic.StoreUint32(&stop, 1)
|
||||||
|
} else {
|
||||||
|
panic(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Call the callack
|
||||||
|
log.Printf("[INFO] Walking: %s", rn.Resource.Id)
|
||||||
|
newVars, err := cb(rn.Resource)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newVars) > 0 {
|
||||||
|
// Acquire a lock since this function is called in parallel
|
||||||
|
l.Lock()
|
||||||
|
defer l.Unlock()
|
||||||
|
|
||||||
|
// Update variables
|
||||||
|
for k, v := range newVars {
|
||||||
|
vars[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,6 +51,123 @@ func TestContextValidate_requiredVar(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextRefresh(t *testing.T) {
|
||||||
|
p := testProvider("aws")
|
||||||
|
c := testConfig(t, "refresh-basic")
|
||||||
|
ctx := testContext(t, &ContextOpts{
|
||||||
|
Config: c,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
p.RefreshFn = nil
|
||||||
|
p.RefreshReturn = &ResourceState{
|
||||||
|
ID: "foo",
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := ctx.Refresh()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if !p.RefreshCalled {
|
||||||
|
t.Fatal("refresh should be called")
|
||||||
|
}
|
||||||
|
if p.RefreshState.ID != "" {
|
||||||
|
t.Fatalf("bad: %#v", p.RefreshState)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(s.Resources["aws_instance.web"], p.RefreshReturn) {
|
||||||
|
t.Fatalf("bad: %#v", s.Resources["aws_instance.web"])
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range s.Resources {
|
||||||
|
if r.Type == "" {
|
||||||
|
t.Fatalf("no type: %#v", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextRefresh_hook(t *testing.T) {
|
||||||
|
h := new(MockHook)
|
||||||
|
p := testProvider("aws")
|
||||||
|
c := testConfig(t, "refresh-basic")
|
||||||
|
ctx := testContext(t, &ContextOpts{
|
||||||
|
Config: c,
|
||||||
|
Hooks: []Hook{h},
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if _, err := ctx.Refresh(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if !h.PreRefreshCalled {
|
||||||
|
t.Fatal("should be called")
|
||||||
|
}
|
||||||
|
if h.PreRefreshState.Type != "aws_instance" {
|
||||||
|
t.Fatalf("bad: %#v", h.PreRefreshState)
|
||||||
|
}
|
||||||
|
if !h.PostRefreshCalled {
|
||||||
|
t.Fatal("should be called")
|
||||||
|
}
|
||||||
|
if h.PostRefreshState.Type != "aws_instance" {
|
||||||
|
t.Fatalf("bad: %#v", h.PostRefreshState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextRefresh_state(t *testing.T) {
|
||||||
|
p := testProvider("aws")
|
||||||
|
c := testConfig(t, "refresh-basic")
|
||||||
|
state := &State{
|
||||||
|
Resources: map[string]*ResourceState{
|
||||||
|
"aws_instance.web": &ResourceState{
|
||||||
|
ID: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctx := testContext(t, &ContextOpts{
|
||||||
|
Config: c,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
State: state,
|
||||||
|
})
|
||||||
|
|
||||||
|
p.RefreshFn = nil
|
||||||
|
p.RefreshReturn = &ResourceState{
|
||||||
|
ID: "foo",
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := ctx.Refresh()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if !p.RefreshCalled {
|
||||||
|
t.Fatal("refresh should be called")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(p.RefreshState, state.Resources["aws_instance.web"]) {
|
||||||
|
t.Fatalf("bad: %#v", p.RefreshState)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(s.Resources["aws_instance.web"], p.RefreshReturn) {
|
||||||
|
t.Fatalf("bad: %#v", s.Resources)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testContext(t *testing.T, opts *ContextOpts) *Context {
|
func testContext(t *testing.T, opts *ContextOpts) *Context {
|
||||||
return NewContext(opts)
|
return NewContext(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testProvider(prefix string) *MockResourceProvider {
|
||||||
|
p := new(MockResourceProvider)
|
||||||
|
p.RefreshFn = func(s *ResourceState) (*ResourceState, error) {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
p.ResourcesReturn = []ResourceType{
|
||||||
|
ResourceType{
|
||||||
|
Name: fmt.Sprintf("%s_instance", prefix),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue