...

Source file src/github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s/references.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  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"strings"
    22  
    23  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
    24  	corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
    25  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
    26  
    27  	corev1 "k8s.io/api/core/v1"
    28  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    29  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	"k8s.io/apimachinery/pkg/types"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  )
    34  
    35  var errContainerAnnotationNotFound = fmt.Errorf("no container annotation found")
    36  
    37  func GetReferencedResourceIfReady(resourceRef *corekccv1alpha1.ResourceReference, gvk schema.GroupVersionKind, resourceNamespace string, kubeClient client.Client) (*Resource, error) {
    38  	r, err := GetReferencedResource(resourceRef, gvk, resourceNamespace, kubeClient)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	if !IsResourceReady(r) {
    43  		return nil, NewReferenceNotReadyErrorForResource(r)
    44  	}
    45  	return r, nil
    46  }
    47  
    48  func GetReferencedResource(resourceRef *corekccv1alpha1.ResourceReference, gvk schema.GroupVersionKind, resourceNamespace string, kubeClient client.Client) (*Resource, error) {
    49  	u, err := GetReferencedResourceAsUnstruct(resourceRef, gvk, resourceNamespace, kubeClient)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	r := &Resource{}
    54  	if err := util.Marshal(u, r); err != nil {
    55  		return nil, fmt.Errorf("error marshalling unstruct for referenced resource %v with GroupVersionKind %v to k8s resource: %w",
    56  			GetNamespacedName(u), u.GroupVersionKind(), err)
    57  	}
    58  	return r, nil
    59  }
    60  
    61  func GetReferencedResourceAsUnstruct(resourceRef *corekccv1alpha1.ResourceReference, gvk schema.GroupVersionKind, resourceNamespace string, kubeClient client.Client) (*unstructured.Unstructured, error) {
    62  	name := resourceRef.Name
    63  	if name == "" {
    64  		return nil, fmt.Errorf("resource reference is missing required 'name' field")
    65  	}
    66  	namespace := resourceRef.Namespace
    67  	if namespace == "" {
    68  		namespace = resourceNamespace
    69  	}
    70  	nn := types.NamespacedName{Name: name, Namespace: namespace}
    71  	u := &unstructured.Unstructured{}
    72  	u.SetGroupVersionKind(gvk)
    73  	if err := kubeClient.Get(context.TODO(), nn, u); err != nil {
    74  		if apierrors.IsNotFound(err) {
    75  			return nil, NewReferenceNotFoundError(gvk, nn)
    76  		}
    77  		return nil, fmt.Errorf("error getting referenced resource %v with GroupVersionKind %v from API server: %w", nn, gvk, err)
    78  	}
    79  	return u, nil
    80  }
    81  
    82  // EnsureHierarchicalReference ensures that the given resource has a
    83  // hierarchical reference and will set one if none is found.
    84  func EnsureHierarchicalReference(ctx context.Context, resource *Resource, hierarchicalRefs []corekccv1alpha1.HierarchicalReference, containers []corekccv1alpha1.Container, c client.Client) error {
    85  	if len(hierarchicalRefs) == 0 {
    86  		return nil
    87  	}
    88  
    89  	ns := corev1.Namespace{}
    90  	if err := c.Get(ctx, types.NamespacedName{Name: resource.GetNamespace()}, &ns); err != nil {
    91  		return fmt.Errorf("error getting namespace %v: %v", resource.GetNamespace(), err)
    92  	}
    93  
    94  	return SetDefaultHierarchicalReference(resource, &ns, hierarchicalRefs, containers)
    95  }
    96  
    97  // SetDefaultHierarchicalReference sets a hierarchical reference on the given
    98  // resource if it doesn't have one. The resulting hierarchical reference is
    99  // based on whichever of the following is found first:
   100  // (1) Resource-level container annotations (if supported)
   101  // (2) Namespace-level container annotations
   102  // (3) Namespace name (if resource supports project references)
   103  func SetDefaultHierarchicalReference(resource *Resource, ns *corev1.Namespace, hierarchicalRefs []corekccv1alpha1.HierarchicalReference, containers []corekccv1alpha1.Container) error {
   104  	if len(hierarchicalRefs) == 0 {
   105  		// No defaulting required.
   106  		return nil
   107  	}
   108  
   109  	// If the resource already has a hierarchical reference, no modification is
   110  	// required.
   111  	ref, _, err := GetHierarchicalReference(resource, hierarchicalRefs)
   112  	if err != nil {
   113  		return fmt.Errorf("error getting hierarchical reference from object: %v", err)
   114  	}
   115  	if ref != nil {
   116  		return nil
   117  	}
   118  
   119  	// If the resource supports the legacy behavior of resource-level
   120  	// container annotations, use that to default a hierarchical reference.
   121  	if len(containers) > 0 {
   122  		annotations := resource.GetAnnotations()
   123  		containerTypes := ContainerTypes(containers)
   124  		err := setHierarchicalReferenceUsingContainerAnnotation(resource, annotations, hierarchicalRefs, containerTypes)
   125  		if err != nil && !errors.Is(err, errContainerAnnotationNotFound) {
   126  			return fmt.Errorf("error setting hierarchical reference using resource-level container annotation: %v", err)
   127  		} else if err == nil {
   128  			return nil
   129  		}
   130  	}
   131  
   132  	// If the namespace has a container annotation, use that to default a
   133  	// hierarchical reference.
   134  	// Note: Use `hierarchicalRefs` to determine list of container types
   135  	// supported by namespace instead of `containers` since new resources won't
   136  	// have a `containers` field.
   137  	nsAnnotations := ns.GetAnnotations()
   138  	nsContainerTypes := ContainerTypesFor(hierarchicalRefs)
   139  	err = setHierarchicalReferenceUsingContainerAnnotation(resource, nsAnnotations, hierarchicalRefs, nsContainerTypes)
   140  	if err != nil && !errors.Is(err, errContainerAnnotationNotFound) {
   141  		return fmt.Errorf("error setting hierarchical reference using namespace-level container annotation: %v", err)
   142  	} else if err == nil {
   143  		return nil
   144  	}
   145  
   146  	// For project-scoped resources, we can use the namespace name as the project ID.
   147  	h := HierarchicalReferenceWithType(hierarchicalRefs, corekccv1alpha1.HierarchicalReferenceTypeProject)
   148  	if h != nil {
   149  		if err := SetHierarchicalReference(resource, h, ns.GetName()); err != nil {
   150  			return fmt.Errorf("error setting hierarchical reference from using namespace name: %v", err)
   151  		}
   152  		return nil
   153  	}
   154  
   155  	possibleFields := HierarchicalReferencesToFields(hierarchicalRefs)
   156  	possibleAnnotations := containerTypesToAnnotations(nsContainerTypes)
   157  	return fmt.Errorf("resource must have one field among [%v], or namespace must have one annotation among [%v]",
   158  		strings.Join(possibleFields, ", "), strings.Join(possibleAnnotations, ", "))
   159  }
   160  
   161  // GetHierarchicalReference gets the resource reference within the resource
   162  // that corresponds to any of the given hierarchical reference configurations,
   163  // as well as the the hierachical reference configuration associated with the
   164  // resource reference. Returns a nil resource reference if none is found.
   165  // Returns an error if multiple resource references are found (an invalid
   166  // resource state as resources can have at most one hierarchical reference).
   167  func GetHierarchicalReference(resource *Resource, hierarchicalRefs []corekccv1alpha1.HierarchicalReference) (
   168  	*corekccv1alpha1.ResourceReference, corekccv1alpha1.HierarchicalReference, error) {
   169  	return GetHierarchicalReferenceFromSpec(resource.Spec, hierarchicalRefs)
   170  }
   171  
   172  func GetHierarchicalReferenceFromSpec(spec map[string]interface{}, hierarchicalRefs []corekccv1alpha1.HierarchicalReference) (
   173  	*corekccv1alpha1.ResourceReference, corekccv1alpha1.HierarchicalReference, error) {
   174  	var resourceRef *corekccv1alpha1.ResourceReference
   175  	var hierarchicalRef corekccv1alpha1.HierarchicalReference
   176  	for _, h := range hierarchicalRefs {
   177  		val, ok, err := unstructured.NestedMap(spec, h.Key)
   178  		if err != nil {
   179  			return nil, corekccv1alpha1.HierarchicalReference{},
   180  				fmt.Errorf("error reading spec.%v: %v", h.Key, err)
   181  		}
   182  		if !ok {
   183  			continue
   184  		}
   185  
   186  		if resourceRef != nil {
   187  			return nil, corekccv1alpha1.HierarchicalReference{},
   188  				fmt.Errorf("only one of spec.%v and spec.%v can be specified", h.Key, hierarchicalRef.Key)
   189  		}
   190  
   191  		ref := &corekccv1alpha1.ResourceReference{}
   192  		if err = util.Marshal(val, ref); err != nil {
   193  			return nil, corekccv1alpha1.HierarchicalReference{},
   194  				fmt.Errorf("error marshalling spec.%v into a resource reference: %v", h.Key, err)
   195  		}
   196  		resourceRef = ref
   197  		hierarchicalRef = h
   198  	}
   199  	return resourceRef, hierarchicalRef, nil
   200  }
   201  
   202  func setHierarchicalReferenceUsingContainerAnnotation(resource *Resource, annotations map[string]string,
   203  	hierarchicalRefs []corekccv1alpha1.HierarchicalReference, containerTypes []corekccv1alpha1.ContainerType) error {
   204  
   205  	containerVal, containerType, err := GetContainerAnnotation(annotations, containerTypes)
   206  	if err != nil {
   207  		return fmt.Errorf("error getting container annotation from annotations: %v", err)
   208  	}
   209  	if containerVal == "" {
   210  		return errContainerAnnotationNotFound
   211  	}
   212  
   213  	h := HierarchicalReferenceWithType(hierarchicalRefs, HierarchicalReferenceTypeFor(containerType))
   214  	if h == nil {
   215  		return fmt.Errorf("no hierarchical reference found for container type %v", containerType)
   216  	}
   217  
   218  	return SetHierarchicalReference(resource, h, containerVal)
   219  }
   220  
   221  func SetHierarchicalReference(resource *Resource, hierarchicalRef *corekccv1alpha1.HierarchicalReference, externalVal string) error {
   222  	val := corekccv1alpha1.ResourceReference{
   223  		External: externalVal,
   224  	}
   225  	var valAsMap map[string]interface{}
   226  	if err := util.Marshal(val, &valAsMap); err != nil {
   227  		return fmt.Errorf("error marshalling resource reference to map: %v", err)
   228  	}
   229  	if resource.Spec == nil {
   230  		resource.Spec = make(map[string]interface{})
   231  	}
   232  	return unstructured.SetNestedMap(resource.Spec, valAsMap, hierarchicalRef.Key)
   233  }
   234  
   235  func HierarchicalReferenceWithType(hierarchicalRefs []corekccv1alpha1.HierarchicalReference, hType corekccv1alpha1.HierarchicalReferenceType) *corekccv1alpha1.HierarchicalReference {
   236  	for _, h := range hierarchicalRefs {
   237  		if h.Type == hType {
   238  			return &h
   239  		}
   240  	}
   241  	return nil
   242  }
   243  
   244  func HierarchicalReferencesToFields(hierarchicalRefs []corekccv1alpha1.HierarchicalReference) []string {
   245  	fields := make([]string, 0)
   246  	for _, h := range hierarchicalRefs {
   247  		fields = append(fields, "spec."+h.Key)
   248  	}
   249  	return fields
   250  }
   251  
   252  func HierarchicalReferenceTypeFor(containerType corekccv1alpha1.ContainerType) corekccv1alpha1.HierarchicalReferenceType {
   253  	switch containerType {
   254  	case v1alpha1.ContainerTypeProject:
   255  		return v1alpha1.HierarchicalReferenceTypeProject
   256  	case v1alpha1.ContainerTypeFolder:
   257  		return v1alpha1.HierarchicalReferenceTypeFolder
   258  	case v1alpha1.ContainerTypeOrganization:
   259  		return v1alpha1.HierarchicalReferenceTypeOrganization
   260  	default:
   261  		panic(fmt.Errorf("unrecognized container type: %v", containerType))
   262  	}
   263  }
   264  
   265  func ContainerTypesFor(hierarchicalRefs []corekccv1alpha1.HierarchicalReference) []corekccv1alpha1.ContainerType {
   266  	types := make([]corekccv1alpha1.ContainerType, 0)
   267  	for _, h := range hierarchicalRefs {
   268  		switch h.Type {
   269  		case corekccv1alpha1.HierarchicalReferenceTypeBillingAccount:
   270  			// Skip conversion of billing account hierarchical references to a
   271  			// container type since there is no container type that corresponds
   272  			// to billing accounts.
   273  			continue
   274  		default:
   275  			types = append(types, containerTypeFor(h))
   276  		}
   277  	}
   278  	return types
   279  }
   280  
   281  func containerTypeFor(hierarchicalRef corekccv1alpha1.HierarchicalReference) corekccv1alpha1.ContainerType {
   282  	switch hierarchicalRef.Type {
   283  	case corekccv1alpha1.HierarchicalReferenceTypeProject:
   284  		return corekccv1alpha1.ContainerTypeProject
   285  	case corekccv1alpha1.HierarchicalReferenceTypeFolder:
   286  		return corekccv1alpha1.ContainerTypeFolder
   287  	case corekccv1alpha1.HierarchicalReferenceTypeOrganization:
   288  		return corekccv1alpha1.ContainerTypeOrganization
   289  	case corekccv1alpha1.HierarchicalReferenceTypeBillingAccount:
   290  		panic(fmt.Errorf("there is no container type equivalent to the hierarchical reference type %v", hierarchicalRef.Type))
   291  	default:
   292  		panic(fmt.Errorf("unrecognized hierarchical reference type %v", hierarchicalRef.Type))
   293  	}
   294  }
   295  

View as plain text