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  }