diff --git a/apis/cluster/v1beta1/zz_generated.deepcopy.go b/apis/cluster/v1beta1/zz_generated.deepcopy.go index 17e71a1a2..7bb7f501c 100644 --- a/apis/cluster/v1beta1/zz_generated.deepcopy.go +++ b/apis/cluster/v1beta1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1beta1 import ( - v1 "k8s.io/api/core/v1" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/apis/placement/v1alpha1/envelope_types.go b/apis/placement/v1alpha1/envelope_types.go new file mode 100644 index 000000000..a8a29fc40 --- /dev/null +++ b/apis/placement/v1alpha1/envelope_types.go @@ -0,0 +1,73 @@ +/* +Copyright 2025 The KubeFleet 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 ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// +genclient +// +genclient:nonNamespaced +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope="Cluster",categories={fleet,fleet-placement} +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ClusterResourceEnvelope wraps cluster-scoped resources for placement. +type ClusterResourceEnvelope struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // The desired state of ClusterResourceEnvelope. + // +kubebuilder:validation:Required + Spec EnvelopeSpec `json:"spec"` +} + +// EnvelopeSpec helps wrap resources for placement. +type EnvelopeSpec struct { + // A map of wrapped manifests. + // + // Each manifest is uniquely identified by a string key, typically a filename that represents + // the manifest. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinProperties=1 + // +kubebuilder:validation:MaxProperties=50 + Manifests map[string]Manifest `json:"manifests"` +} + +// Manifest is a wrapped resource. +type Manifest struct { + // The resource data. + // +kubebuilder:validation:Required + // +kubebuilder:validation:EmbeddedResource + // +kubebuilder:pruning:PreserveUnknownFields + Data runtime.RawExtension `json:"data"` +} + +// +genclient +// +genclient:Namespaced +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope="Namespaced",categories={fleet,fleet-placement} +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ResourceEnvelope struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // The desired state of ResourceEnvelope. + // +kubebuilder:validation:Required + Spec EnvelopeSpec `json:"spec"` +} diff --git a/apis/placement/v1alpha1/zz_generated.deepcopy.go b/apis/placement/v1alpha1/zz_generated.deepcopy.go index ccbe37c26..bdc1ec2a8 100644 --- a/apis/placement/v1alpha1/zz_generated.deepcopy.go +++ b/apis/placement/v1alpha1/zz_generated.deepcopy.go @@ -22,8 +22,8 @@ package v1alpha1 import ( "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -161,6 +161,32 @@ func (in *ClusterApprovalRequestList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterResourceEnvelope) DeepCopyInto(out *ClusterResourceEnvelope) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterResourceEnvelope. +func (in *ClusterResourceEnvelope) DeepCopy() *ClusterResourceEnvelope { + if in == nil { + return nil + } + out := new(ClusterResourceEnvelope) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterResourceEnvelope) 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 *ClusterResourceOverride) DeepCopyInto(out *ClusterResourceOverride) { *out = *in @@ -596,6 +622,28 @@ func (in *ClusterUpdatingStatus) DeepCopy() *ClusterUpdatingStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnvelopeSpec) DeepCopyInto(out *EnvelopeSpec) { + *out = *in + if in.Manifests != nil { + in, out := &in.Manifests, &out.Manifests + *out = make(map[string]Manifest, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvelopeSpec. +func (in *EnvelopeSpec) DeepCopy() *EnvelopeSpec { + if in == nil { + return nil + } + out := new(EnvelopeSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JSONPatchOverride) DeepCopyInto(out *JSONPatchOverride) { *out = *in @@ -612,6 +660,22 @@ func (in *JSONPatchOverride) DeepCopy() *JSONPatchOverride { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Manifest) DeepCopyInto(out *Manifest) { + *out = *in + in.Data.DeepCopyInto(&out.Data) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Manifest. +func (in *Manifest) DeepCopy() *Manifest { + if in == nil { + return nil + } + out := new(Manifest) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OverridePolicy) DeepCopyInto(out *OverridePolicy) { *out = *in @@ -738,6 +802,32 @@ func (in *PlacementRef) DeepCopy() *PlacementRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceEnvelope) DeepCopyInto(out *ResourceEnvelope) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceEnvelope. +func (in *ResourceEnvelope) DeepCopy() *ResourceEnvelope { + if in == nil { + return nil + } + out := new(ResourceEnvelope) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ResourceEnvelope) 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 *ResourceOverride) DeepCopyInto(out *ResourceOverride) { *out = *in diff --git a/apis/placement/v1beta1/zz_generated.deepcopy.go b/apis/placement/v1beta1/zz_generated.deepcopy.go index 69f7db476..dec32e99d 100644 --- a/apis/placement/v1beta1/zz_generated.deepcopy.go +++ b/apis/placement/v1beta1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1beta1 import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" ) diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 85550ca19..27a862c43 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -22,7 +22,7 @@ package v1alpha1 import ( corev1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/config/crd/bases/placement.kubernetes-fleet.io_clusterresourceenvelopes.yaml b/config/crd/bases/placement.kubernetes-fleet.io_clusterresourceenvelopes.yaml new file mode 100644 index 000000000..0c53b05a3 --- /dev/null +++ b/config/crd/bases/placement.kubernetes-fleet.io_clusterresourceenvelopes.yaml @@ -0,0 +1,72 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.0 + name: clusterresourceenvelopes.placement.kubernetes-fleet.io +spec: + group: placement.kubernetes-fleet.io + names: + categories: + - fleet + - fleet-placement + kind: ClusterResourceEnvelope + listKind: ClusterResourceEnvelopeList + plural: clusterresourceenvelopes + singular: clusterresourceenvelope + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ClusterResourceEnvelope wraps cluster-scoped resources for placement. + 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: The desired state of ClusterResourceEnvelope. + properties: + manifests: + additionalProperties: + description: Manifest is a wrapped resource. + properties: + data: + description: The resource data. + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + required: + - data + type: object + description: |- + A map of wrapped manifests. + + Each manifest is uniquely identified by a string key, typically a filename that represents + the manifest. + maxProperties: 50 + minProperties: 1 + type: object + required: + - manifests + type: object + required: + - spec + type: object + served: true + storage: true diff --git a/config/crd/bases/placement.kubernetes-fleet.io_resourceenvelopes.yaml b/config/crd/bases/placement.kubernetes-fleet.io_resourceenvelopes.yaml new file mode 100644 index 000000000..b15627041 --- /dev/null +++ b/config/crd/bases/placement.kubernetes-fleet.io_resourceenvelopes.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.0 + name: resourceenvelopes.placement.kubernetes-fleet.io +spec: + group: placement.kubernetes-fleet.io + names: + categories: + - fleet + - fleet-placement + kind: ResourceEnvelope + listKind: ResourceEnvelopeList + plural: resourceenvelopes + singular: resourceenvelope + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + 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: The desired state of ResourceEnvelope. + properties: + manifests: + additionalProperties: + description: Manifest is a wrapped resource. + properties: + data: + description: The resource data. + type: object + x-kubernetes-embedded-resource: true + x-kubernetes-preserve-unknown-fields: true + required: + - data + type: object + description: |- + A map of wrapped manifests. + + Each manifest is uniquely identified by a string key, typically a filename that represents + the manifest. + maxProperties: 50 + minProperties: 1 + type: object + required: + - manifests + type: object + required: + - spec + type: object + served: true + storage: true diff --git a/examples/envelopes/clusterscoped.yaml b/examples/envelopes/clusterscoped.yaml new file mode 100644 index 000000000..558b86fc5 --- /dev/null +++ b/examples/envelopes/clusterscoped.yaml @@ -0,0 +1,36 @@ +apiVersion: placement.kubernetes-fleet.io/v1alpha1 +kind: ClusterResourceEnvelope +metadata: + name: example +spec: + manifests: + "webhook.yaml": + data: + apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingWebhookConfiguration + metadata: + name: guard + webhooks: + - name: guard.example.com + rules: + - operations: ["CREATE"] + apiGroups: ["*"] + apiVersions: ["*"] + resources: ["*"] + clientConfig: + service: + name: guard + namespace: ops + admissionReviewVersions: ["v1"] + sideEffects: None + timeoutSeconds: 10 + "clusterrole.yaml": + data: + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: pod-reader + rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] diff --git a/examples/envelopes/namespacescoped.yaml b/examples/envelopes/namespacescoped.yaml new file mode 100644 index 000000000..4642b8139 --- /dev/null +++ b/examples/envelopes/namespacescoped.yaml @@ -0,0 +1,33 @@ +apiVersion: placement.kubernetes-fleet.io/v1alpha1 +kind: ResourceEnvelope +metadata: + name: example +spec: + manifests: + "cm.yaml": + data: + apiVersion: v1 + kind: ConfigMap + metadata: + name: app + data: + foo: bar + "deploy.yaml": + data: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: app + spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: web + image: nginx diff --git a/test/apis/v1alpha1/zz_generated.deepcopy.go b/test/apis/v1alpha1/zz_generated.deepcopy.go index 143bdee7b..081bec913 100644 --- a/test/apis/v1alpha1/zz_generated.deepcopy.go +++ b/test/apis/v1alpha1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1alpha1 import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" )