github.com/SamarSidharth/kpt@v0.0.0-20231122062228-c7d747ae3ace/internal/alpha/printers/table/collector.go (about)

     1  // Copyright 2020 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package table
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"sort"
    10  	"sync"
    11  
    12  	"k8s.io/klog/v2"
    13  	"sigs.k8s.io/cli-utils/pkg/apply/event"
    14  	pe "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
    15  	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
    16  	"sigs.k8s.io/cli-utils/pkg/object"
    17  	"sigs.k8s.io/cli-utils/pkg/object/validation"
    18  	"sigs.k8s.io/cli-utils/pkg/print/stats"
    19  	"sigs.k8s.io/cli-utils/pkg/print/table"
    20  )
    21  
    22  const InvalidStatus status.Status = "Invalid"
    23  
    24  func newResourceStateCollector(resourceGroups []event.ActionGroup, _ io.Writer) *resourceStateCollector {
    25  	resourceInfos := make(map[object.ObjMetadata]*resourceInfo)
    26  	for _, group := range resourceGroups {
    27  		action := group.Action
    28  		// Keep the action that describes the operation for the resource
    29  		// rather than that we will wait for it.
    30  		if action == event.WaitAction {
    31  			continue
    32  		}
    33  		for _, identifier := range group.Identifiers {
    34  			resourceInfos[identifier] = &resourceInfo{
    35  				identifier: identifier,
    36  				resourceStatus: &pe.ResourceStatus{
    37  					Identifier: identifier,
    38  					Status:     status.UnknownStatus,
    39  				},
    40  				ResourceAction: action,
    41  			}
    42  		}
    43  	}
    44  	var ids []object.ObjMetadata
    45  	for id := range resourceInfos {
    46  		ids = append(ids, id)
    47  	}
    48  	sort.Slice(ids, func(i, j int) bool {
    49  		if ids[i].Namespace != ids[j].Namespace {
    50  			return ids[i].Namespace < ids[j].Namespace
    51  		}
    52  		if ids[i].GroupKind.Group != ids[j].GroupKind.Group {
    53  			return ids[i].GroupKind.Group < ids[j].GroupKind.Group
    54  		}
    55  		if ids[i].GroupKind.Kind != ids[j].GroupKind.Kind {
    56  			return ids[i].GroupKind.Kind < ids[j].GroupKind.Kind
    57  		}
    58  		if ids[i].Name != ids[j].Name {
    59  			return ids[i].Name < ids[j].Name
    60  		}
    61  		return false
    62  	})
    63  	c := &resourceStateCollector{
    64  		resourceInfos: resourceInfos,
    65  	}
    66  	return c
    67  }
    68  
    69  // resourceStateCollector consumes the events from the applier
    70  // eventChannel and keeps track of the latest state for all resources.
    71  // It also provides functionality for fetching the latest seen
    72  // state and return it in format that can be used by the
    73  // BaseTablePrinter.
    74  type resourceStateCollector struct {
    75  	mux sync.RWMutex
    76  
    77  	// resourceInfos contains a mapping from the unique
    78  	// resource identifier to a ResourceInfo object that captures
    79  	// the latest state for the given resource.
    80  	resourceInfos map[object.ObjMetadata]*resourceInfo
    81  
    82  	// stats collect statistics from handled events
    83  	stats stats.Stats
    84  
    85  	err error
    86  }
    87  
    88  // resourceInfo captures the latest seen state of a single resource.
    89  // This is used for top-level resources that have a ResourceAction
    90  // associated with them.
    91  type resourceInfo struct {
    92  	// identifier contains the information that identifies a
    93  	// single resource.
    94  	identifier object.ObjMetadata
    95  
    96  	// resourceStatus contains the latest status information
    97  	// about the resource.
    98  	resourceStatus *pe.ResourceStatus
    99  
   100  	// ResourceAction defines the action we are performing
   101  	// on this particular resource. This can be either Apply
   102  	// or Prune.
   103  	ResourceAction event.ResourceAction
   104  
   105  	// Error is set if an error occurred trying to perform
   106  	// the desired action on the resource.
   107  	// Error error
   108  
   109  	// lastApplyEvent contains the result after
   110  	// a resource has been applied to the cluster.
   111  	lastApplyEvent event.ApplyEvent
   112  
   113  	// lastPruneEvent contains the result after
   114  	// a prune operation on a resource
   115  	lastPruneEvent event.PruneEvent
   116  
   117  	// lastDeleteEvent contains the result after
   118  	// a delete operation on a resource
   119  	lastDeleteEvent event.DeleteEvent
   120  
   121  	// WaitStatus contains the result after
   122  	// a wait operation on a resource
   123  	WaitStatus event.WaitEventStatus
   124  }
   125  
   126  // Identifier returns the identifier for the given resource.
   127  func (r *resourceInfo) Identifier() object.ObjMetadata {
   128  	return r.identifier
   129  }
   130  
   131  // ResourceStatus returns the latest seen status for the
   132  // resource.
   133  func (r *resourceInfo) ResourceStatus() *pe.ResourceStatus {
   134  	return r.resourceStatus
   135  }
   136  
   137  // SubResources returns a slice of Resource which contains
   138  // any resources created and managed by this resource.
   139  func (r *resourceInfo) SubResources() []table.Resource {
   140  	var resources []table.Resource
   141  	for _, res := range r.resourceStatus.GeneratedResources {
   142  		resources = append(resources, &subResourceInfo{
   143  			resourceStatus: res,
   144  		})
   145  	}
   146  	return resources
   147  }
   148  
   149  // subResourceInfo captures the latest seen state of a
   150  // single subResource, i.e. resources that are created and
   151  // managed by one of the top-level resources we either apply
   152  // or prune.
   153  type subResourceInfo struct {
   154  	// resourceStatus contains the latest status information
   155  	// about the subResource.
   156  	resourceStatus *pe.ResourceStatus
   157  }
   158  
   159  // Identifier returns the identifier for the given subResource.
   160  func (r *subResourceInfo) Identifier() object.ObjMetadata {
   161  	return r.resourceStatus.Identifier
   162  }
   163  
   164  // ResourceStatus returns the latest seen status for the
   165  // subResource.
   166  func (r *subResourceInfo) ResourceStatus() *pe.ResourceStatus {
   167  	return r.resourceStatus
   168  }
   169  
   170  // SubResources returns a slice of Resource which contains
   171  // any resources created and managed by this resource.
   172  func (r *subResourceInfo) SubResources() []table.Resource {
   173  	var resources []table.Resource
   174  	for _, res := range r.resourceStatus.GeneratedResources {
   175  		resources = append(resources, &subResourceInfo{
   176  			resourceStatus: res,
   177  		})
   178  	}
   179  	return resources
   180  }
   181  
   182  // Listen starts a new goroutine that will listen for events on the
   183  // provided eventChannel and keep track of the latest state for
   184  // the resources. The goroutine will exit when the provided
   185  // eventChannel is closed.
   186  // The function returns a channel. When this channel is closed, the
   187  // goroutine has processed all events in the eventChannel and
   188  // exited.
   189  func (r *resourceStateCollector) Listen(eventChannel <-chan event.Event) <-chan listenerResult {
   190  	completed := make(chan listenerResult)
   191  	go func() {
   192  		defer close(completed)
   193  		for ev := range eventChannel {
   194  			if err := r.processEvent(ev); err != nil {
   195  				completed <- listenerResult{err: err}
   196  				return
   197  			}
   198  		}
   199  	}()
   200  	return completed
   201  }
   202  
   203  type listenerResult struct {
   204  	err error
   205  }
   206  
   207  // processEvent processes an event and updates the state.
   208  func (r *resourceStateCollector) processEvent(ev event.Event) error {
   209  	r.mux.Lock()
   210  	defer r.mux.Unlock()
   211  	switch ev.Type {
   212  	case event.ValidationType:
   213  		return r.processValidationEvent(ev.ValidationEvent)
   214  	case event.StatusType:
   215  		r.processStatusEvent(ev.StatusEvent)
   216  	case event.ApplyType:
   217  		r.processApplyEvent(ev.ApplyEvent)
   218  	case event.PruneType:
   219  		r.processPruneEvent(ev.PruneEvent)
   220  	case event.DeleteType:
   221  		r.processDeleteEvent(ev.DeleteEvent)
   222  	case event.WaitType:
   223  		r.processWaitEvent(ev.WaitEvent)
   224  	case event.ErrorType:
   225  		return ev.ErrorEvent.Err
   226  	}
   227  	return nil
   228  }
   229  
   230  // processValidationEvent handles events pertaining to a validation error
   231  // for a resource.
   232  func (r *resourceStateCollector) processValidationEvent(e event.ValidationEvent) error {
   233  	klog.V(7).Infoln("processing validation event")
   234  	// unwrap validation errors
   235  	err := e.Error
   236  	if vErr, ok := err.(*validation.Error); ok {
   237  		err = vErr.Unwrap()
   238  	}
   239  	if len(e.Identifiers) == 0 {
   240  		// no objects, invalid event
   241  		return fmt.Errorf("invalid validation event: no identifiers: %w", err)
   242  	}
   243  	for _, id := range e.Identifiers {
   244  		previous, found := r.resourceInfos[id]
   245  		if !found {
   246  			klog.V(4).Infof("%s status event not found in ResourceInfos; no processing", id)
   247  			continue
   248  		}
   249  		previous.resourceStatus = &pe.ResourceStatus{
   250  			Identifier: id,
   251  			Status:     InvalidStatus,
   252  			Message:    e.Error.Error(),
   253  		}
   254  	}
   255  	return nil
   256  }
   257  
   258  // processStatusEvent handles events pertaining to a status
   259  // update for a resource.
   260  func (r *resourceStateCollector) processStatusEvent(e event.StatusEvent) {
   261  	klog.V(7).Infoln("processing status event")
   262  	previous, found := r.resourceInfos[e.Identifier]
   263  	if !found {
   264  		klog.V(4).Infof("%s status event not found in ResourceInfos; no processing", e.Identifier)
   265  		return
   266  	}
   267  	previous.resourceStatus = e.PollResourceInfo
   268  }
   269  
   270  // processApplyEvent handles events relating to apply operations
   271  func (r *resourceStateCollector) processApplyEvent(e event.ApplyEvent) {
   272  	identifier := e.Identifier
   273  	klog.V(7).Infof("processing apply event for %s", identifier)
   274  	previous, found := r.resourceInfos[identifier]
   275  	if !found {
   276  		klog.V(4).Infof("%s apply event not found in ResourceInfos; no processing", identifier)
   277  		return
   278  	}
   279  	previous.lastApplyEvent = e
   280  
   281  	r.stats.ApplyStats.Inc(e.Status)
   282  }
   283  
   284  // processPruneEvent handles event related to prune operations.
   285  func (r *resourceStateCollector) processPruneEvent(e event.PruneEvent) {
   286  	identifier := e.Identifier
   287  	klog.V(7).Infof("processing prune event for %s", identifier)
   288  	previous, found := r.resourceInfos[identifier]
   289  	if !found {
   290  		klog.V(4).Infof("%s prune event not found in ResourceInfos; no processing", identifier)
   291  		return
   292  	}
   293  
   294  	previous.lastPruneEvent = e
   295  
   296  	r.stats.PruneStats.Inc(e.Status)
   297  }
   298  
   299  // processDeleteEvent handles event related to delete operations.
   300  func (r *resourceStateCollector) processDeleteEvent(e event.DeleteEvent) {
   301  	identifier := e.Identifier
   302  	klog.V(7).Infof("processing delete event for %s", identifier)
   303  	previous, found := r.resourceInfos[identifier]
   304  	if !found {
   305  		klog.V(4).Infof("%s delete event not found in ResourceInfos; no processing", identifier)
   306  		return
   307  	}
   308  	previous.lastDeleteEvent = e
   309  
   310  	r.stats.DeleteStats.Inc(e.Status)
   311  }
   312  
   313  // processPruneEvent handles event related to prune operations.
   314  func (r *resourceStateCollector) processWaitEvent(e event.WaitEvent) {
   315  	identifier := e.Identifier
   316  	klog.V(7).Infof("processing wait event for %s", identifier)
   317  	previous, found := r.resourceInfos[identifier]
   318  	if !found {
   319  		klog.V(4).Infof("%s wait event not found in ResourceInfos; no processing", identifier)
   320  		return
   321  	}
   322  	previous.WaitStatus = e.Status
   323  	r.stats.WaitStats.Inc(e.Status)
   324  }
   325  
   326  // ResourceState contains the latest state for all the resources.
   327  type ResourceState struct {
   328  	resourceInfos ResourceInfos
   329  
   330  	err error
   331  }
   332  
   333  // Resources returns a slice containing the latest state
   334  // for each individual resource.
   335  func (r *ResourceState) Resources() []table.Resource {
   336  	var resources []table.Resource
   337  	for _, res := range r.resourceInfos {
   338  		resources = append(resources, res)
   339  	}
   340  	return resources
   341  }
   342  
   343  func (r *ResourceState) Error() error {
   344  	return r.err
   345  }
   346  
   347  // LatestState returns a ResourceState object that contains
   348  // a copy of the latest state for all resources.
   349  func (r *resourceStateCollector) LatestState() *ResourceState {
   350  	r.mux.RLock()
   351  	defer r.mux.RUnlock()
   352  
   353  	var resourceInfos ResourceInfos
   354  	for _, ri := range r.resourceInfos {
   355  		resourceInfos = append(resourceInfos, &resourceInfo{
   356  			identifier:      ri.identifier,
   357  			resourceStatus:  ri.resourceStatus,
   358  			ResourceAction:  ri.ResourceAction,
   359  			lastApplyEvent:  ri.lastApplyEvent,
   360  			lastDeleteEvent: ri.lastDeleteEvent,
   361  			lastPruneEvent:  ri.lastPruneEvent,
   362  			WaitStatus:      ri.WaitStatus,
   363  		})
   364  	}
   365  	sort.Sort(resourceInfos)
   366  
   367  	return &ResourceState{
   368  		resourceInfos: resourceInfos,
   369  		err:           r.err,
   370  	}
   371  }
   372  
   373  type ResourceInfos []*resourceInfo
   374  
   375  func (g ResourceInfos) Len() int {
   376  	return len(g)
   377  }
   378  
   379  func (g ResourceInfos) Less(i, j int) bool {
   380  	idI := g[i].identifier
   381  	idJ := g[j].identifier
   382  
   383  	if idI.Namespace != idJ.Namespace {
   384  		return idI.Namespace < idJ.Namespace
   385  	}
   386  	if idI.GroupKind.Group != idJ.GroupKind.Group {
   387  		return idI.GroupKind.Group < idJ.GroupKind.Group
   388  	}
   389  	if idI.GroupKind.Kind != idJ.GroupKind.Kind {
   390  		return idI.GroupKind.Kind < idJ.GroupKind.Kind
   391  	}
   392  	return idI.Name < idJ.Name
   393  }
   394  
   395  func (g ResourceInfos) Swap(i, j int) {
   396  	g[i], g[j] = g[j], g[i]
   397  }