github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/kubernetes.go (about)

     1  /*
     2  Copyright 2021 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  	"fmt"
    21  	"regexp"
    22  	"slices"
    23  	"sort"
    24  	"time"
    25  
    26  	"github.com/gravitational/trace"
    27  
    28  	"github.com/gravitational/teleport/api/types/compare"
    29  	"github.com/gravitational/teleport/api/utils"
    30  )
    31  
    32  var _ compare.IsEqual[KubeCluster] = (*KubernetesClusterV3)(nil)
    33  
    34  // KubeCluster represents a kubernetes cluster.
    35  type KubeCluster interface {
    36  	// ResourceWithLabels provides common resource methods.
    37  	ResourceWithLabels
    38  	// GetNamespace returns the kube cluster namespace.
    39  	GetNamespace() string
    40  	// GetStaticLabels returns the kube cluster static labels.
    41  	GetStaticLabels() map[string]string
    42  	// SetStaticLabels sets the kube cluster static labels.
    43  	SetStaticLabels(map[string]string)
    44  	// GetDynamicLabels returns the kube cluster dynamic labels.
    45  	GetDynamicLabels() map[string]CommandLabel
    46  	// SetDynamicLabels sets the kube cluster dynamic labels.
    47  	SetDynamicLabels(map[string]CommandLabel)
    48  	// GetKubeconfig returns the kubeconfig payload.
    49  	GetKubeconfig() []byte
    50  	// SetKubeconfig sets the kubeconfig.
    51  	SetKubeconfig([]byte)
    52  	// String returns string representation of the kube cluster.
    53  	String() string
    54  	// GetDescription returns the kube cluster description.
    55  	GetDescription() string
    56  	// GetAzureConfig gets the Azure config.
    57  	GetAzureConfig() KubeAzure
    58  	// SetAzureConfig sets the Azure config.
    59  	SetAzureConfig(KubeAzure)
    60  	// GetAWSConfig gets the AWS config.
    61  	GetAWSConfig() KubeAWS
    62  	// SetAWSConfig sets the AWS config.
    63  	SetAWSConfig(KubeAWS)
    64  	// GetGCPConfig gets the GCP config.
    65  	GetGCPConfig() KubeGCP
    66  	// SetGCPConfig sets the GCP config.
    67  	SetGCPConfig(KubeGCP)
    68  	// IsAzure indentifies if the KubeCluster contains Azure details.
    69  	IsAzure() bool
    70  	// IsAWS indentifies if the KubeCluster contains AWS details.
    71  	IsAWS() bool
    72  	// IsGCP indentifies if the KubeCluster contains GCP details.
    73  	IsGCP() bool
    74  	// IsKubeconfig identifies if the KubeCluster contains kubeconfig data.
    75  	IsKubeconfig() bool
    76  	// Copy returns a copy of this kube cluster resource.
    77  	Copy() *KubernetesClusterV3
    78  	// GetCloud gets the cloud this kube cluster is running on, or an empty string if it
    79  	// isn't running on a cloud provider.
    80  	GetCloud() string
    81  }
    82  
    83  // DiscoveredEKSCluster represents a server discovered by EKS discovery fetchers.
    84  type DiscoveredEKSCluster interface {
    85  	// KubeCluster is base discovered cluster.
    86  	KubeCluster
    87  	// GetKubeCluster returns base cluster.
    88  	GetKubeCluster() KubeCluster
    89  	// GetIntegration returns integration name used when discovering this cluster.
    90  	GetIntegration() string
    91  	// GetKubeAppDiscovery returns setting showing if Kubernetes App Discovery show be enabled for the discovered cluster.
    92  	GetKubeAppDiscovery() bool
    93  }
    94  
    95  // NewKubernetesClusterV3FromLegacyCluster creates a new Kubernetes cluster resource
    96  // from the legacy type.
    97  func NewKubernetesClusterV3FromLegacyCluster(namespace string, cluster *KubernetesCluster) (*KubernetesClusterV3, error) {
    98  	k := &KubernetesClusterV3{
    99  		Metadata: Metadata{
   100  			Name:      cluster.Name,
   101  			Namespace: namespace,
   102  			Labels:    cluster.StaticLabels,
   103  		},
   104  		Spec: KubernetesClusterSpecV3{
   105  			DynamicLabels: cluster.DynamicLabels,
   106  		},
   107  	}
   108  
   109  	if err := k.CheckAndSetDefaults(); err != nil {
   110  		return nil, trace.Wrap(err)
   111  	}
   112  
   113  	return k, nil
   114  }
   115  
   116  // NewKubernetesClusterV3WithoutSecrets creates a new copy of the provided cluster
   117  // but without secrets/credentials.
   118  func NewKubernetesClusterV3WithoutSecrets(cluster KubeCluster) (*KubernetesClusterV3, error) {
   119  	// Force a copy of the cluster to deep copy the Metadata fields.
   120  	copiedCluster := cluster.Copy()
   121  	clusterWithoutCreds, err := NewKubernetesClusterV3(
   122  		copiedCluster.Metadata,
   123  		KubernetesClusterSpecV3{
   124  			DynamicLabels: copiedCluster.Spec.DynamicLabels,
   125  		},
   126  	)
   127  	return clusterWithoutCreds, trace.Wrap(err)
   128  }
   129  
   130  // NewKubernetesClusterV3 creates a new Kubernetes cluster resource.
   131  func NewKubernetesClusterV3(meta Metadata, spec KubernetesClusterSpecV3) (*KubernetesClusterV3, error) {
   132  	k := &KubernetesClusterV3{
   133  		Metadata: meta,
   134  		Spec:     spec,
   135  	}
   136  
   137  	if err := k.CheckAndSetDefaults(); err != nil {
   138  		return nil, trace.Wrap(err)
   139  	}
   140  
   141  	return k, nil
   142  }
   143  
   144  // GetVersion returns the resource version.
   145  func (k *KubernetesClusterV3) GetVersion() string {
   146  	return k.Version
   147  }
   148  
   149  // GetKind returns the resource kind.
   150  func (k *KubernetesClusterV3) GetKind() string {
   151  	return k.Kind
   152  }
   153  
   154  // GetSubKind returns the app resource subkind.
   155  func (k *KubernetesClusterV3) GetSubKind() string {
   156  	return k.SubKind
   157  }
   158  
   159  // SetSubKind sets the app resource subkind.
   160  func (k *KubernetesClusterV3) SetSubKind(sk string) {
   161  	k.SubKind = sk
   162  }
   163  
   164  // GetResourceID returns the app resource ID.
   165  func (k *KubernetesClusterV3) GetResourceID() int64 {
   166  	return k.Metadata.ID
   167  }
   168  
   169  // SetResourceID sets the resource ID.
   170  func (k *KubernetesClusterV3) SetResourceID(id int64) {
   171  	k.Metadata.ID = id
   172  }
   173  
   174  // GetRevision returns the revision
   175  func (k *KubernetesClusterV3) GetRevision() string {
   176  	return k.Metadata.GetRevision()
   177  }
   178  
   179  // SetRevision sets the revision
   180  func (k *KubernetesClusterV3) SetRevision(rev string) {
   181  	k.Metadata.SetRevision(rev)
   182  }
   183  
   184  // GetMetadata returns the resource metadata.
   185  func (k *KubernetesClusterV3) GetMetadata() Metadata {
   186  	return k.Metadata
   187  }
   188  
   189  // Origin returns the origin value of the resource.
   190  func (k *KubernetesClusterV3) Origin() string {
   191  	return k.Metadata.Origin()
   192  }
   193  
   194  // SetOrigin sets the origin value of the resource.
   195  func (k *KubernetesClusterV3) SetOrigin(origin string) {
   196  	k.Metadata.SetOrigin(origin)
   197  }
   198  
   199  // GetNamespace returns the kube resource namespace.
   200  func (k *KubernetesClusterV3) GetNamespace() string {
   201  	return k.Metadata.Namespace
   202  }
   203  
   204  // SetExpiry sets the kube resource expiration time.
   205  func (k *KubernetesClusterV3) SetExpiry(expiry time.Time) {
   206  	k.Metadata.SetExpiry(expiry)
   207  }
   208  
   209  // Expiry returns the kube resource expiration time.
   210  func (k *KubernetesClusterV3) Expiry() time.Time {
   211  	return k.Metadata.Expiry()
   212  }
   213  
   214  // GetName returns the kube resource name.
   215  func (k *KubernetesClusterV3) GetName() string {
   216  	return k.Metadata.Name
   217  }
   218  
   219  // SetName sets the resource name.
   220  func (k *KubernetesClusterV3) SetName(name string) {
   221  	k.Metadata.Name = name
   222  }
   223  
   224  // GetLabel retrieves the label with the provided key. If not found
   225  // value will be empty and ok will be false.
   226  func (k *KubernetesClusterV3) GetLabel(key string) (value string, ok bool) {
   227  	if cmd, ok := k.Spec.DynamicLabels[key]; ok {
   228  		return cmd.Result, ok
   229  	}
   230  
   231  	v, ok := k.Metadata.Labels[key]
   232  	return v, ok
   233  }
   234  
   235  // GetStaticLabels returns the static labels.
   236  func (k *KubernetesClusterV3) GetStaticLabels() map[string]string {
   237  	return k.Metadata.Labels
   238  }
   239  
   240  // SetStaticLabels sets the static labels.
   241  func (k *KubernetesClusterV3) SetStaticLabels(sl map[string]string) {
   242  	k.Metadata.Labels = sl
   243  }
   244  
   245  // GetKubeconfig returns the kubeconfig payload.
   246  func (k *KubernetesClusterV3) GetKubeconfig() []byte {
   247  	return k.Spec.Kubeconfig
   248  }
   249  
   250  // SetKubeconfig sets the kubeconfig.
   251  func (k *KubernetesClusterV3) SetKubeconfig(cfg []byte) {
   252  	k.Spec.Kubeconfig = cfg
   253  }
   254  
   255  // GetDynamicLabels returns the dynamic labels.
   256  func (k *KubernetesClusterV3) GetDynamicLabels() map[string]CommandLabel {
   257  	if k.Spec.DynamicLabels == nil {
   258  		return nil
   259  	}
   260  	return V2ToLabels(k.Spec.DynamicLabels)
   261  }
   262  
   263  // SetDynamicLabels sets the dynamic labels
   264  func (k *KubernetesClusterV3) SetDynamicLabels(dl map[string]CommandLabel) {
   265  	k.Spec.DynamicLabels = LabelsToV2(dl)
   266  }
   267  
   268  // GetAllLabels returns the combined static and dynamic labels.
   269  func (k *KubernetesClusterV3) GetAllLabels() map[string]string {
   270  	return CombineLabels(k.Metadata.Labels, k.Spec.DynamicLabels)
   271  }
   272  
   273  // GetDescription returns the description.
   274  func (k *KubernetesClusterV3) GetDescription() string {
   275  	return k.Metadata.Description
   276  }
   277  
   278  // GetAzureConfig gets the Azure config.
   279  func (k *KubernetesClusterV3) GetAzureConfig() KubeAzure {
   280  	return k.Spec.Azure
   281  }
   282  
   283  // SetAzureConfig sets the Azure config.
   284  func (k *KubernetesClusterV3) SetAzureConfig(cfg KubeAzure) {
   285  	k.Spec.Azure = cfg
   286  }
   287  
   288  // GetAWSConfig gets the AWS config.
   289  func (k *KubernetesClusterV3) GetAWSConfig() KubeAWS {
   290  	return k.Spec.AWS
   291  }
   292  
   293  // SetAWSConfig sets the AWS config.
   294  func (k *KubernetesClusterV3) SetAWSConfig(cfg KubeAWS) {
   295  	k.Spec.AWS = cfg
   296  }
   297  
   298  // GetGCPConfig gets the GCP config.
   299  func (k *KubernetesClusterV3) GetGCPConfig() KubeGCP {
   300  	return k.Spec.GCP
   301  }
   302  
   303  // SetGCPConfig sets the GCP config.
   304  func (k *KubernetesClusterV3) SetGCPConfig(cfg KubeGCP) {
   305  	k.Spec.GCP = cfg
   306  }
   307  
   308  // IsAzure indentifies if the KubeCluster contains Azure details.
   309  func (k *KubernetesClusterV3) IsAzure() bool {
   310  	return !protoKnownFieldsEqual(&k.Spec.Azure, &KubeAzure{})
   311  }
   312  
   313  // IsAWS indentifies if the KubeCluster contains AWS details.
   314  func (k *KubernetesClusterV3) IsAWS() bool {
   315  	return !protoKnownFieldsEqual(&k.Spec.AWS, &KubeAWS{})
   316  }
   317  
   318  // IsGCP indentifies if the KubeCluster contains GCP details.
   319  func (k *KubernetesClusterV3) IsGCP() bool {
   320  	return !protoKnownFieldsEqual(&k.Spec.GCP, &KubeGCP{})
   321  }
   322  
   323  // GetCloud gets the cloud this kube cluster is running on, or an empty string if it
   324  // isn't running on a cloud provider.
   325  func (k *KubernetesClusterV3) GetCloud() string {
   326  	switch {
   327  	case k.IsAzure():
   328  		return CloudAzure
   329  	case k.IsAWS():
   330  		return CloudAWS
   331  	case k.IsGCP():
   332  		return CloudGCP
   333  	default:
   334  		return ""
   335  	}
   336  }
   337  
   338  // IsKubeconfig identifies if the KubeCluster contains kubeconfig data.
   339  func (k *KubernetesClusterV3) IsKubeconfig() bool {
   340  	return len(k.Spec.Kubeconfig) > 0
   341  }
   342  
   343  // String returns the string representation.
   344  func (k *KubernetesClusterV3) String() string {
   345  	return fmt.Sprintf("KubernetesCluster(Name=%v, Labels=%v)",
   346  		k.GetName(), k.GetAllLabels())
   347  }
   348  
   349  // Copy returns a copy of this resource.
   350  func (k *KubernetesClusterV3) Copy() *KubernetesClusterV3 {
   351  	return utils.CloneProtoMsg(k)
   352  }
   353  
   354  // MatchSearch goes through select field values and tries to
   355  // match against the list of search values.
   356  func (k *KubernetesClusterV3) MatchSearch(values []string) bool {
   357  	fieldVals := append(utils.MapToStrings(k.GetAllLabels()), k.GetName())
   358  	return MatchSearch(fieldVals, values, nil)
   359  }
   360  
   361  // setStaticFields sets static resource header and metadata fields.
   362  func (k *KubernetesClusterV3) setStaticFields() {
   363  	k.Kind = KindKubernetesCluster
   364  	k.Version = V3
   365  }
   366  
   367  // validKubeClusterName filters the allowed characters in kubernetes cluster
   368  // names. We need this because cluster names are used for cert filenames on the
   369  // client side, in the ~/.tsh directory. Restricting characters helps with
   370  // sneaky cluster names being used for client directory traversal and exploits.
   371  var validKubeClusterName = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
   372  
   373  // ValidateKubeClusterName returns an error if a given string is not a valid
   374  // KubeCluster name.
   375  func ValidateKubeClusterName(name string) error {
   376  	return ValidateResourceName(validKubeClusterName, name)
   377  }
   378  
   379  // CheckAndSetDefaults checks and sets default values for any missing fields.
   380  func (k *KubernetesClusterV3) CheckAndSetDefaults() error {
   381  	k.setStaticFields()
   382  	if err := k.Metadata.CheckAndSetDefaults(); err != nil {
   383  		return trace.Wrap(err)
   384  	}
   385  	for key := range k.Spec.DynamicLabels {
   386  		if !IsValidLabelKey(key) {
   387  			return trace.BadParameter("kubernetes cluster %q invalid label key: %q", k.GetName(), key)
   388  		}
   389  	}
   390  
   391  	if err := ValidateKubeClusterName(k.Metadata.Name); err != nil {
   392  		return trace.Wrap(err, "invalid kubernetes cluster name")
   393  	}
   394  
   395  	if err := k.Spec.Azure.CheckAndSetDefaults(); err != nil && k.IsAzure() {
   396  		return trace.Wrap(err)
   397  	}
   398  
   399  	if err := k.Spec.AWS.CheckAndSetDefaults(); err != nil && k.IsAWS() {
   400  		return trace.Wrap(err)
   401  	}
   402  
   403  	if err := k.Spec.GCP.CheckAndSetDefaults(); err != nil && k.IsGCP() {
   404  		return trace.Wrap(err)
   405  	}
   406  
   407  	return nil
   408  }
   409  
   410  // IsEqual determines if two user resources are equivalent to one another.
   411  func (k *KubernetesClusterV3) IsEqual(i KubeCluster) bool {
   412  	if other, ok := i.(*KubernetesClusterV3); ok {
   413  		return deriveTeleportEqualKubernetesClusterV3(k, other)
   414  	}
   415  	return false
   416  }
   417  
   418  func (k KubeAzure) CheckAndSetDefaults() error {
   419  	if len(k.ResourceGroup) == 0 {
   420  		return trace.BadParameter("invalid Azure ResourceGroup")
   421  	}
   422  
   423  	if len(k.ResourceName) == 0 {
   424  		return trace.BadParameter("invalid Azure ResourceName")
   425  	}
   426  
   427  	if len(k.SubscriptionID) == 0 {
   428  		return trace.BadParameter("invalid Azure SubscriptionID")
   429  	}
   430  
   431  	return nil
   432  }
   433  
   434  func (k KubeAWS) CheckAndSetDefaults() error {
   435  	if len(k.Region) == 0 {
   436  		return trace.BadParameter("invalid AWS Region")
   437  	}
   438  
   439  	if len(k.Name) == 0 {
   440  		return trace.BadParameter("invalid AWS Name")
   441  	}
   442  
   443  	if len(k.AccountID) == 0 {
   444  		return trace.BadParameter("invalid AWS AccountID")
   445  	}
   446  
   447  	return nil
   448  }
   449  
   450  func (k KubeGCP) CheckAndSetDefaults() error {
   451  	if len(k.Location) == 0 {
   452  		return trace.BadParameter("invalid GCP Location")
   453  	}
   454  
   455  	if len(k.ProjectID) == 0 {
   456  		return trace.BadParameter("invalid GCP ProjectID")
   457  	}
   458  
   459  	if len(k.Name) == 0 {
   460  		return trace.BadParameter("invalid GCP Name")
   461  	}
   462  
   463  	return nil
   464  }
   465  
   466  // KubeClusters represents a list of kube clusters.
   467  type KubeClusters []KubeCluster
   468  
   469  // Find returns kube cluster with the specified name or nil.
   470  func (s KubeClusters) Find(name string) KubeCluster {
   471  	for _, cluster := range s {
   472  		if cluster.GetName() == name {
   473  			return cluster
   474  		}
   475  	}
   476  	return nil
   477  }
   478  
   479  // ToMap returns these kubernetes clusters as a map keyed by cluster name.
   480  func (s KubeClusters) ToMap() map[string]KubeCluster {
   481  	m := make(map[string]KubeCluster)
   482  	for _, kubeCluster := range s {
   483  		m[kubeCluster.GetName()] = kubeCluster
   484  	}
   485  	return m
   486  }
   487  
   488  // Len returns the slice length.
   489  func (s KubeClusters) Len() int { return len(s) }
   490  
   491  // Less compares kube clusters by name.
   492  func (s KubeClusters) Less(i, j int) bool {
   493  	return s[i].GetName() < s[j].GetName()
   494  }
   495  
   496  // Swap swaps two kube clusters.
   497  func (s KubeClusters) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   498  
   499  // SortByCustom custom sorts by given sort criteria.
   500  func (s KubeClusters) SortByCustom(sortBy SortBy) error {
   501  	if sortBy.Field == "" {
   502  		return nil
   503  	}
   504  
   505  	isDesc := sortBy.IsDesc
   506  	switch sortBy.Field {
   507  	case ResourceMetadataName:
   508  		sort.SliceStable(s, func(i, j int) bool {
   509  			return stringCompare(s[i].GetName(), s[j].GetName(), isDesc)
   510  		})
   511  	default:
   512  		return trace.NotImplemented("sorting by field %q for resource %q is not supported", sortBy.Field, KindKubernetesCluster)
   513  	}
   514  
   515  	return nil
   516  }
   517  
   518  // AsResources returns as type resources with labels.
   519  func (s KubeClusters) AsResources() ResourcesWithLabels {
   520  	resources := make(ResourcesWithLabels, 0, len(s))
   521  	for _, cluster := range s {
   522  		resources = append(resources, ResourceWithLabels(cluster))
   523  	}
   524  	return resources
   525  }
   526  
   527  // GetFieldVals returns list of select field values.
   528  func (s KubeClusters) GetFieldVals(field string) ([]string, error) {
   529  	vals := make([]string, 0, len(s))
   530  	switch field {
   531  	case ResourceMetadataName:
   532  		for _, server := range s {
   533  			vals = append(vals, server.GetName())
   534  		}
   535  	default:
   536  		return nil, trace.NotImplemented("getting field %q for resource %q is not supported", field, KindKubernetesCluster)
   537  	}
   538  
   539  	return vals, nil
   540  }
   541  
   542  // DeduplicateKubeClusters deduplicates kube clusters by name.
   543  func DeduplicateKubeClusters(kubeclusters []KubeCluster) []KubeCluster {
   544  	seen := make(map[string]struct{})
   545  	result := make([]KubeCluster, 0, len(kubeclusters))
   546  
   547  	for _, cluster := range kubeclusters {
   548  		if _, ok := seen[cluster.GetName()]; ok {
   549  			continue
   550  		}
   551  		seen[cluster.GetName()] = struct{}{}
   552  		result = append(result, cluster)
   553  	}
   554  
   555  	return result
   556  }
   557  
   558  var _ ResourceWithLabels = (*KubernetesResourceV1)(nil)
   559  
   560  // NewKubernetesPodV1 creates a new kubernetes resource with kind "pod".
   561  func NewKubernetesPodV1(meta Metadata, spec KubernetesResourceSpecV1) (*KubernetesResourceV1, error) {
   562  	pod := &KubernetesResourceV1{
   563  		Kind:     KindKubePod,
   564  		Metadata: meta,
   565  		Spec:     spec,
   566  	}
   567  
   568  	if err := pod.CheckAndSetDefaults(); err != nil {
   569  		return nil, trace.Wrap(err)
   570  	}
   571  	return pod, nil
   572  }
   573  
   574  // NewKubernetesResourceV1 creates a new kubernetes resource .
   575  func NewKubernetesResourceV1(kind string, meta Metadata, spec KubernetesResourceSpecV1) (*KubernetesResourceV1, error) {
   576  	resource := &KubernetesResourceV1{
   577  		Kind:     kind,
   578  		Metadata: meta,
   579  		Spec:     spec,
   580  	}
   581  	if err := resource.CheckAndSetDefaults(); err != nil {
   582  		return nil, trace.Wrap(err)
   583  	}
   584  	return resource, nil
   585  }
   586  
   587  // GetKind returns resource kind.
   588  func (k *KubernetesResourceV1) GetKind() string {
   589  	return k.Kind
   590  }
   591  
   592  // GetSubKind returns resource subkind.
   593  func (k *KubernetesResourceV1) GetSubKind() string {
   594  	return k.SubKind
   595  }
   596  
   597  // GetVersion returns resource version.
   598  func (k *KubernetesResourceV1) GetVersion() string {
   599  	return k.Version
   600  }
   601  
   602  // GetMetadata returns object metadata.
   603  func (k *KubernetesResourceV1) GetMetadata() Metadata {
   604  	return k.Metadata
   605  }
   606  
   607  // SetSubKind sets resource subkind.
   608  func (k *KubernetesResourceV1) SetSubKind(subKind string) {
   609  	k.SubKind = subKind
   610  }
   611  
   612  // GetName returns the name of the resource.
   613  func (k *KubernetesResourceV1) GetName() string {
   614  	return k.Metadata.GetName()
   615  }
   616  
   617  // SetName sets the name of the resource.
   618  func (k *KubernetesResourceV1) SetName(name string) {
   619  	k.Metadata.SetName(name)
   620  }
   621  
   622  // Expiry returns object expiry setting.
   623  func (k *KubernetesResourceV1) Expiry() time.Time {
   624  	return k.Metadata.Expiry()
   625  }
   626  
   627  // SetExpiry sets object expiry.
   628  func (k *KubernetesResourceV1) SetExpiry(expire time.Time) {
   629  	k.Metadata.SetExpiry(expire)
   630  }
   631  
   632  // GetResourceID returns resource ID.
   633  func (k *KubernetesResourceV1) GetResourceID() int64 {
   634  	return k.Metadata.ID
   635  }
   636  
   637  // SetResourceID sets resource ID.
   638  func (k *KubernetesResourceV1) SetResourceID(id int64) {
   639  	k.Metadata.ID = id
   640  }
   641  
   642  // GetRevision returns the revision
   643  func (k *KubernetesResourceV1) GetRevision() string {
   644  	return k.Metadata.GetRevision()
   645  }
   646  
   647  // SetRevision sets the revision
   648  func (k *KubernetesResourceV1) SetRevision(rev string) {
   649  	k.Metadata.SetRevision(rev)
   650  }
   651  
   652  // CheckAndSetDefaults validates the Resource and sets any empty fields to
   653  // default values.
   654  func (k *KubernetesResourceV1) CheckAndSetDefaults() error {
   655  	k.setStaticFields()
   656  	if !slices.Contains(KubernetesResourcesKinds, k.Kind) {
   657  		return trace.BadParameter("invalid kind %q defined; allowed values: %v", k.Kind, KubernetesResourcesKinds)
   658  	}
   659  	if err := k.Metadata.CheckAndSetDefaults(); err != nil {
   660  		return trace.Wrap(err)
   661  	}
   662  
   663  	// Unless the resource is cluster-wide, it must have a namespace.
   664  	if len(k.Spec.Namespace) == 0 && !slices.Contains(KubernetesClusterWideResourceKinds, k.Kind) {
   665  		return trace.BadParameter("missing kubernetes namespace")
   666  	}
   667  
   668  	return nil
   669  }
   670  
   671  // setStaticFields sets static resource header and metadata fields.
   672  func (k *KubernetesResourceV1) setStaticFields() {
   673  	k.Version = V1
   674  }
   675  
   676  // Origin returns the origin value of the resource.
   677  func (k *KubernetesResourceV1) Origin() string {
   678  	return k.Metadata.Origin()
   679  }
   680  
   681  // SetOrigin sets the origin value of the resource.
   682  func (k *KubernetesResourceV1) SetOrigin(origin string) {
   683  	k.Metadata.SetOrigin(origin)
   684  }
   685  
   686  // GetLabel retrieves the label with the provided key. If not found
   687  // value will be empty and ok will be false.
   688  func (k *KubernetesResourceV1) GetLabel(key string) (value string, ok bool) {
   689  	v, ok := k.Metadata.Labels[key]
   690  	return v, ok
   691  }
   692  
   693  // GetAllLabels returns all resource's labels.
   694  func (k *KubernetesResourceV1) GetAllLabels() map[string]string {
   695  	return k.Metadata.Labels
   696  }
   697  
   698  // GetStaticLabels returns the resource's static labels.
   699  func (k *KubernetesResourceV1) GetStaticLabels() map[string]string {
   700  	return k.Metadata.Labels
   701  }
   702  
   703  // SetStaticLabels sets the resource's static labels.
   704  func (k *KubernetesResourceV1) SetStaticLabels(sl map[string]string) {
   705  	k.Metadata.Labels = sl
   706  }
   707  
   708  // MatchSearch goes through select field values of a resource
   709  // and tries to match against the list of search values.
   710  func (k *KubernetesResourceV1) MatchSearch(searchValues []string) bool {
   711  	fieldVals := append(utils.MapToStrings(k.GetAllLabels()), k.GetName(), k.Spec.Namespace)
   712  	return MatchSearch(fieldVals, searchValues, nil)
   713  }
   714  
   715  // KubeResources represents a list of Kubernetes resources.
   716  type KubeResources []*KubernetesResourceV1
   717  
   718  // Find returns Kubernetes resource with the specified name or nil if the resource
   719  // was not found.
   720  func (k KubeResources) Find(name string) *KubernetesResourceV1 {
   721  	for _, cluster := range k {
   722  		if cluster.GetName() == name {
   723  			return cluster
   724  		}
   725  	}
   726  	return nil
   727  }
   728  
   729  // ToMap returns these kubernetes resources as a map keyed by resource name.
   730  func (k KubeResources) ToMap() map[string]*KubernetesResourceV1 {
   731  	m := make(map[string]*KubernetesResourceV1)
   732  	for _, kubeCluster := range k {
   733  		m[kubeCluster.GetName()] = kubeCluster
   734  	}
   735  	return m
   736  }
   737  
   738  // Len returns the slice length.
   739  func (k KubeResources) Len() int { return len(k) }
   740  
   741  // Less compares Kubernetes resources by name.
   742  func (k KubeResources) Less(i, j int) bool {
   743  	return k[i].GetName() < k[j].GetName()
   744  }
   745  
   746  // Swap swaps two Kubernetes resources.
   747  func (k KubeResources) Swap(i, j int) { k[i], k[j] = k[j], k[i] }
   748  
   749  // SortByCustom custom sorts by given sort criteria.
   750  func (k KubeResources) SortByCustom(sortBy SortBy) error {
   751  	if sortBy.Field == "" {
   752  		return nil
   753  	}
   754  
   755  	isDesc := sortBy.IsDesc
   756  	switch sortBy.Field {
   757  	case ResourceMetadataName:
   758  		sort.SliceStable(k, func(i, j int) bool {
   759  			return stringCompare(k[i].GetName(), k[j].GetName(), isDesc)
   760  		})
   761  	default:
   762  		return trace.NotImplemented("sorting by field %q for kubernetes resources is not supported", sortBy.Field)
   763  	}
   764  
   765  	return nil
   766  }
   767  
   768  // AsResources returns as type resources with labels.
   769  func (k KubeResources) AsResources() ResourcesWithLabels {
   770  	resources := make(ResourcesWithLabels, 0, len(k))
   771  	for _, resource := range k {
   772  		resources = append(resources, ResourceWithLabels(resource))
   773  	}
   774  	return resources
   775  }