github.com/hooklift/terraform@v0.11.0-beta1.0.20171117000744-6786c1361ffe/terraform/eval_state.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  )
     6  
     7  // EvalReadState is an EvalNode implementation that reads the
     8  // primary InstanceState for a specific resource out of the state.
     9  type EvalReadState struct {
    10  	Name   string
    11  	Output **InstanceState
    12  }
    13  
    14  func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) {
    15  	return readInstanceFromState(ctx, n.Name, n.Output, func(rs *ResourceState) (*InstanceState, error) {
    16  		return rs.Primary, nil
    17  	})
    18  }
    19  
    20  // EvalReadStateDeposed is an EvalNode implementation that reads the
    21  // deposed InstanceState for a specific resource out of the state
    22  type EvalReadStateDeposed struct {
    23  	Name   string
    24  	Output **InstanceState
    25  	// Index indicates which instance in the Deposed list to target, or -1 for
    26  	// the last item.
    27  	Index int
    28  }
    29  
    30  func (n *EvalReadStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
    31  	return readInstanceFromState(ctx, n.Name, n.Output, func(rs *ResourceState) (*InstanceState, error) {
    32  		// Get the index. If it is negative, then we get the last one
    33  		idx := n.Index
    34  		if idx < 0 {
    35  			idx = len(rs.Deposed) - 1
    36  		}
    37  		if idx >= 0 && idx < len(rs.Deposed) {
    38  			return rs.Deposed[idx], nil
    39  		} else {
    40  			return nil, fmt.Errorf("bad deposed index: %d, for resource: %#v", idx, rs)
    41  		}
    42  	})
    43  }
    44  
    45  // Does the bulk of the work for the various flavors of ReadState eval nodes.
    46  // Each node just provides a reader function to get from the ResourceState to the
    47  // InstanceState, and this takes care of all the plumbing.
    48  func readInstanceFromState(
    49  	ctx EvalContext,
    50  	resourceName string,
    51  	output **InstanceState,
    52  	readerFn func(*ResourceState) (*InstanceState, error),
    53  ) (*InstanceState, error) {
    54  	state, lock := ctx.State()
    55  
    56  	// Get a read lock so we can access this instance
    57  	lock.RLock()
    58  	defer lock.RUnlock()
    59  
    60  	// Look for the module state. If we don't have one, then it doesn't matter.
    61  	mod := state.ModuleByPath(ctx.Path())
    62  	if mod == nil {
    63  		return nil, nil
    64  	}
    65  
    66  	// Look for the resource state. If we don't have one, then it is okay.
    67  	rs := mod.Resources[resourceName]
    68  	if rs == nil {
    69  		return nil, nil
    70  	}
    71  
    72  	// Use the delegate function to get the instance state from the resource state
    73  	is, err := readerFn(rs)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	// Write the result to the output pointer
    79  	if output != nil {
    80  		*output = is
    81  	}
    82  
    83  	return is, nil
    84  }
    85  
    86  // EvalRequireState is an EvalNode implementation that early exits
    87  // if the state doesn't have an ID.
    88  type EvalRequireState struct {
    89  	State **InstanceState
    90  }
    91  
    92  func (n *EvalRequireState) Eval(ctx EvalContext) (interface{}, error) {
    93  	if n.State == nil {
    94  		return nil, EvalEarlyExitError{}
    95  	}
    96  
    97  	state := *n.State
    98  	if state == nil || state.ID == "" {
    99  		return nil, EvalEarlyExitError{}
   100  	}
   101  
   102  	return nil, nil
   103  }
   104  
   105  // EvalUpdateStateHook is an EvalNode implementation that calls the
   106  // PostStateUpdate hook with the current state.
   107  type EvalUpdateStateHook struct{}
   108  
   109  func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) {
   110  	state, lock := ctx.State()
   111  
   112  	// Get a full lock. Even calling something like WriteState can modify
   113  	// (prune) the state, so we need the full lock.
   114  	lock.Lock()
   115  	defer lock.Unlock()
   116  
   117  	// Call the hook
   118  	err := ctx.Hook(func(h Hook) (HookAction, error) {
   119  		return h.PostStateUpdate(state)
   120  	})
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	return nil, nil
   126  }
   127  
   128  // EvalWriteState is an EvalNode implementation that writes the
   129  // primary InstanceState for a specific resource into the state.
   130  type EvalWriteState struct {
   131  	Name         string
   132  	ResourceType string
   133  	Provider     string
   134  	Dependencies []string
   135  	State        **InstanceState
   136  }
   137  
   138  func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
   139  	return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Provider, n.Dependencies,
   140  		func(rs *ResourceState) error {
   141  			rs.Primary = *n.State
   142  			return nil
   143  		},
   144  	)
   145  }
   146  
   147  // EvalWriteStateDeposed is an EvalNode implementation that writes
   148  // an InstanceState out to the Deposed list of a resource in the state.
   149  type EvalWriteStateDeposed struct {
   150  	Name         string
   151  	ResourceType string
   152  	Provider     string
   153  	Dependencies []string
   154  	State        **InstanceState
   155  	// Index indicates which instance in the Deposed list to target, or -1 to append.
   156  	Index int
   157  }
   158  
   159  func (n *EvalWriteStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
   160  	return writeInstanceToState(ctx, n.Name, n.ResourceType, n.Provider, n.Dependencies,
   161  		func(rs *ResourceState) error {
   162  			if n.Index == -1 {
   163  				rs.Deposed = append(rs.Deposed, *n.State)
   164  			} else {
   165  				rs.Deposed[n.Index] = *n.State
   166  			}
   167  			return nil
   168  		},
   169  	)
   170  }
   171  
   172  // Pulls together the common tasks of the EvalWriteState nodes.  All the args
   173  // are passed directly down from the EvalNode along with a `writer` function
   174  // which is yielded the *ResourceState and is responsible for writing an
   175  // InstanceState to the proper field in the ResourceState.
   176  func writeInstanceToState(
   177  	ctx EvalContext,
   178  	resourceName string,
   179  	resourceType string,
   180  	provider string,
   181  	dependencies []string,
   182  	writerFn func(*ResourceState) error,
   183  ) (*InstanceState, error) {
   184  	state, lock := ctx.State()
   185  	if state == nil {
   186  		return nil, fmt.Errorf("cannot write state to nil state")
   187  	}
   188  
   189  	// Get a write lock so we can access this instance
   190  	lock.Lock()
   191  	defer lock.Unlock()
   192  
   193  	// Look for the module state. If we don't have one, create it.
   194  	mod := state.ModuleByPath(ctx.Path())
   195  	if mod == nil {
   196  		mod = state.AddModule(ctx.Path())
   197  	}
   198  
   199  	// Look for the resource state.
   200  	rs := mod.Resources[resourceName]
   201  	if rs == nil {
   202  		rs = &ResourceState{}
   203  		rs.init()
   204  		mod.Resources[resourceName] = rs
   205  	}
   206  	rs.Type = resourceType
   207  	rs.Dependencies = dependencies
   208  	rs.Provider = provider
   209  
   210  	if err := writerFn(rs); err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	return nil, nil
   215  }
   216  
   217  // EvalDeposeState is an EvalNode implementation that takes the primary
   218  // out of a state and makes it Deposed. This is done at the beginning of
   219  // create-before-destroy calls so that the create can create while preserving
   220  // the old state of the to-be-destroyed resource.
   221  type EvalDeposeState struct {
   222  	Name string
   223  }
   224  
   225  // TODO: test
   226  func (n *EvalDeposeState) Eval(ctx EvalContext) (interface{}, error) {
   227  	state, lock := ctx.State()
   228  
   229  	// Get a read lock so we can access this instance
   230  	lock.RLock()
   231  	defer lock.RUnlock()
   232  
   233  	// Look for the module state. If we don't have one, then it doesn't matter.
   234  	mod := state.ModuleByPath(ctx.Path())
   235  	if mod == nil {
   236  		return nil, nil
   237  	}
   238  
   239  	// Look for the resource state. If we don't have one, then it is okay.
   240  	rs := mod.Resources[n.Name]
   241  	if rs == nil {
   242  		return nil, nil
   243  	}
   244  
   245  	// If we don't have a primary, we have nothing to depose
   246  	if rs.Primary == nil {
   247  		return nil, nil
   248  	}
   249  
   250  	// Depose
   251  	rs.Deposed = append(rs.Deposed, rs.Primary)
   252  	rs.Primary = nil
   253  
   254  	return nil, nil
   255  }
   256  
   257  // EvalUndeposeState is an EvalNode implementation that reads the
   258  // InstanceState for a specific resource out of the state.
   259  type EvalUndeposeState struct {
   260  	Name  string
   261  	State **InstanceState
   262  }
   263  
   264  // TODO: test
   265  func (n *EvalUndeposeState) Eval(ctx EvalContext) (interface{}, error) {
   266  	state, lock := ctx.State()
   267  
   268  	// Get a read lock so we can access this instance
   269  	lock.RLock()
   270  	defer lock.RUnlock()
   271  
   272  	// Look for the module state. If we don't have one, then it doesn't matter.
   273  	mod := state.ModuleByPath(ctx.Path())
   274  	if mod == nil {
   275  		return nil, nil
   276  	}
   277  
   278  	// Look for the resource state. If we don't have one, then it is okay.
   279  	rs := mod.Resources[n.Name]
   280  	if rs == nil {
   281  		return nil, nil
   282  	}
   283  
   284  	// If we don't have any desposed resource, then we don't have anything to do
   285  	if len(rs.Deposed) == 0 {
   286  		return nil, nil
   287  	}
   288  
   289  	// Undepose
   290  	idx := len(rs.Deposed) - 1
   291  	rs.Primary = rs.Deposed[idx]
   292  	rs.Deposed[idx] = *n.State
   293  
   294  	return nil, nil
   295  }