github.com/GoogleContainerTools/kpt@v1.0.0-beta.50.0.20240520170205-c25345ffcbee/pkg/live/inventoryrg.go (about) 1 // Copyright 2020 The kpt Authors 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 // Since the ResourceGroup CRD specifies the status as a sub-resource, this 296 // will not update the status. 297 appliedObj, err := namespacedClient.Update(context.TODO(), invInfo, metav1.UpdateOptions{}) 298 if err != nil { 299 return err 300 } 301 302 // Update status, if status policy allows it. 303 // To avoid losing modifications performed by mutating webhooks, copy the 304 // status from the desired state to the latest state after the previous update. 305 // This also ensures that the ResourceVersion matches the latest state, to 306 // avoid the update being rejected by the server. 307 if statusPolicy == inventory.StatusPolicyAll { 308 status, found, err := unstructured.NestedMap(invInfo.UnstructuredContent(), "status") 309 if err != nil { 310 return err 311 } 312 if found { 313 err = unstructured.SetNestedField(appliedObj.UnstructuredContent(), status, "status") 314 if err != nil { 315 return err 316 } 317 _, err = namespacedClient.UpdateStatus(context.TODO(), appliedObj, metav1.UpdateOptions{}) 318 if err != nil { 319 return err 320 } 321 } 322 } 323 324 return nil 325 } 326 327 func (icm *InventoryResourceGroup) getNamespacedClient(dc dynamic.Interface, mapper meta.RESTMapper) (*unstructured.Unstructured, dynamic.ResourceInterface, error) { 328 invInfo, err := icm.GetObject() 329 if err != nil { 330 return nil, nil, err 331 } 332 if invInfo == nil { 333 return nil, nil, fmt.Errorf("attempting to create a nil inventory object") 334 } 335 336 mapping, err := mapper.RESTMapping(invInfo.GroupVersionKind().GroupKind(), invInfo.GroupVersionKind().Version) 337 if err != nil { 338 return nil, nil, err 339 } 340 341 // Create client to interact with cluster. 342 namespacedClient := dc.Resource(mapping.Resource).Namespace(invInfo.GetNamespace()) 343 344 return invInfo, namespacedClient, nil 345 } 346 347 // IsResourceGroupInventory returns true if the passed object is 348 // a ResourceGroup inventory object; false otherwise. If an error 349 // occurs, then false is returned and the error. 350 func IsResourceGroupInventory(obj *unstructured.Unstructured) (bool, error) { 351 if obj == nil { 352 return false, fmt.Errorf("inventory object is nil") 353 } 354 if !inventory.IsInventoryObject(obj) { 355 return false, nil 356 } 357 invGK := obj.GetObjectKind().GroupVersionKind().GroupKind() 358 if ResourceGroupGVK.GroupKind() != invGK { 359 return false, nil 360 } 361 return true, nil 362 } 363 364 // CustomResourceDefinition schema, without specific version. The default version 365 // is returned when the RESTMapper returns a RESTMapping for this GroupKind. 366 var crdGroupKind = schema.GroupKind{ 367 Group: "apiextensions.k8s.io", 368 Kind: "CustomResourceDefinition", 369 } 370 371 // ResourceGroupCRDApplied returns true if the inventory ResourceGroup 372 // CRD is available from the current RESTMapper, or false otherwise. 373 func ResourceGroupCRDApplied(factory cmdutil.Factory) bool { 374 mapper, err := factory.ToRESTMapper() 375 if err != nil { 376 klog.V(4).Infof("error retrieving RESTMapper when checking ResourceGroup CRD: %s\n", err) 377 return false 378 } 379 _, err = mapper.RESTMapping(ResourceGroupGVK.GroupKind()) 380 if err != nil { 381 klog.V(7).Infof("error retrieving ResourceGroup RESTMapping: %s\n", err) 382 return false 383 } 384 return true 385 } 386 387 // ResourceGroupCRDMatched checks if the ResourceGroup CRD 388 // in the cluster matches the CRD in the kpt binary. 389 func ResourceGroupCRDMatched(factory cmdutil.Factory) bool { 390 mapper, err := factory.ToRESTMapper() 391 if err != nil { 392 klog.V(4).Infof("error retrieving RESTMapper when checking ResourceGroup CRD: %s\n", err) 393 return false 394 } 395 crd, err := rgCRD(mapper) 396 if err != nil { 397 klog.V(7).Infof("failed to get ResourceGroup CRD from string: %s", err) 398 return false 399 } 400 401 dc, err := factory.DynamicClient() 402 if err != nil { 403 klog.V(7).Infof("error getting the dynamic client: %s\n", err) 404 return false 405 } 406 407 mapping, err := mapper.RESTMapping(crdGroupKind) 408 if err != nil { 409 klog.V(7).Infof("Failed to get mapping of CRD type: %s", err) 410 return false 411 } 412 413 liveCRD, err := dc.Resource(mapping.Resource).Get(context.TODO(), "resourcegroups.kpt.dev", metav1.GetOptions{ 414 TypeMeta: metav1.TypeMeta{ 415 APIVersion: crd.GetAPIVersion(), 416 Kind: "CustomResourceDefinition", 417 }, 418 }) 419 if err != nil { 420 klog.V(7).Infof("error getting the ResourceGroup CRD from cluster: %s\n", err) 421 return false 422 } 423 424 liveSpec, _, err := unstructured.NestedMap(liveCRD.Object, "spec") 425 if err != nil { 426 klog.V(7).Infof("error getting the ResourceGroup CRD spec from cluster: %s\n", err) 427 return false 428 } 429 latestspec, _, err := unstructured.NestedMap(crd.Object, "spec") 430 if err != nil { 431 klog.V(7).Infof("error getting the ResourceGroup CRD spec from string: %s\n", err) 432 return false 433 } 434 return reflect.DeepEqual(liveSpec, latestspec) 435 } 436 437 // ResourceGroupInstaller can install the ResourceGroup CRD into a cluster. 438 type ResourceGroupInstaller struct { 439 Factory cmdutil.Factory 440 } 441 442 func (rgi *ResourceGroupInstaller) InstallRG(ctx context.Context) error { 443 poller, err := status.NewStatusPoller(rgi.Factory) 444 if err != nil { 445 return err 446 } 447 448 mapper, err := rgi.Factory.ToRESTMapper() 449 if err != nil { 450 return err 451 } 452 453 crd, err := rgCRD(mapper) 454 if err != nil { 455 return err 456 } 457 458 if err := rgi.applyRG(crd); err != nil { 459 if apierrors.IsAlreadyExists(err) { 460 return nil 461 } 462 return err 463 } 464 465 objs := object.UnstructuredSetToObjMetadataSet([]*unstructured.Unstructured{crd}) 466 ctx, cancel := context.WithTimeout(ctx, applyRGTimeout) 467 return func() error { 468 defer cancel() 469 for e := range poller.Poll(ctx, objs, polling.PollOptions{PollInterval: applyRGPollInterval}) { 470 switch e.Type { 471 case pollevent.ErrorEvent: 472 return e.Error 473 case pollevent.ResourceUpdateEvent: 474 if e.Resource.Status == kstatus.CurrentStatus { 475 meta.MaybeResetRESTMapper(mapper) 476 } 477 } 478 } 479 return nil 480 }() 481 } 482 483 func (rgi *ResourceGroupInstaller) applyRG(crd runtime.Object) error { 484 mapper, err := rgi.Factory.ToRESTMapper() 485 if err != nil { 486 return err 487 } 488 mapping, err := mapper.RESTMapping(crdGroupKind) 489 if err != nil { 490 return err 491 } 492 client, err := rgi.Factory.UnstructuredClientForMapping(mapping) 493 if err != nil { 494 return err 495 } 496 497 // Set the "last-applied-annotation" so future applies work correctly. 498 if err := util.CreateApplyAnnotation(crd, unstructured.UnstructuredJSONScheme); err != nil { 499 return err 500 } 501 // Apply the CRD to the cluster and ignore already exists error. 502 var clearResourceVersion = false 503 var emptyNamespace = "" 504 helper := resource.NewHelper(client, mapping) 505 _, err = helper.Create(emptyNamespace, clearResourceVersion, crd) 506 return err 507 } 508 509 // rgCRD returns the ResourceGroup CRD in Unstructured format or an error. 510 func rgCRD(mapper meta.RESTMapper) (*unstructured.Unstructured, error) { 511 mapping, err := mapper.RESTMapping(crdGroupKind) 512 if err != nil { 513 return nil, err 514 } 515 // mapping contains the full GVK version, which is used to determine 516 // the version of the ResourceGroup CRD to create. We have defined the 517 // v1beta1 and v1 versions of the apiextensions group of the CRD. 518 version := mapping.GroupVersionKind.Version 519 klog.V(4).Infof("using apiextensions.k8s.io version: %s", version) 520 rgCRDStr, ok := resourceGroupCRDs[version] 521 if !ok { 522 klog.V(4).Infof("ResourceGroup CRD version %s not found", version) 523 return nil, err 524 } 525 crd, err := stringToUnstructured(rgCRDStr) 526 if err != nil { 527 return nil, err 528 } 529 return crd, nil 530 } 531 532 // stringToUnstructured transforms a single resource represented by 533 // the passed string into a pointer to an "Unstructured" object, 534 // or an error if one occurred. 535 func stringToUnstructured(str string) (*unstructured.Unstructured, error) { 536 node, err := yaml.Parse(str) 537 if err != nil { 538 return nil, err 539 } 540 s, err := node.String() 541 if err != nil { 542 return nil, err 543 } 544 var m map[string]interface{} 545 if err := yaml.Unmarshal([]byte(s), &m); err != nil { 546 return nil, err 547 } 548 return &unstructured.Unstructured{Object: m}, nil 549 } 550 551 // resourceGroupCRDs maps the apiextensions version to the ResourceGroup 552 // custom resource definition string. 553 var resourceGroupCRDs = map[string]string{ 554 "v1beta1": v1beta1RGCrd, 555 "v1": v1RGCrd, 556 } 557 558 // ResourceGroup custom resource definition using v1beta1 version 559 // of the apiextensions.k8s.io API group. APIServers version 1.15 560 // or less will use this apiextensions group by default. 561 var v1beta1RGCrd = ` 562 apiVersion: apiextensions.k8s.io/v1beta1 563 kind: CustomResourceDefinition 564 metadata: 565 name: resourcegroups.kpt.dev 566 spec: 567 group: kpt.dev 568 names: 569 kind: ResourceGroup 570 listKind: ResourceGroupList 571 plural: resourcegroups 572 singular: resourcegroup 573 scope: Namespaced 574 subresources: 575 status: {} 576 validation: 577 openAPIV3Schema: 578 description: ResourceGroup is the Schema for the resourcegroups API 579 properties: 580 apiVersion: 581 description: 'APIVersion defines the versioned schema of this representation 582 of an object. Servers should convert recognized schemas to the latest 583 internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 584 type: string 585 kind: 586 description: 'Kind is a string value representing the REST resource this 587 object represents. Servers may infer this from the endpoint the client 588 submits requests to. Cannot be updated. In CamelCase. 589 More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 590 type: string 591 metadata: 592 type: object 593 spec: 594 description: ResourceGroupSpec defines the desired state of ResourceGroup 595 properties: 596 descriptor: 597 description: Descriptor regroups the information and metadata about 598 a resource group 599 properties: 600 description: 601 description: Description is a brief description of a group of resources 602 type: string 603 links: 604 description: Links are a list of descriptive URLs intended to be 605 used to surface additional information 606 items: 607 properties: 608 description: 609 description: Description explains the purpose of the link 610 type: string 611 url: 612 description: Url is the URL of the link 613 type: string 614 required: 615 - description 616 - url 617 type: object 618 type: array 619 revision: 620 description: Revision is an optional revision for a group of resources 621 type: string 622 type: 623 description: Type can contain prefix, such as Application/WordPress 624 or Service/Spanner 625 type: string 626 type: object 627 resources: 628 description: Resources contains a list of resources that form the resource group 629 items: 630 description: ObjMetadata organizes and stores the identifying information 631 for an object. This struct (as a string) is stored in a grouping 632 object to keep track of sets of applied objects. 633 properties: 634 group: 635 type: string 636 kind: 637 type: string 638 name: 639 type: string 640 namespace: 641 type: string 642 required: 643 - group 644 - kind 645 - name 646 - namespace 647 type: object 648 type: array 649 type: object 650 status: 651 description: ResourceGroupStatus defines the observed state of ResourceGroup 652 properties: 653 conditions: 654 description: Conditions lists the conditions of the current status for 655 the group 656 items: 657 properties: 658 lastTransitionTime: 659 description: last time the condition transit from one status to 660 another 661 format: date-time 662 type: string 663 message: 664 description: human-readable message indicating details about last 665 transition 666 type: string 667 reason: 668 description: one-word CamelCase reason for the condition's last 669 transition 670 type: string 671 status: 672 description: Status of the condition 673 type: string 674 type: 675 description: Type of the condition 676 type: string 677 required: 678 - status 679 - type 680 type: object 681 type: array 682 observedGeneration: 683 description: ObservedGeneration is the most recent generation observed. 684 It corresponds to the Object's generation, which is updated on mutation 685 by the API Server. Everytime the controller does a successful reconcile, 686 it sets ObservedGeneration to match ResourceGroup.metadata.generation. 687 format: int64 688 type: integer 689 resourceStatuses: 690 description: ResourceStatuses lists the status for each resource in 691 the group 692 items: 693 description: ResourceStatus contains the status of a given resource 694 uniquely identified by its group, kind, name and namespace. 695 properties: 696 actuation: 697 description: actuation indicates whether actuation has been 698 performed yet and how it went. 699 type: string 700 conditions: 701 items: 702 properties: 703 lastTransitionTime: 704 description: last time the condition transit from one status 705 to another 706 format: date-time 707 type: string 708 message: 709 description: human-readable message indicating details about 710 last transition 711 type: string 712 reason: 713 description: one-word CamelCase reason for the condition's 714 last transition 715 type: string 716 status: 717 description: Status of the condition 718 type: string 719 type: 720 description: Type of the condition 721 type: string 722 required: 723 - status 724 - type 725 type: object 726 type: array 727 group: 728 type: string 729 kind: 730 type: string 731 name: 732 type: string 733 namespace: 734 type: string 735 reconcile: 736 description: reconcile indicates whether reconciliation has 737 been performed yet and how it went. 738 type: string 739 sourceHash: 740 type: string 741 status: 742 description: Status describes the status of a resource 743 type: string 744 strategy: 745 description: strategy indicates the method of actuation (apply 746 or delete) used or planned to be used. 747 type: string 748 required: 749 - group 750 - kind 751 - name 752 - namespace 753 - status 754 type: object 755 type: array 756 required: 757 - observedGeneration 758 type: object 759 type: object 760 version: v1alpha1 761 versions: 762 - name: v1alpha1 763 served: true 764 storage: true 765 status: 766 acceptedNames: 767 kind: "" 768 plural: "" 769 conditions: [] 770 storedVersions: [] 771 ` 772 773 // ResourceGroup custom resource definition using v1 version 774 // of the apiextensions.k8s.io API group. APIServers at 1.16 775 // or greater will use this apiextensions group by default. 776 var v1RGCrd = ` 777 apiVersion: apiextensions.k8s.io/v1 778 kind: CustomResourceDefinition 779 metadata: 780 name: resourcegroups.kpt.dev 781 spec: 782 conversion: 783 strategy: None 784 group: kpt.dev 785 names: 786 kind: ResourceGroup 787 listKind: ResourceGroupList 788 plural: resourcegroups 789 singular: resourcegroup 790 scope: Namespaced 791 versions: 792 - name: v1alpha1 793 schema: 794 openAPIV3Schema: 795 description: ResourceGroup is the Schema for the resourcegroups API 796 properties: 797 apiVersion: 798 description: 'APIVersion defines the versioned schema of this representation 799 of an object. Servers should convert recognized schemas to the latest 800 internal value, and may reject unrecognized values. 801 More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 802 type: string 803 kind: 804 description: 'Kind is a string value representing the REST resource this 805 object represents. Servers may infer this from the endpoint the client 806 submits requests to. Cannot be updated. In CamelCase. 807 More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 808 type: string 809 metadata: 810 type: object 811 spec: 812 description: ResourceGroupSpec defines the desired state of ResourceGroup 813 properties: 814 descriptor: 815 description: Descriptor regroups the information and metadata about 816 a resource group 817 properties: 818 description: 819 description: Description is a brief description of a group of 820 resources 821 type: string 822 links: 823 description: Links are a list of descriptive URLs intended to 824 be used to surface additional information 825 items: 826 properties: 827 description: 828 description: Description explains the purpose of the link 829 type: string 830 url: 831 description: Url is the URL of the link 832 type: string 833 required: 834 - description 835 - url 836 type: object 837 type: array 838 revision: 839 description: Revision is an optional revision for a group of resources 840 type: string 841 type: 842 description: Type can contain prefix, such as Application/WordPress 843 or Service/Spanner 844 type: string 845 type: object 846 resources: 847 description: Resources contains a list of resources that form the 848 resource group 849 items: 850 description: ObjMetadata organizes and stores the identifying information 851 for an object. This struct (as a string) is stored in a grouping 852 object to keep track of sets of applied objects. 853 properties: 854 group: 855 type: string 856 kind: 857 type: string 858 name: 859 type: string 860 namespace: 861 type: string 862 required: 863 - group 864 - kind 865 - name 866 - namespace 867 type: object 868 type: array 869 type: object 870 status: 871 description: ResourceGroupStatus defines the observed state of ResourceGroup 872 properties: 873 conditions: 874 description: Conditions lists the conditions of the current status 875 for the group 876 items: 877 properties: 878 lastTransitionTime: 879 description: last time the condition transit from one status 880 to another 881 format: date-time 882 type: string 883 message: 884 description: human-readable message indicating details about 885 last transition 886 type: string 887 reason: 888 description: one-word CamelCase reason for the condition's last 889 transition 890 type: string 891 status: 892 description: Status of the condition 893 type: string 894 type: 895 description: Type of the condition 896 type: string 897 required: 898 - status 899 - type 900 type: object 901 type: array 902 observedGeneration: 903 description: ObservedGeneration is the most recent generation observed. 904 It corresponds to the Object's generation, which is updated on mutation 905 by the API Server. Everytime the controller does a successful reconcile, 906 it sets ObservedGeneration to match ResourceGroup.metadata.generation. 907 format: int64 908 type: integer 909 resourceStatuses: 910 description: ResourceStatuses lists the status for each resource in 911 the group 912 items: 913 description: ResourceStatus contains the status of a given resource 914 uniquely identified by its group, kind, name and namespace. 915 properties: 916 actuation: 917 description: actuation indicates whether actuation has been 918 performed yet and how it went. 919 type: string 920 conditions: 921 items: 922 properties: 923 lastTransitionTime: 924 description: last time the condition transit from one 925 status to another 926 format: date-time 927 type: string 928 message: 929 description: human-readable message indicating details 930 about last transition 931 type: string 932 reason: 933 description: one-word CamelCase reason for the condition's 934 last transition 935 type: string 936 status: 937 description: Status of the condition 938 type: string 939 type: 940 description: Type of the condition 941 type: string 942 required: 943 - status 944 - type 945 type: object 946 type: array 947 group: 948 type: string 949 kind: 950 type: string 951 name: 952 type: string 953 namespace: 954 type: string 955 reconcile: 956 description: reconcile indicates whether reconciliation has 957 been performed yet and how it went. 958 type: string 959 sourceHash: 960 type: string 961 status: 962 description: Status describes the status of a resource 963 type: string 964 strategy: 965 description: strategy indicates the method of actuation (apply 966 or delete) used or planned to be used. 967 type: string 968 required: 969 - group 970 - kind 971 - name 972 - namespace 973 - status 974 type: object 975 type: array 976 required: 977 - observedGeneration 978 type: object 979 type: object 980 served: true 981 storage: true 982 subresources: 983 status: {} 984 status: 985 acceptedNames: 986 kind: "" 987 plural: "" 988 conditions: [] 989 storedVersions: [] 990 `