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 `