github.com/skyscape-cloud-services/terraform@v0.9.2-0.20170609144644-7ece028a1747/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.CopyAttributes() {
    39  		if ad.Type == DiffAttrOutput {
    40  			diff.DelAttribute(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.GetDestroy() || diff.RequiresNew()
    53  	}
    54  
    55  	// With the completed diff, apply!
    56  	log.Printf("[DEBUG] apply: %s: executing Apply", n.Info.Id)
    57  	state, err := provider.Apply(n.Info, state, diff)
    58  	if state == nil {
    59  		state = new(InstanceState)
    60  	}
    61  	state.init()
    62  
    63  	// Force the "id" attribute to be our ID
    64  	if state.ID != "" {
    65  		state.Attributes["id"] = state.ID
    66  	}
    67  
    68  	// If the value is the unknown variable value, then it is an error.
    69  	// In this case we record the error and remove it from the state
    70  	for ak, av := range state.Attributes {
    71  		if av == config.UnknownVariableValue {
    72  			err = multierror.Append(err, fmt.Errorf(
    73  				"Attribute with unknown value: %s", ak))
    74  			delete(state.Attributes, ak)
    75  		}
    76  	}
    77  
    78  	// Write the final state
    79  	if n.Output != nil {
    80  		*n.Output = state
    81  	}
    82  
    83  	// If there are no errors, then we append it to our output error
    84  	// if we have one, otherwise we just output it.
    85  	if err != nil {
    86  		if n.Error != nil {
    87  			helpfulErr := fmt.Errorf("%s: %s", n.Info.Id, err.Error())
    88  			*n.Error = multierror.Append(*n.Error, helpfulErr)
    89  		} else {
    90  			return nil, err
    91  		}
    92  	}
    93  
    94  	return nil, nil
    95  }
    96  
    97  // EvalApplyPre is an EvalNode implementation that does the pre-Apply work
    98  type EvalApplyPre struct {
    99  	Info  *InstanceInfo
   100  	State **InstanceState
   101  	Diff  **InstanceDiff
   102  }
   103  
   104  // TODO: test
   105  func (n *EvalApplyPre) Eval(ctx EvalContext) (interface{}, error) {
   106  	state := *n.State
   107  	diff := *n.Diff
   108  
   109  	// If the state is nil, make it non-nil
   110  	if state == nil {
   111  		state = new(InstanceState)
   112  	}
   113  	state.init()
   114  
   115  	{
   116  		// Call post-apply hook
   117  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   118  			return h.PreApply(n.Info, state, diff)
   119  		})
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  	}
   124  
   125  	return nil, nil
   126  }
   127  
   128  // EvalApplyPost is an EvalNode implementation that does the post-Apply work
   129  type EvalApplyPost struct {
   130  	Info  *InstanceInfo
   131  	State **InstanceState
   132  	Error *error
   133  }
   134  
   135  // TODO: test
   136  func (n *EvalApplyPost) Eval(ctx EvalContext) (interface{}, error) {
   137  	state := *n.State
   138  
   139  	{
   140  		// Call post-apply hook
   141  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   142  			return h.PostApply(n.Info, state, *n.Error)
   143  		})
   144  		if err != nil {
   145  			return nil, err
   146  		}
   147  	}
   148  
   149  	return nil, *n.Error
   150  }
   151  
   152  // EvalApplyProvisioners is an EvalNode implementation that executes
   153  // the provisioners for a resource.
   154  //
   155  // TODO(mitchellh): This should probably be split up into a more fine-grained
   156  // ApplyProvisioner (single) that is looped over.
   157  type EvalApplyProvisioners struct {
   158  	Info           *InstanceInfo
   159  	State          **InstanceState
   160  	Resource       *config.Resource
   161  	InterpResource *Resource
   162  	CreateNew      *bool
   163  	Error          *error
   164  
   165  	// When is the type of provisioner to run at this point
   166  	When config.ProvisionerWhen
   167  }
   168  
   169  // TODO: test
   170  func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
   171  	state := *n.State
   172  
   173  	if n.CreateNew != nil && !*n.CreateNew {
   174  		// If we're not creating a new resource, then don't run provisioners
   175  		return nil, nil
   176  	}
   177  
   178  	provs := n.filterProvisioners()
   179  	if len(provs) == 0 {
   180  		// We have no provisioners, so don't do anything
   181  		return nil, nil
   182  	}
   183  
   184  	// taint tells us whether to enable tainting.
   185  	taint := n.When == config.ProvisionerWhenCreate
   186  
   187  	if n.Error != nil && *n.Error != nil {
   188  		if taint {
   189  			state.Tainted = true
   190  		}
   191  
   192  		// We're already tainted, so just return out
   193  		return nil, nil
   194  	}
   195  
   196  	{
   197  		// Call pre hook
   198  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   199  			return h.PreProvisionResource(n.Info, state)
   200  		})
   201  		if err != nil {
   202  			return nil, err
   203  		}
   204  	}
   205  
   206  	// If there are no errors, then we append it to our output error
   207  	// if we have one, otherwise we just output it.
   208  	err := n.apply(ctx, provs)
   209  	if err != nil {
   210  		if taint {
   211  			state.Tainted = true
   212  		}
   213  
   214  		if n.Error != nil {
   215  			*n.Error = multierror.Append(*n.Error, err)
   216  		} else {
   217  			return nil, err
   218  		}
   219  	}
   220  
   221  	{
   222  		// Call post hook
   223  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   224  			return h.PostProvisionResource(n.Info, state)
   225  		})
   226  		if err != nil {
   227  			return nil, err
   228  		}
   229  	}
   230  
   231  	return nil, nil
   232  }
   233  
   234  // filterProvisioners filters the provisioners on the resource to only
   235  // the provisioners specified by the "when" option.
   236  func (n *EvalApplyProvisioners) filterProvisioners() []*config.Provisioner {
   237  	// Fast path the zero case
   238  	if n.Resource == nil {
   239  		return nil
   240  	}
   241  
   242  	if len(n.Resource.Provisioners) == 0 {
   243  		return nil
   244  	}
   245  
   246  	result := make([]*config.Provisioner, 0, len(n.Resource.Provisioners))
   247  	for _, p := range n.Resource.Provisioners {
   248  		if p.When == n.When {
   249  			result = append(result, p)
   250  		}
   251  	}
   252  
   253  	return result
   254  }
   255  
   256  func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*config.Provisioner) error {
   257  	state := *n.State
   258  
   259  	// Store the original connection info, restore later
   260  	origConnInfo := state.Ephemeral.ConnInfo
   261  	defer func() {
   262  		state.Ephemeral.ConnInfo = origConnInfo
   263  	}()
   264  
   265  	for _, prov := range provs {
   266  		// Get the provisioner
   267  		provisioner := ctx.Provisioner(prov.Type)
   268  
   269  		// Interpolate the provisioner config
   270  		provConfig, err := ctx.Interpolate(prov.RawConfig.Copy(), n.InterpResource)
   271  		if err != nil {
   272  			return err
   273  		}
   274  
   275  		// Interpolate the conn info, since it may contain variables
   276  		connInfo, err := ctx.Interpolate(prov.ConnInfo.Copy(), n.InterpResource)
   277  		if err != nil {
   278  			return err
   279  		}
   280  
   281  		// Merge the connection information
   282  		overlay := make(map[string]string)
   283  		if origConnInfo != nil {
   284  			for k, v := range origConnInfo {
   285  				overlay[k] = v
   286  			}
   287  		}
   288  		for k, v := range connInfo.Config {
   289  			switch vt := v.(type) {
   290  			case string:
   291  				overlay[k] = vt
   292  			case int64:
   293  				overlay[k] = strconv.FormatInt(vt, 10)
   294  			case int32:
   295  				overlay[k] = strconv.FormatInt(int64(vt), 10)
   296  			case int:
   297  				overlay[k] = strconv.FormatInt(int64(vt), 10)
   298  			case float32:
   299  				overlay[k] = strconv.FormatFloat(float64(vt), 'f', 3, 32)
   300  			case float64:
   301  				overlay[k] = strconv.FormatFloat(vt, 'f', 3, 64)
   302  			case bool:
   303  				overlay[k] = strconv.FormatBool(vt)
   304  			default:
   305  				overlay[k] = fmt.Sprintf("%v", vt)
   306  			}
   307  		}
   308  		state.Ephemeral.ConnInfo = overlay
   309  
   310  		{
   311  			// Call pre hook
   312  			err := ctx.Hook(func(h Hook) (HookAction, error) {
   313  				return h.PreProvision(n.Info, prov.Type)
   314  			})
   315  			if err != nil {
   316  				return err
   317  			}
   318  		}
   319  
   320  		// The output function
   321  		outputFn := func(msg string) {
   322  			ctx.Hook(func(h Hook) (HookAction, error) {
   323  				h.ProvisionOutput(n.Info, prov.Type, msg)
   324  				return HookActionContinue, nil
   325  			})
   326  		}
   327  
   328  		// Invoke the Provisioner
   329  		output := CallbackUIOutput{OutputFn: outputFn}
   330  		applyErr := provisioner.Apply(&output, state, provConfig)
   331  
   332  		// Call post hook
   333  		hookErr := ctx.Hook(func(h Hook) (HookAction, error) {
   334  			return h.PostProvision(n.Info, prov.Type, applyErr)
   335  		})
   336  
   337  		// Handle the error before we deal with the hook
   338  		if applyErr != nil {
   339  			// Determine failure behavior
   340  			switch prov.OnFailure {
   341  			case config.ProvisionerOnFailureContinue:
   342  				log.Printf(
   343  					"[INFO] apply: %s [%s]: error during provision, continue requested",
   344  					n.Info.Id, prov.Type)
   345  
   346  			case config.ProvisionerOnFailureFail:
   347  				return applyErr
   348  			}
   349  		}
   350  
   351  		// Deal with the hook
   352  		if hookErr != nil {
   353  			return hookErr
   354  		}
   355  	}
   356  
   357  	return nil
   358  
   359  }