github.com/magodo/terraform@v0.11.12-beta1/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  	if resourceHasUserVisibleApply(n.Info) {
   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  	if resourceHasUserVisibleApply(n.Info) {
   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  // resourceHasUserVisibleApply returns true if the given resource is one where
   153  // apply actions should be exposed to the user.
   154  //
   155  // Certain resources do apply actions only as an implementation detail, so
   156  // these should not be advertised to code outside of this package.
   157  func resourceHasUserVisibleApply(info *InstanceInfo) bool {
   158  	addr := info.ResourceAddress()
   159  
   160  	// Only managed resources have user-visible apply actions.
   161  	// In particular, this excludes data resources since we "apply" these
   162  	// only as an implementation detail of removing them from state when
   163  	// they are destroyed. (When reading, they don't get here at all because
   164  	// we present them as "Refresh" actions.)
   165  	return addr.Mode == config.ManagedResourceMode
   166  }
   167  
   168  // EvalApplyProvisioners is an EvalNode implementation that executes
   169  // the provisioners for a resource.
   170  //
   171  // TODO(mitchellh): This should probably be split up into a more fine-grained
   172  // ApplyProvisioner (single) that is looped over.
   173  type EvalApplyProvisioners struct {
   174  	Info           *InstanceInfo
   175  	State          **InstanceState
   176  	Resource       *config.Resource
   177  	InterpResource *Resource
   178  	CreateNew      *bool
   179  	Error          *error
   180  
   181  	// When is the type of provisioner to run at this point
   182  	When config.ProvisionerWhen
   183  }
   184  
   185  // TODO: test
   186  func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) {
   187  	state := *n.State
   188  
   189  	if n.CreateNew != nil && !*n.CreateNew {
   190  		// If we're not creating a new resource, then don't run provisioners
   191  		return nil, nil
   192  	}
   193  
   194  	provs := n.filterProvisioners()
   195  	if len(provs) == 0 {
   196  		// We have no provisioners, so don't do anything
   197  		return nil, nil
   198  	}
   199  
   200  	// taint tells us whether to enable tainting.
   201  	taint := n.When == config.ProvisionerWhenCreate
   202  
   203  	if n.Error != nil && *n.Error != nil {
   204  		if taint {
   205  			state.Tainted = true
   206  		}
   207  
   208  		// We're already tainted, so just return out
   209  		return nil, nil
   210  	}
   211  
   212  	{
   213  		// Call pre hook
   214  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   215  			return h.PreProvisionResource(n.Info, state)
   216  		})
   217  		if err != nil {
   218  			return nil, err
   219  		}
   220  	}
   221  
   222  	// If there are no errors, then we append it to our output error
   223  	// if we have one, otherwise we just output it.
   224  	err := n.apply(ctx, provs)
   225  	if err != nil {
   226  		if taint {
   227  			state.Tainted = true
   228  		}
   229  
   230  		*n.Error = multierror.Append(*n.Error, err)
   231  		return nil, err
   232  	}
   233  
   234  	{
   235  		// Call post hook
   236  		err := ctx.Hook(func(h Hook) (HookAction, error) {
   237  			return h.PostProvisionResource(n.Info, state)
   238  		})
   239  		if err != nil {
   240  			return nil, err
   241  		}
   242  	}
   243  
   244  	return nil, nil
   245  }
   246  
   247  // filterProvisioners filters the provisioners on the resource to only
   248  // the provisioners specified by the "when" option.
   249  func (n *EvalApplyProvisioners) filterProvisioners() []*config.Provisioner {
   250  	// Fast path the zero case
   251  	if n.Resource == nil {
   252  		return nil
   253  	}
   254  
   255  	if len(n.Resource.Provisioners) == 0 {
   256  		return nil
   257  	}
   258  
   259  	result := make([]*config.Provisioner, 0, len(n.Resource.Provisioners))
   260  	for _, p := range n.Resource.Provisioners {
   261  		if p.When == n.When {
   262  			result = append(result, p)
   263  		}
   264  	}
   265  
   266  	return result
   267  }
   268  
   269  func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*config.Provisioner) error {
   270  	state := *n.State
   271  
   272  	// Store the original connection info, restore later
   273  	origConnInfo := state.Ephemeral.ConnInfo
   274  	defer func() {
   275  		state.Ephemeral.ConnInfo = origConnInfo
   276  	}()
   277  
   278  	for _, prov := range provs {
   279  		// Get the provisioner
   280  		provisioner := ctx.Provisioner(prov.Type)
   281  
   282  		// Interpolate the provisioner config
   283  		provConfig, err := ctx.Interpolate(prov.RawConfig.Copy(), n.InterpResource)
   284  		if err != nil {
   285  			return err
   286  		}
   287  
   288  		// Interpolate the conn info, since it may contain variables
   289  		connInfo, err := ctx.Interpolate(prov.ConnInfo.Copy(), n.InterpResource)
   290  		if err != nil {
   291  			return err
   292  		}
   293  
   294  		// Merge the connection information
   295  		overlay := make(map[string]string)
   296  		if origConnInfo != nil {
   297  			for k, v := range origConnInfo {
   298  				overlay[k] = v
   299  			}
   300  		}
   301  		for k, v := range connInfo.Config {
   302  			switch vt := v.(type) {
   303  			case string:
   304  				overlay[k] = vt
   305  			case int64:
   306  				overlay[k] = strconv.FormatInt(vt, 10)
   307  			case int32:
   308  				overlay[k] = strconv.FormatInt(int64(vt), 10)
   309  			case int:
   310  				overlay[k] = strconv.FormatInt(int64(vt), 10)
   311  			case float32:
   312  				overlay[k] = strconv.FormatFloat(float64(vt), 'f', 3, 32)
   313  			case float64:
   314  				overlay[k] = strconv.FormatFloat(vt, 'f', 3, 64)
   315  			case bool:
   316  				overlay[k] = strconv.FormatBool(vt)
   317  			default:
   318  				overlay[k] = fmt.Sprintf("%v", vt)
   319  			}
   320  		}
   321  		state.Ephemeral.ConnInfo = overlay
   322  
   323  		{
   324  			// Call pre hook
   325  			err := ctx.Hook(func(h Hook) (HookAction, error) {
   326  				return h.PreProvision(n.Info, prov.Type)
   327  			})
   328  			if err != nil {
   329  				return err
   330  			}
   331  		}
   332  
   333  		// The output function
   334  		outputFn := func(msg string) {
   335  			ctx.Hook(func(h Hook) (HookAction, error) {
   336  				h.ProvisionOutput(n.Info, prov.Type, msg)
   337  				return HookActionContinue, nil
   338  			})
   339  		}
   340  
   341  		// Invoke the Provisioner
   342  		output := CallbackUIOutput{OutputFn: outputFn}
   343  		applyErr := provisioner.Apply(&output, state, provConfig)
   344  
   345  		// Call post hook
   346  		hookErr := ctx.Hook(func(h Hook) (HookAction, error) {
   347  			return h.PostProvision(n.Info, prov.Type, applyErr)
   348  		})
   349  
   350  		// Handle the error before we deal with the hook
   351  		if applyErr != nil {
   352  			// Determine failure behavior
   353  			switch prov.OnFailure {
   354  			case config.ProvisionerOnFailureContinue:
   355  				log.Printf(
   356  					"[INFO] apply: %s [%s]: error during provision, continue requested",
   357  					n.Info.Id, prov.Type)
   358  
   359  			case config.ProvisionerOnFailureFail:
   360  				return applyErr
   361  			}
   362  		}
   363  
   364  		// Deal with the hook
   365  		if hookErr != nil {
   366  			return hookErr
   367  		}
   368  	}
   369  
   370  	return nil
   371  
   372  }