opentofu/terraform/terraform.go

343 lines
7.6 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 {
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 {
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{
providers: c.Providers,
}, nil
}
2014-06-20 14:49:01 -05:00
func (t *Terraform) Apply(p *Plan) (*State, error) {
2014-06-25 20:12:03 -05:00
g, err := t.Graph(p.Config, 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)
}
// Graph returns the dependency graph for the given configuration and
// state file.
//
// The resulting graph may have more resources than the configuration, because
// it can contain resources in the state file that need to be modified.
func (t *Terraform) Graph(c *config.Config, s *State) (*depgraph.Graph, error) {
2014-06-24 17:25:04 -05:00
// Get the basic graph with the raw metadata
g := Graph(c, s)
if err := g.Validate(); err != nil {
return nil, err
}
// Fill the graph with the providers
if err := GraphFull(g, t.providers); err != nil {
return nil, err
}
2014-06-24 17:25:04 -05:00
2014-06-25 17:39:44 -05:00
// Validate the graph so that it can setup a root and such
if err := g.Validate(); err != nil {
return nil, err
}
return g, nil
}
2014-06-25 19:40:50 -05:00
func (t *Terraform) Plan(
c *config.Config, s *State, vs map[string]string) (*Plan, error) {
g, err := t.Graph(c, s)
2014-06-05 04:32:10 -05:00
if err != nil {
return nil, err
}
2014-06-25 19:40:50 -05:00
return t.plan(g, c, s, vs)
}
// 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) {
2014-06-25 17:39:44 -05:00
g, err := t.Graph(c, 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.Vars))
return s, err
}
2014-06-25 19:40:50 -05:00
func (t *Terraform) plan(
g *depgraph.Graph,
c *config.Config,
s *State,
vs map[string]string) (*Plan, error) {
p := &Plan{
Config: c,
Vars: vs,
2014-06-25 20:22:42 -05:00
State: s,
2014-06-25 19:40:50 -05:00
}
err := g.Walk(t.planWalkFn(p, vs))
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) {
rs, err := r.Provider.Refresh(r.State)
if err != nil {
return nil, err
}
// 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()
return nil, nil
}
return t.genericWalkFn(nil, cb)
}
func (t *Terraform) applyWalkFn(
2014-06-25 20:12:03 -05:00
result *State,
vs map[string]string) depgraph.WalkFunc {
var l sync.Mutex
// Initialize the result
result.init()
cb := func(r *Resource) (map[string]string, error) {
2014-06-23 14:19:41 -05:00
// Get the latest diff since there are no computed values anymore
diff, err := r.Provider.Diff(r.State, r.Config)
if err != nil {
return nil, err
}
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)
// 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()
// Determine the new state and update variables
vars := make(map[string]string)
for ak, av := range rs.Attributes {
vars[fmt.Sprintf("%s.%s", r.Id, ak)] = av
}
err = nil
if len(errs) > 0 {
err = &MultiError{Errors: errs}
}
return vars, err
}
2014-06-25 20:12:03 -05:00
return t.genericWalkFn(vs, cb)
}
func (t *Terraform) planWalkFn(
2014-06-25 19:40:50 -05:00
result *Plan, vs map[string]string) 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
if r.Config == nil {
// This is an orphan (no config), so we mark it to be destroyed
diff = &ResourceDiff{Destroy: true}
} else {
// 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()
// Determine the new state and update variables
vars := make(map[string]string)
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-25 19:40:50 -05:00
if r.State != nil {
for ak, av := range r.State.Attributes {
vars[fmt.Sprintf("%s.%s", r.Id, ak)] = av
}
}
return vars, nil
}
2014-06-25 19:40:50 -05:00
return t.genericWalkFn(vs, cb)
}
func (t *Terraform) genericWalkFn(
invars map[string]string,
cb genericWalkFunc) depgraph.WalkFunc {
2014-06-25 17:39:44 -05:00
var l sync.Mutex
// 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 {
log.Printf("Configuring provider: %s", k)
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)
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
}
// Make sure that at least some resource configuration is set
2014-06-25 23:58:33 -05:00
if rn.Resource.Config == nil && !rn.Orphan {
2014-06-25 19:40:50 -05:00
if rn.Config == nil {
rn.Resource.Config = new(ResourceConfig)
} else {
rn.Resource.Config = NewResourceConfig(rn.Config.RawConfig)
}
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
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
}
}