opentofu/terraform/terraform.go

383 lines
8.2 KiB
Go
Raw Normal View History

package terraform
import (
"fmt"
2014-06-25 17:39:44 -05:00
"log"
2014-06-05 04:32:10 -05:00
"sync"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/depgraph"
)
// Terraform is the primary structure that is used to interact with
// Terraform from code, and can perform operations such as returning
// all resources, a resource tree, a specific resource, etc.
type Terraform struct {
2014-06-26 18:52:15 -05:00
hooks []Hook
providers map[string]ResourceProviderFactory
}
// This is a function type used to implement a walker for the resource
// tree internally on the Terraform structure.
type genericWalkFunc func(*Resource) (map[string]string, error)
// Config is the configuration that must be given to instantiate
// a Terraform structure.
type Config struct {
2014-06-26 18:52:15 -05:00
Hooks []Hook
Providers map[string]ResourceProviderFactory
}
// New creates a new Terraform structure, initializes resource providers
// for the given configuration, etc.
//
// Semantic checks of the entire configuration structure are done at this
// time, as well as richer checks such as verifying that the resource providers
// can be properly initialized, can be configured, etc.
func New(c *Config) (*Terraform, error) {
return &Terraform{
2014-06-26 18:52:15 -05:00
hooks: c.Hooks,
providers: c.Providers,
}, nil
}
2014-06-20 14:49:01 -05:00
func (t *Terraform) Apply(p *Plan) (*State, error) {
// Make sure we're working with a plan that doesn't have null pointers
// everywhere, and is instead just empty otherwise.
p.init()
g, err := Graph(&GraphOpts{
Config: p.Config,
Diff: p.Diff,
Providers: t.providers,
State: p.State,
})
2014-06-24 17:25:04 -05:00
if err != nil {
return nil, err
}
2014-06-25 20:12:03 -05:00
return t.apply(g, p)
}
func (t *Terraform) Plan(opts *PlanOpts) (*Plan, error) {
g, err := Graph(&GraphOpts{
Config: opts.Config,
Providers: t.providers,
State: opts.State,
})
2014-06-05 04:32:10 -05:00
if err != nil {
return nil, err
}
return t.plan(g, opts)
}
// Refresh goes through all the resources in the state and refreshes them
// to their latest status.
func (t *Terraform) Refresh(c *config.Config, s *State) (*State, error) {
g, err := Graph(&GraphOpts{
Config: c,
Providers: t.providers,
State: s,
})
if err != nil {
return s, err
}
return t.refresh(g)
2014-06-25 17:39:44 -05:00
}
2014-06-25 20:12:03 -05:00
func (t *Terraform) apply(
g *depgraph.Graph,
p *Plan) (*State, error) {
s := new(State)
err := g.Walk(t.applyWalkFn(s, p))
2014-06-25 20:12:03 -05:00
return s, err
}
func (t *Terraform) plan(g *depgraph.Graph, opts *PlanOpts) (*Plan, error) {
2014-06-25 19:40:50 -05:00
p := &Plan{
Config: opts.Config,
Vars: opts.Vars,
State: opts.State,
2014-06-25 19:40:50 -05:00
}
err := g.Walk(t.planWalkFn(p, opts))
2014-06-25 19:40:50 -05:00
return p, err
}
func (t *Terraform) refresh(g *depgraph.Graph) (*State, error) {
2014-06-25 17:39:44 -05:00
s := new(State)
err := g.Walk(t.refreshWalkFn(s))
2014-06-25 17:39:44 -05:00
return s, err
}
func (t *Terraform) refreshWalkFn(result *State) depgraph.WalkFunc {
2014-06-25 17:39:44 -05:00
var l sync.Mutex
// Initialize the result so we don't have to nil check everywhere
result.init()
cb := func(r *Resource) (map[string]string, error) {
2014-06-26 18:52:15 -05:00
for _, h := range t.hooks {
// TODO: return value
2014-06-26 19:05:21 -05:00
h.PreRefresh(r.Id, r.State)
2014-06-26 18:52:15 -05:00
}
2014-06-25 17:39:44 -05:00
rs, err := r.Provider.Refresh(r.State)
if err != nil {
return nil, err
}
if rs == nil {
rs = new(ResourceState)
}
2014-06-25 17:39:44 -05:00
// Fix the type to be the type we have
rs.Type = r.State.Type
2014-06-25 17:39:44 -05:00
l.Lock()
result.Resources[r.Id] = rs
l.Unlock()
2014-06-26 18:52:15 -05:00
for _, h := range t.hooks {
// TODO: return value
2014-06-26 19:05:21 -05:00
h.PostRefresh(r.Id, rs)
2014-06-26 18:52:15 -05:00
}
2014-06-25 17:39:44 -05:00
return nil, nil
}
return t.genericWalkFn(nil, cb)
}
func (t *Terraform) applyWalkFn(
2014-06-25 20:12:03 -05:00
result *State,
p *Plan) depgraph.WalkFunc {
var l sync.Mutex
// Initialize the result
result.init()
cb := func(r *Resource) (map[string]string, error) {
2014-06-30 21:29:07 -05:00
diff := r.Diff
if diff.Empty() {
return r.Vars(), nil
}
if !diff.Destroy {
var err error
diff, err = r.Provider.Diff(r.State, r.Config)
if err != nil {
return nil, err
}
2014-06-23 14:19:41 -05:00
}
2014-06-23 14:30:29 -05:00
// TODO(mitchellh): we need to verify the diff doesn't change
// anything and that the diff has no computed values (pre-computed)
2014-06-27 00:09:16 -05:00
for _, h := range t.hooks {
// TODO: return value
h.PreApply(r.Id, r.State, diff)
}
2014-06-23 14:30:29 -05:00
// With the completed diff, apply!
2014-06-23 14:19:41 -05:00
rs, err := r.Provider.Apply(r.State, diff)
if err != nil {
return nil, err
}
// Make sure the result is instantiated
if rs == nil {
rs = new(ResourceState)
}
2014-06-25 20:33:32 -05:00
// Force the resource state type to be our type
rs.Type = r.State.Type
// If no state was returned, then no variables were updated so
// just return.
if rs == nil {
return nil, nil
}
var errs []error
for ak, av := range rs.Attributes {
// If the value is the unknown variable value, then it is an error.
// In this case we record the error and remove it from the state
if av == config.UnknownVariableValue {
errs = append(errs, fmt.Errorf(
"Attribute with unknown value: %s", ak))
delete(rs.Attributes, ak)
}
}
// Update the resulting diff
l.Lock()
result.Resources[r.Id] = rs
l.Unlock()
2014-06-30 21:29:07 -05:00
// Update the state for the resource itself
r.State = rs
2014-06-27 00:09:16 -05:00
for _, h := range t.hooks {
// TODO: return value
h.PostApply(r.Id, r.State)
}
// Determine the new state and update variables
err = nil
if len(errs) > 0 {
err = &MultiError{Errors: errs}
}
2014-06-30 21:29:07 -05:00
return r.Vars(), err
}
return t.genericWalkFn(p.Vars, cb)
}
func (t *Terraform) planWalkFn(result *Plan, opts *PlanOpts) depgraph.WalkFunc {
var l sync.Mutex
2014-06-25 19:40:50 -05:00
// Initialize the result
result.init()
cb := func(r *Resource) (map[string]string, error) {
2014-06-25 23:58:33 -05:00
var diff *ResourceDiff
2014-06-26 19:17:10 -05:00
for _, h := range t.hooks {
// TODO: return value
h.PreDiff(r.Id, r.State)
}
if opts.Destroy {
if r.State.ID != "" {
log.Printf("[DEBUG] %s: Making for destroy", r.Id)
diff = &ResourceDiff{Destroy: true}
} else {
log.Printf("[DEBUG] %s: Not marking for destroy, no ID", r.Id)
}
} else if r.Config == nil {
log.Printf("[DEBUG] %s: Orphan, marking for destroy", r.Id)
2014-06-25 23:58:33 -05:00
// This is an orphan (no config), so we mark it to be destroyed
diff = &ResourceDiff{Destroy: true}
} else {
log.Printf("[DEBUG] %s: Executing diff", r.Id)
2014-06-25 23:58:33 -05:00
// Get a diff from the newest state
var err error
diff, err = r.Provider.Diff(r.State, r.Config)
if err != nil {
return nil, err
}
}
l.Lock()
2014-06-20 13:03:33 -05:00
if !diff.Empty() {
result.Diff.Resources[r.Id] = diff
}
l.Unlock()
2014-06-26 19:17:10 -05:00
for _, h := range t.hooks {
// TODO: return value
h.PostDiff(r.Id, diff)
}
// Determine the new state and update variables
2014-06-20 13:03:33 -05:00
if !diff.Empty() {
2014-06-25 19:40:50 -05:00
r.State = r.State.MergeDiff(diff)
2014-06-20 13:03:33 -05:00
}
2014-06-30 21:29:07 -05:00
return r.Vars(), nil
}
return t.genericWalkFn(opts.Vars, cb)
}
func (t *Terraform) genericWalkFn(
invars map[string]string,
cb genericWalkFunc) depgraph.WalkFunc {
2014-07-01 12:11:07 -05:00
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
}
2014-06-05 04:32:10 -05:00
return func(n *depgraph.Noun) error {
2014-06-25 17:39:44 -05:00
// If it is the root node, ignore
if n.Name == GraphRootNode {
return nil
}
2014-06-25 17:39:44 -05:00
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)
}
2014-06-05 04:32:10 -05:00
2014-06-25 17:39:44 -05:00
for k, p := range m.Providers {
2014-06-26 19:17:10 -05:00
log.Printf("[INFO] Configuring provider: %s", k)
2014-06-25 17:39:44 -05:00
err := p.Configure(rc)
if err != nil {
return err
}
}
2014-06-05 21:56:35 -05:00
2014-06-25 17:39:44 -05:00
return nil
}
2014-06-25 17:39:44 -05:00
rn := n.Meta.(*GraphNodeResource)
2014-07-01 12:11:07 -05:00
l.RLock()
2014-06-25 17:39:44 -05:00
if len(vars) > 0 && rn.Config != nil {
if err := rn.Config.RawConfig.Interpolate(vars); err != nil {
panic(fmt.Sprintf("Interpolate error: %s", err))
}
2014-06-18 11:30:59 -05:00
2014-06-25 19:40:50 -05:00
// Force the config to be set later
rn.Resource.Config = nil
}
2014-07-01 12:11:07 -05:00
l.RUnlock()
2014-06-25 19:40:50 -05:00
// 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)
}
2014-06-25 19:40:50 -05:00
}
} else {
rn.Resource.Config = nil
2014-06-25 17:39:44 -05:00
}
2014-06-18 11:30:59 -05:00
2014-06-25 17:39:44 -05:00
// Call the callack
2014-06-26 19:17:10 -05:00
log.Printf("[INFO] Walking: %s", rn.Resource.Id)
2014-06-25 17:39:44 -05:00
newVars, err := cb(rn.Resource)
if err != nil {
return err
}
2014-06-05 04:32:10 -05:00
2014-06-25 17:39:44 -05:00
if len(newVars) > 0 {
// Acquire a lock since this function is called in parallel
l.Lock()
defer l.Unlock()
2014-06-05 04:32:10 -05:00
2014-06-25 17:39:44 -05:00
// Update variables
for k, v := range newVars {
vars[k] = v
}
2014-06-25 17:39:44 -05:00
}
2014-06-05 04:32:10 -05:00
return nil
}
}