...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s/resource.go

Documentation: github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s

     1  // Copyright 2022 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 k8s
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"reflect"
    21  
    22  	k8sv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/k8s/v1alpha1"
    23  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  	"k8s.io/apimachinery/pkg/types"
    29  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    30  )
    31  
    32  // Resource represents a resource in KRM
    33  type Resource struct {
    34  	// Fundamental fields
    35  	metav1.TypeMeta   `json:",inline"`
    36  	metav1.ObjectMeta `json:"metadata,omitempty"`
    37  	Spec              map[string]interface{} `json:"spec,omitempty"`
    38  	Status            map[string]interface{} `json:"status,omitempty"`
    39  
    40  	// Fields related to KRM processing
    41  
    42  	// ManagedFields is the set of spec fields whose desired state is managed
    43  	// by Kubernetes. Fields that are not part of this set are considered
    44  	// unmanaged, and their values in etcd will be updated to match the
    45  	// underlying API.
    46  	//
    47  	// If this object is nil, all fields in the spec in etcd are considered
    48  	// managed and their values will be constantly enforced.
    49  	ManagedFields *fieldpath.Set `json:"-"`
    50  }
    51  
    52  func (r *Resource) GetNamespacedName() types.NamespacedName {
    53  	return types.NamespacedName{
    54  		Namespace: r.GetNamespace(),
    55  		Name:      r.GetName(),
    56  	}
    57  }
    58  
    59  // NewResource creates a Resource based on the given unstructured. NewResource
    60  // can be used to create deep copies of a Resource by calling NewResource
    61  // multiple times on the same unstructured since the Resource objects created
    62  // are separate copies.
    63  func NewResource(u *unstructured.Unstructured) (*Resource, error) {
    64  	resource := &Resource{}
    65  	if err := util.Marshal(u, resource); err != nil {
    66  		return nil, err
    67  	}
    68  	managedFields, err := GetK8sManagedFields(u)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	resource.ManagedFields = managedFields
    73  	return resource, nil
    74  }
    75  
    76  func (r *Resource) MarshalAsUnstructured() (*unstructured.Unstructured, error) {
    77  	u := &unstructured.Unstructured{}
    78  	if err := util.Marshal(r, u); err != nil {
    79  		return nil, fmt.Errorf("error marshing resource to Unstructured %w", err)
    80  	}
    81  	removeNilCreationTimestamp(u.Object)
    82  	return u, nil
    83  }
    84  
    85  func (r *Resource) IsResourceIDConfigured() (bool, error) {
    86  	val, exists, err := unstructured.NestedString(r.Spec, ResourceIDFieldName)
    87  	if err != nil {
    88  		return false, fmt.Errorf("error getting the value of "+
    89  			"\"spec.%s\": %w", ResourceIDFieldName, err)
    90  	}
    91  
    92  	if !exists {
    93  		return false, nil
    94  	}
    95  
    96  	if val == "" {
    97  		return false, fmt.Errorf("the value of '%s' is invalid: '' (empty "+
    98  			"string)", ResourceIDFieldPath)
    99  	}
   100  	return true, nil
   101  }
   102  
   103  func (r *Resource) HasResourceIDField() bool {
   104  	_, ok := r.Spec[ResourceIDFieldName]
   105  	return ok
   106  }
   107  
   108  // The creation timestamp value is 'nil' after marshalling a new ObjectMeta
   109  func removeNilCreationTimestamp(object map[string]interface{}) {
   110  	metadata, ok := object["metadata"].(map[string]interface{})
   111  	if !ok {
   112  		panic("expected object to have a metadata field of type 'map[string]interface{}'")
   113  	}
   114  	creationTimestampKey := "creationTimestamp"
   115  	if _, ok := metadata[creationTimestampKey]; ok {
   116  		if creationTimeStamp, ok := metadata[creationTimestampKey]; ok {
   117  			if creationTimeStamp == nil {
   118  				delete(metadata, creationTimestampKey)
   119  			}
   120  		}
   121  	}
   122  }
   123  
   124  func IsResourceReady(r *Resource) bool {
   125  	cond, found := GetReadyCondition(r)
   126  	return found && cond.Status == corev1.ConditionTrue
   127  }
   128  
   129  func GetReadyCondition(r *Resource) (condition k8sv1alpha1.Condition, found bool) {
   130  	if currConditionsRaw, ok := r.Status["conditions"].([]interface{}); ok {
   131  		if currConditions, err := MarshalAsConditionsSlice(currConditionsRaw); err == nil {
   132  			for _, condition := range currConditions {
   133  				if condition.Type == k8sv1alpha1.ReadyConditionType {
   134  					return condition, true
   135  				}
   136  			}
   137  		}
   138  	}
   139  	return k8sv1alpha1.Condition{}, false
   140  }
   141  
   142  func ReadyConditionMatches(resource *Resource, status corev1.ConditionStatus, rs, msg string) bool {
   143  	cond, found := GetReadyCondition(resource)
   144  	if !found {
   145  		return false
   146  	}
   147  	return ConditionsEqualIgnoreTransitionTime(cond, NewCustomReadyCondition(status, rs, msg))
   148  }
   149  
   150  func IsSpecOrStatusUpdateRequired(resource *Resource, original *Resource) bool {
   151  	if !reflect.DeepEqual(resource.Spec, original.Spec) {
   152  		return true
   153  	}
   154  	if !reflect.DeepEqual(resource.Status, original.Status) {
   155  		return true
   156  	}
   157  	// JSON marshall will turn all numbers to float64 type, we convert generation to float64 for comparison
   158  	if len(resource.Status) == 0 || resource.Status["observedGeneration"] != float64(original.GetGeneration()) {
   159  		return true
   160  	}
   161  	return false
   162  }
   163  
   164  func IsAnnotationsUpdateRequired(resource *Resource, original *Resource) bool {
   165  	return !reflect.DeepEqual(resource.GetAnnotations(), original.GetAnnotations())
   166  }
   167  
   168  func MarshalObjectAsUnstructured(o metav1.Object) (*unstructured.Unstructured, error) {
   169  	b, err := json.Marshal(o)
   170  	if err != nil {
   171  		return nil, fmt.Errorf("error marshalling object %v: %w", o.GetName(), err)
   172  	}
   173  	u := &unstructured.Unstructured{}
   174  	if err := json.Unmarshal(b, u); err != nil {
   175  		return nil, fmt.Errorf("error unmarshalling object %v to unstructured.Unstructured: %w", o.GetName(), err)
   176  	}
   177  	removeNilCreationTimestamp(u.Object)
   178  	return u, nil
   179  }
   180  

View as plain text