diff --git a/cmd/main.go b/cmd/main.go index a3220668..658b7c84 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -41,6 +41,7 @@ import ( "github.com/kcp-dev/kcp-operator/internal/controller/kubeconfig" "github.com/kcp-dev/kcp-operator/internal/controller/rootshard" "github.com/kcp-dev/kcp-operator/internal/controller/shard" + "github.com/kcp-dev/kcp-operator/internal/controller/workspaceobject" "github.com/kcp-dev/kcp-operator/internal/reconciling" operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1" ) @@ -188,6 +189,13 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Kubeconfig") os.Exit(1) } + if err = (&workspaceobject.WorkspaceObjectReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "WorkspaceObject") + os.Exit(1) + } // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/crd/bases/operator.kcp.io_workspaceobjects.yaml b/config/crd/bases/operator.kcp.io_workspaceobjects.yaml new file mode 100644 index 00000000..b80eddfc --- /dev/null +++ b/config/crd/bases/operator.kcp.io_workspaceobjects.yaml @@ -0,0 +1,171 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.1 + name: workspaceobjects.operator.kcp.io +spec: + group: operator.kcp.io + names: + kind: WorkspaceObject + listKind: WorkspaceObjectList + plural: workspaceobjects + singular: workspaceobject + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.rootShard.ref.name + name: RootShard + type: string + - jsonPath: .status.conditions[?(@.type=='Available')].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: WorkspaceObject is the Schema for the WorkspaceObjects API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: WorkspaceObjectSpec defines the desired state of WorkspaceObject + properties: + managementPolicies: + default: + - '*' + description: |- + ManagementPolicies specify the operations that should be executed by the + operator. By default, the operator manages creation, update and deletion + of objects. + items: + type: string + type: array + manifest: + description: Manifest is the desired state of the object in the workspace. + x-kubernetes-preserve-unknown-fields: true + rootShard: + description: |- + RootShard is a reference to the root shard that holds the workspace in + which the manifest should be applied. + properties: + ref: + description: Reference references a local RootShard object. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: object + workspace: + description: Workspace specifies in which workspace the manifest should + be applied. + properties: + path: + description: Path is the path of the workspace inside kcp. + type: string + required: + - path + type: object + required: + - managementPolicies + - manifest + - rootShard + - workspace + type: object + status: + description: WorkspaceObjectStatus defines the observed state of WorkspaceObject + properties: + conditions: + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + manifest: + description: Manifest is the actual state of the object in the workspace. + x-kubernetes-preserve-unknown-fields: true + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 3a6ccb66..306a27fd 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -7,6 +7,7 @@ resources: - bases/operator.kcp.io_shards.yaml - bases/operator.kcp.io_cacheservers.yaml - bases/operator.kcp.io_kubeconfigs.yaml +- bases/operator.kcp.io_workspaceobjects.yaml # +kubebuilder:scaffold:crdkustomizeresource patches: [] diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 33a14316..45b567da 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -64,6 +64,7 @@ rules: - kubeconfigs/finalizers - rootshards/finalizers - shards/finalizers + - workspaceobjects/finalizers verbs: - update - apiGroups: @@ -74,6 +75,7 @@ rules: - kubeconfigs/status - rootshards/status - shards/status + - workspaceobjects/status verbs: - get - patch @@ -84,9 +86,18 @@ rules: - kubeconfigs - rootshards - shards + - workspaceobjects verbs: - get - list - patch - update - watch +- apiGroups: + - operator.kcp.io + resources: + - workspaces + verbs: + - get + - list + - watch diff --git a/config/samples/operator.kcp.io_v1alpha1_workspaceobject.yaml b/config/samples/operator.kcp.io_v1alpha1_workspaceobject.yaml new file mode 100644 index 00000000..8fb22633 --- /dev/null +++ b/config/samples/operator.kcp.io_v1alpha1_workspaceobject.yaml @@ -0,0 +1,21 @@ +apiVersion: operator.kcp.io/v1alpha1 +kind: WorkspaceObject +metadata: + labels: + app.kubernetes.io/name: kcp-operator + app.kubernetes.io/managed-by: kustomize + name: object.workspace.custom + namespace: kcp +spec: + rootShard: + ref: + # name: shard-sample + name: root + workspace: + path: root + manifest: + apiVersion: tenancy.kcp.io/v1alpha1 + kind: Workspace + metadata: + name: test + spec: {} diff --git a/go.mod b/go.mod index 46fc179b..644f02e3 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( go.uber.org/zap v1.27.0 k8c.io/reconciler v0.5.0 k8s.io/api v0.32.0 + k8s.io/apiextensions-apiserver v0.32.0 k8s.io/apimachinery v0.32.0 k8s.io/client-go v0.32.0 k8s.io/code-generator v0.32.0 @@ -105,7 +106,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.32.0 // indirect k8s.io/apiserver v0.32.0 // indirect k8s.io/component-base v0.32.0 // indirect k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect diff --git a/internal/controller/workspaceobject/client.go b/internal/controller/workspaceobject/client.go new file mode 100644 index 00000000..179d2661 --- /dev/null +++ b/internal/controller/workspaceobject/client.go @@ -0,0 +1,168 @@ +package workspaceobject + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" + + operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1" +) + +type workspaceClientCreatorFunc func(ctx context.Context, kube ctrlruntimeclient.Client, workspaceObject *operatorv1alpha1.WorkspaceObject) (dynamic.Interface, *rest.Config, error) + +// getWorkspaceDynamicClient retrieves the kubeconfig from the RootShard and creates a dynamic client +// configured for the target workspace +func getWorkspaceDynamicClient(ctx context.Context, kube ctrlruntimeclient.Client, workspaceObject *operatorv1alpha1.WorkspaceObject) (dynamic.Interface, *rest.Config, error) { + // Get RootShard + var rootShard operatorv1alpha1.RootShard + rootShardKey := types.NamespacedName{ + Namespace: workspaceObject.Namespace, + Name: workspaceObject.Spec.RootShard.Reference.Name, + } + if err := kube.Get(ctx, rootShardKey, &rootShard); err != nil { + return nil, nil, fmt.Errorf("failed to get RootShard %s: %w", rootShardKey, err) + } + + // Get workspace path from the WorkspaceConfig + workspacePath := workspaceObject.Spec.Workspace.Path + if workspacePath == "" { + return nil, nil, fmt.Errorf("workspace path is empty in spec") + } + + // Get kubeconfig secret from RootShard + // secretName := fmt.Sprintf("%s-logical-cluster-admin-kubeconfig", rootShard.Name) + secretName := fmt.Sprintf("%s-proxy-dynamic-kubeconfig", rootShard.Name) + var secret corev1.Secret + secretKey := types.NamespacedName{ + Namespace: rootShard.Namespace, + Name: secretName, + } + if err := kube.Get(ctx, secretKey, &secret); err != nil { + return nil, nil, fmt.Errorf("failed to get kubeconfig secret %s: %w", secretKey, err) + } + + kubeconfigData, ok := secret.Data["kubeconfig"] + if !ok { + return nil, nil, fmt.Errorf("kubeconfig not found in secret %s", secretKey) + } + + // Build REST config (handles TLS embedding & host workspace path) + config, err := buildWorkspaceRESTConfig(ctx, kube, &rootShard, workspacePath, kubeconfigData) + if err != nil { + return nil, nil, err + } + + // Create dynamic client + dynamicClient, err := dynamic.NewForConfig(config) + if err != nil { + return nil, nil, fmt.Errorf("failed to create dynamic client: %w", err) + } + + return dynamicClient, config, nil +} + +// buildWorkspaceRESTConfig constructs a *rest.Config for a workspace. +func buildWorkspaceRESTConfig(ctx context.Context, kube ctrlruntimeclient.Client, rootShard *operatorv1alpha1.RootShard, workspacePath string, kubeconfigData []byte) (*rest.Config, error) { + certData, keyData, caData, caOK, err := fetchTLSSecret(ctx, kube, rootShard) + if err != nil { + return nil, err + } + + rawConfig, err := clientcmd.Load(kubeconfigData) + if err != nil { + return nil, fmt.Errorf("failed to parse kubeconfig: %w", err) + } + + for name, cluster := range rawConfig.Clusters { + cluster.CertificateAuthority = "" // remove path reference + if caOK { + cluster.CertificateAuthorityData = caData + } + rawConfig.Clusters[name] = cluster + } + for name, authInfo := range rawConfig.AuthInfos { + authInfo.ClientCertificate = "" + authInfo.ClientKey = "" + authInfo.ClientCertificateData = certData + authInfo.ClientKeyData = keyData + rawConfig.AuthInfos[name] = authInfo + } + + modifiedKubeconfigData, err := clientcmd.Write(*rawConfig) + if err != nil { + return nil, fmt.Errorf("failed to serialize modified kubeconfig: %w", err) + } + + config, err := clientcmd.RESTConfigFromKubeConfig(modifiedKubeconfigData) + if err != nil { + return nil, fmt.Errorf("failed to build REST config: %w", err) + } + + // Always overwrite TLS fields. + config.CertData = certData + config.KeyData = keyData + if caOK { + config.CAData = caData + } + + // Append workspace path to host. + config.Host = fmt.Sprintf("%s/clusters/%s", config.Host, workspacePath) + + return config, nil +} + +// fetchTLSSecret retrieves TLS materials (client cert, key, optional CA) from the "%s-logical-cluster-admin" secret. +// Returns certData, keyData, caData, caOK flag and error. +func fetchTLSSecret(ctx context.Context, kube ctrlruntimeclient.Client, rootShard *operatorv1alpha1.RootShard) ([]byte, []byte, []byte, bool, error) { + // tlsSecretName := fmt.Sprintf("%s-logical-cluster-admin", rootShard.Name) + tlsSecretName := fmt.Sprintf("%s-proxy-kubeconfig", rootShard.Name) + var tlsSecret corev1.Secret + tlsSecretKey := types.NamespacedName{Namespace: rootShard.Namespace, Name: tlsSecretName} + if err := kube.Get(ctx, tlsSecretKey, &tlsSecret); err != nil { + return nil, nil, nil, false, fmt.Errorf("failed to get TLS secret %s: %w", tlsSecretKey, err) + } + + certData, certOK := tlsSecret.Data["tls.crt"] + keyData, keyOK := tlsSecret.Data["tls.key"] + caData, caOK := tlsSecret.Data["ca.crt"] // optional + + if !certOK || !keyOK { + return nil, nil, nil, false, fmt.Errorf("TLS secret %s missing tls.crt or tls.key", tlsSecretKey) + } + + return certData, keyData, caData, caOK, nil +} + +type mapperGVRFromGVKFunc func(restConfig *rest.Config, gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) + +// getGVRFromGVK uses the discovery client to find the proper GroupVersionResource for a given GroupVersionKind +func getGVRFromGVK(restConfig *rest.Config, gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) { + // Create discovery client + discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig) + if err != nil { + return schema.GroupVersionResource{}, fmt.Errorf("failed to create discovery client: %w", err) + } + + // Get API resources for the group/version + apiResourceList, err := discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String()) + if err != nil { + return schema.GroupVersionResource{}, fmt.Errorf("failed to get API resources for %s: %w", gvk.GroupVersion().String(), err) + } + + // Find the resource that matches our kind + for _, apiResource := range apiResourceList.APIResources { + if apiResource.Kind == gvk.Kind { + return gvk.GroupVersion().WithResource(apiResource.Name), nil + } + } + + return schema.GroupVersionResource{}, fmt.Errorf("no resource found for kind %s in %s", gvk.Kind, gvk.GroupVersion().String()) +} diff --git a/internal/controller/workspaceobject/controller.go b/internal/controller/workspaceobject/controller.go new file mode 100644 index 00000000..bfb11dcd --- /dev/null +++ b/internal/controller/workspaceobject/controller.go @@ -0,0 +1,383 @@ +/* +Copyright 2024 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package workspaceobject + +import ( + "context" + "encoding/json" + "fmt" + "time" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + apimeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + kerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1" +) + +const ( + WorkspaceObjectFinalizer = "operator.kcp.io/workspaceobject-cleanup" + RequeueDelay = 10 * time.Second + fieldOwner = "kcp-operator" +) + +// WorkspaceObjectReconciler reconciles a WorkspaceObject object +type WorkspaceObjectReconciler struct { + ctrlruntimeclient.Client + Scheme *runtime.Scheme + + // stubs that can be replaced with mocks for testing + getWorkspaceDynamicClient workspaceClientCreatorFunc + getGVRFromGVK mapperGVRFromGVKFunc +} + +// SetupWithManager sets up the controller with the Manager. +func (r *WorkspaceObjectReconciler) SetupWithManager(mgr ctrl.Manager) error { + // register default handlers + // (others are only used for tests) + if r.getWorkspaceDynamicClient == nil { + r.getWorkspaceDynamicClient = getWorkspaceDynamicClient + } + if r.getGVRFromGVK == nil { + r.getGVRFromGVK = getGVRFromGVK + } + + rootShardHandler := handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, obj ctrlruntimeclient.Object) []reconcile.Request { + rootShard := obj.(*operatorv1alpha1.RootShard) + + var workspaceObjectList operatorv1alpha1.WorkspaceObjectList + if err := mgr.GetClient().List(ctx, &workspaceObjectList); err != nil { + return nil + } + + requests := make([]reconcile.Request, 0, len(workspaceObjectList.Items)) + for _, workspaceObject := range workspaceObjectList.Items { + if workspaceObject.Spec.RootShard.Reference != nil && + workspaceObject.Spec.RootShard.Reference.Name == rootShard.Name && + workspaceObject.GetNamespace() == rootShard.GetNamespace() { + requests = append(requests, reconcile.Request{NamespacedName: ctrlruntimeclient.ObjectKeyFromObject(&workspaceObject)}) + } + } + + return requests + }) + + return ctrl.NewControllerManagedBy(mgr). + For(&operatorv1alpha1.WorkspaceObject{}). + Watches(&operatorv1alpha1.RootShard{}, rootShardHandler). + Complete(r) +} + +// +kubebuilder:rbac:groups=operator.kcp.io,resources=workspaceobjects,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=operator.kcp.io,resources=workspaceobjects/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=operator.kcp.io,resources=workspaceobjects/finalizers,verbs=update +// +kubebuilder:rbac:groups=operator.kcp.io,resources=rootshards,verbs=get;list;watch +// +kubebuilder:rbac:groups=operator.kcp.io,resources=workspaces,verbs=get;list;watch +// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +func (r *WorkspaceObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, recErr error) { + logger := log.FromContext(ctx) + logger.V(4).Info("Reconciling WorkspaceObject") + + var workspaceObject operatorv1alpha1.WorkspaceObject + if err := r.Get(ctx, req.NamespacedName, &workspaceObject); err != nil { + if ctrlruntimeclient.IgnoreNotFound(err) != nil { + return ctrl.Result{}, fmt.Errorf("failed to find %s/%s: %w", req.Namespace, req.Name, err) + } + + // Object has apparently been deleted already. + return ctrl.Result{}, nil + } + + // Save old state before reconciliation for status patching + oldWorkspaceObject := workspaceObject.DeepCopy() + + conditions, recErr := r.reconcile(ctx, &workspaceObject, oldWorkspaceObject) + + if err := r.reconcileStatus(ctx, &workspaceObject, oldWorkspaceObject, conditions); err != nil { + recErr = kerrors.NewAggregate([]error{recErr, err}) + } + + // If we encounter transient errors, requeue after delay + if recErr != nil { + return ctrl.Result{RequeueAfter: RequeueDelay}, recErr + } + + return ctrl.Result{}, nil +} + +func (r *WorkspaceObjectReconciler) reconcile(ctx context.Context, workspaceObject, oldWorkspaceObject *operatorv1alpha1.WorkspaceObject) ([]metav1.Condition, error) { + var ( + errs []error + conditions []metav1.Condition + ) + + // Handle deletion + if workspaceObject.DeletionTimestamp != nil { + if controllerutil.ContainsFinalizer(workspaceObject, WorkspaceObjectFinalizer) { + if err := r.deleteWorkspaceObject(ctx, workspaceObject); err != nil { + conditions = append(conditions, metav1.Condition{ + Type: string(operatorv1alpha1.ConditionTypeAvailable), + Status: metav1.ConditionFalse, + Reason: "DeletionFailed", + Message: fmt.Sprintf("Failed to delete object in workspace: %v", err), + LastTransitionTime: metav1.Now(), + }) + return conditions, err + } + + // Remove finalizer after successful deletion using server-side apply + controllerutil.RemoveFinalizer(workspaceObject, WorkspaceObjectFinalizer) + if err := r.applyFinalizers(ctx, workspaceObject, oldWorkspaceObject); err != nil { + return conditions, fmt.Errorf("failed to remove finalizer: %w", err) + } + } + return conditions, nil + } + + // Add finalizer if not present (server-side apply) + if !controllerutil.ContainsFinalizer(workspaceObject, WorkspaceObjectFinalizer) { + controllerutil.AddFinalizer(workspaceObject, WorkspaceObjectFinalizer) + if err := r.applyFinalizers(ctx, workspaceObject, oldWorkspaceObject); err != nil { + return conditions, fmt.Errorf("failed to add finalizer: %w", err) + } + } + + // Get dynamic client for the workspace + dynamicClient, restConfig, err := r.getWorkspaceDynamicClient(ctx, r.Client, workspaceObject) + if err != nil { + conditions = append(conditions, metav1.Condition{ + Type: string(operatorv1alpha1.ConditionTypeAvailable), + Status: metav1.ConditionFalse, + Reason: "WorkspaceConnectionFailed", + Message: fmt.Sprintf("Failed to connect to workspace: %v", err), + LastTransitionTime: metav1.Now(), + }) + errs = append(errs, err) + return conditions, kerrors.NewAggregate(errs) + } + + // Apply the manifest in the workspace + appliedManifest, err := r.applyManifest(ctx, dynamicClient, restConfig, workspaceObject) + if err != nil { + conditions = append(conditions, metav1.Condition{ + Type: string(operatorv1alpha1.ConditionTypeAvailable), + Status: metav1.ConditionFalse, + Reason: "ManifestApplyFailed", + Message: fmt.Sprintf("Failed to apply manifest: %v", err), + LastTransitionTime: metav1.Now(), + }) + errs = append(errs, err) + return conditions, kerrors.NewAggregate(errs) + } + + // Update status with applied manifest + workspaceObject.Status.Manifest = appliedManifest + + conditions = append(conditions, metav1.Condition{ + Type: string(operatorv1alpha1.ConditionTypeAvailable), + Status: metav1.ConditionTrue, + Reason: "ManifestApplied", + Message: "Manifest successfully applied in workspace", + LastTransitionTime: metav1.Now(), + }) + + return conditions, kerrors.NewAggregate(errs) +} + +// applyManifest applies the manifest from the WorkspaceObject spec to the workspace +func (r *WorkspaceObjectReconciler) applyManifest(ctx context.Context, dynamicClient dynamic.Interface, restConfig *rest.Config, workspaceObject *operatorv1alpha1.WorkspaceObject) (*apiextensionsv1.JSON, error) { + // Parse the manifest into an unstructured object + var obj unstructured.Unstructured + if err := json.Unmarshal(workspaceObject.Spec.Manifest.Raw, &obj); err != nil { + return nil, fmt.Errorf("failed to unmarshal manifest: %w", err) + } + + gvk := obj.GroupVersionKind() + + // Get the GVR from the discovery client + gvr, err := r.getGVRFromGVK(restConfig, gvk) + if err != nil { + return nil, fmt.Errorf("failed to get resource mapping for %s: %w", gvk.String(), err) + } + + // Check management policies + shouldCreate := r.shouldManage(workspaceObject, operatorv1alpha1.WorkspaceObjectManagementPolicyCreate) + shouldUpdate := r.shouldManage(workspaceObject, operatorv1alpha1.WorkspaceObjectManagementPolicyUpdate) + + // Get the resource interface + var resourceInterface dynamic.ResourceInterface + if obj.GetNamespace() != "" { + resourceInterface = dynamicClient.Resource(gvr).Namespace(obj.GetNamespace()) + } else { + resourceInterface = dynamicClient.Resource(gvr) + } + + // Determine existence for policy enforcement + existing, err := resourceInterface.Get(ctx, obj.GetName(), metav1.GetOptions{}) + notFound := apierrors.IsNotFound(err) + if err != nil && !notFound { + return nil, fmt.Errorf("failed to get object: %w", err) + } + if notFound && !shouldCreate { + return nil, fmt.Errorf("object does not exist and create policy is not enabled") + } + if !notFound && !shouldUpdate { + return marshalToJSON(existing) + } + + // Server-side apply: always send entire desired object. + obj.SetManagedFields(nil) + // Ensure GVK present (usually already from manifest) + patchBytes, err := json.Marshal(&obj) + if err != nil { + return nil, fmt.Errorf("failed to marshal object for apply: %w", err) + } + force := true + applied, err := resourceInterface.Patch(ctx, obj.GetName(), types.ApplyPatchType, patchBytes, metav1.PatchOptions{FieldManager: fieldOwner, Force: &force}) + if err != nil { + return nil, fmt.Errorf("failed to apply object: %w", err) + } + return marshalToJSON(applied) +} + +// applyFinalizers updates the finalizers using a merge patch. +func (r *WorkspaceObjectReconciler) applyFinalizers(ctx context.Context, workspaceObject, oldWorkspaceObject *operatorv1alpha1.WorkspaceObject) error { + patch := ctrlruntimeclient.MergeFrom(oldWorkspaceObject) + if err := r.Patch(ctx, workspaceObject, patch); err != nil { + return err + } + return nil +} + +// deleteWorkspaceObject removes the object from the workspace +func (r *WorkspaceObjectReconciler) deleteWorkspaceObject(ctx context.Context, workspaceObject *operatorv1alpha1.WorkspaceObject) error { + // Check if delete is allowed by management policies + if !r.shouldManage(workspaceObject, operatorv1alpha1.WorkspaceObjectManagementPolicyDelete) { + // Deletion not managed, just remove finalizer + return nil + } + + dynamicClient, restConfig, err := r.getWorkspaceDynamicClient(ctx, r.Client, workspaceObject) + if err != nil { + // If we can't connect to workspace, log but don't block deletion + return nil + } + + // Parse the manifest to get object details + var obj unstructured.Unstructured + if err := json.Unmarshal(workspaceObject.Spec.Manifest.Raw, &obj); err != nil { + return fmt.Errorf("failed to unmarshal manifest: %w", err) + } + + gvk := obj.GroupVersionKind() + + // Get the GVR from the discovery client + gvr, err := r.getGVRFromGVK(restConfig, gvk) + if err != nil { + return fmt.Errorf("failed to get resource mapping for %s: %w", gvk.String(), err) + } + + // Get the resource interface + var resourceInterface dynamic.ResourceInterface + if obj.GetNamespace() != "" { + resourceInterface = dynamicClient.Resource(gvr).Namespace(obj.GetNamespace()) + } else { + resourceInterface = dynamicClient.Resource(gvr) + } + + // Delete the object + err = resourceInterface.Delete(ctx, obj.GetName(), metav1.DeleteOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to delete object %s: %w", obj.GetName(), err) + } + + return nil +} + +// reconcileStatus updates the status of the WorkspaceObject +func (r *WorkspaceObjectReconciler) reconcileStatus(ctx context.Context, workspaceObject *operatorv1alpha1.WorkspaceObject, oldWorkspaceObject *operatorv1alpha1.WorkspaceObject, conditions []metav1.Condition) error { + // Update conditions + for _, condition := range conditions { + condition.ObservedGeneration = workspaceObject.Generation + workspaceObject.Status.Conditions = updateCondition(workspaceObject.Status.Conditions, condition) + } + + // Only patch the status if there are actual changes + if !equality.Semantic.DeepEqual(oldWorkspaceObject.Status, workspaceObject.Status) { + if err := r.Status().Patch(ctx, workspaceObject, ctrlruntimeclient.MergeFrom(oldWorkspaceObject)); err != nil { + return fmt.Errorf("failed to update status: %w", err) + } + } + + return nil +} + +// updateCondition updates or appends a condition to the condition list +func updateCondition(conditions []metav1.Condition, newCondition metav1.Condition) []metav1.Condition { + existingCondition := apimeta.FindStatusCondition(conditions, newCondition.Type) + if existingCondition == nil { + return append(conditions, newCondition) + } + + if existingCondition.Status != newCondition.Status || + existingCondition.Reason != newCondition.Reason || + existingCondition.Message != newCondition.Message { + existingCondition.Status = newCondition.Status + existingCondition.Reason = newCondition.Reason + existingCondition.Message = newCondition.Message + existingCondition.LastTransitionTime = metav1.Now() + existingCondition.ObservedGeneration = newCondition.ObservedGeneration + } + + return conditions +} + +// shouldManage checks if a given management policy is enabled +func (r *WorkspaceObjectReconciler) shouldManage(workspaceObject *operatorv1alpha1.WorkspaceObject, policy operatorv1alpha1.WorkspaceObjectManagementPolicy) bool { + for _, p := range workspaceObject.Spec.ManagementPolicies { + if p == operatorv1alpha1.WorkspaceObjectManagementPolicyAll || p == policy { + return true + } + } + return false +} + +// marshalToJSON converts an unstructured object to extv1.JSON +func marshalToJSON(obj any) (*apiextensionsv1.JSON, error) { + data, err := json.Marshal(obj) + if err != nil { + return nil, fmt.Errorf("failed to marshal object: %w", err) + } + return &apiextensionsv1.JSON{Raw: data}, nil +} diff --git a/internal/controller/workspaceobject/controller_test.go b/internal/controller/workspaceobject/controller_test.go new file mode 100644 index 00000000..dc818f27 --- /dev/null +++ b/internal/controller/workspaceobject/controller_test.go @@ -0,0 +1,379 @@ +/* +Copyright 2024 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package workspaceobject + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/dynamic" + dynamicfake "k8s.io/client-go/dynamic/fake" + "k8s.io/client-go/rest" + k8stesting "k8s.io/client-go/testing" + ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client" + ctrlruntimefakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/kcp-dev/kcp-operator/internal/controller/util" + operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1" +) + +const ( + testNamespace = "workspaceobject-tests" + testRootShardName = "test-rootshard" + testWorkspacePath = "root:org:team" + testSecretName = "test-rootshard-admin-kubeconfig" +) + +// fakeWorkspaceClientCreator creates a fake workspace client creator function for testing. +// It returns a workspaceClientCreatorFunc that creates a fake dynamic client with the provided objects. +func fakeWorkspaceClientCreator(workspaceObjects ...runtime.Object) workspaceClientCreatorFunc { + return func(ctx context.Context, kube ctrlruntimeclient.Client, workspaceObject *operatorv1alpha1.WorkspaceObject) (dynamic.Interface, *rest.Config, error) { + // Create scheme with core types + scheme := runtime.NewScheme() + _ = corev1.AddToScheme(scheme) + + // Create fake dynamic client + fakeDynamicClient := dynamicfake.NewSimpleDynamicClient(scheme, workspaceObjects...) + + // Add reactor to handle server-side apply (Patch with ApplyPatchType) + // The fake client doesn't support ApplyPatchType by default, so we convert it to create/update + fakeDynamicClient.PrependReactor("patch", "*", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) { + patchAction := action.(k8stesting.PatchAction) + if patchAction.GetPatchType() == types.ApplyPatchType { + // Deserialize the patch to get the object + var obj unstructured.Unstructured + if err := json.Unmarshal(patchAction.GetPatch(), &obj); err != nil { + return true, nil, err + } + + // For the fake client, we'll simulate apply by treating it as an update + // The fake tracker will handle whether it needs to be created or updated + gvr := patchAction.GetResource() + tracker := fakeDynamicClient.Tracker() + + // Try to get existing object from tracker directly (no lock conflict) + existing, err := tracker.Get(gvr, patchAction.GetNamespace(), patchAction.GetName()) + if err != nil { + // Object doesn't exist, create it + if err := tracker.Create(gvr, &obj, patchAction.GetNamespace()); err != nil { + return true, nil, err + } + return true, &obj, nil + } + + // Object exists, update it with resource version + if existingUnstructured, ok := existing.(*unstructured.Unstructured); ok { + obj.SetResourceVersion(existingUnstructured.GetResourceVersion()) + obj.SetUID(existingUnstructured.GetUID()) + } + + if err := tracker.Update(gvr, &obj, patchAction.GetNamespace()); err != nil { + return true, nil, err + } + return true, &obj, nil + } + return false, nil, nil + }) + + // Create a fake REST config - the actual values don't matter for the fake client + // but we need it to pass to getGVRFromGVK + fakeRestConfig := &rest.Config{ + Host: "https://fake-kcp-server", + } + + return fakeDynamicClient, fakeRestConfig, nil + } +} + +// fakeGVRMapper creates a fake GVR mapper that maps common Kubernetes resources. +// This is used in tests to avoid needing a real discovery client. +func fakeGVRMapper(restConfig *rest.Config, gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) { + // Map common resources + switch gvk.GroupVersion().String() { + case "v1": + if gvk.Kind == "ConfigMap" { + return schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}, nil + } + if gvk.Kind == "Secret" { + return schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secrets"}, nil + } + } + return schema.GroupVersionResource{}, fmt.Errorf("unknown GVK: %s", gvk.String()) +} + +func TestReconciling(t *testing.T) { + testcases := []struct { + name string + workspaceObject *operatorv1alpha1.WorkspaceObject + rootShard *operatorv1alpha1.RootShard + secret *corev1.Secret + workspaceObjects []runtime.Object // Objects pre-existing in the KCP workspace + }{ + { + name: "vanilla configmap with all policies", + workspaceObjects: []runtime.Object{}, // ConfigMap will be created + workspaceObject: &operatorv1alpha1.WorkspaceObject{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-workspaceobject", + Namespace: testNamespace, + }, + Spec: operatorv1alpha1.WorkspaceObjectSpec{ + RootShard: operatorv1alpha1.RootShardConfig{ + Reference: &corev1.LocalObjectReference{ + Name: testRootShardName, + }, + }, + Workspace: operatorv1alpha1.WorkspaceConfig{ + Path: testWorkspacePath, + }, + Manifest: mustMarshalToJSON(&corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-configmap", + Namespace: "default", + }, + Data: map[string]string{ + "key": "value", + }, + }), + ManagementPolicies: []operatorv1alpha1.WorkspaceObjectManagementPolicy{ + operatorv1alpha1.WorkspaceObjectManagementPolicyAll, + }, + }, + }, + rootShard: &operatorv1alpha1.RootShard{ + ObjectMeta: metav1.ObjectMeta{ + Name: testRootShardName, + Namespace: testNamespace, + }, + Spec: operatorv1alpha1.RootShardSpec{ + External: operatorv1alpha1.ExternalConfig{ + Hostname: "example.kcp.io", + Port: 6443, + }, + }, + }, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: testSecretName, + Namespace: testNamespace, + }, + Data: map[string][]byte{ + "kubeconfig": []byte("fake-kubeconfig"), + }, + }, + }, + { + name: "create not allowed with Update policy only", + workspaceObjects: []runtime.Object{}, // No objects, create will fail + workspaceObject: &operatorv1alpha1.WorkspaceObject{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-update-only", + Namespace: testNamespace, + }, + Spec: operatorv1alpha1.WorkspaceObjectSpec{ + RootShard: operatorv1alpha1.RootShardConfig{ + Reference: &corev1.LocalObjectReference{ + Name: testRootShardName, + }, + }, + Workspace: operatorv1alpha1.WorkspaceConfig{ + Path: testWorkspacePath, + }, + Manifest: mustMarshalToJSON(&corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-configmap", + Namespace: "default", + }, + Data: map[string]string{ + "key": "value", + }, + }), + ManagementPolicies: []operatorv1alpha1.WorkspaceObjectManagementPolicy{ + operatorv1alpha1.WorkspaceObjectManagementPolicyUpdate, + }, + }, + }, + rootShard: &operatorv1alpha1.RootShard{ + ObjectMeta: metav1.ObjectMeta{ + Name: testRootShardName, + Namespace: testNamespace, + }, + Spec: operatorv1alpha1.RootShardSpec{ + External: operatorv1alpha1.ExternalConfig{ + Hostname: "example.kcp.io", + Port: 6443, + }, + }, + }, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: testSecretName, + Namespace: testNamespace, + }, + Data: map[string][]byte{ + "kubeconfig": []byte("fake-kubeconfig"), + }, + }, + }, + { + name: "delete not allowed with Create and Update policies only", + workspaceObjects: []runtime.Object{}, // ConfigMap will be created + workspaceObject: &operatorv1alpha1.WorkspaceObject{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-no-delete", + Namespace: testNamespace, + }, + Spec: operatorv1alpha1.WorkspaceObjectSpec{ + RootShard: operatorv1alpha1.RootShardConfig{ + Reference: &corev1.LocalObjectReference{ + Name: testRootShardName, + }, + }, + Workspace: operatorv1alpha1.WorkspaceConfig{ + Path: testWorkspacePath, + }, + Manifest: mustMarshalToJSON(&corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-configmap", + Namespace: "default", + }, + Data: map[string]string{ + "key": "value", + }, + }), + ManagementPolicies: []operatorv1alpha1.WorkspaceObjectManagementPolicy{ + operatorv1alpha1.WorkspaceObjectManagementPolicyCreate, + operatorv1alpha1.WorkspaceObjectManagementPolicyUpdate, + }, + }, + }, + rootShard: &operatorv1alpha1.RootShard{ + ObjectMeta: metav1.ObjectMeta{ + Name: testRootShardName, + Namespace: testNamespace, + }, + Spec: operatorv1alpha1.RootShardSpec{ + External: operatorv1alpha1.ExternalConfig{ + Hostname: "example.kcp.io", + Port: 6443, + }, + }, + }, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: testSecretName, + Namespace: testNamespace, + }, + Data: map[string][]byte{ + "kubeconfig": []byte("fake-kubeconfig"), + }, + }, + }, + } + + scheme := util.GetTestScheme() + + for _, testcase := range testcases { + t.Run(testcase.name, func(t *testing.T) { + // Create fake client for the host cluster + client := ctrlruntimefakeclient. + NewClientBuilder(). + WithScheme(scheme). + WithStatusSubresource(testcase.workspaceObject). + WithObjects(testcase.workspaceObject, testcase.rootShard, testcase.secret). + Build() + + ctx := context.Background() + + // Create controller with fake workspace client injected + controllerReconciler := &WorkspaceObjectReconciler{ + Client: client, + Scheme: client.Scheme(), + getWorkspaceDynamicClient: fakeWorkspaceClientCreator(testcase.workspaceObjects...), + getGVRFromGVK: fakeGVRMapper, + } + + // Run reconciliation + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: ctrlruntimeclient.ObjectKeyFromObject(testcase.workspaceObject), + }) + + // Verify the object still exists and get fresh copy with status + var retrievedObj operatorv1alpha1.WorkspaceObject + getErr := client.Get(ctx, ctrlruntimeclient.ObjectKeyFromObject(testcase.workspaceObject), &retrievedObj) + require.NoError(t, getErr) + + // Verify finalizer was added + assert.Contains(t, retrievedObj.Finalizers, WorkspaceObjectFinalizer, "Finalizer should be added") + + // For test cases that expect errors (e.g., create not allowed) + if testcase.name == "create not allowed with Update policy only" { + assert.Error(t, err, "Expected error when create policy is not enabled") + return + } + + // Otherwise, reconciliation should succeed + assert.NoError(t, err, "Reconciliation should succeed") + + // Verify status manifest was populated + require.NotNil(t, retrievedObj.Status.Manifest, "Status manifest should be populated") + require.NotEmpty(t, retrievedObj.Status.Manifest.Raw, "Status manifest should have content") + + // Unmarshal and verify the status manifest contains the expected resource + var actualObj unstructured.Unstructured + err = json.Unmarshal(retrievedObj.Status.Manifest.Raw, &actualObj) + require.NoError(t, err) + + // Verify the kind and basic fields match what we expect + assert.Equal(t, "ConfigMap", actualObj.GetKind(), "Object kind should be ConfigMap") + assert.NotEmpty(t, actualObj.GetName(), "Object name should be set") + }) + } +} + +func mustMarshalToJSON(obj any) apiextensionsv1.JSON { + raw, err := marshalToJSON(obj) + if err != nil { + panic(fmt.Sprintf("failed to marshal: %v", err)) + } + return *raw +} diff --git a/sdk/apis/operator/v1alpha1/common.go b/sdk/apis/operator/v1alpha1/common.go index 1b7d9732..2efdff7f 100644 --- a/sdk/apis/operator/v1alpha1/common.go +++ b/sdk/apis/operator/v1alpha1/common.go @@ -45,6 +45,11 @@ type RootShardConfig struct { Reference *corev1.LocalObjectReference `json:"ref,omitempty"` } +type WorkspaceConfig struct { + // Path is the path of the workspace inside kcp. + Path string `json:"path"` +} + type EtcdConfig struct { // Endpoints is a list of http urls at which etcd nodes are available. The expected format is "https://etcd-hostname:2379". Endpoints []string `json:"endpoints"` diff --git a/sdk/apis/operator/v1alpha1/register.go b/sdk/apis/operator/v1alpha1/register.go index c4b40879..1c0d6d73 100644 --- a/sdk/apis/operator/v1alpha1/register.go +++ b/sdk/apis/operator/v1alpha1/register.go @@ -61,6 +61,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &ShardList{}, &Kubeconfig{}, &KubeconfigList{}, + &WorkspaceObject{}, + &WorkspaceObjectList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) diff --git a/sdk/apis/operator/v1alpha1/workspace_object_types.go b/sdk/apis/operator/v1alpha1/workspace_object_types.go new file mode 100644 index 00000000..5f70bd60 --- /dev/null +++ b/sdk/apis/operator/v1alpha1/workspace_object_types.go @@ -0,0 +1,86 @@ +/* +Copyright 2024 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type WorkspaceObjectManagementPolicy string + +const ( + WorkspaceObjectManagementPolicyAll WorkspaceObjectManagementPolicy = "*" + WorkspaceObjectManagementPolicyCreate WorkspaceObjectManagementPolicy = "Create" + WorkspaceObjectManagementPolicyUpdate WorkspaceObjectManagementPolicy = "Update" + WorkspaceObjectManagementPolicyDelete WorkspaceObjectManagementPolicy = "Delete" +) + +// WorkspaceObjectSpec defines the desired state of WorkspaceObject +type WorkspaceObjectSpec struct { + // RootShard is a reference to the root shard that holds the workspace in + // which the manifest should be applied. + RootShard RootShardConfig `json:"rootShard"` + + // Workspace specifies in which workspace the manifest should be applied. + Workspace WorkspaceConfig `json:"workspace"` + + // Manifest is the desired state of the object in the workspace. + Manifest extv1.JSON `json:"manifest"` + + // ManagementPolicies specify the operations that should be executed by the + // operator. By default, the operator manages creation, update and deletion + // of objects. + // + // +kubebuilder:default={"*"} + ManagementPolicies []WorkspaceObjectManagementPolicy `json:"managementPolicies"` +} + +// WorkspaceObjectStatus defines the observed state of WorkspaceObject +type WorkspaceObjectStatus struct { + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty"` + + // Manifest is the actual state of the object in the workspace. + Manifest *extv1.JSON `json:"manifest,omitempty"` +} + +// +genclient +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:JSONPath=".spec.rootShard.ref.name",name="RootShard",type="string" +// +kubebuilder:printcolumn:JSONPath=".status.conditions[?(@.type=='Available')].reason",name="Status",type="string" +// +kubebuilder:printcolumn:JSONPath=".metadata.creationTimestamp",name="Age",type="date" + +// WorkspaceObject is the Schema for the WorkspaceObjects API +type WorkspaceObject struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec WorkspaceObjectSpec `json:"spec,omitempty"` + Status WorkspaceObjectStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// WorkspaceObjectList contains a list of WorkspaceObject +type WorkspaceObjectList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []WorkspaceObject `json:"items"` +} diff --git a/sdk/apis/operator/v1alpha1/zz_generated.deepcopy.go b/sdk/apis/operator/v1alpha1/zz_generated.deepcopy.go index faa20522..579e79c2 100644 --- a/sdk/apis/operator/v1alpha1/zz_generated.deepcopy.go +++ b/sdk/apis/operator/v1alpha1/zz_generated.deepcopy.go @@ -6,6 +6,7 @@ package v1alpha1 import ( "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -1565,6 +1566,130 @@ func (in *ShardStatus) DeepCopy() *ShardStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkspaceConfig) DeepCopyInto(out *WorkspaceConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceConfig. +func (in *WorkspaceConfig) DeepCopy() *WorkspaceConfig { + if in == nil { + return nil + } + out := new(WorkspaceConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkspaceObject) DeepCopyInto(out *WorkspaceObject) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceObject. +func (in *WorkspaceObject) DeepCopy() *WorkspaceObject { + if in == nil { + return nil + } + out := new(WorkspaceObject) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *WorkspaceObject) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkspaceObjectList) DeepCopyInto(out *WorkspaceObjectList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]WorkspaceObject, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceObjectList. +func (in *WorkspaceObjectList) DeepCopy() *WorkspaceObjectList { + if in == nil { + return nil + } + out := new(WorkspaceObjectList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *WorkspaceObjectList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkspaceObjectSpec) DeepCopyInto(out *WorkspaceObjectSpec) { + *out = *in + in.RootShard.DeepCopyInto(&out.RootShard) + out.Workspace = in.Workspace + in.Manifest.DeepCopyInto(&out.Manifest) + if in.ManagementPolicies != nil { + in, out := &in.ManagementPolicies, &out.ManagementPolicies + *out = make([]WorkspaceObjectManagementPolicy, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceObjectSpec. +func (in *WorkspaceObjectSpec) DeepCopy() *WorkspaceObjectSpec { + if in == nil { + return nil + } + out := new(WorkspaceObjectSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkspaceObjectStatus) DeepCopyInto(out *WorkspaceObjectStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Manifest != nil { + in, out := &in.Manifest, &out.Manifest + *out = new(apiextensionsv1.JSON) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkspaceObjectStatus. +func (in *WorkspaceObjectStatus) DeepCopy() *WorkspaceObjectStatus { + if in == nil { + return nil + } + out := new(WorkspaceObjectStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *X509Subject) DeepCopyInto(out *X509Subject) { *out = *in diff --git a/sdk/applyconfiguration/operator/v1alpha1/workspaceconfig.go b/sdk/applyconfiguration/operator/v1alpha1/workspaceconfig.go new file mode 100644 index 00000000..4977d4fd --- /dev/null +++ b/sdk/applyconfiguration/operator/v1alpha1/workspaceconfig.go @@ -0,0 +1,39 @@ +/* +Copyright 2024 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// WorkspaceConfigApplyConfiguration represents a declarative configuration of the WorkspaceConfig type for use +// with apply. +type WorkspaceConfigApplyConfiguration struct { + Path *string `json:"path,omitempty"` +} + +// WorkspaceConfigApplyConfiguration constructs a declarative configuration of the WorkspaceConfig type for use with +// apply. +func WorkspaceConfig() *WorkspaceConfigApplyConfiguration { + return &WorkspaceConfigApplyConfiguration{} +} + +// WithPath sets the Path field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Path field is set to the value of the last call. +func (b *WorkspaceConfigApplyConfiguration) WithPath(value string) *WorkspaceConfigApplyConfiguration { + b.Path = &value + return b +} diff --git a/sdk/applyconfiguration/operator/v1alpha1/workspaceobject.go b/sdk/applyconfiguration/operator/v1alpha1/workspaceobject.go new file mode 100644 index 00000000..88d7ccdd --- /dev/null +++ b/sdk/applyconfiguration/operator/v1alpha1/workspaceobject.go @@ -0,0 +1,225 @@ +/* +Copyright 2024 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// WorkspaceObjectApplyConfiguration represents a declarative configuration of the WorkspaceObject type for use +// with apply. +type WorkspaceObjectApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *WorkspaceObjectSpecApplyConfiguration `json:"spec,omitempty"` + Status *WorkspaceObjectStatusApplyConfiguration `json:"status,omitempty"` +} + +// WorkspaceObject constructs a declarative configuration of the WorkspaceObject type for use with +// apply. +func WorkspaceObject(name, namespace string) *WorkspaceObjectApplyConfiguration { + b := &WorkspaceObjectApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("WorkspaceObject") + b.WithAPIVersion("operator.kcp.io/v1alpha1") + return b +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *WorkspaceObjectApplyConfiguration) WithKind(value string) *WorkspaceObjectApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *WorkspaceObjectApplyConfiguration) WithAPIVersion(value string) *WorkspaceObjectApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *WorkspaceObjectApplyConfiguration) WithName(value string) *WorkspaceObjectApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *WorkspaceObjectApplyConfiguration) WithGenerateName(value string) *WorkspaceObjectApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *WorkspaceObjectApplyConfiguration) WithNamespace(value string) *WorkspaceObjectApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *WorkspaceObjectApplyConfiguration) WithUID(value types.UID) *WorkspaceObjectApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *WorkspaceObjectApplyConfiguration) WithResourceVersion(value string) *WorkspaceObjectApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *WorkspaceObjectApplyConfiguration) WithGeneration(value int64) *WorkspaceObjectApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *WorkspaceObjectApplyConfiguration) WithCreationTimestamp(value metav1.Time) *WorkspaceObjectApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *WorkspaceObjectApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *WorkspaceObjectApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *WorkspaceObjectApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *WorkspaceObjectApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *WorkspaceObjectApplyConfiguration) WithLabels(entries map[string]string) *WorkspaceObjectApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *WorkspaceObjectApplyConfiguration) WithAnnotations(entries map[string]string) *WorkspaceObjectApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *WorkspaceObjectApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *WorkspaceObjectApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *WorkspaceObjectApplyConfiguration) WithFinalizers(values ...string) *WorkspaceObjectApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *WorkspaceObjectApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *WorkspaceObjectApplyConfiguration) WithSpec(value *WorkspaceObjectSpecApplyConfiguration) *WorkspaceObjectApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *WorkspaceObjectApplyConfiguration) WithStatus(value *WorkspaceObjectStatusApplyConfiguration) *WorkspaceObjectApplyConfiguration { + b.Status = value + return b +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *WorkspaceObjectApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} diff --git a/sdk/applyconfiguration/operator/v1alpha1/workspaceobjectspec.go b/sdk/applyconfiguration/operator/v1alpha1/workspaceobjectspec.go new file mode 100644 index 00000000..396e679e --- /dev/null +++ b/sdk/applyconfiguration/operator/v1alpha1/workspaceobjectspec.go @@ -0,0 +1,74 @@ +/* +Copyright 2024 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + + operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1" +) + +// WorkspaceObjectSpecApplyConfiguration represents a declarative configuration of the WorkspaceObjectSpec type for use +// with apply. +type WorkspaceObjectSpecApplyConfiguration struct { + RootShard *RootShardConfigApplyConfiguration `json:"rootShard,omitempty"` + Workspace *WorkspaceConfigApplyConfiguration `json:"workspace,omitempty"` + Manifest *v1.JSON `json:"manifest,omitempty"` + ManagementPolicies []operatorv1alpha1.WorkspaceObjectManagementPolicy `json:"managementPolicies,omitempty"` +} + +// WorkspaceObjectSpecApplyConfiguration constructs a declarative configuration of the WorkspaceObjectSpec type for use with +// apply. +func WorkspaceObjectSpec() *WorkspaceObjectSpecApplyConfiguration { + return &WorkspaceObjectSpecApplyConfiguration{} +} + +// WithRootShard sets the RootShard field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RootShard field is set to the value of the last call. +func (b *WorkspaceObjectSpecApplyConfiguration) WithRootShard(value *RootShardConfigApplyConfiguration) *WorkspaceObjectSpecApplyConfiguration { + b.RootShard = value + return b +} + +// WithWorkspace sets the Workspace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Workspace field is set to the value of the last call. +func (b *WorkspaceObjectSpecApplyConfiguration) WithWorkspace(value *WorkspaceConfigApplyConfiguration) *WorkspaceObjectSpecApplyConfiguration { + b.Workspace = value + return b +} + +// WithManifest sets the Manifest field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Manifest field is set to the value of the last call. +func (b *WorkspaceObjectSpecApplyConfiguration) WithManifest(value v1.JSON) *WorkspaceObjectSpecApplyConfiguration { + b.Manifest = &value + return b +} + +// WithManagementPolicies adds the given value to the ManagementPolicies field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the ManagementPolicies field. +func (b *WorkspaceObjectSpecApplyConfiguration) WithManagementPolicies(values ...operatorv1alpha1.WorkspaceObjectManagementPolicy) *WorkspaceObjectSpecApplyConfiguration { + for i := range values { + b.ManagementPolicies = append(b.ManagementPolicies, values[i]) + } + return b +} diff --git a/sdk/applyconfiguration/operator/v1alpha1/workspaceobjectstatus.go b/sdk/applyconfiguration/operator/v1alpha1/workspaceobjectstatus.go new file mode 100644 index 00000000..ca9b6d60 --- /dev/null +++ b/sdk/applyconfiguration/operator/v1alpha1/workspaceobjectstatus.go @@ -0,0 +1,58 @@ +/* +Copyright 2024 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// WorkspaceObjectStatusApplyConfiguration represents a declarative configuration of the WorkspaceObjectStatus type for use +// with apply. +type WorkspaceObjectStatusApplyConfiguration struct { + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + Manifest *apiextensionsv1.JSON `json:"manifest,omitempty"` +} + +// WorkspaceObjectStatusApplyConfiguration constructs a declarative configuration of the WorkspaceObjectStatus type for use with +// apply. +func WorkspaceObjectStatus() *WorkspaceObjectStatusApplyConfiguration { + return &WorkspaceObjectStatusApplyConfiguration{} +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *WorkspaceObjectStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *WorkspaceObjectStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} + +// WithManifest sets the Manifest field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Manifest field is set to the value of the last call. +func (b *WorkspaceObjectStatusApplyConfiguration) WithManifest(value apiextensionsv1.JSON) *WorkspaceObjectStatusApplyConfiguration { + b.Manifest = &value + return b +} diff --git a/sdk/applyconfiguration/utils.go b/sdk/applyconfiguration/utils.go index 8a984f51..62df397c 100644 --- a/sdk/applyconfiguration/utils.go +++ b/sdk/applyconfiguration/utils.go @@ -135,6 +135,14 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &operatorv1alpha1.ShardSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("ShardStatus"): return &operatorv1alpha1.ShardStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("WorkspaceConfig"): + return &operatorv1alpha1.WorkspaceConfigApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("WorkspaceObject"): + return &operatorv1alpha1.WorkspaceObjectApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("WorkspaceObjectSpec"): + return &operatorv1alpha1.WorkspaceObjectSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("WorkspaceObjectStatus"): + return &operatorv1alpha1.WorkspaceObjectStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("X509Subject"): return &operatorv1alpha1.X509SubjectApplyConfiguration{} diff --git a/sdk/clientset/versioned/cluster/typed/operator/v1alpha1/fake/operator_client.go b/sdk/clientset/versioned/cluster/typed/operator/v1alpha1/fake/operator_client.go index ae1a6c71..52a84d32 100644 --- a/sdk/clientset/versioned/cluster/typed/operator/v1alpha1/fake/operator_client.go +++ b/sdk/clientset/versioned/cluster/typed/operator/v1alpha1/fake/operator_client.go @@ -64,6 +64,10 @@ func (c *OperatorV1alpha1ClusterClient) Shards() kcpoperatorv1alpha1.ShardCluste return &shardsClusterClient{Fake: c.Fake} } +func (c *OperatorV1alpha1ClusterClient) WorkspaceObjects() kcpoperatorv1alpha1.WorkspaceObjectClusterInterface { + return &workspaceObjectsClusterClient{Fake: c.Fake} +} + var _ operatorv1alpha1.OperatorV1alpha1Interface = (*OperatorV1alpha1Client)(nil) type OperatorV1alpha1Client struct { @@ -95,3 +99,7 @@ func (c *OperatorV1alpha1Client) RootShards(namespace string) operatorv1alpha1.R func (c *OperatorV1alpha1Client) Shards(namespace string) operatorv1alpha1.ShardInterface { return &shardsClient{Fake: c.Fake, ClusterPath: c.ClusterPath, Namespace: namespace} } + +func (c *OperatorV1alpha1Client) WorkspaceObjects(namespace string) operatorv1alpha1.WorkspaceObjectInterface { + return &workspaceObjectsClient{Fake: c.Fake, ClusterPath: c.ClusterPath, Namespace: namespace} +} diff --git a/sdk/clientset/versioned/cluster/typed/operator/v1alpha1/fake/workspaceobject.go b/sdk/clientset/versioned/cluster/typed/operator/v1alpha1/fake/workspaceobject.go new file mode 100644 index 00000000..b82c3cdb --- /dev/null +++ b/sdk/clientset/versioned/cluster/typed/operator/v1alpha1/fake/workspaceobject.go @@ -0,0 +1,213 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2024 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by kcp code-generator. DO NOT EDIT. + +package fake + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/kcp-dev/logicalcluster/v3" + + kcptesting "github.com/kcp-dev/client-go/third_party/k8s.io/client-go/testing" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/testing" + + operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1" + applyconfigurationsoperatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/applyconfiguration/operator/v1alpha1" + kcpoperatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/clientset/versioned/cluster/typed/operator/v1alpha1" + operatorv1alpha1client "github.com/kcp-dev/kcp-operator/sdk/clientset/versioned/typed/operator/v1alpha1" +) + +var workspaceObjectsResource = schema.GroupVersionResource{Group: "operator.kcp.io", Version: "v1alpha1", Resource: "workspaceobjects"} +var workspaceObjectsKind = schema.GroupVersionKind{Group: "operator.kcp.io", Version: "v1alpha1", Kind: "WorkspaceObject"} + +type workspaceObjectsClusterClient struct { + *kcptesting.Fake +} + +// Cluster scopes the client down to a particular cluster. +func (c *workspaceObjectsClusterClient) Cluster(clusterPath logicalcluster.Path) kcpoperatorv1alpha1.WorkspaceObjectsNamespacer { + if clusterPath == logicalcluster.Wildcard { + panic("A specific cluster must be provided when scoping, not the wildcard.") + } + + return &workspaceObjectsNamespacer{Fake: c.Fake, ClusterPath: clusterPath} +} + +// List takes label and field selectors, and returns the list of WorkspaceObjects that match those selectors across all clusters. +func (c *workspaceObjectsClusterClient) List(ctx context.Context, opts metav1.ListOptions) (*operatorv1alpha1.WorkspaceObjectList, error) { + obj, err := c.Fake.Invokes(kcptesting.NewListAction(workspaceObjectsResource, workspaceObjectsKind, logicalcluster.Wildcard, metav1.NamespaceAll, opts), &operatorv1alpha1.WorkspaceObjectList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &operatorv1alpha1.WorkspaceObjectList{ListMeta: obj.(*operatorv1alpha1.WorkspaceObjectList).ListMeta} + for _, item := range obj.(*operatorv1alpha1.WorkspaceObjectList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested WorkspaceObjects across all clusters. +func (c *workspaceObjectsClusterClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + return c.Fake.InvokesWatch(kcptesting.NewWatchAction(workspaceObjectsResource, logicalcluster.Wildcard, metav1.NamespaceAll, opts)) +} + +type workspaceObjectsNamespacer struct { + *kcptesting.Fake + ClusterPath logicalcluster.Path +} + +func (n *workspaceObjectsNamespacer) Namespace(namespace string) operatorv1alpha1client.WorkspaceObjectInterface { + return &workspaceObjectsClient{Fake: n.Fake, ClusterPath: n.ClusterPath, Namespace: namespace} +} + +type workspaceObjectsClient struct { + *kcptesting.Fake + ClusterPath logicalcluster.Path + Namespace string +} + +func (c *workspaceObjectsClient) Create(ctx context.Context, workspaceObject *operatorv1alpha1.WorkspaceObject, opts metav1.CreateOptions) (*operatorv1alpha1.WorkspaceObject, error) { + obj, err := c.Fake.Invokes(kcptesting.NewCreateAction(workspaceObjectsResource, c.ClusterPath, c.Namespace, workspaceObject), &operatorv1alpha1.WorkspaceObject{}) + if obj == nil { + return nil, err + } + return obj.(*operatorv1alpha1.WorkspaceObject), err +} + +func (c *workspaceObjectsClient) Update(ctx context.Context, workspaceObject *operatorv1alpha1.WorkspaceObject, opts metav1.UpdateOptions) (*operatorv1alpha1.WorkspaceObject, error) { + obj, err := c.Fake.Invokes(kcptesting.NewUpdateAction(workspaceObjectsResource, c.ClusterPath, c.Namespace, workspaceObject), &operatorv1alpha1.WorkspaceObject{}) + if obj == nil { + return nil, err + } + return obj.(*operatorv1alpha1.WorkspaceObject), err +} + +func (c *workspaceObjectsClient) UpdateStatus(ctx context.Context, workspaceObject *operatorv1alpha1.WorkspaceObject, opts metav1.UpdateOptions) (*operatorv1alpha1.WorkspaceObject, error) { + obj, err := c.Fake.Invokes(kcptesting.NewUpdateSubresourceAction(workspaceObjectsResource, c.ClusterPath, "status", c.Namespace, workspaceObject), &operatorv1alpha1.WorkspaceObject{}) + if obj == nil { + return nil, err + } + return obj.(*operatorv1alpha1.WorkspaceObject), err +} + +func (c *workspaceObjectsClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { + _, err := c.Fake.Invokes(kcptesting.NewDeleteActionWithOptions(workspaceObjectsResource, c.ClusterPath, c.Namespace, name, opts), &operatorv1alpha1.WorkspaceObject{}) + return err +} + +func (c *workspaceObjectsClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { + action := kcptesting.NewDeleteCollectionAction(workspaceObjectsResource, c.ClusterPath, c.Namespace, listOpts) + + _, err := c.Fake.Invokes(action, &operatorv1alpha1.WorkspaceObjectList{}) + return err +} + +func (c *workspaceObjectsClient) Get(ctx context.Context, name string, options metav1.GetOptions) (*operatorv1alpha1.WorkspaceObject, error) { + obj, err := c.Fake.Invokes(kcptesting.NewGetAction(workspaceObjectsResource, c.ClusterPath, c.Namespace, name), &operatorv1alpha1.WorkspaceObject{}) + if obj == nil { + return nil, err + } + return obj.(*operatorv1alpha1.WorkspaceObject), err +} + +// List takes label and field selectors, and returns the list of WorkspaceObjects that match those selectors. +func (c *workspaceObjectsClient) List(ctx context.Context, opts metav1.ListOptions) (*operatorv1alpha1.WorkspaceObjectList, error) { + obj, err := c.Fake.Invokes(kcptesting.NewListAction(workspaceObjectsResource, workspaceObjectsKind, c.ClusterPath, c.Namespace, opts), &operatorv1alpha1.WorkspaceObjectList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &operatorv1alpha1.WorkspaceObjectList{ListMeta: obj.(*operatorv1alpha1.WorkspaceObjectList).ListMeta} + for _, item := range obj.(*operatorv1alpha1.WorkspaceObjectList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +func (c *workspaceObjectsClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + return c.Fake.InvokesWatch(kcptesting.NewWatchAction(workspaceObjectsResource, c.ClusterPath, c.Namespace, opts)) +} + +func (c *workspaceObjectsClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*operatorv1alpha1.WorkspaceObject, error) { + obj, err := c.Fake.Invokes(kcptesting.NewPatchSubresourceAction(workspaceObjectsResource, c.ClusterPath, c.Namespace, name, pt, data, subresources...), &operatorv1alpha1.WorkspaceObject{}) + if obj == nil { + return nil, err + } + return obj.(*operatorv1alpha1.WorkspaceObject), err +} + +func (c *workspaceObjectsClient) Apply(ctx context.Context, applyConfiguration *applyconfigurationsoperatorv1alpha1.WorkspaceObjectApplyConfiguration, opts metav1.ApplyOptions) (*operatorv1alpha1.WorkspaceObject, error) { + if applyConfiguration == nil { + return nil, fmt.Errorf("applyConfiguration provided to Apply must not be nil") + } + data, err := json.Marshal(applyConfiguration) + if err != nil { + return nil, err + } + name := applyConfiguration.Name + if name == nil { + return nil, fmt.Errorf("applyConfiguration.Name must be provided to Apply") + } + obj, err := c.Fake.Invokes(kcptesting.NewPatchSubresourceAction(workspaceObjectsResource, c.ClusterPath, c.Namespace, *name, types.ApplyPatchType, data), &operatorv1alpha1.WorkspaceObject{}) + if obj == nil { + return nil, err + } + return obj.(*operatorv1alpha1.WorkspaceObject), err +} + +func (c *workspaceObjectsClient) ApplyStatus(ctx context.Context, applyConfiguration *applyconfigurationsoperatorv1alpha1.WorkspaceObjectApplyConfiguration, opts metav1.ApplyOptions) (*operatorv1alpha1.WorkspaceObject, error) { + if applyConfiguration == nil { + return nil, fmt.Errorf("applyConfiguration provided to Apply must not be nil") + } + data, err := json.Marshal(applyConfiguration) + if err != nil { + return nil, err + } + name := applyConfiguration.Name + if name == nil { + return nil, fmt.Errorf("applyConfiguration.Name must be provided to Apply") + } + obj, err := c.Fake.Invokes(kcptesting.NewPatchSubresourceAction(workspaceObjectsResource, c.ClusterPath, c.Namespace, *name, types.ApplyPatchType, data, "status"), &operatorv1alpha1.WorkspaceObject{}) + if obj == nil { + return nil, err + } + return obj.(*operatorv1alpha1.WorkspaceObject), err +} diff --git a/sdk/clientset/versioned/cluster/typed/operator/v1alpha1/operator_client.go b/sdk/clientset/versioned/cluster/typed/operator/v1alpha1/operator_client.go index 625996db..09f01a74 100644 --- a/sdk/clientset/versioned/cluster/typed/operator/v1alpha1/operator_client.go +++ b/sdk/clientset/versioned/cluster/typed/operator/v1alpha1/operator_client.go @@ -39,6 +39,7 @@ type OperatorV1alpha1ClusterInterface interface { KubeconfigsClusterGetter RootShardsClusterGetter ShardsClusterGetter + WorkspaceObjectsClusterGetter } type OperatorV1alpha1ClusterScoper interface { @@ -76,6 +77,10 @@ func (c *OperatorV1alpha1ClusterClient) Shards() ShardClusterInterface { return &shardsClusterInterface{clientCache: c.clientCache} } +func (c *OperatorV1alpha1ClusterClient) WorkspaceObjects() WorkspaceObjectClusterInterface { + return &workspaceObjectsClusterInterface{clientCache: c.clientCache} +} + // NewForConfig creates a new OperatorV1alpha1ClusterClient for the given config. // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), // where httpClient was generated with rest.HTTPClientFor(c). diff --git a/sdk/clientset/versioned/cluster/typed/operator/v1alpha1/workspaceobject.go b/sdk/clientset/versioned/cluster/typed/operator/v1alpha1/workspaceobject.go new file mode 100644 index 00000000..2a476da5 --- /dev/null +++ b/sdk/clientset/versioned/cluster/typed/operator/v1alpha1/workspaceobject.go @@ -0,0 +1,86 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2024 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by kcp code-generator. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + + kcpclient "github.com/kcp-dev/apimachinery/v2/pkg/client" + "github.com/kcp-dev/logicalcluster/v3" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/watch" + + operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1" + operatorv1alpha1client "github.com/kcp-dev/kcp-operator/sdk/clientset/versioned/typed/operator/v1alpha1" +) + +// WorkspaceObjectsClusterGetter has a method to return a WorkspaceObjectClusterInterface. +// A group's cluster client should implement this interface. +type WorkspaceObjectsClusterGetter interface { + WorkspaceObjects() WorkspaceObjectClusterInterface +} + +// WorkspaceObjectClusterInterface can operate on WorkspaceObjects across all clusters, +// or scope down to one cluster and return a WorkspaceObjectsNamespacer. +type WorkspaceObjectClusterInterface interface { + Cluster(logicalcluster.Path) WorkspaceObjectsNamespacer + List(ctx context.Context, opts metav1.ListOptions) (*operatorv1alpha1.WorkspaceObjectList, error) + Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) +} + +type workspaceObjectsClusterInterface struct { + clientCache kcpclient.Cache[*operatorv1alpha1client.OperatorV1alpha1Client] +} + +// Cluster scopes the client down to a particular cluster. +func (c *workspaceObjectsClusterInterface) Cluster(clusterPath logicalcluster.Path) WorkspaceObjectsNamespacer { + if clusterPath == logicalcluster.Wildcard { + panic("A specific cluster must be provided when scoping, not the wildcard.") + } + + return &workspaceObjectsNamespacer{clientCache: c.clientCache, clusterPath: clusterPath} +} + +// List returns the entire collection of all WorkspaceObjects across all clusters. +func (c *workspaceObjectsClusterInterface) List(ctx context.Context, opts metav1.ListOptions) (*operatorv1alpha1.WorkspaceObjectList, error) { + return c.clientCache.ClusterOrDie(logicalcluster.Wildcard).WorkspaceObjects(metav1.NamespaceAll).List(ctx, opts) +} + +// Watch begins to watch all WorkspaceObjects across all clusters. +func (c *workspaceObjectsClusterInterface) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + return c.clientCache.ClusterOrDie(logicalcluster.Wildcard).WorkspaceObjects(metav1.NamespaceAll).Watch(ctx, opts) +} + +// WorkspaceObjectsNamespacer can scope to objects within a namespace, returning a operatorv1alpha1client.WorkspaceObjectInterface. +type WorkspaceObjectsNamespacer interface { + Namespace(string) operatorv1alpha1client.WorkspaceObjectInterface +} + +type workspaceObjectsNamespacer struct { + clientCache kcpclient.Cache[*operatorv1alpha1client.OperatorV1alpha1Client] + clusterPath logicalcluster.Path +} + +func (n *workspaceObjectsNamespacer) Namespace(namespace string) operatorv1alpha1client.WorkspaceObjectInterface { + return n.clientCache.ClusterOrDie(n.clusterPath).WorkspaceObjects(namespace) +} diff --git a/sdk/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go b/sdk/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go index 036e1ac0..f14d3d13 100644 --- a/sdk/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go +++ b/sdk/clientset/versioned/typed/operator/v1alpha1/fake/fake_operator_client.go @@ -49,6 +49,10 @@ func (c *FakeOperatorV1alpha1) Shards(namespace string) v1alpha1.ShardInterface return newFakeShards(c, namespace) } +func (c *FakeOperatorV1alpha1) WorkspaceObjects(namespace string) v1alpha1.WorkspaceObjectInterface { + return newFakeWorkspaceObjects(c, namespace) +} + // RESTClient returns a RESTClient that is used to communicate // with API server by this client implementation. func (c *FakeOperatorV1alpha1) RESTClient() rest.Interface { diff --git a/sdk/clientset/versioned/typed/operator/v1alpha1/fake/fake_workspaceobject.go b/sdk/clientset/versioned/typed/operator/v1alpha1/fake/fake_workspaceobject.go new file mode 100644 index 00000000..aaccd447 --- /dev/null +++ b/sdk/clientset/versioned/typed/operator/v1alpha1/fake/fake_workspaceobject.go @@ -0,0 +1,53 @@ +/* +Copyright 2024 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + gentype "k8s.io/client-go/gentype" + + v1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1" + operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/clientset/versioned/typed/operator/v1alpha1" +) + +// fakeWorkspaceObjects implements WorkspaceObjectInterface +type fakeWorkspaceObjects struct { + *gentype.FakeClientWithList[*v1alpha1.WorkspaceObject, *v1alpha1.WorkspaceObjectList] + Fake *FakeOperatorV1alpha1 +} + +func newFakeWorkspaceObjects(fake *FakeOperatorV1alpha1, namespace string) operatorv1alpha1.WorkspaceObjectInterface { + return &fakeWorkspaceObjects{ + gentype.NewFakeClientWithList[*v1alpha1.WorkspaceObject, *v1alpha1.WorkspaceObjectList]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("workspaceobjects"), + v1alpha1.SchemeGroupVersion.WithKind("WorkspaceObject"), + func() *v1alpha1.WorkspaceObject { return &v1alpha1.WorkspaceObject{} }, + func() *v1alpha1.WorkspaceObjectList { return &v1alpha1.WorkspaceObjectList{} }, + func(dst, src *v1alpha1.WorkspaceObjectList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.WorkspaceObjectList) []*v1alpha1.WorkspaceObject { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.WorkspaceObjectList, items []*v1alpha1.WorkspaceObject) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/sdk/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go b/sdk/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go index be7dc8bf..fc143a21 100644 --- a/sdk/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go +++ b/sdk/clientset/versioned/typed/operator/v1alpha1/generated_expansion.go @@ -27,3 +27,5 @@ type KubeconfigExpansion interface{} type RootShardExpansion interface{} type ShardExpansion interface{} + +type WorkspaceObjectExpansion interface{} diff --git a/sdk/clientset/versioned/typed/operator/v1alpha1/operator_client.go b/sdk/clientset/versioned/typed/operator/v1alpha1/operator_client.go index a0fc13de..c418e155 100644 --- a/sdk/clientset/versioned/typed/operator/v1alpha1/operator_client.go +++ b/sdk/clientset/versioned/typed/operator/v1alpha1/operator_client.go @@ -34,6 +34,7 @@ type OperatorV1alpha1Interface interface { KubeconfigsGetter RootShardsGetter ShardsGetter + WorkspaceObjectsGetter } // OperatorV1alpha1Client is used to interact with features provided by the operator.kcp.io group. @@ -61,6 +62,10 @@ func (c *OperatorV1alpha1Client) Shards(namespace string) ShardInterface { return newShards(c, namespace) } +func (c *OperatorV1alpha1Client) WorkspaceObjects(namespace string) WorkspaceObjectInterface { + return newWorkspaceObjects(c, namespace) +} + // NewForConfig creates a new OperatorV1alpha1Client for the given config. // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), // where httpClient was generated with rest.HTTPClientFor(c). diff --git a/sdk/clientset/versioned/typed/operator/v1alpha1/workspaceobject.go b/sdk/clientset/versioned/typed/operator/v1alpha1/workspaceobject.go new file mode 100644 index 00000000..dda4efca --- /dev/null +++ b/sdk/clientset/versioned/typed/operator/v1alpha1/workspaceobject.go @@ -0,0 +1,71 @@ +/* +Copyright 2024 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" + + operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1" + scheme "github.com/kcp-dev/kcp-operator/sdk/clientset/versioned/scheme" +) + +// WorkspaceObjectsGetter has a method to return a WorkspaceObjectInterface. +// A group's client should implement this interface. +type WorkspaceObjectsGetter interface { + WorkspaceObjects(namespace string) WorkspaceObjectInterface +} + +// WorkspaceObjectInterface has methods to work with WorkspaceObject resources. +type WorkspaceObjectInterface interface { + Create(ctx context.Context, workspaceObject *operatorv1alpha1.WorkspaceObject, opts v1.CreateOptions) (*operatorv1alpha1.WorkspaceObject, error) + Update(ctx context.Context, workspaceObject *operatorv1alpha1.WorkspaceObject, opts v1.UpdateOptions) (*operatorv1alpha1.WorkspaceObject, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, workspaceObject *operatorv1alpha1.WorkspaceObject, opts v1.UpdateOptions) (*operatorv1alpha1.WorkspaceObject, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*operatorv1alpha1.WorkspaceObject, error) + List(ctx context.Context, opts v1.ListOptions) (*operatorv1alpha1.WorkspaceObjectList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *operatorv1alpha1.WorkspaceObject, err error) + WorkspaceObjectExpansion +} + +// workspaceObjects implements WorkspaceObjectInterface +type workspaceObjects struct { + *gentype.ClientWithList[*operatorv1alpha1.WorkspaceObject, *operatorv1alpha1.WorkspaceObjectList] +} + +// newWorkspaceObjects returns a WorkspaceObjects +func newWorkspaceObjects(c *OperatorV1alpha1Client, namespace string) *workspaceObjects { + return &workspaceObjects{ + gentype.NewClientWithList[*operatorv1alpha1.WorkspaceObject, *operatorv1alpha1.WorkspaceObjectList]( + "workspaceobjects", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *operatorv1alpha1.WorkspaceObject { return &operatorv1alpha1.WorkspaceObject{} }, + func() *operatorv1alpha1.WorkspaceObjectList { return &operatorv1alpha1.WorkspaceObjectList{} }, + ), + } +} diff --git a/sdk/go.mod b/sdk/go.mod index 211c83cd..4e8470b2 100644 --- a/sdk/go.mod +++ b/sdk/go.mod @@ -7,6 +7,7 @@ require ( github.com/kcp-dev/client-go v0.0.0-20250223133118-3dea338dc267 github.com/kcp-dev/logicalcluster/v3 v3.0.5 k8s.io/api v0.32.0 + k8s.io/apiextensions-apiserver v0.32.0 k8s.io/apimachinery v0.32.0 k8s.io/client-go v0.32.0 sigs.k8s.io/structured-merge-diff/v4 v4.5.0 diff --git a/sdk/go.sum b/sdk/go.sum index a68d4e40..de9e9410 100644 --- a/sdk/go.sum +++ b/sdk/go.sum @@ -132,6 +132,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= +k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0= +k8s.io/apiextensions-apiserver v0.32.0/go.mod h1:86hblMvN5yxMvZrZFX2OhIHAuFIMJIZ19bTvzkP+Fmw= k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= diff --git a/sdk/informers/externalversions/generic.go b/sdk/informers/externalversions/generic.go index f89361c7..317ab19e 100644 --- a/sdk/informers/externalversions/generic.go +++ b/sdk/informers/externalversions/generic.go @@ -97,6 +97,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericClusterInformer{resource: resource.GroupResource(), informer: f.Operator().V1alpha1().RootShards().Informer()}, nil case operatorv1alpha1.SchemeGroupVersion.WithResource("shards"): return &genericClusterInformer{resource: resource.GroupResource(), informer: f.Operator().V1alpha1().Shards().Informer()}, nil + case operatorv1alpha1.SchemeGroupVersion.WithResource("workspaceobjects"): + return &genericClusterInformer{resource: resource.GroupResource(), informer: f.Operator().V1alpha1().WorkspaceObjects().Informer()}, nil } return nil, fmt.Errorf("no informer found for %v", resource) @@ -122,6 +124,9 @@ func (f *sharedScopedInformerFactory) ForResource(resource schema.GroupVersionRe case operatorv1alpha1.SchemeGroupVersion.WithResource("shards"): informer := f.Operator().V1alpha1().Shards().Informer() return &genericInformer{lister: cache.NewGenericLister(informer.GetIndexer(), resource.GroupResource()), informer: informer}, nil + case operatorv1alpha1.SchemeGroupVersion.WithResource("workspaceobjects"): + informer := f.Operator().V1alpha1().WorkspaceObjects().Informer() + return &genericInformer{lister: cache.NewGenericLister(informer.GetIndexer(), resource.GroupResource()), informer: informer}, nil } return nil, fmt.Errorf("no informer found for %v", resource) diff --git a/sdk/informers/externalversions/operator/v1alpha1/interface.go b/sdk/informers/externalversions/operator/v1alpha1/interface.go index 64e3ac6f..1e110f96 100644 --- a/sdk/informers/externalversions/operator/v1alpha1/interface.go +++ b/sdk/informers/externalversions/operator/v1alpha1/interface.go @@ -36,6 +36,8 @@ type ClusterInterface interface { RootShards() RootShardClusterInformer // Shards returns a ShardClusterInformer Shards() ShardClusterInformer + // WorkspaceObjects returns a WorkspaceObjectClusterInformer + WorkspaceObjects() WorkspaceObjectClusterInformer } type version struct { @@ -73,6 +75,11 @@ func (v *version) Shards() ShardClusterInformer { return &shardClusterInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// WorkspaceObjects returns a WorkspaceObjectClusterInformer +func (v *version) WorkspaceObjects() WorkspaceObjectClusterInformer { + return &workspaceObjectClusterInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + type Interface interface { // CacheServers returns a CacheServerInformer CacheServers() CacheServerInformer @@ -84,6 +91,8 @@ type Interface interface { RootShards() RootShardInformer // Shards returns a ShardInformer Shards() ShardInformer + // WorkspaceObjects returns a WorkspaceObjectInformer + WorkspaceObjects() WorkspaceObjectInformer } type scopedVersion struct { @@ -121,3 +130,8 @@ func (v *scopedVersion) RootShards() RootShardInformer { func (v *scopedVersion) Shards() ShardInformer { return &shardScopedInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } + +// WorkspaceObjects returns a WorkspaceObjectInformer +func (v *scopedVersion) WorkspaceObjects() WorkspaceObjectInformer { + return &workspaceObjectScopedInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/sdk/informers/externalversions/operator/v1alpha1/workspaceobject.go b/sdk/informers/externalversions/operator/v1alpha1/workspaceobject.go new file mode 100644 index 00000000..0ac173cf --- /dev/null +++ b/sdk/informers/externalversions/operator/v1alpha1/workspaceobject.go @@ -0,0 +1,182 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2024 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by kcp code-generator. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + kcpcache "github.com/kcp-dev/apimachinery/v2/pkg/cache" + kcpinformers "github.com/kcp-dev/apimachinery/v2/third_party/informers" + "github.com/kcp-dev/logicalcluster/v3" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" + + operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1" + scopedclientset "github.com/kcp-dev/kcp-operator/sdk/clientset/versioned" + clientset "github.com/kcp-dev/kcp-operator/sdk/clientset/versioned/cluster" + "github.com/kcp-dev/kcp-operator/sdk/informers/externalversions/internalinterfaces" + operatorv1alpha1listers "github.com/kcp-dev/kcp-operator/sdk/listers/operator/v1alpha1" +) + +// WorkspaceObjectClusterInformer provides access to a shared informer and lister for +// WorkspaceObjects. +type WorkspaceObjectClusterInformer interface { + Cluster(logicalcluster.Name) WorkspaceObjectInformer + Informer() kcpcache.ScopeableSharedIndexInformer + Lister() operatorv1alpha1listers.WorkspaceObjectClusterLister +} + +type workspaceObjectClusterInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewWorkspaceObjectClusterInformer constructs a new informer for WorkspaceObject type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewWorkspaceObjectClusterInformer(client clientset.ClusterInterface, resyncPeriod time.Duration, indexers cache.Indexers) kcpcache.ScopeableSharedIndexInformer { + return NewFilteredWorkspaceObjectClusterInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredWorkspaceObjectClusterInformer constructs a new informer for WorkspaceObject type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredWorkspaceObjectClusterInformer(client clientset.ClusterInterface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) kcpcache.ScopeableSharedIndexInformer { + return kcpinformers.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().WorkspaceObjects().List(context.TODO(), options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().WorkspaceObjects().Watch(context.TODO(), options) + }, + }, + &operatorv1alpha1.WorkspaceObject{}, + resyncPeriod, + indexers, + ) +} + +func (f *workspaceObjectClusterInformer) defaultInformer(client clientset.ClusterInterface, resyncPeriod time.Duration) kcpcache.ScopeableSharedIndexInformer { + return NewFilteredWorkspaceObjectClusterInformer(client, resyncPeriod, cache.Indexers{ + kcpcache.ClusterIndexName: kcpcache.ClusterIndexFunc, + kcpcache.ClusterAndNamespaceIndexName: kcpcache.ClusterAndNamespaceIndexFunc}, + f.tweakListOptions, + ) +} + +func (f *workspaceObjectClusterInformer) Informer() kcpcache.ScopeableSharedIndexInformer { + return f.factory.InformerFor(&operatorv1alpha1.WorkspaceObject{}, f.defaultInformer) +} + +func (f *workspaceObjectClusterInformer) Lister() operatorv1alpha1listers.WorkspaceObjectClusterLister { + return operatorv1alpha1listers.NewWorkspaceObjectClusterLister(f.Informer().GetIndexer()) +} + +// WorkspaceObjectInformer provides access to a shared informer and lister for +// WorkspaceObjects. +type WorkspaceObjectInformer interface { + Informer() cache.SharedIndexInformer + Lister() operatorv1alpha1listers.WorkspaceObjectLister +} + +func (f *workspaceObjectClusterInformer) Cluster(clusterName logicalcluster.Name) WorkspaceObjectInformer { + return &workspaceObjectInformer{ + informer: f.Informer().Cluster(clusterName), + lister: f.Lister().Cluster(clusterName), + } +} + +type workspaceObjectInformer struct { + informer cache.SharedIndexInformer + lister operatorv1alpha1listers.WorkspaceObjectLister +} + +func (f *workspaceObjectInformer) Informer() cache.SharedIndexInformer { + return f.informer +} + +func (f *workspaceObjectInformer) Lister() operatorv1alpha1listers.WorkspaceObjectLister { + return f.lister +} + +type workspaceObjectScopedInformer struct { + factory internalinterfaces.SharedScopedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +func (f *workspaceObjectScopedInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&operatorv1alpha1.WorkspaceObject{}, f.defaultInformer) +} + +func (f *workspaceObjectScopedInformer) Lister() operatorv1alpha1listers.WorkspaceObjectLister { + return operatorv1alpha1listers.NewWorkspaceObjectLister(f.Informer().GetIndexer()) +} + +// NewWorkspaceObjectInformer constructs a new informer for WorkspaceObject type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewWorkspaceObjectInformer(client scopedclientset.Interface, resyncPeriod time.Duration, namespace string, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredWorkspaceObjectInformer(client, resyncPeriod, namespace, indexers, nil) +} + +// NewFilteredWorkspaceObjectInformer constructs a new informer for WorkspaceObject type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredWorkspaceObjectInformer(client scopedclientset.Interface, resyncPeriod time.Duration, namespace string, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().WorkspaceObjects(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.OperatorV1alpha1().WorkspaceObjects(namespace).Watch(context.TODO(), options) + }, + }, + &operatorv1alpha1.WorkspaceObject{}, + resyncPeriod, + indexers, + ) +} + +func (f *workspaceObjectScopedInformer) defaultInformer(client scopedclientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredWorkspaceObjectInformer(client, resyncPeriod, f.namespace, cache.Indexers{ + cache.NamespaceIndex: cache.MetaNamespaceIndexFunc, + }, f.tweakListOptions) +} diff --git a/sdk/listers/operator/v1alpha1/workspaceobject.go b/sdk/listers/operator/v1alpha1/workspaceobject.go new file mode 100644 index 00000000..193524ce --- /dev/null +++ b/sdk/listers/operator/v1alpha1/workspaceobject.go @@ -0,0 +1,196 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2024 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by kcp code-generator. DO NOT EDIT. + +package v1alpha1 + +import ( + kcpcache "github.com/kcp-dev/apimachinery/v2/pkg/cache" + "github.com/kcp-dev/logicalcluster/v3" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + + operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1" +) + +// WorkspaceObjectClusterLister can list WorkspaceObjects across all workspaces, or scope down to a WorkspaceObjectLister for one workspace. +// All objects returned here must be treated as read-only. +type WorkspaceObjectClusterLister interface { + // List lists all WorkspaceObjects in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*operatorv1alpha1.WorkspaceObject, err error) + // Cluster returns a lister that can list and get WorkspaceObjects in one workspace. + Cluster(clusterName logicalcluster.Name) WorkspaceObjectLister + WorkspaceObjectClusterListerExpansion +} + +type workspaceObjectClusterLister struct { + indexer cache.Indexer +} + +// NewWorkspaceObjectClusterLister returns a new WorkspaceObjectClusterLister. +// We assume that the indexer: +// - is fed by a cross-workspace LIST+WATCH +// - uses kcpcache.MetaClusterNamespaceKeyFunc as the key function +// - has the kcpcache.ClusterIndex as an index +// - has the kcpcache.ClusterAndNamespaceIndex as an index +func NewWorkspaceObjectClusterLister(indexer cache.Indexer) *workspaceObjectClusterLister { + return &workspaceObjectClusterLister{indexer: indexer} +} + +// List lists all WorkspaceObjects in the indexer across all workspaces. +func (s *workspaceObjectClusterLister) List(selector labels.Selector) (ret []*operatorv1alpha1.WorkspaceObject, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*operatorv1alpha1.WorkspaceObject)) + }) + return ret, err +} + +// Cluster scopes the lister to one workspace, allowing users to list and get WorkspaceObjects. +func (s *workspaceObjectClusterLister) Cluster(clusterName logicalcluster.Name) WorkspaceObjectLister { + return &workspaceObjectLister{indexer: s.indexer, clusterName: clusterName} +} + +// WorkspaceObjectLister can list WorkspaceObjects across all namespaces, or scope down to a WorkspaceObjectNamespaceLister for one namespace. +// All objects returned here must be treated as read-only. +type WorkspaceObjectLister interface { + // List lists all WorkspaceObjects in the workspace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*operatorv1alpha1.WorkspaceObject, err error) + // WorkspaceObjects returns a lister that can list and get WorkspaceObjects in one workspace and namespace. + WorkspaceObjects(namespace string) WorkspaceObjectNamespaceLister + WorkspaceObjectListerExpansion +} + +// workspaceObjectLister can list all WorkspaceObjects inside a workspace or scope down to a WorkspaceObjectLister for one namespace. +type workspaceObjectLister struct { + indexer cache.Indexer + clusterName logicalcluster.Name +} + +// List lists all WorkspaceObjects in the indexer for a workspace. +func (s *workspaceObjectLister) List(selector labels.Selector) (ret []*operatorv1alpha1.WorkspaceObject, err error) { + err = kcpcache.ListAllByCluster(s.indexer, s.clusterName, selector, func(i interface{}) { + ret = append(ret, i.(*operatorv1alpha1.WorkspaceObject)) + }) + return ret, err +} + +// WorkspaceObjects returns an object that can list and get WorkspaceObjects in one namespace. +func (s *workspaceObjectLister) WorkspaceObjects(namespace string) WorkspaceObjectNamespaceLister { + return &workspaceObjectNamespaceLister{indexer: s.indexer, clusterName: s.clusterName, namespace: namespace} +} + +// workspaceObjectNamespaceLister helps list and get WorkspaceObjects. +// All objects returned here must be treated as read-only. +type WorkspaceObjectNamespaceLister interface { + // List lists all WorkspaceObjects in the workspace and namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*operatorv1alpha1.WorkspaceObject, err error) + // Get retrieves the WorkspaceObject from the indexer for a given workspace, namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*operatorv1alpha1.WorkspaceObject, error) + WorkspaceObjectNamespaceListerExpansion +} + +// workspaceObjectNamespaceLister helps list and get WorkspaceObjects. +// All objects returned here must be treated as read-only. +type workspaceObjectNamespaceLister struct { + indexer cache.Indexer + clusterName logicalcluster.Name + namespace string +} + +// List lists all WorkspaceObjects in the indexer for a given workspace and namespace. +func (s *workspaceObjectNamespaceLister) List(selector labels.Selector) (ret []*operatorv1alpha1.WorkspaceObject, err error) { + err = kcpcache.ListAllByClusterAndNamespace(s.indexer, s.clusterName, s.namespace, selector, func(i interface{}) { + ret = append(ret, i.(*operatorv1alpha1.WorkspaceObject)) + }) + return ret, err +} + +// Get retrieves the WorkspaceObject from the indexer for a given workspace, namespace and name. +func (s *workspaceObjectNamespaceLister) Get(name string) (*operatorv1alpha1.WorkspaceObject, error) { + key := kcpcache.ToClusterAwareKey(s.clusterName.String(), s.namespace, name) + obj, exists, err := s.indexer.GetByKey(key) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(operatorv1alpha1.Resource("workspaceobjects"), name) + } + return obj.(*operatorv1alpha1.WorkspaceObject), nil +} + +// NewWorkspaceObjectLister returns a new WorkspaceObjectLister. +// We assume that the indexer: +// - is fed by a workspace-scoped LIST+WATCH +// - uses cache.MetaNamespaceKeyFunc as the key function +// - has the cache.NamespaceIndex as an index +func NewWorkspaceObjectLister(indexer cache.Indexer) *workspaceObjectScopedLister { + return &workspaceObjectScopedLister{indexer: indexer} +} + +// workspaceObjectScopedLister can list all WorkspaceObjects inside a workspace or scope down to a WorkspaceObjectLister for one namespace. +type workspaceObjectScopedLister struct { + indexer cache.Indexer +} + +// List lists all WorkspaceObjects in the indexer for a workspace. +func (s *workspaceObjectScopedLister) List(selector labels.Selector) (ret []*operatorv1alpha1.WorkspaceObject, err error) { + err = cache.ListAll(s.indexer, selector, func(i interface{}) { + ret = append(ret, i.(*operatorv1alpha1.WorkspaceObject)) + }) + return ret, err +} + +// WorkspaceObjects returns an object that can list and get WorkspaceObjects in one namespace. +func (s *workspaceObjectScopedLister) WorkspaceObjects(namespace string) WorkspaceObjectNamespaceLister { + return &workspaceObjectScopedNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// workspaceObjectScopedNamespaceLister helps list and get WorkspaceObjects. +type workspaceObjectScopedNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all WorkspaceObjects in the indexer for a given workspace and namespace. +func (s *workspaceObjectScopedNamespaceLister) List(selector labels.Selector) (ret []*operatorv1alpha1.WorkspaceObject, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(i interface{}) { + ret = append(ret, i.(*operatorv1alpha1.WorkspaceObject)) + }) + return ret, err +} + +// Get retrieves the WorkspaceObject from the indexer for a given workspace, namespace and name. +func (s *workspaceObjectScopedNamespaceLister) Get(name string) (*operatorv1alpha1.WorkspaceObject, error) { + key := s.namespace + "/" + name + obj, exists, err := s.indexer.GetByKey(key) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(operatorv1alpha1.Resource("workspaceobjects"), name) + } + return obj.(*operatorv1alpha1.WorkspaceObject), nil +} diff --git a/sdk/listers/operator/v1alpha1/workspaceobject_expansion.go b/sdk/listers/operator/v1alpha1/workspaceobject_expansion.go new file mode 100644 index 00000000..fa469415 --- /dev/null +++ b/sdk/listers/operator/v1alpha1/workspaceobject_expansion.go @@ -0,0 +1,31 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2024 The KCP Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by kcp code-generator. DO NOT EDIT. + +package v1alpha1 + +// WorkspaceObjectClusterListerExpansion allows custom methods to be added to WorkspaceObjectClusterLister. +type WorkspaceObjectClusterListerExpansion interface{} + +// WorkspaceObjectListerExpansion allows custom methods to be added to WorkspaceObjectLister. +type WorkspaceObjectListerExpansion interface{} + +// WorkspaceObjectNamespaceListerExpansion allows custom methods to be added to WorkspaceObjectNamespaceLister. +type WorkspaceObjectNamespaceListerExpansion interface{}