| package terraform |
| |
| import ( |
| "fmt" |
| "sort" |
| ) |
| |
| // StateFilter is responsible for filtering and searching a state. |
| // |
| // This is a separate struct from State rather than a method on State |
| // because StateFilter might create sidecar data structures to optimize |
| // filtering on the state. |
| // |
| // If you change the State, the filter created is invalid and either |
| // Reset should be called or a new one should be allocated. StateFilter |
| // will not watch State for changes and do this for you. If you filter after |
| // changing the State without calling Reset, the behavior is not defined. |
| type StateFilter struct { |
| State *State |
| } |
| |
| // Filter takes the addresses specified by fs and finds all the matches. |
| // The values of fs are resource addressing syntax that can be parsed by |
| // ParseResourceAddress. |
| func (f *StateFilter) Filter(fs ...string) ([]*StateFilterResult, error) { |
| // Parse all the addresses |
| as := make([]*ResourceAddress, len(fs)) |
| for i, v := range fs { |
| a, err := ParseResourceAddress(v) |
| if err != nil { |
| return nil, fmt.Errorf("Error parsing address '%s': %s", v, err) |
| } |
| |
| as[i] = a |
| } |
| |
| // If we weren't given any filters, then we list all |
| if len(fs) == 0 { |
| as = append(as, &ResourceAddress{Index: -1}) |
| } |
| |
| // Filter each of the address. We keep track of this in a map to |
| // strip duplicates. |
| resultSet := make(map[string]*StateFilterResult) |
| for _, a := range as { |
| for _, r := range f.filterSingle(a) { |
| resultSet[r.String()] = r |
| } |
| } |
| |
| // Make the result list |
| results := make([]*StateFilterResult, 0, len(resultSet)) |
| for _, v := range resultSet { |
| results = append(results, v) |
| } |
| |
| // Sort them and return |
| sort.Sort(StateFilterResultSlice(results)) |
| return results, nil |
| } |
| |
| func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult { |
| // The slice to keep track of results |
| var results []*StateFilterResult |
| |
| // Go through modules first. |
| modules := make([]*ModuleState, 0, len(f.State.Modules)) |
| for _, m := range f.State.Modules { |
| if f.relevant(a, m) { |
| modules = append(modules, m) |
| |
| // Only add the module to the results if we haven't specified a type. |
| // We also ignore the root module. |
| if a.Type == "" && len(m.Path) > 1 { |
| results = append(results, &StateFilterResult{ |
| Path: m.Path[1:], |
| Address: (&ResourceAddress{Path: m.Path[1:]}).String(), |
| Value: m, |
| }) |
| } |
| } |
| } |
| |
| // With the modules set, go through all the resources within |
| // the modules to find relevant resources. |
| for _, m := range modules { |
| for n, r := range m.Resources { |
| // The name in the state contains valuable information. Parse. |
| key, err := ParseResourceStateKey(n) |
| if err != nil { |
| // If we get an error parsing, then just ignore it |
| // out of the state. |
| continue |
| } |
| |
| // Older states and test fixtures often don't contain the |
| // type directly on the ResourceState. We add this so StateFilter |
| // is a bit more robust. |
| if r.Type == "" { |
| r.Type = key.Type |
| } |
| |
| if f.relevant(a, r) { |
| if a.Name != "" && a.Name != key.Name { |
| // Name doesn't match |
| continue |
| } |
| |
| if a.Index >= 0 && key.Index != a.Index { |
| // Index doesn't match |
| continue |
| } |
| |
| if a.Name != "" && a.Name != key.Name { |
| continue |
| } |
| |
| // Build the address for this resource |
| addr := &ResourceAddress{ |
| Path: m.Path[1:], |
| Name: key.Name, |
| Type: key.Type, |
| Index: key.Index, |
| } |
| |
| // Add the resource level result |
| resourceResult := &StateFilterResult{ |
| Path: addr.Path, |
| Address: addr.String(), |
| Value: r, |
| } |
| if !a.InstanceTypeSet { |
| results = append(results, resourceResult) |
| } |
| |
| // Add the instances |
| if r.Primary != nil { |
| addr.InstanceType = TypePrimary |
| addr.InstanceTypeSet = false |
| results = append(results, &StateFilterResult{ |
| Path: addr.Path, |
| Address: addr.String(), |
| Parent: resourceResult, |
| Value: r.Primary, |
| }) |
| } |
| |
| for _, instance := range r.Deposed { |
| if f.relevant(a, instance) { |
| addr.InstanceType = TypeDeposed |
| addr.InstanceTypeSet = true |
| results = append(results, &StateFilterResult{ |
| Path: addr.Path, |
| Address: addr.String(), |
| Parent: resourceResult, |
| Value: instance, |
| }) |
| } |
| } |
| } |
| } |
| } |
| |
| return results |
| } |
| |
| // relevant checks for relevance of this address against the given value. |
| func (f *StateFilter) relevant(addr *ResourceAddress, raw interface{}) bool { |
| switch v := raw.(type) { |
| case *ModuleState: |
| path := v.Path[1:] |
| |
| if len(addr.Path) > len(path) { |
| // Longer path in address means there is no way we match. |
| return false |
| } |
| |
| // Check for a prefix match |
| for i, p := range addr.Path { |
| if path[i] != p { |
| // Any mismatches don't match. |
| return false |
| } |
| } |
| |
| return true |
| case *ResourceState: |
| if addr.Type == "" { |
| // If we have no resource type, then we're interested in all! |
| return true |
| } |
| |
| // If the type doesn't match we fail immediately |
| if v.Type != addr.Type { |
| return false |
| } |
| |
| return true |
| default: |
| // If we don't know about it, let's just say no |
| return false |
| } |
| } |
| |
| // StateFilterResult is a single result from a filter operation. Filter |
| // can match multiple things within a state (module, resource, instance, etc.) |
| // and this unifies that. |
| type StateFilterResult struct { |
| // Module path of the result |
| Path []string |
| |
| // Address is the address that can be used to reference this exact result. |
| Address string |
| |
| // Parent, if non-nil, is a parent of this result. For instances, the |
| // parent would be a resource. For resources, the parent would be |
| // a module. For modules, this is currently nil. |
| Parent *StateFilterResult |
| |
| // Value is the actual value. This must be type switched on. It can be |
| // any data structures that `State` can hold: `ModuleState`, |
| // `ResourceState`, `InstanceState`. |
| Value interface{} |
| } |
| |
| func (r *StateFilterResult) String() string { |
| return fmt.Sprintf("%T: %s", r.Value, r.Address) |
| } |
| |
| func (r *StateFilterResult) sortedType() int { |
| switch r.Value.(type) { |
| case *ModuleState: |
| return 0 |
| case *ResourceState: |
| return 1 |
| case *InstanceState: |
| return 2 |
| default: |
| return 50 |
| } |
| } |
| |
| // StateFilterResultSlice is a slice of results that implements |
| // sort.Interface. The sorting goal is what is most appealing to |
| // human output. |
| type StateFilterResultSlice []*StateFilterResult |
| |
| func (s StateFilterResultSlice) Len() int { return len(s) } |
| func (s StateFilterResultSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
| func (s StateFilterResultSlice) Less(i, j int) bool { |
| a, b := s[i], s[j] |
| |
| // if these address contain an index, we want to sort by index rather than name |
| addrA, errA := ParseResourceAddress(a.Address) |
| addrB, errB := ParseResourceAddress(b.Address) |
| if errA == nil && errB == nil && addrA.Name == addrB.Name && addrA.Index != addrB.Index { |
| return addrA.Index < addrB.Index |
| } |
| |
| // If the addresses are different it is just lexographic sorting |
| if a.Address != b.Address { |
| return a.Address < b.Address |
| } |
| |
| // Addresses are the same, which means it matters on the type |
| return a.sortedType() < b.sortedType() |
| } |