...

Source file src/sigs.k8s.io/cli-utils/pkg/kstatus/polling/statusreaders/common.go

Documentation: sigs.k8s.io/cli-utils/pkg/kstatus/polling/statusreaders

     1  // Copyright 2020 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package statusreaders
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"sort"
    12  
    13  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    14  	"k8s.io/apimachinery/pkg/api/meta"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    17  	"k8s.io/apimachinery/pkg/labels"
    18  	"k8s.io/apimachinery/pkg/runtime/schema"
    19  	"k8s.io/apimachinery/pkg/types"
    20  	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/engine"
    21  	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
    22  	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
    23  	"sigs.k8s.io/cli-utils/pkg/object"
    24  )
    25  
    26  // baseStatusReader is the implementation of the StatusReader interface defined
    27  // in the engine package. It contains the basic logic needed for every resource.
    28  // In order to handle resource specific logic, it must include an implementation
    29  // of the resourceTypeStatusReader interface.
    30  // In practice we will create many instances of baseStatusReader, each with a different
    31  // implementation of the resourceTypeStatusReader interface and therefore each
    32  // of the instances will be able to handle different resource types.
    33  type baseStatusReader struct {
    34  	// mapper provides a way to look up the resource types that are available
    35  	// in the cluster.
    36  	mapper meta.RESTMapper
    37  
    38  	// resourceStatusReader is an resource-type specific implementation
    39  	// of the resourceTypeStatusReader interface. While the baseStatusReader
    40  	// contains the logic shared between all resource types, this implementation
    41  	// will contain the resource specific info.
    42  	resourceStatusReader resourceTypeStatusReader
    43  }
    44  
    45  // resourceTypeStatusReader is an interface that can be implemented differently
    46  // for each resource type.
    47  type resourceTypeStatusReader interface {
    48  	Supports(gk schema.GroupKind) bool
    49  	ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, object *unstructured.Unstructured) (*event.ResourceStatus, error)
    50  }
    51  
    52  func (b *baseStatusReader) Supports(gk schema.GroupKind) bool {
    53  	return b.resourceStatusReader.Supports(gk)
    54  }
    55  
    56  // ReadStatus reads the object identified by the passed-in identifier and computes it's status. It reads
    57  // the resource here, but computing status is delegated to the ReadStatusForObject function.
    58  func (b *baseStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, identifier object.ObjMetadata) (*event.ResourceStatus, error) {
    59  	object, err := b.lookupResource(ctx, reader, identifier)
    60  	if err != nil {
    61  		return errIdentifierToResourceStatus(err, identifier)
    62  	}
    63  	return b.resourceStatusReader.ReadStatusForObject(ctx, reader, object)
    64  }
    65  
    66  // ReadStatusForObject computes the status for the passed-in object. Since this is specific for each
    67  // resource type, the actual work is delegated to the implementation of the resourceTypeStatusReader interface.
    68  func (b *baseStatusReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, object *unstructured.Unstructured) (*event.ResourceStatus, error) {
    69  	return b.resourceStatusReader.ReadStatusForObject(ctx, reader, object)
    70  }
    71  
    72  // lookupResource looks up a resource with the given identifier. It will use the rest mapper to resolve
    73  // the version of the GroupKind given in the identifier.
    74  // If the resource is found, it is returned. If it is not found or something
    75  // went wrong, the function will return an error.
    76  func (b *baseStatusReader) lookupResource(ctx context.Context, reader engine.ClusterReader, identifier object.ObjMetadata) (*unstructured.Unstructured, error) {
    77  	GVK, err := gvk(identifier.GroupKind, b.mapper)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	var u unstructured.Unstructured
    83  	u.SetGroupVersionKind(GVK)
    84  	key := types.NamespacedName{
    85  		Name:      identifier.Name,
    86  		Namespace: identifier.Namespace,
    87  	}
    88  	err = reader.Get(ctx, key, &u)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	return &u, nil
    93  }
    94  
    95  // statusForGenResourcesFunc defines the function type used by the statusForGeneratedResource function.
    96  // TODO: Find a better solution for this. Maybe put the logic for looking up generated resources
    97  // into a separate type.
    98  type statusForGenResourcesFunc func(ctx context.Context, mapper meta.RESTMapper, reader engine.ClusterReader, statusReader resourceTypeStatusReader,
    99  	object *unstructured.Unstructured, gk schema.GroupKind, selectorPath ...string) (event.ResourceStatuses, error)
   100  
   101  // statusForGeneratedResources provides a way to fetch the statuses for all resources of a given GroupKind
   102  // that match the selector in the provided resource. Typically, this is used to fetch the status of generated
   103  // resources.
   104  func statusForGeneratedResources(ctx context.Context, mapper meta.RESTMapper, reader engine.ClusterReader, statusReader resourceTypeStatusReader,
   105  	object *unstructured.Unstructured, gk schema.GroupKind, selectorPath ...string) (event.ResourceStatuses, error) {
   106  	selector, err := toSelector(object, selectorPath...)
   107  	if err != nil {
   108  		return event.ResourceStatuses{}, err
   109  	}
   110  
   111  	var objectList unstructured.UnstructuredList
   112  	gvk, err := gvk(gk, mapper)
   113  	if err != nil {
   114  		return event.ResourceStatuses{}, err
   115  	}
   116  	objectList.SetGroupVersionKind(gvk)
   117  	err = reader.ListNamespaceScoped(ctx, &objectList, object.GetNamespace(), selector)
   118  	if err != nil {
   119  		return event.ResourceStatuses{}, err
   120  	}
   121  
   122  	var resourceStatuses event.ResourceStatuses
   123  	for i := range objectList.Items {
   124  		generatedObject := objectList.Items[i]
   125  		resourceStatus, err := statusReader.ReadStatusForObject(ctx, reader, &generatedObject)
   126  		if err != nil {
   127  			return event.ResourceStatuses{}, err
   128  		}
   129  		resourceStatuses = append(resourceStatuses, resourceStatus)
   130  	}
   131  	sort.Sort(resourceStatuses)
   132  	return resourceStatuses, nil
   133  }
   134  
   135  // gvk looks up the GVK from a GroupKind using the rest mapper.
   136  func gvk(gk schema.GroupKind, mapper meta.RESTMapper) (schema.GroupVersionKind, error) {
   137  	mapping, err := mapper.RESTMapping(gk)
   138  	if err != nil {
   139  		return schema.GroupVersionKind{}, err
   140  	}
   141  	return mapping.GroupVersionKind, nil
   142  }
   143  
   144  func toSelector(resource *unstructured.Unstructured, path ...string) (labels.Selector, error) {
   145  	selector, found, err := unstructured.NestedMap(resource.Object, path...)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	if !found {
   150  		return nil, fmt.Errorf("no selector found")
   151  	}
   152  	bytes, err := json.Marshal(selector)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  	var s metav1.LabelSelector
   157  	err = json.Unmarshal(bytes, &s)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	return metav1.LabelSelectorAsSelector(&s)
   162  }
   163  
   164  // errResourceToResourceStatus construct the appropriate ResourceStatus
   165  // object based on an error and the resource itself.
   166  func errResourceToResourceStatus(err error, resource *unstructured.Unstructured, genResources ...*event.ResourceStatus) (*event.ResourceStatus, error) {
   167  	// If the error is from the context, we don't attach that to the ResourceStatus,
   168  	// but just return it directly so the caller can decide how to handle this
   169  	// situation.
   170  	if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
   171  		return nil, err
   172  	}
   173  	identifier := object.UnstructuredToObjMetadata(resource)
   174  	if apierrors.IsNotFound(err) {
   175  		return &event.ResourceStatus{
   176  			Identifier: identifier,
   177  			Status:     status.NotFoundStatus,
   178  			Message:    "Resource not found",
   179  		}, nil
   180  	}
   181  	return &event.ResourceStatus{
   182  		Identifier:         identifier,
   183  		Status:             status.UnknownStatus,
   184  		Resource:           resource,
   185  		Error:              err,
   186  		GeneratedResources: genResources,
   187  	}, nil
   188  }
   189  
   190  // errIdentifierToResourceStatus construct the appropriate ResourceStatus
   191  // object based on an error and the identifier for a resource.
   192  func errIdentifierToResourceStatus(err error, identifier object.ObjMetadata) (*event.ResourceStatus, error) {
   193  	// If the error is from the context, we don't attach that to the ResourceStatus,
   194  	// but just return it directly so the caller can decide how to handle this
   195  	// situation.
   196  	if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
   197  		return nil, err
   198  	}
   199  	if apierrors.IsNotFound(err) {
   200  		return &event.ResourceStatus{
   201  			Identifier: identifier,
   202  			Status:     status.NotFoundStatus,
   203  			Message:    "Resource not found",
   204  		}, nil
   205  	}
   206  	return &event.ResourceStatus{
   207  		Identifier: identifier,
   208  		Status:     status.UnknownStatus,
   209  		Error:      err,
   210  	}, nil
   211  }
   212  

View as plain text