diff --git a/incubator/hnc/api/v1alpha1/hnc_config.go b/incubator/hnc/api/v1alpha1/hnc_config.go new file mode 100644 index 000000000..be488ff4d --- /dev/null +++ b/incubator/hnc/api/v1alpha1/hnc_config.go @@ -0,0 +1,104 @@ +/* + +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" +) + +// Constants for types and well-known names. +const ( + HNCConfigSingleton = "config" +) + +// SynchronizationMode describes propogation mode of objects of the same kind. +// The only three modes currently supported are "propagate", "ignore", and "remove". +// See detailed definition below. An unsupported mode will be treated as "ignore". +type SynchronizationMode string + +const ( + // Propagate objects from ancestors to descendants and deletes obsolete descendants. + Propagate SynchronizationMode = "propagate" + + // Ignore the modification of this type. New or changed objects will not be propagated, + // and obsolete objects will not be deleted. The inheritedFrom label is not removed. + // Any unknown mode is treated as ignore. + Ignore SynchronizationMode = "ignore" + + // Remove all existing propagated copies. + Remove SynchronizationMode = "remove" +) + +// TypeSynchronizationSpec defines the desired synchronization state of a specific kind. +type TypeSynchronizationSpec struct { + // API version of the kind defined below. This is used to unambiguously identifies the kind. + APIVersion string `json:"apiVersion,omitempty"` + // Kind to be configured. + Kind string `json:"kind,omitempty"` + // Synchronization mode of the kind. + // +optional + Mode SynchronizationMode `json:"mode,omitempty"` +} + +// TypeSynchronizationStatus defines the observed synchronization state of a specific kind. +type TypeSynchronizationStatus struct { + // API version of the kind defined below. This is used to unambiguously identifies the kind. + APIVersion string `json:"apiVersion,omitempty"` + // Kind to be configured. + Kind string `json:"kind,omitempty"` + + // Tracks the number of original objects that are being propagated to descendant namespaces. + // +kubebuilder:validation:Minimum=0 + // +optional + NumPropagated *int32 `json:"numPropagated,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=hncconfigurations,scope=Cluster + +// HNCConfiguration is a cluster-wide configuration for HNC as a whole. See details in http://bit.ly/hnc-type-configuration +type HNCConfiguration struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec HNCConfigurationSpec `json:"spec,omitempty"` + Status HNCConfigurationStatus `json:"status,omitempty"` +} + +// HNCConfigurationSpec defines the desired state of HNC configuration. +type HNCConfigurationSpec struct { + // Types indicates the desired synchronization states of kinds, if any. + Types []TypeSynchronizationSpec `json:"types,omitempty"` +} + +// HNCConfigurationStatus defines the observed state of HNC configuration. +type HNCConfigurationStatus struct { + // Types indicates the observed synchronization states of kinds, if any. + Types []TypeSynchronizationStatus `json:"types,omitempty"` +} + +// +kubebuilder:object:root=true + +// HNCConfigurationList contains a list of HNCConfiguration. +type HNCConfigurationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []HNCConfiguration `json:"items"` +} + +func init() { + SchemeBuilder.Register(&HNCConfiguration{}, &HNCConfigurationList{}) +} diff --git a/incubator/hnc/api/v1alpha1/zz_generated.deepcopy.go b/incubator/hnc/api/v1alpha1/zz_generated.deepcopy.go index 19275c75c..80273d8af 100644 --- a/incubator/hnc/api/v1alpha1/zz_generated.deepcopy.go +++ b/incubator/hnc/api/v1alpha1/zz_generated.deepcopy.go @@ -58,6 +58,107 @@ func (in *Condition) DeepCopy() *Condition { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HNCConfiguration) DeepCopyInto(out *HNCConfiguration) { + *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 HNCConfiguration. +func (in *HNCConfiguration) DeepCopy() *HNCConfiguration { + if in == nil { + return nil + } + out := new(HNCConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HNCConfiguration) 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 *HNCConfigurationList) DeepCopyInto(out *HNCConfigurationList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]HNCConfiguration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HNCConfigurationList. +func (in *HNCConfigurationList) DeepCopy() *HNCConfigurationList { + if in == nil { + return nil + } + out := new(HNCConfigurationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *HNCConfigurationList) 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 *HNCConfigurationSpec) DeepCopyInto(out *HNCConfigurationSpec) { + *out = *in + if in.Types != nil { + in, out := &in.Types, &out.Types + *out = make([]TypeSynchronizationSpec, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HNCConfigurationSpec. +func (in *HNCConfigurationSpec) DeepCopy() *HNCConfigurationSpec { + if in == nil { + return nil + } + out := new(HNCConfigurationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HNCConfigurationStatus) DeepCopyInto(out *HNCConfigurationStatus) { + *out = *in + if in.Types != nil { + in, out := &in.Types, &out.Types + *out = make([]TypeSynchronizationStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HNCConfigurationStatus. +func (in *HNCConfigurationStatus) DeepCopy() *HNCConfigurationStatus { + if in == nil { + return nil + } + out := new(HNCConfigurationStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HierarchyConfiguration) DeepCopyInto(out *HierarchyConfiguration) { *out = *in @@ -163,3 +264,38 @@ func (in *HierarchyConfigurationStatus) DeepCopy() *HierarchyConfigurationStatus in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TypeSynchronizationSpec) DeepCopyInto(out *TypeSynchronizationSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypeSynchronizationSpec. +func (in *TypeSynchronizationSpec) DeepCopy() *TypeSynchronizationSpec { + if in == nil { + return nil + } + out := new(TypeSynchronizationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TypeSynchronizationStatus) DeepCopyInto(out *TypeSynchronizationStatus) { + *out = *in + if in.NumPropagated != nil { + in, out := &in.NumPropagated, &out.NumPropagated + *out = new(int32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypeSynchronizationStatus. +func (in *TypeSynchronizationStatus) DeepCopy() *TypeSynchronizationStatus { + if in == nil { + return nil + } + out := new(TypeSynchronizationStatus) + in.DeepCopyInto(out) + return out +} diff --git a/incubator/hnc/config/crd/bases/hnc.x-k8s.io_hierarchyconfigurations.yaml b/incubator/hnc/config/crd/bases/hnc.x-k8s.io_hierarchyconfigurations.yaml index d917363a5..7a3c6a8b3 100644 --- a/incubator/hnc/config/crd/bases/hnc.x-k8s.io_hierarchyconfigurations.yaml +++ b/incubator/hnc/config/crd/bases/hnc.x-k8s.io_hierarchyconfigurations.yaml @@ -3,6 +3,8 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.4 creationTimestamp: null name: hierarchyconfigurations.hnc.x-k8s.io spec: @@ -12,7 +14,7 @@ spec: listKind: HierarchyConfigurationList plural: hierarchyconfigurations singular: hierarchyconfiguration - scope: "" + scope: Namespaced validation: openAPIV3Schema: description: Hierarchy is the Schema for the hierarchies API diff --git a/incubator/hnc/config/crd/bases/hnc.x-k8s.io_hncconfigurations.yaml b/incubator/hnc/config/crd/bases/hnc.x-k8s.io_hncconfigurations.yaml new file mode 100644 index 000000000..1d404f74c --- /dev/null +++ b/incubator/hnc/config/crd/bases/hnc.x-k8s.io_hncconfigurations.yaml @@ -0,0 +1,95 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.4 + creationTimestamp: null + name: hncconfigurations.hnc.x-k8s.io +spec: + group: hnc.x-k8s.io + names: + kind: HNCConfiguration + listKind: HNCConfigurationList + plural: hncconfigurations + singular: hncconfiguration + scope: Cluster + validation: + openAPIV3Schema: + description: HNCConfiguration is a cluster-wide configuration for HNC as a whole. + See details in http://bit.ly/hnc-type-configuration + 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/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/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: HNCConfigurationSpec defines the desired state of HNC configuration. + properties: + types: + description: Types indicates the desired synchronization states of kinds, + if any. + items: + description: TypeSynchronizationSpec defines the desired synchronization + state of a specific kind. + properties: + apiVersion: + description: API version of the kind defined below. This is used + to unambiguously identifies the kind. + type: string + kind: + description: Kind to be configured. + type: string + mode: + description: Synchronization mode of the kind. + type: string + type: object + type: array + type: object + status: + description: HNCConfigurationStatus defines the observed state of HNC configuration. + properties: + types: + description: Types indicates the observed synchronization states of + kinds, if any. + items: + description: TypeSynchronizationStatus defines the observed synchronization + state of a specific kind. + properties: + apiVersion: + description: API version of the kind defined below. This is used + to unambiguously identifies the kind. + type: string + kind: + description: Kind to be configured. + type: string + numPropagated: + description: Tracks the number of original objects that are being + propagated to descendant namespaces. + format: int32 + minimum: 0 + type: integer + type: object + type: array + type: object + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/incubator/hnc/config/crd/kustomization.yaml b/incubator/hnc/config/crd/kustomization.yaml index 115c70d03..4a3014ad2 100644 --- a/incubator/hnc/config/crd/kustomization.yaml +++ b/incubator/hnc/config/crd/kustomization.yaml @@ -3,6 +3,7 @@ # It should be run by config/default resources: - bases/hnc.x-k8s.io_hierarchyconfigurations.yaml +- bases/hnc.x-k8s.io_hncconfigurations.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: diff --git a/incubator/hnc/config/samples/hnc_v1_config.yaml b/incubator/hnc/config/samples/hnc_v1_config.yaml new file mode 100644 index 000000000..7fedf7f8b --- /dev/null +++ b/incubator/hnc/config/samples/hnc_v1_config.yaml @@ -0,0 +1,12 @@ +apiVersion: hnc.x-k8s.io/v1alpha1 +kind: HNCConfiguration +metadata: + name: config +spec: + types: + - apiVersion: v1 + kind: Secret + mode: propagate + - apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + mode: ignore diff --git a/incubator/hnc/pkg/config/default_config.go b/incubator/hnc/pkg/config/default_config.go new file mode 100644 index 000000000..9026d6905 --- /dev/null +++ b/incubator/hnc/pkg/config/default_config.go @@ -0,0 +1,16 @@ +package config + +import ( + api "github.com/kubernetes-sigs/multi-tenancy/incubator/hnc/api/v1alpha1" +) + +// GetDefaultConfigSpec creates the default configuration for HNCConfiguration Spec. +// By default, HNC configuration should always propagate Roles and RoleBindings. +// See details in http://bit.ly/hnc-type-configuration +func GetDefaultConfigSpec() api.HNCConfigurationSpec { + return api.HNCConfigurationSpec{ + Types: []api.TypeSynchronizationSpec{ + {APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role", Mode: api.Propagate}, + {APIVersion: "rbac.authorization.k8s.io/v1", Kind: "RoleBinding", Mode: api.Propagate}}, + } +} diff --git a/incubator/hnc/pkg/controllers/config_controller.go b/incubator/hnc/pkg/controllers/config_controller.go new file mode 100644 index 000000000..820f018ca --- /dev/null +++ b/incubator/hnc/pkg/controllers/config_controller.go @@ -0,0 +1,111 @@ +package controllers + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + api "github.com/kubernetes-sigs/multi-tenancy/incubator/hnc/api/v1alpha1" + "github.com/kubernetes-sigs/multi-tenancy/incubator/hnc/pkg/config" +) + +// ConfigReconciler is responsible for determining the HNC configuration from the HNCConfiguration CR, +// as well as ensuring all objects are propagated correctly when the HNC configuration changes. +// It can also set the status of the HNCConfiguration CR. +type ConfigReconciler struct { + client.Client + Log logr.Logger +} + +// Reconcile sets up some basic variable and logs the Spec. +// TODO: Updates the comment above when adding more logic to the Reconcile method. +func (r *ConfigReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + // TODO: Surface the error more prominently and add a validating admission controller to prevent + // the problem in the first place. + if req.NamespacedName.Name != api.HNCConfigSingleton { + r.Log.Error(nil, "Singleton name is wrong. It should be 'config'.") + return ctrl.Result{}, nil + } + + ctx := context.Background() + inst, err := r.getSingleton(ctx) + if err != nil { + r.Log.Error(err, "Couldn't read singleton.") + return ctrl.Result{}, nil + } + + // TODO: Modify this and other reconcilers (e.g., hierarchy and object reconcilers) to + // achieve the reconciliation. + r.Log.Info("Reconciling cluster-wide HNC configuration.") + + // Write back to the apiserver. + if err := r.writeSingleton(ctx, inst); err != nil { + r.Log.Error(err, "Couldn't write singleton.") + return ctrl.Result{}, err + } + + r.logSpec(inst) + return ctrl.Result{}, nil +} + +// getSingleton returns the singleton if it exists, or creates a default one if it doesn't. +func (r *ConfigReconciler) getSingleton(ctx context.Context) (*api.HNCConfiguration, error) { + nnm := types.NamespacedName{Name: api.HNCConfigSingleton} + inst := &api.HNCConfiguration{} + if err := r.Get(ctx, nnm, inst); err != nil { + if !errors.IsNotFound(err) { + return nil, err + } + + // It doesn't exist - initialize it to a default value. + inst.Spec = config.GetDefaultConfigSpec() + inst.ObjectMeta.Name = api.HNCConfigSingleton + } + + return inst, nil +} + +// writeSingleton creates a singleton on the apiserver if it does not exist. +// Otherwise, it updates existing singleton on the apiserver. +// We will write the singleton to apiserver even it is not changed because we assume this +// reconciler is called very infrequently and is not performance critical. +func (r *ConfigReconciler) writeSingleton(ctx context.Context, inst *api.HNCConfiguration) error { + if inst.CreationTimestamp.IsZero() { + r.Log.Info("Creating a default singleton on apiserver") + if err := r.Create(ctx, inst); err != nil { + r.Log.Error(err, "while creating on apiserver") + return err + } + } else { + r.Log.Info("Updating the singleton on apiserver") + if err := r.Update(ctx, inst); err != nil { + r.Log.Error(err, "while updating apiserver") + return err + } + } + + return nil +} + +// logSpec logs current Spec of the CRD. +// TODO: This method is mainly for debuging and testing in the early development stage. Remove +// this method when the implementation is compeleted. +func (r *ConfigReconciler) logSpec(inst *api.HNCConfiguration) { + r.Log.Info("Record length of Types", "length", len(inst.Spec.Types)) + for _, t := range inst.Spec.Types { + r.Log.Info("spec:", "apiVersion: ", t.APIVersion) + r.Log.Info("spec:", "kind: ", t.Kind) + r.Log.Info("spec:", "mode: ", t.Mode) + } +} + +// SetupWithManager builds a controller with the reconciler. +func (r *ConfigReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&api.HNCConfiguration{}). + Complete(r) +} diff --git a/incubator/hnc/pkg/controllers/setup.go b/incubator/hnc/pkg/controllers/setup.go index 027bd9c02..082bf287a 100644 --- a/incubator/hnc/pkg/controllers/setup.go +++ b/incubator/hnc/pkg/controllers/setup.go @@ -55,5 +55,14 @@ func Create(mgr ctrl.Manager, f *forest.Forest, maxReconciles int) error { return fmt.Errorf("cannot create Hierarchy controller: %s", err.Error()) } + // Create the ConfigReconciler. + cr := &ConfigReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("HNCConfiguration"), + } + if err := cr.SetupWithManager(mgr); err != nil { + return fmt.Errorf("cannot create Config controller: %s", err.Error()) + } + return nil }