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 }