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

     1  // Copyright 2020 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 live
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"reflect"
    21  	"strings"
    22  	"time"
    23  
    24  	"github.com/GoogleContainerTools/kpt/pkg/status"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	"k8s.io/apimachinery/pkg/api/meta"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	"k8s.io/cli-runtime/pkg/resource"
    32  	"k8s.io/client-go/dynamic"
    33  	"k8s.io/klog/v2"
    34  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    35  	"k8s.io/kubectl/pkg/util"
    36  	"sigs.k8s.io/cli-utils/pkg/apis/actuation"
    37  	"sigs.k8s.io/cli-utils/pkg/common"
    38  	"sigs.k8s.io/cli-utils/pkg/inventory"
    39  	"sigs.k8s.io/cli-utils/pkg/kstatus/polling"
    40  	pollevent "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
    41  	kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
    42  	"sigs.k8s.io/cli-utils/pkg/object"
    43  	"sigs.k8s.io/kustomize/kyaml/yaml"
    44  )
    45  
    46  const (
    47  	applyRGTimeout      = 10 * time.Second
    48  	applyRGPollInterval = 2 * time.Second
    49  )
    50  
    51  // ResourceGroupGVK is the group/version/kind of the custom
    52  // resource used to store inventory.
    53  var ResourceGroupGVK = schema.GroupVersionKind{
    54  	Group:   "kpt.dev",
    55  	Version: "v1alpha1",
    56  	Kind:    "ResourceGroup",
    57  }
    58  
    59  // InventoryResourceGroup wraps a ResourceGroup resource and implements
    60  // the Inventory and InventoryInfo interface. This wrapper loads and stores the
    61  // object metadata (inventory) to and from the wrapped ResourceGroup.
    62  type InventoryResourceGroup struct {
    63  	inv       *unstructured.Unstructured
    64  	objMetas  []object.ObjMetadata
    65  	objStatus []actuation.ObjectStatus
    66  }
    67  
    68  func (icm *InventoryResourceGroup) Strategy() inventory.Strategy {
    69  	return inventory.NameStrategy
    70  }
    71  
    72  var _ inventory.Storage = &InventoryResourceGroup{}
    73  var _ inventory.Info = &InventoryResourceGroup{}
    74  
    75  // WrapInventoryObj takes a passed ResourceGroup (as a resource.Info),
    76  // wraps it with the InventoryResourceGroup and upcasts the wrapper as
    77  // an the Inventory interface.
    78  func WrapInventoryObj(obj *unstructured.Unstructured) inventory.Storage {
    79  	if obj != nil {
    80  		klog.V(4).Infof("wrapping Inventory obj: %s/%s\n", obj.GetNamespace(), obj.GetName())
    81  	}
    82  	return &InventoryResourceGroup{inv: obj}
    83  }
    84  
    85  func WrapInventoryInfoObj(obj *unstructured.Unstructured) inventory.Info {
    86  	if obj != nil {
    87  		klog.V(4).Infof("wrapping InventoryInfo obj: %s/%s\n", obj.GetNamespace(), obj.GetName())
    88  	}
    89  	return &InventoryResourceGroup{inv: obj}
    90  }
    91  
    92  func InvToUnstructuredFunc(inv inventory.Info) *unstructured.Unstructured {
    93  	switch invInfo := inv.(type) {
    94  	case *InventoryResourceGroup:
    95  		return invInfo.inv
    96  	default:
    97  		return nil
    98  	}
    99  }
   100  
   101  // Name(), Namespace(), and ID() are InventoryResourceGroup functions to
   102  // implement the InventoryInfo interface.
   103  func (icm *InventoryResourceGroup) Name() string {
   104  	return icm.inv.GetName()
   105  }
   106  
   107  func (icm *InventoryResourceGroup) Namespace() string {
   108  	return icm.inv.GetNamespace()
   109  }
   110  
   111  func (icm *InventoryResourceGroup) ID() string {
   112  	labels := icm.inv.GetLabels()
   113  	if val, found := labels[common.InventoryLabel]; found {
   114  		return val
   115  	}
   116  	return ""
   117  }
   118  
   119  // Load is an Inventory interface function returning the set of
   120  // object metadata from the wrapped ResourceGroup, or an error.
   121  func (icm *InventoryResourceGroup) Load() (object.ObjMetadataSet, error) {
   122  	objs := object.ObjMetadataSet{}
   123  	if icm.inv == nil {
   124  		return objs, fmt.Errorf("inventory info is nil")
   125  	}
   126  	klog.V(4).Infof("loading inventory...")
   127  	items, exists, err := unstructured.NestedSlice(icm.inv.Object, "spec", "resources")
   128  	if err != nil {
   129  		err := fmt.Errorf("error retrieving object metadata from inventory object")
   130  		return objs, err
   131  	}
   132  	if !exists {
   133  		klog.V(4).Infof("Inventory (spec.resources) does not exist")
   134  		return objs, nil
   135  	}
   136  	klog.V(4).Infof("loading %d inventory items", len(items))
   137  	for _, itemUncast := range items {
   138  		item := itemUncast.(map[string]interface{})
   139  		namespace, _, err := unstructured.NestedString(item, "namespace")
   140  		if err != nil {
   141  			return []object.ObjMetadata{}, err
   142  		}
   143  		name, _, err := unstructured.NestedString(item, "name")
   144  		if err != nil {
   145  			return []object.ObjMetadata{}, err
   146  		}
   147  		group, _, err := unstructured.NestedString(item, "group")
   148  		if err != nil {
   149  			return []object.ObjMetadata{}, err
   150  		}
   151  		kind, _, err := unstructured.NestedString(item, "kind")
   152  		if err != nil {
   153  			return []object.ObjMetadata{}, err
   154  		}
   155  		groupKind := schema.GroupKind{
   156  			Group: strings.TrimSpace(group),
   157  			Kind:  strings.TrimSpace(kind),
   158  		}
   159  		klog.V(4).Infof("creating obj metadata: %s/%s/%s", namespace, name, groupKind)
   160  		objMeta := object.ObjMetadata{
   161  			GroupKind: groupKind,
   162  			Name:      name,
   163  			Namespace: namespace,
   164  		}
   165  		objs = append(objs, objMeta)
   166  	}
   167  	return objs, nil
   168  }
   169  
   170  // Store is an Inventory interface function implemented to store
   171  // the object metadata in the wrapped ResourceGroup. Actual storing
   172  // happens in "GetObject".
   173  func (icm *InventoryResourceGroup) Store(objMetas object.ObjMetadataSet, status []actuation.ObjectStatus) error {
   174  	icm.objMetas = objMetas
   175  	icm.objStatus = status
   176  	return nil
   177  }
   178  
   179  // GetObject returns the wrapped object (ResourceGroup) as a resource.Info
   180  // or an error if one occurs.
   181  func (icm *InventoryResourceGroup) GetObject() (*unstructured.Unstructured, error) {
   182  	if icm.inv == nil {
   183  		return nil, fmt.Errorf("inventory info is nil")
   184  	}
   185  	objStatusMap := map[object.ObjMetadata]actuation.ObjectStatus{}
   186  	for _, s := range icm.objStatus {
   187  		objStatusMap[inventory.ObjMetadataFromObjectReference(s.ObjectReference)] = s
   188  	}
   189  	klog.V(4).Infof("getting inventory resource group")
   190  	// Create a slice of Resources as empty Interface
   191  	klog.V(4).Infof("Creating list of %d resources", len(icm.objMetas))
   192  	var objs []interface{}
   193  	for _, objMeta := range icm.objMetas {
   194  		klog.V(4).Infof("storing inventory obj refercence: %s/%s", objMeta.Namespace, objMeta.Name)
   195  		objs = append(objs, map[string]interface{}{
   196  			"group":     objMeta.GroupKind.Group,
   197  			"kind":      objMeta.GroupKind.Kind,
   198  			"namespace": objMeta.Namespace,
   199  			"name":      objMeta.Name,
   200  		})
   201  	}
   202  	klog.V(4).Infof("Creating list of %d resources status", len(icm.objMetas))
   203  	var objStatus []interface{}
   204  	for _, objMeta := range icm.objMetas {
   205  		status, found := objStatusMap[objMeta]
   206  		if found {
   207  			klog.V(4).Infof("storing inventory obj refercence and its status: %s/%s", objMeta.Namespace, objMeta.Name)
   208  			objStatus = append(objStatus, map[string]interface{}{
   209  				"group":     objMeta.GroupKind.Group,
   210  				"kind":      objMeta.GroupKind.Kind,
   211  				"namespace": objMeta.Namespace,
   212  				"name":      objMeta.Name,
   213  				"status":    "Unknown",
   214  				"strategy":  status.Strategy.String(),
   215  				"actuation": status.Actuation.String(),
   216  				"reconcile": status.Reconcile.String(),
   217  			})
   218  		}
   219  	}
   220  
   221  	// Create the inventory object by copying the template.
   222  	invCopy := icm.inv.DeepCopy()
   223  	// Adds or clears the inventory ObjMetadata to the ResourceGroup "spec.resources" section
   224  	if len(objs) == 0 {
   225  		klog.V(4).Infoln("clearing inventory resources")
   226  		unstructured.RemoveNestedField(invCopy.UnstructuredContent(),
   227  			"spec", "resources")
   228  		unstructured.RemoveNestedField(invCopy.UnstructuredContent(),
   229  			"status", "resourceStatuses")
   230  	} else {
   231  		klog.V(4).Infof("storing inventory (%d) resources", len(objs))
   232  		err := unstructured.SetNestedSlice(invCopy.UnstructuredContent(),
   233  			objs, "spec", "resources")
   234  		if err != nil {
   235  			return nil, err
   236  		}
   237  		err = unstructured.SetNestedSlice(invCopy.UnstructuredContent(),
   238  			objStatus, "status", "resourceStatuses")
   239  		if err != nil {
   240  			return nil, err
   241  		}
   242  		generation := invCopy.GetGeneration()
   243  		err = unstructured.SetNestedField(invCopy.UnstructuredContent(),
   244  			generation, "status", "observedGeneration")
   245  		if err != nil {
   246  			return nil, err
   247  		}
   248  	}
   249  	return invCopy, nil
   250  }
   251  
   252  // Apply is a Storage interface function implemented to apply the inventory
   253  // object.
   254  func (icm *InventoryResourceGroup) Apply(dc dynamic.Interface, mapper meta.RESTMapper, statusPolicy inventory.StatusPolicy) error {
   255  	invInfo, namespacedClient, err := icm.getNamespacedClient(dc, mapper)
   256  	if err != nil {
   257  		return err
   258  	}
   259  
   260  	// Get cluster object, if exsists.
   261  	clusterObj, err := namespacedClient.Get(context.TODO(), invInfo.GetName(), metav1.GetOptions{})
   262  	if err != nil && !apierrors.IsNotFound(err) {
   263  		return err
   264  	}
   265  
   266  	var appliedObj *unstructured.Unstructured
   267  
   268  	if clusterObj == nil {
   269  		// Create cluster inventory object, if it does not exist on cluster.
   270  		appliedObj, err = namespacedClient.Create(context.TODO(), invInfo, metav1.CreateOptions{})
   271  	} else {
   272  		// Update the cluster inventory object instead.
   273  		appliedObj, err = namespacedClient.Update(context.TODO(), invInfo, metav1.UpdateOptions{})
   274  	}
   275  	if err != nil {
   276  		return err
   277  	}
   278  
   279  	// Update status.
   280  	if statusPolicy == inventory.StatusPolicyAll {
   281  		invInfo.SetResourceVersion(appliedObj.GetResourceVersion())
   282  		_, err = namespacedClient.UpdateStatus(context.TODO(), invInfo, metav1.UpdateOptions{})
   283  	}
   284  
   285  	return err
   286  }
   287  
   288  func (icm *InventoryResourceGroup) ApplyWithPrune(dc dynamic.Interface, mapper meta.RESTMapper, statusPolicy inventory.StatusPolicy, _ object.ObjMetadataSet) error {
   289  	invInfo, namespacedClient, err := icm.getNamespacedClient(dc, mapper)
   290  	if err != nil {
   291  		return err
   292  	}
   293  
   294  	// Update the cluster inventory object.
   295  	appliedObj, err := namespacedClient.Update(context.TODO(), invInfo, metav1.UpdateOptions{})
   296  
   297  	// Update status.
   298  	if statusPolicy == inventory.StatusPolicyAll {
   299  		invInfo.SetResourceVersion(appliedObj.GetResourceVersion())
   300  		_, err = namespacedClient.UpdateStatus(context.TODO(), invInfo, metav1.UpdateOptions{})
   301  	}
   302  
   303  	return err
   304  }
   305  
   306  func (icm *InventoryResourceGroup) getNamespacedClient(dc dynamic.Interface, mapper meta.RESTMapper) (*unstructured.Unstructured, dynamic.ResourceInterface, error) {
   307  	invInfo, err := icm.GetObject()
   308  	if err != nil {
   309  		return nil, nil, err
   310  	}
   311  	if invInfo == nil {
   312  		return nil, nil, fmt.Errorf("attempting to create a nil inventory object")
   313  	}
   314  
   315  	mapping, err := mapper.RESTMapping(invInfo.GroupVersionKind().GroupKind(), invInfo.GroupVersionKind().Version)
   316  	if err != nil {
   317  		return nil, nil, err
   318  	}
   319  
   320  	// Create client to interact with cluster.
   321  	namespacedClient := dc.Resource(mapping.Resource).Namespace(invInfo.GetNamespace())
   322  
   323  	return invInfo, namespacedClient, nil
   324  }
   325  
   326  // IsResourceGroupInventory returns true if the passed object is
   327  // a ResourceGroup inventory object; false otherwise. If an error
   328  // occurs, then false is returned and the error.
   329  func IsResourceGroupInventory(obj *unstructured.Unstructured) (bool, error) {
   330  	if obj == nil {
   331  		return false, fmt.Errorf("inventory object is nil")
   332  	}
   333  	if !inventory.IsInventoryObject(obj) {
   334  		return false, nil
   335  	}
   336  	invGK := obj.GetObjectKind().GroupVersionKind().GroupKind()
   337  	if ResourceGroupGVK.GroupKind() != invGK {
   338  		return false, nil
   339  	}
   340  	return true, nil
   341  }
   342  
   343  // CustomResourceDefinition schema, without specific version. The default version
   344  // is returned when the RESTMapper returns a RESTMapping for this GroupKind.
   345  var crdGroupKind = schema.GroupKind{
   346  	Group: "apiextensions.k8s.io",
   347  	Kind:  "CustomResourceDefinition",
   348  }
   349  
   350  // ResourceGroupCRDApplied returns true if the inventory ResourceGroup
   351  // CRD is available from the current RESTMapper, or false otherwise.
   352  func ResourceGroupCRDApplied(factory cmdutil.Factory) bool {
   353  	mapper, err := factory.ToRESTMapper()
   354  	if err != nil {
   355  		klog.V(4).Infof("error retrieving RESTMapper when checking ResourceGroup CRD: %s\n", err)
   356  		return false
   357  	}
   358  	_, err = mapper.RESTMapping(ResourceGroupGVK.GroupKind())
   359  	if err != nil {
   360  		klog.V(7).Infof("error retrieving ResourceGroup RESTMapping: %s\n", err)
   361  		return false
   362  	}
   363  	return true
   364  }
   365  
   366  // ResourceGroupCRDMatched checks if the ResourceGroup CRD
   367  // in the cluster matches the CRD in the kpt binary.
   368  func ResourceGroupCRDMatched(factory cmdutil.Factory) bool {
   369  	mapper, err := factory.ToRESTMapper()
   370  	if err != nil {
   371  		klog.V(4).Infof("error retrieving RESTMapper when checking ResourceGroup CRD: %s\n", err)
   372  		return false
   373  	}
   374  	crd, err := rgCRD(mapper)
   375  	if err != nil {
   376  		klog.V(7).Infof("failed to get ResourceGroup CRD from string: %s", err)
   377  		return false
   378  	}
   379  
   380  	dc, err := factory.DynamicClient()
   381  	if err != nil {
   382  		klog.V(7).Infof("error getting the dynamic client: %s\n", err)
   383  		return false
   384  	}
   385  
   386  	mapping, err := mapper.RESTMapping(crdGroupKind)
   387  	if err != nil {
   388  		klog.V(7).Infof("Failed to get mapping of CRD type: %s", err)
   389  		return false
   390  	}
   391  
   392  	liveCRD, err := dc.Resource(mapping.Resource).Get(context.TODO(), "resourcegroups.kpt.dev", metav1.GetOptions{
   393  		TypeMeta: metav1.TypeMeta{
   394  			APIVersion: crd.GetAPIVersion(),
   395  			Kind:       "CustomResourceDefinition",
   396  		},
   397  	})
   398  	if err != nil {
   399  		klog.V(7).Infof("error getting the ResourceGroup CRD from cluster: %s\n", err)
   400  		return false
   401  	}
   402  
   403  	liveSpec, _, err := unstructured.NestedMap(liveCRD.Object, "spec")
   404  	if err != nil {
   405  		klog.V(7).Infof("error getting the ResourceGroup CRD spec from cluster: %s\n", err)
   406  		return false
   407  	}
   408  	latestspec, _, err := unstructured.NestedMap(crd.Object, "spec")
   409  	if err != nil {
   410  		klog.V(7).Infof("error getting the ResourceGroup CRD spec from string: %s\n", err)
   411  		return false
   412  	}
   413  	return reflect.DeepEqual(liveSpec, latestspec)
   414  }
   415  
   416  // ResourceGroupInstaller can install the ResourceGroup CRD into a cluster.
   417  type ResourceGroupInstaller struct {
   418  	Factory cmdutil.Factory
   419  }
   420  
   421  func (rgi *ResourceGroupInstaller) InstallRG(ctx context.Context) error {
   422  	poller, err := status.NewStatusPoller(rgi.Factory)
   423  	if err != nil {
   424  		return err
   425  	}
   426  
   427  	mapper, err := rgi.Factory.ToRESTMapper()
   428  	if err != nil {
   429  		return err
   430  	}
   431  
   432  	crd, err := rgCRD(mapper)
   433  	if err != nil {
   434  		return err
   435  	}
   436  
   437  	if err := rgi.applyRG(crd); err != nil {
   438  		if apierrors.IsAlreadyExists(err) {
   439  			return nil
   440  		}
   441  		return err
   442  	}
   443  
   444  	objs := object.UnstructuredSetToObjMetadataSet([]*unstructured.Unstructured{crd})
   445  	ctx, cancel := context.WithTimeout(ctx, applyRGTimeout)
   446  	return func() error {
   447  		defer cancel()
   448  		for e := range poller.Poll(ctx, objs, polling.PollOptions{PollInterval: applyRGPollInterval}) {
   449  			switch e.Type {
   450  			case pollevent.ErrorEvent:
   451  				return e.Error
   452  			case pollevent.ResourceUpdateEvent:
   453  				if e.Resource.Status == kstatus.CurrentStatus {
   454  					meta.MaybeResetRESTMapper(mapper)
   455  				}
   456  			}
   457  		}
   458  		return nil
   459  	}()
   460  }
   461  
   462  func (rgi *ResourceGroupInstaller) applyRG(crd runtime.Object) error {
   463  	mapper, err := rgi.Factory.ToRESTMapper()
   464  	if err != nil {
   465  		return err
   466  	}
   467  	mapping, err := mapper.RESTMapping(crdGroupKind)
   468  	if err != nil {
   469  		return err
   470  	}
   471  	client, err := rgi.Factory.UnstructuredClientForMapping(mapping)
   472  	if err != nil {
   473  		return err
   474  	}
   475  
   476  	// Set the "last-applied-annotation" so future applies work correctly.
   477  	if err := util.CreateApplyAnnotation(crd, unstructured.UnstructuredJSONScheme); err != nil {
   478  		return err
   479  	}
   480  	// Apply the CRD to the cluster and ignore already exists error.
   481  	var clearResourceVersion = false
   482  	var emptyNamespace = ""
   483  	helper := resource.NewHelper(client, mapping)
   484  	_, err = helper.Create(emptyNamespace, clearResourceVersion, crd)
   485  	return err
   486  }
   487  
   488  // rgCRD returns the ResourceGroup CRD in Unstructured format or an error.
   489  func rgCRD(mapper meta.RESTMapper) (*unstructured.Unstructured, error) {
   490  	mapping, err := mapper.RESTMapping(crdGroupKind)
   491  	if err != nil {
   492  		return nil, err
   493  	}
   494  	// mapping contains the full GVK version, which is used to determine
   495  	// the version of the ResourceGroup CRD to create. We have defined the
   496  	// v1beta1 and v1 versions of the apiextensions group of the CRD.
   497  	version := mapping.GroupVersionKind.Version
   498  	klog.V(4).Infof("using apiextensions.k8s.io version: %s", version)
   499  	rgCRDStr, ok := resourceGroupCRDs[version]
   500  	if !ok {
   501  		klog.V(4).Infof("ResourceGroup CRD version %s not found", version)
   502  		return nil, err
   503  	}
   504  	crd, err := stringToUnstructured(rgCRDStr)
   505  	if err != nil {
   506  		return nil, err
   507  	}
   508  	return crd, nil
   509  }
   510  
   511  // stringToUnstructured transforms a single resource represented by
   512  // the passed string into a pointer to an "Unstructured" object,
   513  // or an error if one occurred.
   514  func stringToUnstructured(str string) (*unstructured.Unstructured, error) {
   515  	node, err := yaml.Parse(str)
   516  	if err != nil {
   517  		return nil, err
   518  	}
   519  	s, err := node.String()
   520  	if err != nil {
   521  		return nil, err
   522  	}
   523  	var m map[string]interface{}
   524  	if err := yaml.Unmarshal([]byte(s), &m); err != nil {
   525  		return nil, err
   526  	}
   527  	return &unstructured.Unstructured{Object: m}, nil
   528  }
   529  
   530  // resourceGroupCRDs maps the apiextensions version to the ResourceGroup
   531  // custom resource definition string.
   532  var resourceGroupCRDs = map[string]string{
   533  	"v1beta1": v1beta1RGCrd,
   534  	"v1":      v1RGCrd,
   535  }
   536  
   537  // ResourceGroup custom resource definition using v1beta1 version
   538  // of the apiextensions.k8s.io API group. APIServers version 1.15
   539  // or less will use this apiextensions group by default.
   540  var v1beta1RGCrd = `
   541  apiVersion: apiextensions.k8s.io/v1beta1
   542  kind: CustomResourceDefinition
   543  metadata:
   544    name: resourcegroups.kpt.dev
   545  spec:
   546    group: kpt.dev
   547    names:
   548      kind: ResourceGroup
   549      listKind: ResourceGroupList
   550      plural: resourcegroups
   551      singular: resourcegroup
   552    scope: Namespaced
   553    subresources:
   554      status: {}
   555    validation:
   556      openAPIV3Schema:
   557        description: ResourceGroup is the Schema for the resourcegroups API
   558        properties:
   559          apiVersion:
   560            description: 'APIVersion defines the versioned schema of this representation
   561              of an object. Servers should convert recognized schemas to the latest
   562              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
   563            type: string
   564          kind:
   565            description: 'Kind is a string value representing the REST resource this
   566              object represents. Servers may infer this from the endpoint the client
   567              submits requests to. Cannot be updated. In CamelCase.
   568              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
   569            type: string
   570          metadata:
   571            type: object
   572          spec:
   573            description: ResourceGroupSpec defines the desired state of ResourceGroup
   574            properties:
   575              descriptor:
   576                description: Descriptor regroups the information and metadata about
   577                  a resource group
   578                properties:
   579                  description:
   580                    description: Description is a brief description of a group of resources
   581                    type: string
   582                  links:
   583                    description: Links are a list of descriptive URLs intended to be
   584                      used to surface additional information
   585                    items:
   586                      properties:
   587                        description:
   588                          description: Description explains the purpose of the link
   589                          type: string
   590                        url:
   591                          description: Url is the URL of the link
   592                          type: string
   593                      required:
   594                      - description
   595                      - url
   596                      type: object
   597                    type: array
   598                  revision:
   599                    description: Revision is an optional revision for a group of resources
   600                    type: string
   601                  type:
   602                    description: Type can contain prefix, such as Application/WordPress
   603                      or Service/Spanner
   604                    type: string
   605                type: object
   606              resources:
   607                description: Resources contains a list of resources that form the resource group
   608                items:
   609                  description: ObjMetadata organizes and stores the identifying information
   610                    for an object. This struct (as a string) is stored in a grouping
   611                    object to keep track of sets of applied objects.
   612                  properties:
   613                    group:
   614                      type: string
   615                    kind:
   616                      type: string
   617                    name:
   618                      type: string
   619                    namespace:
   620                      type: string
   621                  required:
   622                  - group
   623                  - kind
   624                  - name
   625                  - namespace
   626                  type: object
   627                type: array
   628            type: object
   629          status:
   630            description: ResourceGroupStatus defines the observed state of ResourceGroup
   631            properties:
   632              conditions:
   633                description: Conditions lists the conditions of the current status for
   634                  the group
   635                items:
   636                  properties:
   637                    lastTransitionTime:
   638                      description: last time the condition transit from one status to
   639                        another
   640                      format: date-time
   641                      type: string
   642                    message:
   643                      description: human-readable message indicating details about last
   644                        transition
   645                      type: string
   646                    reason:
   647                      description: one-word CamelCase reason for the condition's last
   648                        transition
   649                      type: string
   650                    status:
   651                      description: Status of the condition
   652                      type: string
   653                    type:
   654                      description: Type of the condition
   655                      type: string
   656                  required:
   657                  - status
   658                  - type
   659                  type: object
   660                type: array
   661              observedGeneration:
   662                description: ObservedGeneration is the most recent generation observed.
   663                  It corresponds to the Object's generation, which is updated on mutation
   664                  by the API Server. Everytime the controller does a successful reconcile,
   665                  it sets ObservedGeneration to match ResourceGroup.metadata.generation.
   666                format: int64
   667                type: integer
   668              resourceStatuses:
   669                description: ResourceStatuses lists the status for each resource in
   670                  the group
   671                items:
   672                  description: ResourceStatus contains the status of a given resource
   673                    uniquely identified by its group, kind, name and namespace.
   674                  properties:
   675                    actuation:
   676                      description: actuation indicates whether actuation has been
   677                        performed yet and how it went.
   678                      type: string
   679                    conditions:
   680                      items:
   681                        properties:
   682                          lastTransitionTime:
   683                            description: last time the condition transit from one status
   684                              to another
   685                            format: date-time
   686                            type: string
   687                          message:
   688                            description: human-readable message indicating details about
   689                              last transition
   690                            type: string
   691                          reason:
   692                            description: one-word CamelCase reason for the condition's
   693                              last transition
   694                            type: string
   695                          status:
   696                            description: Status of the condition
   697                            type: string
   698                          type:
   699                            description: Type of the condition
   700                            type: string
   701                        required:
   702                        - status
   703                        - type
   704                        type: object
   705                      type: array
   706                    group:
   707                      type: string
   708                    kind:
   709                      type: string
   710                    name:
   711                      type: string
   712                    namespace:
   713                      type: string
   714                    reconcile:
   715                      description: reconcile indicates whether reconciliation has
   716                        been performed yet and how it went.
   717                      type: string
   718                    sourceHash:
   719                      type: string
   720                    status:
   721                      description: Status describes the status of a resource
   722                      type: string
   723                    strategy:
   724                      description: strategy indicates the method of actuation (apply
   725                        or delete) used or planned to be used.
   726                      type: string
   727                  required:
   728                  - group
   729                  - kind
   730                  - name
   731                  - namespace
   732                  - status
   733                  type: object
   734                type: array
   735            required:
   736            - observedGeneration
   737            type: object
   738        type: object
   739    version: v1alpha1
   740    versions:
   741    - name: v1alpha1
   742      served: true
   743      storage: true
   744  status:
   745    acceptedNames:
   746      kind: ""
   747      plural: ""
   748    conditions: []
   749    storedVersions: []
   750  `
   751  
   752  // ResourceGroup custom resource definition using v1 version
   753  // of the apiextensions.k8s.io API group. APIServers at 1.16
   754  // or greater will use this apiextensions group by default.
   755  var v1RGCrd = `
   756  apiVersion: apiextensions.k8s.io/v1
   757  kind: CustomResourceDefinition
   758  metadata:
   759    name: resourcegroups.kpt.dev
   760  spec:
   761    conversion:
   762      strategy: None
   763    group: kpt.dev
   764    names:
   765      kind: ResourceGroup
   766      listKind: ResourceGroupList
   767      plural: resourcegroups
   768      singular: resourcegroup
   769    scope: Namespaced
   770    versions:
   771    - name: v1alpha1
   772      schema:
   773        openAPIV3Schema:
   774          description: ResourceGroup is the Schema for the resourcegroups API
   775          properties:
   776            apiVersion:
   777              description: 'APIVersion defines the versioned schema of this representation
   778                of an object. Servers should convert recognized schemas to the latest
   779                internal value, and may reject unrecognized values.
   780                More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
   781              type: string
   782            kind:
   783              description: 'Kind is a string value representing the REST resource this
   784                object represents. Servers may infer this from the endpoint the client
   785                submits requests to. Cannot be updated. In CamelCase.
   786                More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
   787              type: string
   788            metadata:
   789              type: object
   790            spec:
   791              description: ResourceGroupSpec defines the desired state of ResourceGroup
   792              properties:
   793                descriptor:
   794                  description: Descriptor regroups the information and metadata about
   795                    a resource group
   796                  properties:
   797                    description:
   798                      description: Description is a brief description of a group of
   799                        resources
   800                      type: string
   801                    links:
   802                      description: Links are a list of descriptive URLs intended to
   803                        be used to surface additional information
   804                      items:
   805                        properties:
   806                          description:
   807                            description: Description explains the purpose of the link
   808                            type: string
   809                          url:
   810                            description: Url is the URL of the link
   811                            type: string
   812                        required:
   813                        - description
   814                        - url
   815                        type: object
   816                      type: array
   817                    revision:
   818                      description: Revision is an optional revision for a group of resources
   819                      type: string
   820                    type:
   821                      description: Type can contain prefix, such as Application/WordPress
   822                        or Service/Spanner
   823                      type: string
   824                  type: object
   825                resources:
   826                  description: Resources contains a list of resources that form the
   827                    resource group
   828                  items:
   829                    description: ObjMetadata organizes and stores the identifying information
   830                      for an object. This struct (as a string) is stored in a grouping
   831                      object to keep track of sets of applied objects.
   832                    properties:
   833                      group:
   834                        type: string
   835                      kind:
   836                        type: string
   837                      name:
   838                        type: string
   839                      namespace:
   840                        type: string
   841                    required:
   842                    - group
   843                    - kind
   844                    - name
   845                    - namespace
   846                    type: object
   847                  type: array
   848              type: object
   849            status:
   850              description: ResourceGroupStatus defines the observed state of ResourceGroup
   851              properties:
   852                conditions:
   853                  description: Conditions lists the conditions of the current status
   854                    for the group
   855                  items:
   856                    properties:
   857                      lastTransitionTime:
   858                        description: last time the condition transit from one status
   859                          to another
   860                        format: date-time
   861                        type: string
   862                      message:
   863                        description: human-readable message indicating details about
   864                          last transition
   865                        type: string
   866                      reason:
   867                        description: one-word CamelCase reason for the condition's last
   868                          transition
   869                        type: string
   870                      status:
   871                        description: Status of the condition
   872                        type: string
   873                      type:
   874                        description: Type of the condition
   875                        type: string
   876                    required:
   877                    - status
   878                    - type
   879                    type: object
   880                  type: array
   881                observedGeneration:
   882                  description: ObservedGeneration is the most recent generation observed.
   883                    It corresponds to the Object's generation, which is updated on mutation
   884                    by the API Server. Everytime the controller does a successful reconcile,
   885                    it sets ObservedGeneration to match ResourceGroup.metadata.generation.
   886                  format: int64
   887                  type: integer
   888                resourceStatuses:
   889                  description: ResourceStatuses lists the status for each resource in
   890                    the group
   891                  items:
   892                    description: ResourceStatus contains the status of a given resource
   893                      uniquely identified by its group, kind, name and namespace.
   894                    properties:
   895                      actuation:
   896                        description: actuation indicates whether actuation has been
   897                          performed yet and how it went.
   898                        type: string
   899                      conditions:
   900                        items:
   901                          properties:
   902                            lastTransitionTime:
   903                              description: last time the condition transit from one
   904                                status to another
   905                              format: date-time
   906                              type: string
   907                            message:
   908                              description: human-readable message indicating details
   909                                about last transition
   910                              type: string
   911                            reason:
   912                              description: one-word CamelCase reason for the condition's
   913                                last transition
   914                              type: string
   915                            status:
   916                              description: Status of the condition
   917                              type: string
   918                            type:
   919                              description: Type of the condition
   920                              type: string
   921                          required:
   922                          - status
   923                          - type
   924                          type: object
   925                        type: array
   926                      group:
   927                        type: string
   928                      kind:
   929                        type: string
   930                      name:
   931                        type: string
   932                      namespace:
   933                        type: string
   934                      reconcile:
   935                        description: reconcile indicates whether reconciliation has
   936                          been performed yet and how it went.
   937                        type: string
   938                      sourceHash:
   939                        type: string
   940                      status:
   941                        description: Status describes the status of a resource
   942                        type: string
   943                      strategy:
   944                        description: strategy indicates the method of actuation (apply
   945                          or delete) used or planned to be used.
   946                        type: string
   947                    required:
   948                    - group
   949                    - kind
   950                    - name
   951                    - namespace
   952                    - status
   953                    type: object
   954                  type: array
   955              required:
   956              - observedGeneration
   957              type: object
   958          type: object
   959      served: true
   960      storage: true
   961      subresources:
   962        status: {}
   963  status:
   964    acceptedNames:
   965      kind: ""
   966      plural: ""
   967    conditions: []
   968    storedVersions: []
   969  `