github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/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  	Tainted        *bool
   143  	Error          *error
   144  }
   145  
   146  // TODO: test
   147  func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
   148  	state := *n.State
   149  
   150  	if !*n.CreateNew {
   151  		// If we're not creating a new resource, then don't run provisioners
   152  		return nil, nil
   153  	}
   154  
   155  	if len(n.Resource.Provisioners) == 0 {
   156  		// We have no provisioners, so don't do anything
   157  		return nil, nil
   158  	}
   159  
   160  	if n.Error != nil && *n.Error != nil {
   161  		// We're already errored creating, so mark as tainted and continue
   162  		if n.Tainted != nil {
   163  			*n.Tainted = true
   164  		}
   165  
   166  		// We're already tainted, so just return out
   167  		return nil, nil
   168  	}
   169  
   170  	{
   171  		// Call pre hook
   172  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   173  			return h.PreProvisionResource(n.Info, state)
   174  		})
   175  		if err != nil {
   176  			return nil, err
   177  		}
   178  	}
   179  
   180  	// If there are no errors, then we append it to our output error
   181  	// if we have one, otherwise we just output it.
   182  	err := n.apply(ctx)
   183  	if n.Tainted != nil {
   184  		*n.Tainted = err != nil
   185  	}
   186  	if err != nil {
   187  		if n.Error != nil {
   188  			*n.Error = multierror.Append(*n.Error, err)
   189  		} else {
   190  			return nil, err
   191  		}
   192  	}
   193  
   194  	{
   195  		// Call post hook
   196  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   197  			return h.PostProvisionResource(n.Info, state)
   198  		})
   199  		if err != nil {
   200  			return nil, err
   201  		}
   202  	}
   203  
   204  	return nil, nil
   205  }
   206  
   207  func (n *EvalApplyProvisioners) apply(ctx EvalContext) error {
   208  	state := *n.State
   209  
   210  	// Store the original connection info, restore later
   211  	origConnInfo := state.Ephemeral.ConnInfo
   212  	defer func() {
   213  		state.Ephemeral.ConnInfo = origConnInfo
   214  	}()
   215  
   216  	for _, prov := range n.Resource.Provisioners {
   217  		// Get the provisioner
   218  		provisioner := ctx.Provisioner(prov.Type)
   219  
   220  		// Interpolate the provisioner config
   221  		provConfig, err := ctx.Interpolate(prov.RawConfig, n.InterpResource)
   222  		if err != nil {
   223  			return err
   224  		}
   225  
   226  		// Interpolate the conn info, since it may contain variables
   227  		connInfo, err := ctx.Interpolate(prov.ConnInfo, n.InterpResource)
   228  		if err != nil {
   229  			return err
   230  		}
   231  
   232  		// Merge the connection information
   233  		overlay := make(map[string]string)
   234  		if origConnInfo != nil {
   235  			for k, v := range origConnInfo {
   236  				overlay[k] = v
   237  			}
   238  		}
   239  		for k, v := range connInfo.Config {
   240  			switch vt := v.(type) {
   241  			case string:
   242  				overlay[k] = vt
   243  			case int64:
   244  				overlay[k] = strconv.FormatInt(vt, 10)
   245  			case int32:
   246  				overlay[k] = strconv.FormatInt(int64(vt), 10)
   247  			case int:
   248  				overlay[k] = strconv.FormatInt(int64(vt), 10)
   249  			case float32:
   250  				overlay[k] = strconv.FormatFloat(float64(vt), 'f', 3, 32)
   251  			case float64:
   252  				overlay[k] = strconv.FormatFloat(vt, 'f', 3, 64)
   253  			case bool:
   254  				overlay[k] = strconv.FormatBool(vt)
   255  			default:
   256  				overlay[k] = fmt.Sprintf("%v", vt)
   257  			}
   258  		}
   259  		state.Ephemeral.ConnInfo = overlay
   260  
   261  		{
   262  			// Call pre hook
   263  			err := ctx.Hook(func(h Hook) (HookAction, error) {
   264  				return h.PreProvision(n.Info, prov.Type)
   265  			})
   266  			if err != nil {
   267  				return err
   268  			}
   269  		}
   270  
   271  		// The output function
   272  		outputFn := func(msg string) {
   273  			ctx.Hook(func(h Hook) (HookAction, error) {
   274  				h.ProvisionOutput(n.Info, prov.Type, msg)
   275  				return HookActionContinue, nil
   276  			})
   277  		}
   278  
   279  		// Invoke the Provisioner
   280  		output := CallbackUIOutput{OutputFn: outputFn}
   281  		if err := provisioner.Apply(&output, state, provConfig); err != nil {
   282  			return err
   283  		}
   284  
   285  		{
   286  			// Call post hook
   287  			err := ctx.Hook(func(h Hook) (HookAction, error) {
   288  				return h.PostProvision(n.Info, prov.Type)
   289  			})
   290  			if err != nil {
   291  				return err
   292  			}
   293  		}
   294  	}
   295  
   296  	return nil
   297  
   298  }