github.com/jgadling/terraform@v0.3.8-0.20150227214559-abd68c2c87bc/terraform/eval_apply.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strconv"
     7  
     8  	"github.com/hashicorp/go-multierror"
     9  	"github.com/hashicorp/terraform/config"
    10  )
    11  
    12  // EvalApply is an EvalNode implementation that writes the diff to
    13  // the full diff.
    14  type EvalApply struct {
    15  	Info      *InstanceInfo
    16  	State     **InstanceState
    17  	Diff      **InstanceDiff
    18  	Provider  *ResourceProvider
    19  	Output    **InstanceState
    20  	CreateNew *bool
    21  	Error     *error
    22  }
    23  
    24  // TODO: test
    25  func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) {
    26  	diff := *n.Diff
    27  	provider := *n.Provider
    28  	state := *n.State
    29  
    30  	// If we have no diff, we have nothing to do!
    31  	if diff.Empty() {
    32  		log.Printf(
    33  			"[DEBUG] apply: %s: diff is empty, doing nothing.", n.Info.Id)
    34  		return nil, nil
    35  	}
    36  
    37  	// Remove any output values from the diff
    38  	for k, ad := range diff.Attributes {
    39  		if ad.Type == DiffAttrOutput {
    40  			delete(diff.Attributes, k)
    41  		}
    42  	}
    43  
    44  	// If the state is nil, make it non-nil
    45  	if state == nil {
    46  		state = new(InstanceState)
    47  	}
    48  	state.init()
    49  
    50  	// Flag if we're creating a new instance
    51  	if n.CreateNew != nil {
    52  		*n.CreateNew = (state.ID == "" && !diff.Destroy) || diff.RequiresNew()
    53  	}
    54  
    55  	{
    56  		// Call pre-apply hook
    57  		err := ctx.Hook(func(h Hook) (HookAction, error) {
    58  			return h.PreApply(n.Info, state, diff)
    59  		})
    60  		if err != nil {
    61  			return nil, err
    62  		}
    63  	}
    64  
    65  	// With the completed diff, apply!
    66  	log.Printf("[DEBUG] apply: %s: executing Apply", n.Info.Id)
    67  	state, err := provider.Apply(n.Info, state, diff)
    68  	if state == nil {
    69  		state = new(InstanceState)
    70  	}
    71  	state.init()
    72  
    73  	// Force the "id" attribute to be our ID
    74  	if state.ID != "" {
    75  		state.Attributes["id"] = state.ID
    76  	}
    77  
    78  	// If the value is the unknown variable value, then it is an error.
    79  	// In this case we record the error and remove it from the state
    80  	for ak, av := range state.Attributes {
    81  		if av == config.UnknownVariableValue {
    82  			err = multierror.Append(err, fmt.Errorf(
    83  				"Attribute with unknown value: %s", ak))
    84  			delete(state.Attributes, ak)
    85  		}
    86  	}
    87  
    88  	// Write the final state
    89  	if n.Output != nil {
    90  		*n.Output = state
    91  	}
    92  
    93  	// If there are no errors, then we append it to our output error
    94  	// if we have one, otherwise we just output it.
    95  	if err != nil {
    96  		if n.Error != nil {
    97  			*n.Error = multierror.Append(*n.Error, err)
    98  		} else {
    99  			return nil, err
   100  		}
   101  	}
   102  
   103  	return nil, nil
   104  }
   105  
   106  // EvalApplyPost is an EvalNode implementation that does the post-Apply work
   107  type EvalApplyPost struct {
   108  	Info  *InstanceInfo
   109  	State **InstanceState
   110  	Error *error
   111  }
   112  
   113  // TODO: test
   114  func (n *EvalApplyPost) Eval(ctx EvalContext) (interface{}, error) {
   115  	state := *n.State
   116  
   117  	{
   118  		// Call post-apply hook
   119  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   120  			return h.PostApply(n.Info, state, *n.Error)
   121  		})
   122  		if err != nil {
   123  			return nil, err
   124  		}
   125  	}
   126  
   127  	return nil, *n.Error
   128  }
   129  
   130  // EvalApplyProvisioners is an EvalNode implementation that executes
   131  // the provisioners for a resource.
   132  //
   133  // TODO(mitchellh): This should probably be split up into a more fine-grained
   134  // ApplyProvisioner (single) that is looped over.
   135  type EvalApplyProvisioners struct {
   136  	Info           *InstanceInfo
   137  	State          **InstanceState
   138  	Resource       *config.Resource
   139  	InterpResource *Resource
   140  	CreateNew      *bool
   141  	Tainted        *bool
   142  	Error          *error
   143  }
   144  
   145  // TODO: test
   146  func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
   147  	state := *n.State
   148  
   149  	if !*n.CreateNew {
   150  		// If we're not creating a new resource, then don't run provisioners
   151  		return nil, nil
   152  	}
   153  
   154  	if len(n.Resource.Provisioners) == 0 {
   155  		// We have no provisioners, so don't do anything
   156  		return nil, nil
   157  	}
   158  
   159  	if n.Error != nil && *n.Error != nil {
   160  		// We're already errored creating, so mark as tainted and continue
   161  		if n.Tainted != nil {
   162  			*n.Tainted = true
   163  		}
   164  
   165  		// We're already tainted, so just return out
   166  		return nil, nil
   167  	}
   168  
   169  	{
   170  		// Call pre hook
   171  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   172  			return h.PreProvisionResource(n.Info, state)
   173  		})
   174  		if err != nil {
   175  			return nil, err
   176  		}
   177  	}
   178  
   179  	// If there are no errors, then we append it to our output error
   180  	// if we have one, otherwise we just output it.
   181  	err := n.apply(ctx)
   182  	if n.Tainted != nil {
   183  		*n.Tainted = err != nil
   184  	}
   185  	if err != nil {
   186  		if n.Error != nil {
   187  			*n.Error = multierror.Append(*n.Error, err)
   188  		} else {
   189  			return nil, err
   190  		}
   191  	}
   192  
   193  	{
   194  		// Call post hook
   195  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   196  			return h.PostProvisionResource(n.Info, state)
   197  		})
   198  		if err != nil {
   199  			return nil, err
   200  		}
   201  	}
   202  
   203  	return nil, nil
   204  }
   205  
   206  func (n *EvalApplyProvisioners) apply(ctx EvalContext) error {
   207  	state := *n.State
   208  
   209  	// Store the original connection info, restore later
   210  	origConnInfo := state.Ephemeral.ConnInfo
   211  	defer func() {
   212  		state.Ephemeral.ConnInfo = origConnInfo
   213  	}()
   214  
   215  	for _, prov := range n.Resource.Provisioners {
   216  		// Get the provisioner
   217  		provisioner := ctx.Provisioner(prov.Type)
   218  
   219  		// Interpolate the provisioner config
   220  		provConfig, err := ctx.Interpolate(prov.RawConfig, n.InterpResource)
   221  		if err != nil {
   222  			return err
   223  		}
   224  
   225  		// Interpolate the conn info, since it may contain variables
   226  		connInfo, err := ctx.Interpolate(prov.ConnInfo, n.InterpResource)
   227  		if err != nil {
   228  			return err
   229  		}
   230  
   231  		// Merge the connection information
   232  		overlay := make(map[string]string)
   233  		if origConnInfo != nil {
   234  			for k, v := range origConnInfo {
   235  				overlay[k] = v
   236  			}
   237  		}
   238  		for k, v := range connInfo.Config {
   239  			switch vt := v.(type) {
   240  			case string:
   241  				overlay[k] = vt
   242  			case int64:
   243  				overlay[k] = strconv.FormatInt(vt, 10)
   244  			case int32:
   245  				overlay[k] = strconv.FormatInt(int64(vt), 10)
   246  			case int:
   247  				overlay[k] = strconv.FormatInt(int64(vt), 10)
   248  			case float32:
   249  				overlay[k] = strconv.FormatFloat(float64(vt), 'f', 3, 32)
   250  			case float64:
   251  				overlay[k] = strconv.FormatFloat(vt, 'f', 3, 64)
   252  			case bool:
   253  				overlay[k] = strconv.FormatBool(vt)
   254  			default:
   255  				overlay[k] = fmt.Sprintf("%v", vt)
   256  			}
   257  		}
   258  		state.Ephemeral.ConnInfo = overlay
   259  
   260  		{
   261  			// Call pre hook
   262  			err := ctx.Hook(func(h Hook) (HookAction, error) {
   263  				return h.PreProvision(n.Info, prov.Type)
   264  			})
   265  			if err != nil {
   266  				return err
   267  			}
   268  		}
   269  
   270  		// The output function
   271  		outputFn := func(msg string) {
   272  			ctx.Hook(func(h Hook) (HookAction, error) {
   273  				h.ProvisionOutput(n.Info, prov.Type, msg)
   274  				return HookActionContinue, nil
   275  			})
   276  		}
   277  
   278  		// Invoke the Provisioner
   279  		output := CallbackUIOutput{OutputFn: outputFn}
   280  		if err := provisioner.Apply(&output, state, provConfig); err != nil {
   281  			return err
   282  		}
   283  
   284  		{
   285  			// Call post hook
   286  			err := ctx.Hook(func(h Hook) (HookAction, error) {
   287  				return h.PostProvision(n.Info, prov.Type)
   288  			})
   289  			if err != nil {
   290  				return err
   291  			}
   292  		}
   293  	}
   294  
   295  	return nil
   296  
   297  }