github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/operations/resources.go (about) 1 // Copyright 2016-2018, Pulumi Corporation. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package operations 16 17 import ( 18 "sort" 19 "strings" 20 21 "github.com/hashicorp/go-multierror" 22 "github.com/pulumi/pulumi/sdk/v3/go/common/resource" 23 "github.com/pulumi/pulumi/sdk/v3/go/common/resource/config" 24 "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" 25 "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 26 ) 27 28 // Resource is a tree representation of a resource/component hierarchy 29 type Resource struct { 30 Stack tokens.QName 31 Project tokens.PackageName 32 State *resource.State 33 Parent *Resource 34 Children map[resource.URN]*Resource 35 } 36 37 // NewResourceMap constructs a map of resources with parent/child relations, indexed by URN. 38 func NewResourceMap(source []*resource.State) map[resource.URN]*Resource { 39 _, resources := makeResourceTreeMap(source) 40 return resources 41 } 42 43 // NewResourceTree constructs a tree representation of a resource/component hierarchy 44 func NewResourceTree(source []*resource.State) *Resource { 45 root, _ := makeResourceTreeMap(source) 46 return root 47 } 48 49 // makeResourceTreeMap is a helper used by the two above functions to construct a resource hierarchy. 50 func makeResourceTreeMap(source []*resource.State) (*Resource, map[resource.URN]*Resource) { 51 resources := make(map[resource.URN]*Resource) 52 53 var stack tokens.QName 54 var proj tokens.PackageName 55 56 // First create a list of resource nodes, without parent/child relations hooked up. 57 for _, state := range source { 58 stack = state.URN.Stack() 59 proj = state.URN.Project() 60 if !state.Delete { 61 // Only include resources which are not marked as pending-deletion. 62 contract.Assertf(resources[state.URN] == nil, "Unexpected duplicate resource %s", state.URN) 63 resources[state.URN] = &Resource{ 64 Stack: stack, 65 Project: proj, 66 State: state, 67 Children: make(map[resource.URN]*Resource), 68 } 69 } 70 } 71 72 // Next, walk the list of resources, and wire up parents and children. We do this in a second pass so 73 // that the creation of the tree isn't order dependent. 74 for _, child := range resources { 75 if parurn := child.State.Parent; parurn != "" { 76 parent, ok := resources[parurn] 77 contract.Assertf(ok, "Expected to find parent node '%v' in checkpoint tree nodes", parurn) 78 child.Parent = parent 79 parent.Children[child.State.URN] = child 80 } 81 } 82 83 // Create a single root node which is the parent of all unparented nodes 84 root := &Resource{ 85 Stack: stack, 86 Project: proj, 87 State: nil, 88 Parent: nil, 89 Children: make(map[resource.URN]*Resource), 90 } 91 for _, node := range resources { 92 if node.Parent == nil { 93 root.Children[node.State.URN] = node 94 node.Parent = root 95 } 96 } 97 98 // Return the root node and map of children. 99 return root, resources 100 } 101 102 // GetChild find a child with the given type and name or returns `nil`. 103 func (r *Resource) GetChild(typ string, name string) (*Resource, bool) { 104 for childURN, childResource := range r.Children { 105 if childURN.Stack() == r.Stack && 106 childURN.Project() == r.Project && 107 childURN.Type() == tokens.Type(typ) && 108 childURN.Name() == tokens.QName(name) { 109 return childResource, true 110 } 111 } 112 113 return nil, false 114 } 115 116 // OperationsProvider gets an OperationsProvider for this resource. 117 func (r *Resource) OperationsProvider(config map[config.Key]string) Provider { 118 return &resourceOperations{ 119 resource: r, 120 config: config, 121 } 122 } 123 124 // ResourceOperations is an OperationsProvider for Resources 125 type resourceOperations struct { 126 resource *Resource 127 config map[config.Key]string 128 } 129 130 var _ Provider = (*resourceOperations)(nil) 131 132 // GetLogs gets logs for a Resource 133 func (ops *resourceOperations) GetLogs(query LogQuery) (*[]LogEntry, error) { 134 if ops.resource == nil { 135 return nil, nil 136 } 137 138 // Only get logs for this resource if it matches the resource filter query 139 if ops.matchesResourceFilter(query.ResourceFilter) { 140 // Set query to be a new query with `ResourceFilter` nil so that we don't filter out logs from any children of 141 // this resource since this resource did match the resource filter. 142 query = LogQuery{ 143 StartTime: query.StartTime, 144 EndTime: query.EndTime, 145 ResourceFilter: nil, 146 } 147 // Try to get an operations provider for this resource, it may be `nil` 148 opsProvider, err := ops.getOperationsProvider() 149 if err != nil { 150 return nil, err 151 } 152 if opsProvider != nil { 153 // If this resource has an operations provider - use it and don't recur into children. It is the 154 // responsibility of it's GetLogs implementation to aggregate all logs from children, either by passing them 155 // through or by filtering specific content out. 156 logsResult, err := opsProvider.GetLogs(query) 157 if err != nil { 158 return logsResult, err 159 } 160 if logsResult != nil { 161 return logsResult, nil 162 } 163 } 164 } 165 // If this resource did not choose to provide it's own logs, recur into children and collect + aggregate their logs. 166 var logs []LogEntry 167 // Kick off GetLogs on all children in parallel, writing results to shared channels 168 ch := make(chan *[]LogEntry) 169 errch := make(chan error) 170 for _, child := range ops.resource.Children { 171 childOps := &resourceOperations{ 172 resource: child, 173 config: ops.config, 174 } 175 go func() { 176 childLogs, err := childOps.GetLogs(query) 177 ch <- childLogs 178 errch <- err 179 }() 180 } 181 // Handle results from GetLogs calls as they complete 182 var err error 183 for range ops.resource.Children { 184 childLogs := <-ch 185 childErr := <-errch 186 if childErr != nil { 187 err = multierror.Append(err, childErr) 188 } 189 if childLogs != nil { 190 logs = append(logs, *childLogs...) 191 } 192 } 193 if err != nil { 194 return &logs, err 195 } 196 // Sort 197 sort.SliceStable(logs, func(i, j int) bool { return logs[i].Timestamp < logs[j].Timestamp }) 198 // Remove duplicates 199 var retLogs []LogEntry 200 var lastLogTimestamp int64 201 var lastLogs []LogEntry 202 for _, log := range logs { 203 shouldContinue := false 204 if log.Timestamp == lastLogTimestamp { 205 for _, lastLog := range lastLogs { 206 if log.Message == lastLog.Message { 207 shouldContinue = true 208 break 209 } 210 } 211 } else { 212 lastLogs = nil 213 } 214 if shouldContinue { 215 continue 216 } 217 lastLogs = append(lastLogs, log) 218 lastLogTimestamp = log.Timestamp 219 retLogs = append(retLogs, log) 220 } 221 return &retLogs, nil 222 } 223 224 // matchesResourceFilter determines whether this resource matches the provided resource filter. 225 func (ops *resourceOperations) matchesResourceFilter(filter *ResourceFilter) bool { 226 if filter == nil { 227 // No filter, all resources match it. 228 return true 229 } 230 if ops.resource == nil || ops.resource.State == nil { 231 return false 232 } 233 urn := ops.resource.State.URN 234 if resource.URN(*filter) == urn { 235 // The filter matched the full URN 236 return true 237 } 238 if string(*filter) == string(urn.Type())+"::"+string(urn.Name()) { 239 // The filter matched the '<type>::<name>' part of the URN 240 return true 241 } 242 if tokens.QName(*filter) == urn.Name() { 243 // The filter matched the '<name>' part of the URN 244 return true 245 } 246 return false 247 } 248 249 func (ops *resourceOperations) getOperationsProvider() (Provider, error) { 250 if ops.resource == nil || ops.resource.State == nil { 251 return nil, nil 252 } 253 254 tokenSeparators := strings.Count(ops.resource.State.Type.String(), ":") 255 if tokenSeparators != 2 { 256 return nil, nil 257 } 258 259 switch ops.resource.State.Type.Package() { 260 case "cloud": 261 return CloudOperationsProvider(ops.config, ops.resource) 262 case "aws": 263 return AWSOperationsProvider(ops.config, ops.resource) 264 case "gcp": 265 return GCPOperationsProvider(ops.config, ops.resource) 266 default: 267 return nil, nil 268 } 269 }