github.com/ljosa/terraform@v0.7.0-rc2.0.20160617205345-fe540b408f59/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  			helpfulErr := fmt.Errorf("%s: %s", n.Info.Id, err.Error())
    98  			*n.Error = multierror.Append(*n.Error, helpfulErr)
    99  		} else {
   100  			return nil, err
   101  		}
   102  	}
   103  
   104  	return nil, nil
   105  }
   106  
   107  // EvalApplyPost is an EvalNode implementation that does the post-Apply work
   108  type EvalApplyPost struct {
   109  	Info  *InstanceInfo
   110  	State **InstanceState
   111  	Error *error
   112  }
   113  
   114  // TODO: test
   115  func (n *EvalApplyPost) Eval(ctx EvalContext) (interface{}, error) {
   116  	state := *n.State
   117  
   118  	{
   119  		// Call post-apply hook
   120  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   121  			return h.PostApply(n.Info, state, *n.Error)
   122  		})
   123  		if err != nil {
   124  			return nil, err
   125  		}
   126  	}
   127  
   128  	return nil, *n.Error
   129  }
   130  
   131  // EvalApplyProvisioners is an EvalNode implementation that executes
   132  // the provisioners for a resource.
   133  //
   134  // TODO(mitchellh): This should probably be split up into a more fine-grained
   135  // ApplyProvisioner (single) that is looped over.
   136  type EvalApplyProvisioners struct {
   137  	Info           *InstanceInfo
   138  	State          **InstanceState
   139  	Resource       *config.Resource
   140  	InterpResource *Resource
   141  	CreateNew      *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  		state.Tainted = true
   162  
   163  		// We're already tainted, so just return out
   164  		return nil, nil
   165  	}
   166  
   167  	{
   168  		// Call pre hook
   169  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   170  			return h.PreProvisionResource(n.Info, state)
   171  		})
   172  		if err != nil {
   173  			return nil, err
   174  		}
   175  	}
   176  
   177  	// If there are no errors, then we append it to our output error
   178  	// if we have one, otherwise we just output it.
   179  	err := n.apply(ctx)
   180  	if err != nil {
   181  		// Provisioning failed, so mark the resource as tainted
   182  		state.Tainted = true
   183  
   184  		if n.Error != nil {
   185  			*n.Error = multierror.Append(*n.Error, err)
   186  		} else {
   187  			return nil, err
   188  		}
   189  	}
   190  
   191  	{
   192  		// Call post hook
   193  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   194  			return h.PostProvisionResource(n.Info, state)
   195  		})
   196  		if err != nil {
   197  			return nil, err
   198  		}
   199  	}
   200  
   201  	return nil, nil
   202  }
   203  
   204  func (n *EvalApplyProvisioners) apply(ctx EvalContext) error {
   205  	state := *n.State
   206  
   207  	// Store the original connection info, restore later
   208  	origConnInfo := state.Ephemeral.ConnInfo
   209  	defer func() {
   210  		state.Ephemeral.ConnInfo = origConnInfo
   211  	}()
   212  
   213  	for _, prov := range n.Resource.Provisioners {
   214  		// Get the provisioner
   215  		provisioner := ctx.Provisioner(prov.Type)
   216  
   217  		// Interpolate the provisioner config
   218  		provConfig, err := ctx.Interpolate(prov.RawConfig, n.InterpResource)
   219  		if err != nil {
   220  			return err
   221  		}
   222  
   223  		// Interpolate the conn info, since it may contain variables
   224  		connInfo, err := ctx.Interpolate(prov.ConnInfo, n.InterpResource)
   225  		if err != nil {
   226  			return err
   227  		}
   228  
   229  		// Merge the connection information
   230  		overlay := make(map[string]string)
   231  		if origConnInfo != nil {
   232  			for k, v := range origConnInfo {
   233  				overlay[k] = v
   234  			}
   235  		}
   236  		for k, v := range connInfo.Config {
   237  			switch vt := v.(type) {
   238  			case string:
   239  				overlay[k] = vt
   240  			case int64:
   241  				overlay[k] = strconv.FormatInt(vt, 10)
   242  			case int32:
   243  				overlay[k] = strconv.FormatInt(int64(vt), 10)
   244  			case int:
   245  				overlay[k] = strconv.FormatInt(int64(vt), 10)
   246  			case float32:
   247  				overlay[k] = strconv.FormatFloat(float64(vt), 'f', 3, 32)
   248  			case float64:
   249  				overlay[k] = strconv.FormatFloat(vt, 'f', 3, 64)
   250  			case bool:
   251  				overlay[k] = strconv.FormatBool(vt)
   252  			default:
   253  				overlay[k] = fmt.Sprintf("%v", vt)
   254  			}
   255  		}
   256  		state.Ephemeral.ConnInfo = overlay
   257  
   258  		{
   259  			// Call pre hook
   260  			err := ctx.Hook(func(h Hook) (HookAction, error) {
   261  				return h.PreProvision(n.Info, prov.Type)
   262  			})
   263  			if err != nil {
   264  				return err
   265  			}
   266  		}
   267  
   268  		// The output function
   269  		outputFn := func(msg string) {
   270  			ctx.Hook(func(h Hook) (HookAction, error) {
   271  				h.ProvisionOutput(n.Info, prov.Type, msg)
   272  				return HookActionContinue, nil
   273  			})
   274  		}
   275  
   276  		// Invoke the Provisioner
   277  		output := CallbackUIOutput{OutputFn: outputFn}
   278  		if err := provisioner.Apply(&output, state, provConfig); err != nil {
   279  			return err
   280  		}
   281  
   282  		{
   283  			// Call post hook
   284  			err := ctx.Hook(func(h Hook) (HookAction, error) {
   285  				return h.PostProvision(n.Info, prov.Type)
   286  			})
   287  			if err != nil {
   288  				return err
   289  			}
   290  		}
   291  	}
   292  
   293  	return nil
   294  
   295  }