github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/resource.go (about) 1 /* 2 Copyright 2020 Gravitational, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package types 18 19 import ( 20 "regexp" 21 "slices" 22 "sort" 23 "strings" 24 "time" 25 26 "github.com/gravitational/trace" 27 28 "github.com/gravitational/teleport/api/defaults" 29 "github.com/gravitational/teleport/api/types/common" 30 "github.com/gravitational/teleport/api/types/compare" 31 "github.com/gravitational/teleport/api/utils" 32 ) 33 34 var ( 35 _ compare.IsEqual[*ResourceHeader] = (*ResourceHeader)(nil) 36 _ compare.IsEqual[*Metadata] = (*Metadata)(nil) 37 ) 38 39 // Resource represents common properties for all resources. 40 // 41 // Please avoid adding new uses of Resource in the codebase. Instead, consider 42 // using concrete proto types directly or a manually declared subset of the 43 // Resource153 interface for new-style resources. 44 type Resource interface { 45 // GetKind returns resource kind 46 GetKind() string 47 // GetSubKind returns resource subkind 48 GetSubKind() string 49 // SetSubKind sets resource subkind 50 SetSubKind(string) 51 // GetVersion returns resource version 52 GetVersion() string 53 // GetName returns the name of the resource 54 GetName() string 55 // SetName sets the name of the resource 56 SetName(string) 57 // Expiry returns object expiry setting 58 Expiry() time.Time 59 // SetExpiry sets object expiry 60 SetExpiry(time.Time) 61 // GetMetadata returns object metadata 62 GetMetadata() Metadata 63 // GetResourceID returns resource ID 64 // Deprecated: use GetRevision instead 65 GetResourceID() int64 66 // SetResourceID sets resource ID 67 // Deprecated: use SetRevision instead 68 SetResourceID(int64) 69 // GetRevision returns the revision 70 GetRevision() string 71 // SetRevision sets the revision 72 SetRevision(string) 73 } 74 75 // IsSystemResource checks to see if the given resource is considered 76 // part of the teleport system, as opposed to some user created resource 77 // or preset. 78 func IsSystemResource(r Resource) bool { 79 metadata := r.GetMetadata() 80 if t, ok := metadata.Labels[TeleportInternalResourceType]; ok { 81 return t == SystemResource 82 } 83 return false 84 } 85 86 // GetName fetches the name of the supplied resource. Useful when sorting lists 87 // of resources or building maps, etc. 88 func GetName[R Resource](r R) string { 89 return r.GetName() 90 } 91 92 // ResourceDetails includes details about the resource 93 type ResourceDetails struct { 94 Hostname string 95 FriendlyName string 96 } 97 98 // ResourceWithSecrets includes additional properties which must 99 // be provided by resources which *may* contain secrets. 100 type ResourceWithSecrets interface { 101 Resource 102 // WithoutSecrets returns an instance of the resource which 103 // has had all secrets removed. If the current resource has 104 // already had its secrets removed, this may be a no-op. 105 WithoutSecrets() Resource 106 } 107 108 // ResourceWithOrigin provides information on the origin of the resource 109 // (defaults, config-file, dynamic). 110 type ResourceWithOrigin interface { 111 Resource 112 // Origin returns the origin value of the resource. 113 Origin() string 114 // SetOrigin sets the origin value of the resource. 115 SetOrigin(string) 116 } 117 118 // ResourceWithLabels is a common interface for resources that have labels. 119 type ResourceWithLabels interface { 120 // ResourceWithOrigin is the base resource interface. 121 ResourceWithOrigin 122 // GetLabel retrieves the label with the provided key. 123 GetLabel(key string) (value string, ok bool) 124 // GetAllLabels returns all resource's labels. 125 GetAllLabels() map[string]string 126 // GetStaticLabels returns the resource's static labels. 127 GetStaticLabels() map[string]string 128 // SetStaticLabels sets the resource's static labels. 129 SetStaticLabels(sl map[string]string) 130 // MatchSearch goes through select field values of a resource 131 // and tries to match against the list of search values. 132 MatchSearch(searchValues []string) bool 133 } 134 135 // EnrichedResource is a [ResourceWithLabels] wrapped with 136 // additional user-specific information. 137 type EnrichedResource struct { 138 // ResourceWithLabels is the underlying resource. 139 ResourceWithLabels 140 // Logins that the user is allowed to access the above resource with. 141 Logins []string 142 // RequiresRequest is true if a resource is being returned to the user but requires 143 // an access request to access. This is done during `ListUnifiedResources` when 144 // searchAsRoles is true 145 RequiresRequest bool 146 } 147 148 // ResourcesWithLabels is a list of labeled resources. 149 type ResourcesWithLabels []ResourceWithLabels 150 151 // ResourcesWithLabelsMap is like ResourcesWithLabels, but a map from resource name to its value. 152 type ResourcesWithLabelsMap map[string]ResourceWithLabels 153 154 // ToMap returns these databases as a map keyed by database name. 155 func (r ResourcesWithLabels) ToMap() ResourcesWithLabelsMap { 156 rm := make(ResourcesWithLabelsMap, len(r)) 157 158 // there may be duplicate resources in the input list. 159 // by iterating from end to start, the first resource of given name wins. 160 for i := len(r) - 1; i >= 0; i-- { 161 resource := r[i] 162 rm[resource.GetName()] = resource 163 } 164 165 return rm 166 } 167 168 // Len returns the slice length. 169 func (r ResourcesWithLabels) Len() int { return len(r) } 170 171 // Less compares resources by name. 172 func (r ResourcesWithLabels) Less(i, j int) bool { return r[i].GetName() < r[j].GetName() } 173 174 // Swap swaps two resources. 175 func (r ResourcesWithLabels) Swap(i, j int) { r[i], r[j] = r[j], r[i] } 176 177 // AsAppServers converts each resource into type AppServer. 178 func (r ResourcesWithLabels) AsAppServers() ([]AppServer, error) { 179 apps := make([]AppServer, 0, len(r)) 180 for _, resource := range r { 181 app, ok := resource.(AppServer) 182 if !ok { 183 return nil, trace.BadParameter("expected types.AppServer, got: %T", resource) 184 } 185 apps = append(apps, app) 186 } 187 return apps, nil 188 } 189 190 // AsServers converts each resource into type Server. 191 func (r ResourcesWithLabels) AsServers() ([]Server, error) { 192 servers := make([]Server, 0, len(r)) 193 for _, resource := range r { 194 server, ok := resource.(Server) 195 if !ok { 196 return nil, trace.BadParameter("expected types.Server, got: %T", resource) 197 } 198 servers = append(servers, server) 199 } 200 return servers, nil 201 } 202 203 // AsDatabases converts each resource into type Database. 204 func (r ResourcesWithLabels) AsDatabases() ([]Database, error) { 205 dbs := make([]Database, 0, len(r)) 206 for _, resource := range r { 207 db, ok := resource.(Database) 208 if !ok { 209 return nil, trace.BadParameter("expected types.Database, got: %T", resource) 210 } 211 dbs = append(dbs, db) 212 } 213 return dbs, nil 214 } 215 216 // AsDatabaseServers converts each resource into type DatabaseServer. 217 func (r ResourcesWithLabels) AsDatabaseServers() ([]DatabaseServer, error) { 218 dbs := make([]DatabaseServer, 0, len(r)) 219 for _, resource := range r { 220 db, ok := resource.(DatabaseServer) 221 if !ok { 222 return nil, trace.BadParameter("expected types.DatabaseServer, got: %T", resource) 223 } 224 dbs = append(dbs, db) 225 } 226 return dbs, nil 227 } 228 229 // AsDatabaseServices converts each resource into type DatabaseService. 230 func (r ResourcesWithLabels) AsDatabaseServices() ([]DatabaseService, error) { 231 services := make([]DatabaseService, len(r)) 232 for i, resource := range r { 233 dbService, ok := resource.(DatabaseService) 234 if !ok { 235 return nil, trace.BadParameter("expected types.DatabaseService, got: %T", resource) 236 } 237 services[i] = dbService 238 } 239 return services, nil 240 } 241 242 // AsWindowsDesktops converts each resource into type WindowsDesktop. 243 func (r ResourcesWithLabels) AsWindowsDesktops() ([]WindowsDesktop, error) { 244 desktops := make([]WindowsDesktop, 0, len(r)) 245 for _, resource := range r { 246 desktop, ok := resource.(WindowsDesktop) 247 if !ok { 248 return nil, trace.BadParameter("expected types.WindowsDesktop, got: %T", resource) 249 } 250 desktops = append(desktops, desktop) 251 } 252 return desktops, nil 253 } 254 255 // AsWindowsDesktopServices converts each resource into type WindowsDesktop. 256 func (r ResourcesWithLabels) AsWindowsDesktopServices() ([]WindowsDesktopService, error) { 257 desktopServices := make([]WindowsDesktopService, 0, len(r)) 258 for _, resource := range r { 259 desktopService, ok := resource.(WindowsDesktopService) 260 if !ok { 261 return nil, trace.BadParameter("expected types.WindowsDesktopService, got: %T", resource) 262 } 263 desktopServices = append(desktopServices, desktopService) 264 } 265 return desktopServices, nil 266 } 267 268 // AsKubeClusters converts each resource into type KubeCluster. 269 func (r ResourcesWithLabels) AsKubeClusters() ([]KubeCluster, error) { 270 clusters := make([]KubeCluster, 0, len(r)) 271 for _, resource := range r { 272 cluster, ok := resource.(KubeCluster) 273 if !ok { 274 return nil, trace.BadParameter("expected types.KubeCluster, got: %T", resource) 275 } 276 clusters = append(clusters, cluster) 277 } 278 return clusters, nil 279 } 280 281 // AsKubeServers converts each resource into type KubeServer. 282 func (r ResourcesWithLabels) AsKubeServers() ([]KubeServer, error) { 283 servers := make([]KubeServer, 0, len(r)) 284 for _, resource := range r { 285 server, ok := resource.(KubeServer) 286 if !ok { 287 return nil, trace.BadParameter("expected types.KubeServer, got: %T", resource) 288 } 289 servers = append(servers, server) 290 } 291 return servers, nil 292 } 293 294 // AsUserGroups converts each resource into type UserGroup. 295 func (r ResourcesWithLabels) AsUserGroups() ([]UserGroup, error) { 296 userGroups := make([]UserGroup, 0, len(r)) 297 for _, resource := range r { 298 userGroup, ok := resource.(UserGroup) 299 if !ok { 300 return nil, trace.BadParameter("expected types.UserGroup, got: %T", resource) 301 } 302 userGroups = append(userGroups, userGroup) 303 } 304 return userGroups, nil 305 } 306 307 // GetVersion returns resource version 308 func (h *ResourceHeader) GetVersion() string { 309 return h.Version 310 } 311 312 // GetResourceID returns resource ID 313 // Deprecated: Use GetRevision instead. 314 func (h *ResourceHeader) GetResourceID() int64 { 315 return h.Metadata.ID 316 } 317 318 // SetResourceID sets resource ID 319 // Deprecated: Use SetRevision instead. 320 func (h *ResourceHeader) SetResourceID(id int64) { 321 h.Metadata.ID = id 322 } 323 324 // GetRevision returns the revision 325 func (h *ResourceHeader) GetRevision() string { 326 return h.Metadata.GetRevision() 327 } 328 329 // SetRevision sets the revision 330 func (h *ResourceHeader) SetRevision(rev string) { 331 h.Metadata.SetRevision(rev) 332 } 333 334 // GetName returns the name of the resource 335 func (h *ResourceHeader) GetName() string { 336 return h.Metadata.Name 337 } 338 339 // SetName sets the name of the resource 340 func (h *ResourceHeader) SetName(v string) { 341 h.Metadata.SetName(v) 342 } 343 344 // Expiry returns object expiry setting 345 func (h *ResourceHeader) Expiry() time.Time { 346 return h.Metadata.Expiry() 347 } 348 349 // SetExpiry sets object expiry 350 func (h *ResourceHeader) SetExpiry(t time.Time) { 351 h.Metadata.SetExpiry(t) 352 } 353 354 // GetMetadata returns object metadata 355 func (h *ResourceHeader) GetMetadata() Metadata { 356 return h.Metadata 357 } 358 359 // GetKind returns resource kind 360 func (h *ResourceHeader) GetKind() string { 361 return h.Kind 362 } 363 364 // GetSubKind returns resource subkind 365 func (h *ResourceHeader) GetSubKind() string { 366 return h.SubKind 367 } 368 369 // SetSubKind sets resource subkind 370 func (h *ResourceHeader) SetSubKind(s string) { 371 h.SubKind = s 372 } 373 374 // Origin returns the origin value of the resource. 375 func (h *ResourceHeader) Origin() string { 376 return h.Metadata.Origin() 377 } 378 379 // SetOrigin sets the origin value of the resource. 380 func (h *ResourceHeader) SetOrigin(origin string) { 381 h.Metadata.SetOrigin(origin) 382 } 383 384 // GetStaticLabels returns the static labels for the resource. 385 func (h *ResourceHeader) GetStaticLabels() map[string]string { 386 return h.Metadata.Labels 387 } 388 389 // SetStaticLabels sets the static labels for the resource. 390 func (h *ResourceHeader) SetStaticLabels(sl map[string]string) { 391 h.Metadata.Labels = sl 392 } 393 394 // GetLabel retrieves the label with the provided key. If not found 395 // value will be empty and ok will be false. 396 func (h *ResourceHeader) GetLabel(key string) (value string, ok bool) { 397 v, ok := h.Metadata.Labels[key] 398 return v, ok 399 } 400 401 // GetAllLabels returns all labels from the resource.. 402 func (h *ResourceHeader) GetAllLabels() map[string]string { 403 return h.Metadata.Labels 404 } 405 406 // IsEqual determines if two resource header resources are equivalent to one another. 407 func (h *ResourceHeader) IsEqual(other *ResourceHeader) bool { 408 return deriveTeleportEqualResourceHeader(h, other) 409 } 410 411 func (h *ResourceHeader) CheckAndSetDefaults() error { 412 if h.Kind == "" { 413 return trace.BadParameter("resource has an empty Kind field") 414 } 415 if h.Version == "" { 416 return trace.BadParameter("resource has an empty Version field") 417 } 418 return trace.Wrap(h.Metadata.CheckAndSetDefaults()) 419 } 420 421 // GetID returns resource ID 422 func (m *Metadata) GetID() int64 { 423 return m.ID 424 } 425 426 // SetID sets resource ID 427 func (m *Metadata) SetID(id int64) { 428 m.ID = id 429 } 430 431 // GetRevision returns the revision 432 func (m *Metadata) GetRevision() string { 433 return m.Revision 434 } 435 436 // SetRevision sets the revision 437 func (m *Metadata) SetRevision(rev string) { 438 m.Revision = rev 439 } 440 441 // GetMetadata returns object metadata 442 func (m *Metadata) GetMetadata() Metadata { 443 return *m 444 } 445 446 // GetName returns the name of the resource 447 func (m *Metadata) GetName() string { 448 return m.Name 449 } 450 451 // SetName sets the name of the resource 452 func (m *Metadata) SetName(name string) { 453 m.Name = name 454 } 455 456 // SetExpiry sets expiry time for the object 457 func (m *Metadata) SetExpiry(expires time.Time) { 458 m.Expires = &expires 459 } 460 461 // Expiry returns object expiry setting. 462 func (m *Metadata) Expiry() time.Time { 463 if m.Expires == nil { 464 return time.Time{} 465 } 466 return *m.Expires 467 } 468 469 // Origin returns the origin value of the resource. 470 func (m *Metadata) Origin() string { 471 if m.Labels == nil { 472 return "" 473 } 474 return m.Labels[OriginLabel] 475 } 476 477 // SetOrigin sets the origin value of the resource. 478 func (m *Metadata) SetOrigin(origin string) { 479 if m.Labels == nil { 480 m.Labels = map[string]string{} 481 } 482 m.Labels[OriginLabel] = origin 483 } 484 485 // IsEqual determines if two metadata resources are equivalent to one another. 486 func (m *Metadata) IsEqual(other *Metadata) bool { 487 return deriveTeleportEqualMetadata(m, other) 488 } 489 490 // CheckAndSetDefaults checks validity of all parameters and sets defaults 491 func (m *Metadata) CheckAndSetDefaults() error { 492 if m.Name == "" { 493 return trace.BadParameter("missing parameter Name") 494 } 495 if m.Namespace == "" { 496 m.Namespace = defaults.Namespace 497 } 498 499 // adjust expires time to UTC if it's set 500 if m.Expires != nil { 501 utils.UTC(m.Expires) 502 } 503 504 for key := range m.Labels { 505 if !IsValidLabelKey(key) { 506 return trace.BadParameter("invalid label key: %q", key) 507 } 508 } 509 510 // Check the origin value. 511 if m.Origin() != "" { 512 if !slices.Contains(OriginValues, m.Origin()) { 513 return trace.BadParameter("invalid origin value %q, must be one of %v", m.Origin(), OriginValues) 514 } 515 } 516 517 return nil 518 } 519 520 // MatchLabels takes a map of labels and returns `true` if the resource has ALL 521 // of them. 522 func MatchLabels(resource ResourceWithLabels, labels map[string]string) bool { 523 for key, value := range labels { 524 if v, ok := resource.GetLabel(key); !ok || v != value { 525 return false 526 } 527 } 528 529 return true 530 } 531 532 // MatchKinds takes an array of strings that represent a Kind and 533 // returns true if the resource's kind matches any item in the given array. 534 func MatchKinds(resource ResourceWithLabels, kinds []string) bool { 535 if len(kinds) == 0 { 536 return true 537 } 538 resourceKind := resource.GetKind() 539 switch resourceKind { 540 case KindApp, KindSAMLIdPServiceProvider: 541 return slices.Contains(kinds, KindApp) 542 default: 543 return slices.Contains(kinds, resourceKind) 544 } 545 } 546 547 // IsValidLabelKey checks if the supplied string matches the 548 // label key regexp. 549 func IsValidLabelKey(s string) bool { 550 return common.IsValidLabelKey(s) 551 } 552 553 // MatchSearch goes through select field values from a resource 554 // and tries to match against the list of search values, ignoring case and order. 555 // Returns true if all search vals were matched (or if nil search vals). 556 // Returns false if no or partial match (or nil field values). 557 func MatchSearch(fieldVals []string, searchVals []string, customMatch func(val string) bool) bool { 558 Outer: 559 for _, searchV := range searchVals { 560 // Iterate through field values to look for a match. 561 for _, fieldV := range fieldVals { 562 if containsFold(fieldV, searchV) { 563 continue Outer 564 } 565 } 566 567 if customMatch != nil && customMatch(searchV) { 568 continue 569 } 570 571 // When no fields matched a value, prematurely end if we can. 572 return false 573 } 574 575 return true 576 } 577 578 // containsFold is a case-insensitive alternative to strings.Contains, used to help avoid excess allocations during searches. 579 func containsFold(s, substr string) bool { 580 if len(s) < len(substr) { 581 return false 582 } 583 584 n := len(s) - len(substr) 585 586 for i := 0; i <= n; i++ { 587 if strings.EqualFold(s[i:i+len(substr)], substr) { 588 return true 589 } 590 } 591 592 return false 593 } 594 595 func stringCompare(a string, b string, isDesc bool) bool { 596 if isDesc { 597 return a > b 598 } 599 return a < b 600 } 601 602 var kindsOrder = []string{ 603 "app", "db", "windows_desktop", "kube_cluster", "node", 604 } 605 606 // unifiedKindCompare compares two resource kinds and returns true if a is less than b. 607 // Note that it's not just a simple string comparison, since the UI names these 608 // kinds slightly differently, and hence uses a different alphabetical order for 609 // them. 610 // 611 // If resources are of the same kind, this function falls back to comparing 612 // their unified names. 613 func unifiedKindCompare(a, b ResourceWithLabels, isDesc bool) bool { 614 ak := a.GetKind() 615 bk := b.GetKind() 616 617 if ak == bk { 618 return unifiedNameCompare(a, b, isDesc) 619 } 620 621 ia := slices.Index(kindsOrder, ak) 622 ib := slices.Index(kindsOrder, bk) 623 if ia < 0 && ib < 0 { 624 // Fallback for a case of two unknown resources. 625 return stringCompare(ak, bk, isDesc) 626 } 627 if isDesc { 628 return ia > ib 629 } 630 return ia < ib 631 } 632 633 func unifiedNameCompare(a ResourceWithLabels, b ResourceWithLabels, isDesc bool) bool { 634 var nameA, nameB string 635 switch r := a.(type) { 636 case AppServer: 637 nameA = r.GetApp().GetName() 638 case DatabaseServer: 639 nameA = r.GetDatabase().GetName() 640 case KubeServer: 641 nameA = r.GetCluster().GetName() 642 case Server: 643 nameA = r.GetHostname() 644 default: 645 nameA = a.GetName() 646 } 647 648 switch r := b.(type) { 649 case AppServer: 650 nameB = r.GetApp().GetName() 651 case DatabaseServer: 652 nameB = r.GetDatabase().GetName() 653 case KubeServer: 654 nameB = r.GetCluster().GetName() 655 case Server: 656 nameB = r.GetHostname() 657 default: 658 nameB = a.GetName() 659 } 660 661 return stringCompare(strings.ToLower(nameA), strings.ToLower(nameB), isDesc) 662 } 663 664 func (r ResourcesWithLabels) SortByCustom(by SortBy) error { 665 isDesc := by.IsDesc 666 switch by.Field { 667 case ResourceMetadataName: 668 sort.SliceStable(r, func(i, j int) bool { 669 return unifiedNameCompare(r[i], r[j], isDesc) 670 }) 671 case ResourceKind: 672 sort.SliceStable(r, func(i, j int) bool { 673 return unifiedKindCompare(r[i], r[j], isDesc) 674 }) 675 default: 676 return trace.NotImplemented("sorting by field %q for unified resource %q is not supported", by.Field, KindUnifiedResource) 677 } 678 return nil 679 } 680 681 // ListResourcesResponse describes a non proto response to ListResources. 682 type ListResourcesResponse struct { 683 // Resources is a list of resource. 684 Resources []ResourceWithLabels 685 // NextKey is the next key to use as a starting point. 686 NextKey string 687 // TotalCount is the total number of resources available as a whole. 688 TotalCount int 689 } 690 691 // ValidateResourceName validates a resource name using a given regexp. 692 func ValidateResourceName(validationRegex *regexp.Regexp, name string) error { 693 if validationRegex.MatchString(name) { 694 return nil 695 } 696 return trace.BadParameter( 697 "%q does not match regex used for validation %q", 698 name, validationRegex.String(), 699 ) 700 } 701 702 // FriendlyName will return the friendly name for a resource if it has one. Otherwise, it 703 // will return an empty string. 704 func FriendlyName(resource ResourceWithLabels) string { 705 // Right now, only resources sourced from Okta and nodes have friendly names. 706 if resource.Origin() == OriginOkta { 707 if appName, ok := resource.GetLabel(OktaAppNameLabel); ok { 708 return appName 709 } else if groupName, ok := resource.GetLabel(OktaGroupNameLabel); ok { 710 return groupName 711 } else if roleName, ok := resource.GetLabel(OktaRoleNameLabel); ok { 712 return roleName 713 } 714 return resource.GetMetadata().Description 715 } 716 717 if hn, ok := resource.(interface{ GetHostname() string }); ok { 718 return hn.GetHostname() 719 } 720 721 return "" 722 } 723 724 // GetOrigin returns the value set for the [OriginLabel]. 725 // If the label is missing, an empty string is returned. 726 // 727 // Works for both [ResourceWithOrigin] and [ResourceMetadata] instances. 728 func GetOrigin(v any) (string, error) { 729 switch r := v.(type) { 730 case ResourceWithOrigin: 731 return r.Origin(), nil 732 case ResourceMetadata: 733 meta := r.GetMetadata() 734 if meta.Labels == nil { 735 return "", nil 736 } 737 return meta.Labels[OriginLabel], nil 738 } 739 return "", trace.BadParameter("unable to determine origin from resource of type %T", v) 740 } 741 742 // GetKind returns the kind, if one can be obtained, otherwise 743 // an empty string is returned. 744 // 745 // Works for both [Resource] and [ResourceMetadata] instances. 746 func GetKind(v any) (string, error) { 747 type kinder interface { 748 GetKind() string 749 } 750 if k, ok := v.(kinder); ok { 751 return k.GetKind(), nil 752 } 753 return "", trace.BadParameter("unable to determine kind from resource of type %T", v) 754 } 755 756 // GetRevision returns the revision, if one can be obtained, otherwise 757 // an empty string is returned. 758 // 759 // Works for both [Resource] and [ResourceMetadata] instances. 760 func GetRevision(v any) (string, error) { 761 switch r := v.(type) { 762 case Resource: 763 return r.GetRevision(), nil 764 case ResourceMetadata: 765 return r.GetMetadata().Revision, nil 766 } 767 return "", trace.BadParameter("unable to determine revision from resource of type %T", v) 768 } 769 770 // SetRevision updates the revision if v supports the concept of revisions. 771 // 772 // Works for both [Resource] and [ResourceMetadata] instances. 773 func SetRevision(v any, revision string) error { 774 switch r := v.(type) { 775 case Resource: 776 r.SetRevision(revision) 777 return nil 778 case ResourceMetadata: 779 r.GetMetadata().Revision = revision 780 return nil 781 } 782 return trace.BadParameter("unable to set revision on resource of type %T", v) 783 } 784 785 // GetExpiry returns the expiration, if one can be obtained, otherwise returns 786 // an empty time `time.Time{}`, which is equivalent to no expiry. 787 // 788 // Works for both [Resource] and [ResourceMetadata] instances. 789 func GetExpiry(v any) (time.Time, error) { 790 switch r := v.(type) { 791 case Resource: 792 return r.Expiry(), nil 793 case ResourceMetadata: 794 // ResourceMetadata uses *timestamppb.Timestamp instead of time.Time. The zero value for this type is 01/01/1970. 795 // This is a problem for resources without explicit expiry set: they'd become obsolete on creation. 796 // For this reason, we check for nil expiry explicitly, and default it to time.Time{}. 797 exp := r.GetMetadata().GetExpires() 798 if exp == nil { 799 return time.Time{}, nil 800 } 801 return exp.AsTime(), nil 802 } 803 return time.Time{}, trace.BadParameter("unable to determine expiry from resource of type %T", v) 804 } 805 806 // GetResourceID returns the id, if one can be obtained, otherwise returns 807 // zero. 808 // 809 // Works for both [Resource] and [ResourceMetadata] instances. 810 // 811 // Deprecated: GetRevision should be used instead. 812 func GetResourceID(v any) (int64, error) { 813 switch r := v.(type) { 814 case Resource: 815 //nolint:staticcheck // SA1019. Added for backward compatibility. 816 return r.GetResourceID(), nil 817 case ResourceMetadata: 818 //nolint:staticcheck // SA1019. Added for backward compatibility. 819 return r.GetMetadata().Id, nil 820 } 821 return 0, trace.BadParameter("unable to determine resource ID from resource of type %T", v) 822 }