github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/terraform/node_data_refresh.go (about)

     1  package terraform
     2  
     3  import (
     4  	"github.com/hashicorp/terraform/dag"
     5  )
     6  
     7  // NodeRefreshableDataResource represents a resource that is "plannable":
     8  // it is ready to be planned in order to create a diff.
     9  type NodeRefreshableDataResource struct {
    10  	*NodeAbstractCountResource
    11  }
    12  
    13  // GraphNodeDynamicExpandable
    14  func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
    15  	// Grab the state which we read
    16  	state, lock := ctx.State()
    17  	lock.RLock()
    18  	defer lock.RUnlock()
    19  
    20  	// Expand the resource count which must be available by now from EvalTree
    21  	count, err := n.Config.Count()
    22  	if err != nil {
    23  		return nil, err
    24  	}
    25  
    26  	// The concrete resource factory we'll use
    27  	concreteResource := func(a *NodeAbstractResource) dag.Vertex {
    28  		// Add the config and state since we don't do that via transforms
    29  		a.Config = n.Config
    30  
    31  		return &NodeRefreshableDataResourceInstance{
    32  			NodeAbstractResource: a,
    33  		}
    34  	}
    35  
    36  	// We also need a destroyable resource for orphans that are a result of a
    37  	// scaled-in count.
    38  	concreteResourceDestroyable := func(a *NodeAbstractResource) dag.Vertex {
    39  		// Add the config since we don't do that via transforms
    40  		a.Config = n.Config
    41  
    42  		return &NodeDestroyableDataResource{
    43  			NodeAbstractResource: a,
    44  		}
    45  	}
    46  
    47  	// Start creating the steps
    48  	steps := []GraphTransformer{
    49  		// Expand the count.
    50  		&ResourceCountTransformer{
    51  			Concrete: concreteResource,
    52  			Count:    count,
    53  			Addr:     n.ResourceAddr(),
    54  		},
    55  
    56  		// Add the count orphans. As these are orphaned refresh nodes, we add them
    57  		// directly as NodeDestroyableDataResource.
    58  		&OrphanResourceCountTransformer{
    59  			Concrete: concreteResourceDestroyable,
    60  			Count:    count,
    61  			Addr:     n.ResourceAddr(),
    62  			State:    state,
    63  		},
    64  
    65  		// Attach the state
    66  		&AttachStateTransformer{State: state},
    67  
    68  		// Targeting
    69  		&TargetsTransformer{ParsedTargets: n.Targets},
    70  
    71  		// Connect references so ordering is correct
    72  		&ReferenceTransformer{},
    73  
    74  		// Make sure there is a single root
    75  		&RootTransformer{},
    76  	}
    77  
    78  	// Build the graph
    79  	b := &BasicGraphBuilder{
    80  		Steps:    steps,
    81  		Validate: true,
    82  		Name:     "NodeRefreshableDataResource",
    83  	}
    84  
    85  	return b.Build(ctx.Path())
    86  }
    87  
    88  // NodeRefreshableDataResourceInstance represents a _single_ resource instance
    89  // that is refreshable.
    90  type NodeRefreshableDataResourceInstance struct {
    91  	*NodeAbstractResource
    92  }
    93  
    94  // GraphNodeEvalable
    95  func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode {
    96  	addr := n.NodeAbstractResource.Addr
    97  
    98  	// stateId is the ID to put into the state
    99  	stateId := addr.stateId()
   100  
   101  	// Build the instance info. More of this will be populated during eval
   102  	info := &InstanceInfo{
   103  		Id:   stateId,
   104  		Type: addr.Type,
   105  	}
   106  
   107  	// Get the state if we have it, if not we build it
   108  	rs := n.ResourceState
   109  	if rs == nil {
   110  		rs = &ResourceState{}
   111  	}
   112  
   113  	// If the config isn't empty we update the state
   114  	if n.Config != nil {
   115  		rs = &ResourceState{
   116  			Type:         n.Config.Type,
   117  			Provider:     n.Config.Provider,
   118  			Dependencies: n.StateReferences(),
   119  		}
   120  	}
   121  
   122  	// Build the resource for eval
   123  	resource := &Resource{
   124  		Name:       addr.Name,
   125  		Type:       addr.Type,
   126  		CountIndex: addr.Index,
   127  	}
   128  	if resource.CountIndex < 0 {
   129  		resource.CountIndex = 0
   130  	}
   131  
   132  	// Declare a bunch of variables that are used for state during
   133  	// evaluation. Most of this are written to by-address below.
   134  	var config *ResourceConfig
   135  	var diff *InstanceDiff
   136  	var provider ResourceProvider
   137  	var state *InstanceState
   138  
   139  	return &EvalSequence{
   140  		Nodes: []EvalNode{
   141  			// Always destroy the existing state first, since we must
   142  			// make sure that values from a previous read will not
   143  			// get interpolated if we end up needing to defer our
   144  			// loading until apply time.
   145  			&EvalWriteState{
   146  				Name:         stateId,
   147  				ResourceType: rs.Type,
   148  				Provider:     rs.Provider,
   149  				Dependencies: rs.Dependencies,
   150  				State:        &state, // state is nil here
   151  			},
   152  
   153  			&EvalInterpolate{
   154  				Config:   n.Config.RawConfig.Copy(),
   155  				Resource: resource,
   156  				Output:   &config,
   157  			},
   158  
   159  			// The rest of this pass can proceed only if there are no
   160  			// computed values in our config.
   161  			// (If there are, we'll deal with this during the plan and
   162  			// apply phases.)
   163  			&EvalIf{
   164  				If: func(ctx EvalContext) (bool, error) {
   165  					if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 {
   166  						return true, EvalEarlyExitError{}
   167  					}
   168  
   169  					// If the config explicitly has a depends_on for this
   170  					// data source, assume the intention is to prevent
   171  					// refreshing ahead of that dependency.
   172  					if len(n.Config.DependsOn) > 0 {
   173  						return true, EvalEarlyExitError{}
   174  					}
   175  
   176  					return true, nil
   177  				},
   178  
   179  				Then: EvalNoop{},
   180  			},
   181  
   182  			// The remainder of this pass is the same as running
   183  			// a "plan" pass immediately followed by an "apply" pass,
   184  			// populating the state early so it'll be available to
   185  			// provider configurations that need this data during
   186  			// refresh/plan.
   187  			&EvalGetProvider{
   188  				Name:   n.ProvidedBy()[0],
   189  				Output: &provider,
   190  			},
   191  
   192  			&EvalReadDataDiff{
   193  				Info:        info,
   194  				Config:      &config,
   195  				Provider:    &provider,
   196  				Output:      &diff,
   197  				OutputState: &state,
   198  			},
   199  
   200  			&EvalReadDataApply{
   201  				Info:     info,
   202  				Diff:     &diff,
   203  				Provider: &provider,
   204  				Output:   &state,
   205  			},
   206  
   207  			&EvalWriteState{
   208  				Name:         stateId,
   209  				ResourceType: rs.Type,
   210  				Provider:     rs.Provider,
   211  				Dependencies: rs.Dependencies,
   212  				State:        &state,
   213  			},
   214  
   215  			&EvalUpdateStateHook{},
   216  		},
   217  	}
   218  }