opentofu/terraform/terraform.go

202 lines
4.6 KiB
Go
Raw Normal View History

package terraform
import (
"fmt"
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 {
config *config.Config
graph *depgraph.Graph
2014-06-05 21:56:35 -05:00
mapping map[*config.Resource]*terraformProvider
variables map[string]string
}
2014-06-05 21:56:35 -05:00
// terraformProvider contains internal state information about a resource
// provider for Terraform.
type terraformProvider struct {
Provider ResourceProvider
Config *config.ProviderConfig
2014-06-05 21:56:35 -05:00
sync.Once
2014-06-05 21:56:35 -05:00
}
// Config is the configuration that must be given to instantiate
// a Terraform structure.
type Config struct {
Config *config.Config
Providers map[string]ResourceProviderFactory
Variables map[string]string
2014-06-05 14:09:25 -05:00
computedPlaceholder string
}
// 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) {
2014-06-03 18:11:02 -05:00
var errs []error
2014-06-05 14:09:25 -05:00
// Calculate the computed key placeholder
c.computedPlaceholder = "tf_computed_placeholder"
// Validate that all required variables have values
if err := smcVariables(c); err != nil {
errs = append(errs, err...)
}
// Match all the resources with a provider and initialize the providers
mapping, err := smcProviders(c)
if err != nil {
errs = append(errs, err...)
}
// Build the resource graph
2014-06-05 14:55:54 -05:00
graph := c.Config.Graph()
if err := graph.Validate(); err != nil {
errs = append(errs, fmt.Errorf(
"Resource graph has an error: %s", err))
}
2014-06-03 18:11:02 -05:00
// If we accumulated any errors, then return them all
if len(errs) > 0 {
return nil, &MultiError{Errors: errs}
}
return &Terraform{
config: c.Config,
graph: graph,
mapping: mapping,
variables: c.Variables,
}, nil
}
func (t *Terraform) Apply(*State, *Diff) (*State, error) {
return nil, nil
}
2014-06-05 04:32:10 -05:00
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
}
2014-06-05 04:32:10 -05:00
func (t *Terraform) diffWalkFn(
state *State, result *Diff) depgraph.WalkFunc {
var l sync.RWMutex
// Initialize the result diff so we can write to it
result.init()
// Initialize the variables for application
vars := make(map[string]string)
for k, v := range t.variables {
vars[k] = v
}
2014-06-05 04:32:10 -05:00
return func(n *depgraph.Noun) error {
// If it is the root node, ignore
if n.Meta == nil {
2014-06-05 04:32:10 -05:00
return nil
}
switch n.Meta.(type) {
case *config.ProviderConfig:
// Ignore, we don't treat this any differently since we always
// initialize the provider on first use and use a lock to make
// sure we only do this once.
return nil
case *config.Resource:
// Continue
}
2014-06-05 04:32:10 -05:00
r := n.Meta.(*config.Resource)
p := t.mapping[r]
if p == nil {
panic(fmt.Sprintf("No provider for resource: %s", r.Id()))
}
// Initialize the provider if we haven't already
if err := p.init(vars); err != nil {
return err
}
2014-06-05 21:56:35 -05:00
l.RLock()
var rs *ResourceState
if state != nil {
rs = state.resources[r.Id()]
}
if len(vars) > 0 {
if err := r.RawConfig.Interpolate(vars); err != nil {
panic(fmt.Sprintf("Interpolate error: %s", err))
}
}
l.RUnlock()
2014-06-12 19:59:59 -05:00
diff, err := p.Provider.Diff(rs, &ResourceConfig{
ComputedKeys: r.RawConfig.UnknownKeys(),
Raw: r.RawConfig.Config(),
2014-06-12 19:59:59 -05:00
})
2014-06-05 04:32:10 -05:00
if err != nil {
return err
}
// If there were no diff items, return right away
if diff == nil || len(diff.Attributes) == 0 {
2014-06-05 04:32:10 -05:00
return nil
}
// Acquire a lock since this function is called in parallel
l.Lock()
defer l.Unlock()
// Update the resulting diff
2014-06-10 13:22:32 -05:00
result.Resources[r.Id()] = diff
2014-06-05 04:32:10 -05:00
// Determine the new state and update variables
rs = rs.MergeDiff(diff.Attributes)
for ak, av := range rs.Attributes {
vars[fmt.Sprintf("%s.%s", r.Id(), ak)] = av
}
2014-06-05 04:32:10 -05:00
return nil
}
}
func (t *terraformProvider) init(vars map[string]string) (err error) {
t.Once.Do(func() {
var rc *ResourceConfig
if t.Config != nil {
if err := t.Config.RawConfig.Interpolate(vars); err != nil {
panic(err)
}
rc = &ResourceConfig{
ComputedKeys: t.Config.RawConfig.UnknownKeys(),
Raw: t.Config.RawConfig.Config(),
}
}
err = t.Provider.Configure(rc)
})
return
}