github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/pkg/status/configconnector.go (about)

     1  // Copyright 2021 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package status
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"strings"
    22  
    23  	corev1 "k8s.io/api/core/v1"
    24  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    25  	"k8s.io/apimachinery/pkg/api/meta"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/runtime/schema"
    28  	"k8s.io/apimachinery/pkg/types"
    29  	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/engine"
    30  	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
    31  	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
    32  	"sigs.k8s.io/cli-utils/pkg/object"
    33  )
    34  
    35  const (
    36  	ConditionReady = "Ready"
    37  )
    38  
    39  // ConfigConnectorStatusReader can compute reconcile status for Config Connector
    40  // resources. It leverages information in the `Reason` field of the `Ready` condition.
    41  // TODO(mortent): Make more of the convencience functions and types from cli-utils
    42  // exported so we can simplify this.
    43  type ConfigConnectorStatusReader struct {
    44  	Mapper meta.RESTMapper
    45  }
    46  
    47  func NewConfigConnectorStatusReader(mapper meta.RESTMapper) engine.StatusReader {
    48  	return &ConfigConnectorStatusReader{
    49  		Mapper: mapper,
    50  	}
    51  }
    52  
    53  var _ engine.StatusReader = &ConfigConnectorStatusReader{}
    54  
    55  // Supports returns true for all Config Connector resources.
    56  func (c *ConfigConnectorStatusReader) Supports(gk schema.GroupKind) bool {
    57  	return strings.HasSuffix(gk.Group, "cnrm.cloud.google.com")
    58  }
    59  
    60  func (c *ConfigConnectorStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, id object.ObjMetadata) (*event.ResourceStatus, error) {
    61  	gvk, err := toGVK(id.GroupKind, c.Mapper)
    62  	if err != nil {
    63  		return newUnknownResourceStatus(id, nil, err), nil
    64  	}
    65  
    66  	key := types.NamespacedName{
    67  		Name:      id.Name,
    68  		Namespace: id.Namespace,
    69  	}
    70  
    71  	var u unstructured.Unstructured
    72  	u.SetGroupVersionKind(gvk)
    73  	err = reader.Get(ctx, key, &u)
    74  	if err != nil {
    75  		if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
    76  			return nil, err
    77  		}
    78  		if apierrors.IsNotFound(err) {
    79  			return newResourceStatus(id, status.NotFoundStatus, &u, "Resource not found"), nil
    80  		}
    81  		return newUnknownResourceStatus(id, nil, err), nil
    82  	}
    83  
    84  	return c.ReadStatusForObject(ctx, reader, &u)
    85  }
    86  
    87  func (c *ConfigConnectorStatusReader) ReadStatusForObject(_ context.Context, _ engine.ClusterReader, u *unstructured.Unstructured) (*event.ResourceStatus, error) {
    88  	id := object.UnstructuredToObjMetadata(u)
    89  
    90  	// First check if the resource is in the process of being deleted.
    91  	deletionTimestamp, found, err := unstructured.NestedString(u.Object, "metadata", "deletionTimestamp")
    92  	if err != nil {
    93  		return newUnknownResourceStatus(id, u, err), nil
    94  	}
    95  	if found && deletionTimestamp != "" {
    96  		return newResourceStatus(id, status.TerminatingStatus, u, "Resource scheduled for deletion"), nil
    97  	}
    98  
    99  	res, err := c.Compute(u)
   100  
   101  	if err != nil {
   102  		return newUnknownResourceStatus(id, u, err), nil
   103  	}
   104  	return newResourceStatus(id, res.Status, u, res.Message), nil
   105  }
   106  
   107  func (c *ConfigConnectorStatusReader) Compute(u *unstructured.Unstructured) (*status.Result, error) {
   108  	if u.GroupVersionKind().Kind == "ConfigConnectorContext" {
   109  		return computeStatusForConfigConnectorContext(u)
   110  	}
   111  
   112  	return computeStatusForConfigConnector(u)
   113  }
   114  
   115  func computeStatusForConfigConnectorContext(u *unstructured.Unstructured) (*status.Result, error) {
   116  	healthy, found, err := unstructured.NestedBool(u.Object, "status", "healthy")
   117  	if err != nil {
   118  		e := fmt.Errorf("looking up status.healthy from resource: %w", err)
   119  		return nil, e
   120  	}
   121  	if !found {
   122  		msg := "status.healthy property not set"
   123  		return &status.Result{
   124  			Status:  status.InProgressStatus,
   125  			Message: msg,
   126  			Conditions: []status.Condition{
   127  				{
   128  					Type:    status.ConditionReconciling,
   129  					Status:  corev1.ConditionTrue,
   130  					Reason:  "NotReady",
   131  					Message: msg,
   132  				},
   133  			},
   134  		}, nil
   135  	}
   136  	if !healthy {
   137  		msg := "status.healthy is false"
   138  		return &status.Result{
   139  			Status:  status.InProgressStatus,
   140  			Message: msg,
   141  			Conditions: []status.Condition{
   142  				{
   143  					Type:    status.ConditionReconciling,
   144  					Status:  corev1.ConditionTrue,
   145  					Reason:  "NotReady",
   146  					Message: msg,
   147  				},
   148  			},
   149  		}, nil
   150  	}
   151  	return &status.Result{
   152  		Status:  status.CurrentStatus,
   153  		Message: "status.healthy is true",
   154  	}, nil
   155  }
   156  
   157  func computeStatusForConfigConnector(u *unstructured.Unstructured) (*status.Result, error) {
   158  	// ensure that the meta generation is observed
   159  	generation, found, err := unstructured.NestedInt64(u.Object, "metadata", "generation")
   160  	if err != nil {
   161  		e := fmt.Errorf("looking up metadata.generation from resource: %w", err)
   162  		return nil, e
   163  	}
   164  	if !found {
   165  		e := fmt.Errorf("metadata.generation not found")
   166  		return nil, e
   167  	}
   168  
   169  	observedGeneration, found, err := unstructured.NestedInt64(u.Object, "status", "observedGeneration")
   170  	if err != nil {
   171  		e := fmt.Errorf("looking up status.observedGeneration from resource: %w", err)
   172  		return nil, e
   173  	}
   174  	if !found {
   175  		// We know that Config Connector resources uses the ObservedGeneration pattern, so consider it
   176  		// an error if it is not found.
   177  		e := fmt.Errorf("status.ObservedGeneration not found")
   178  		return nil, e
   179  	}
   180  	if generation != observedGeneration {
   181  		msg := fmt.Sprintf("%s generation is %d, but latest observed generation is %d", u.GetKind(), generation, observedGeneration)
   182  		return &status.Result{
   183  			Status:  status.InProgressStatus,
   184  			Message: msg,
   185  			Conditions: []status.Condition{
   186  				{
   187  					Type:    status.ConditionReconciling,
   188  					Status:  corev1.ConditionTrue,
   189  					Reason:  "LatestGenerationNotObserved",
   190  					Message: msg,
   191  				},
   192  			},
   193  		}, nil
   194  	}
   195  
   196  	obj, err := status.GetObjectWithConditions(u.Object)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  
   201  	var readyCond status.BasicCondition
   202  	foundCond := false
   203  	for i := range obj.Status.Conditions {
   204  		if obj.Status.Conditions[i].Type == ConditionReady {
   205  			readyCond = obj.Status.Conditions[i]
   206  			foundCond = true
   207  		}
   208  	}
   209  
   210  	if !foundCond {
   211  		msg := "Ready condition not set"
   212  		return &status.Result{
   213  			Status:  status.InProgressStatus,
   214  			Message: msg,
   215  			Conditions: []status.Condition{
   216  				{
   217  					Type:    status.ConditionReconciling,
   218  					Status:  corev1.ConditionTrue,
   219  					Reason:  "NoReadyCondition",
   220  					Message: msg,
   221  				},
   222  			},
   223  		}, nil
   224  	}
   225  
   226  	if readyCond.Status == corev1.ConditionTrue {
   227  		return &status.Result{
   228  			Status:  status.CurrentStatus,
   229  			Message: "Resource is Current",
   230  		}, nil
   231  	}
   232  
   233  	switch readyCond.Reason {
   234  	case "ManagementConflict", "UpdateFailed", "DeleteFailed", "DependencyInvalid":
   235  		return &status.Result{
   236  			Status:  status.FailedStatus,
   237  			Message: readyCond.Message,
   238  			Conditions: []status.Condition{
   239  				{
   240  					Type:    status.ConditionStalled,
   241  					Status:  corev1.ConditionTrue,
   242  					Reason:  readyCond.Reason,
   243  					Message: readyCond.Message,
   244  				},
   245  			},
   246  		}, nil
   247  	}
   248  
   249  	return &status.Result{
   250  		Status:  status.InProgressStatus,
   251  		Message: readyCond.Message,
   252  		Conditions: []status.Condition{
   253  			{
   254  				Type:    status.ConditionReconciling,
   255  				Status:  corev1.ConditionTrue,
   256  				Reason:  readyCond.Reason,
   257  				Message: readyCond.Message,
   258  			},
   259  		},
   260  	}, nil
   261  }
   262  
   263  func toGVK(gk schema.GroupKind, mapper meta.RESTMapper) (schema.GroupVersionKind, error) {
   264  	mapping, err := mapper.RESTMapping(gk)
   265  	if err != nil {
   266  		return schema.GroupVersionKind{}, err
   267  	}
   268  	return mapping.GroupVersionKind, nil
   269  }
   270  
   271  func newResourceStatus(id object.ObjMetadata, s status.Status, u *unstructured.Unstructured, msg string) *event.ResourceStatus {
   272  	return &event.ResourceStatus{
   273  		Identifier: id,
   274  		Status:     s,
   275  		Resource:   u,
   276  		Message:    msg,
   277  	}
   278  }
   279  
   280  func newUnknownResourceStatus(id object.ObjMetadata, u *unstructured.Unstructured, err error) *event.ResourceStatus {
   281  	return &event.ResourceStatus{
   282  		Identifier: id,
   283  		Status:     status.UnknownStatus,
   284  		Error:      err,
   285  		Resource:   u,
   286  	}
   287  }