diff --git a/Makefile b/Makefile index ee549e6..5755d26 100644 --- a/Makefile +++ b/Makefile @@ -100,7 +100,7 @@ help: ## Display this help. manifests: SED_SEARCH_STRING := scope: Namespaced manifests: SED_REPLACE_STRING := scope: Cluster manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./vendor/github.com/loft-sh/api/v3/pkg/apis/storage/v1/;./controllers/..." output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./vendor/github.com/loft-sh/api/v3/pkg/apis/storage/v1/;./api/v1/;./controllers/..." output:crd:artifacts:config=config/crd/bases grep -l -RE "$(SED_SEARCH_STRING)" config/crd/bases \ | xargs $(SED) -i -E 's#$(SED_SEARCH_STRING)$$#$(SED_REPLACE_STRING)#g' diff --git a/PROJECT b/PROJECT index 2bbcfa4..0d4f4c2 100644 --- a/PROJECT +++ b/PROJECT @@ -21,4 +21,12 @@ resources: group: storage kind: VirtualClusterTemplate version: v1 +- api: + crdVersion: v1 + controller: true + domain: openloft.cn + group: storage + kind: ClusterDomain + path: github.com/openloft/openloft/api/v1 + version: v1 version: "3" diff --git a/README.md b/README.md index f9da143..cd16ac8 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,13 @@ kubectl apply -f https://raw.githubusercontent.com/openloft/openloft/main/deploy/manifests.yaml ``` -### 3. Create a vCluster Template +### 3. Create a Cluster Domain + +```bash +kubectl apply -f https://raw.githubusercontent.com/openloft/openloft/main/config/samples/storage_v1_clusterdomain.yaml +``` + +### 4. Create a vCluster Template There are two types of vCluster templates in sample, `isolated` and `default`. The `isolated` template will create a vCluster with a [resource quota](https://kubernetes.io/docs/concepts/policy/resource-quotas/). The `default` template will create a vCluster with no resource quota. @@ -41,20 +47,20 @@ The below command will create a `isolated` vCluster template. kubectl apply -f https://raw.githubusercontent.com/openloft/openloft/main/config/samples/isolated/storage_v1_virtualclustertemplate.yaml ``` -### 4. Create a vCluster Instance +### 5. Create a vCluster Instance ```bash kubectl apply -f https://raw.githubusercontent.com/openloft/openloft/main/config/samples/isolated/storage_v1_virtualclusterinstance.yaml ``` -By running the above command, a vCluster called `isolated-sample-openloft-cn` would be created in the namespace `vc-isolated-sample-openloft-cn`. And an ingress would be created with the name `ingress-isolated-sample-openloft-cn`. +By running the above command, a vCluster called `isolated-sample` would be created in the namespace `vc-isolated-sample`. -### 5. Retrieving the kube config from the vCluster secret +### 6. Retrieving the kube config from the vCluster secret -The secret is prefixed with `vc-` and ends with the vCluster name, so a vCluster instance called `isolated-sample.openloft.cn` would create a secret called `vc-isolated-sample-openloft-cn` in the namespace `vc-isolated-sample-openloft-cn`. You can retrieve the kube config after the vCluster has started via: +The secret is prefixed with `vc-` and ends with the vCluster name, so a vCluster instance called `isolated-sample` would create a secret called `vc-isolated-sample` in the namespace `vc-isolated-sample`. You can retrieve the kube config after the vCluster has started via: ``` -kubectl get secret vc-isolated-sample-openloft-cn -n vc-isolated-sample-openloft-cn --template={{.data.config}} | base64 -D > kubeconfig.yaml +kubectl get secret vc-isolated-sample -n vc-isolated-sample --template={{.data.config}} | base64 -D > kubeconfig.yaml ``` The secret will hold a kube config in this format: @@ -89,7 +95,7 @@ e.g. `/etc/hosts` on macOS/Linux: 127.0.0.1 isolated-sample.openloft.cn ``` -### 6. Accessing the vCluster +### 7. Accessing the vCluster You can access the vCluster via: diff --git a/api/v1/clusterdomain_types.go b/api/v1/clusterdomain_types.go new file mode 100644 index 0000000..41157d9 --- /dev/null +++ b/api/v1/clusterdomain_types.go @@ -0,0 +1,56 @@ +/* +Copyright 2023. + +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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ClusterDomainSpec defines the desired state of ClusterDomain +type ClusterDomainSpec struct { + Domain string `json:"domain,omitempty"` +} + +// ClusterDomainStatus defines the observed state of ClusterDomain +type ClusterDomainStatus struct { +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Cluster + +// ClusterDomain is the Schema for the clusterdomains API +type ClusterDomain struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ClusterDomainSpec `json:"spec,omitempty"` + Status ClusterDomainStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// ClusterDomainList contains a list of ClusterDomain +type ClusterDomainList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ClusterDomain `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ClusterDomain{}, &ClusterDomainList{}) +} diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index aa7fa0a..e079199 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -27,6 +27,95 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterDomain) DeepCopyInto(out *ClusterDomain) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterDomain. +func (in *ClusterDomain) DeepCopy() *ClusterDomain { + if in == nil { + return nil + } + out := new(ClusterDomain) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterDomain) 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 *ClusterDomainList) DeepCopyInto(out *ClusterDomainList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterDomain, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterDomainList. +func (in *ClusterDomainList) DeepCopy() *ClusterDomainList { + if in == nil { + return nil + } + out := new(ClusterDomainList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterDomainList) 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 *ClusterDomainSpec) DeepCopyInto(out *ClusterDomainSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterDomainSpec. +func (in *ClusterDomainSpec) DeepCopy() *ClusterDomainSpec { + if in == nil { + return nil + } + out := new(ClusterDomainSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterDomainStatus) DeepCopyInto(out *ClusterDomainStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterDomainStatus. +func (in *ClusterDomainStatus) DeepCopy() *ClusterDomainStatus { + if in == nil { + return nil + } + out := new(ClusterDomainStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CoreDNS) DeepCopyInto(out *CoreDNS) { *out = *in diff --git a/config/crd/bases/storage.openloft.cn_clusterdomains.yaml b/config/crd/bases/storage.openloft.cn_clusterdomains.yaml new file mode 100644 index 0000000..3d8279c --- /dev/null +++ b/config/crd/bases/storage.openloft.cn_clusterdomains.yaml @@ -0,0 +1,48 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: clusterdomains.storage.openloft.cn +spec: + group: storage.openloft.cn + names: + kind: ClusterDomain + listKind: ClusterDomainList + plural: clusterdomains + singular: clusterdomain + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: ClusterDomain is the Schema for the clusterdomains 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: ClusterDomainSpec defines the desired state of ClusterDomain + properties: + domain: + type: string + type: object + status: + description: ClusterDomainStatus defines the observed state of ClusterDomain + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/storage.openloft.cn_virtualclusters.yaml b/config/crd/bases/storage.openloft.cn_virtualclusters.yaml new file mode 100644 index 0000000..eba0c30 --- /dev/null +++ b/config/crd/bases/storage.openloft.cn_virtualclusters.yaml @@ -0,0 +1,1198 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: virtualclusters.storage.openloft.cn +spec: + group: storage.openloft.cn + names: + kind: VirtualCluster + listKind: VirtualClusterList + plural: virtualclusters + singular: virtualcluster + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: VirtualCluster is the Schema for the virtualclusters 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: VirtualClusterSpec defines the desired state of VirtualCluster + properties: + affinity: + description: Affinity is a group of affinity scheduling rules. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the + pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node matches + the corresponding matchExpressions; the node(s) with the + highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects (i.e. + is also a no-op). + properties: + preference: + description: A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string values. If + the operator is In or NotIn, the values + array must be non-empty. If the operator + is Exists or DoesNotExist, the values array + must be empty. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string values. If + the operator is In or NotIn, the values + array must be non-empty. If the operator + is Exists or DoesNotExist, the values array + must be empty. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to an update), the system may or may not try to + eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term matches + no objects. The requirements of them are ANDed. The + TopologySelectorTerm type implements a subset of the + NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string values. If + the operator is In or NotIn, the values + array must be non-empty. If the operator + is Exists or DoesNotExist, the values array + must be empty. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string values. If + the operator is In or NotIn, the values + array must be non-empty. If the operator + is Exists or DoesNotExist, the values array + must be empty. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate + this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to a pod label update), the system may or may + not try to eventually evict the pod from its node. When + there are multiple elements, the lists of nodes corresponding + to each podAffinityTerm are intersected, i.e. all terms + must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied to the + union of the namespaces selected by this field and + the ones listed in the namespaces field. null selector + and null or empty namespaces list means "this pod's + namespace". An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list of namespace + names that the term applies to. The term is applied + to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. null or + empty namespaces list and null namespaceSelector means + "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. + avoid putting this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the anti-affinity expressions specified + by this field, but it may choose a node that violates one + or more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the anti-affinity requirements + specified by this field cease to be met at some point during + pod execution (e.g. due to a pod label update), the system + may or may not try to eventually evict the pod from its + node. When there are multiple elements, the lists of nodes + corresponding to each podAffinityTerm are intersected, i.e. + all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied to the + union of the namespaces selected by this field and + the ones listed in the namespaces field. null selector + and null or empty namespaces list means "this pod's + namespace". An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list of namespace + names that the term applies to. The term is applied + to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. null or + empty namespaces list and null namespaceSelector means + "this pod's namespace". + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + annotations: + additionalProperties: + type: string + type: object + coredns: + description: CoreDNS defines the configuration for CoreDNS + properties: + enabled: + type: boolean + integrated: + type: boolean + plugin: + description: CoreDNSPluginConfig defines the configuration for + CoreDNS plugins + properties: + enabled: + type: boolean + type: object + podAnnotations: + additionalProperties: + type: string + type: object + podLabels: + additionalProperties: + type: string + type: object + replicas: + type: integer + resources: + description: ResourceRequirements describes the compute resource + requirements. + properties: + claims: + description: "Claims lists the names of resources, defined + in spec.resourceClaims, that are used by this container. + \n This is an alpha field and requires enabling the DynamicResourceAllocation + feature gate. \n This field is immutable. It can only be + set for containers." + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: Name must match the name of one entry in + pod.spec.resourceClaims of the Pod where this field + is used. It makes that resource available inside a + container. + type: string + required: + - name + type: object + type: array + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. Requests cannot exceed + Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + type: object + ingress: + description: Ingress defines the configuration for ingress + properties: + annotations: + additionalProperties: + type: string + type: object + apiVersion: + type: string + enabled: + type: boolean + host: + type: string + ingressClassName: + type: string + pathType: + description: PathType represents the type of path referred to + by a HTTPIngressPath. + type: string + tls: + items: + description: IngressTLS describes the transport layer security + associated with an ingress. + properties: + hosts: + description: hosts is a list of hosts included in the TLS + certificate. The values in this list must match the name/s + used in the tlsSecret. Defaults to the wildcard host setting + for the loadbalancer controller fulfilling this Ingress, + if left unspecified. + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: secretName is the name of the secret used to + terminate TLS traffic on port 443. Field is left optional + to allow TLS routing based on SNI hostname alone. If the + SNI host in a listener conflicts with the "Host" header + field used by an IngressRule, the SNI host is used for + termination and value of the "Host" header is used for + routing. + type: string + type: object + type: array + type: object + isolation: + description: Isolation defines the configuration for isolation + properties: + enabled: + type: boolean + namespace: + type: string + nodeProxyPermission: + properties: + enabled: + type: boolean + type: object + podSecurityStandard: + enum: + - privileged + - baseline + - restricted + type: string + resourceQuota: + properties: + enabled: + type: boolean + quota: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: ResourceList is a set of (resource name, quantity) + pairs. + type: object + scopeSelector: + description: A scope selector represents the AND of the selectors + represented by the scoped-resource selector requirements. + properties: + matchExpressions: + description: A list of scope selector requirements by + scope of the resources. + items: + description: A scoped-resource selector requirement + is a selector that contains values, a scope name, + and an operator that relates the scope name and values. + properties: + operator: + description: Represents a scope's relationship to + a set of values. Valid operators are In, NotIn, + Exists, DoesNotExist. + type: string + scopeName: + description: The name of the scope that the selector + applies to. + type: string + values: + description: An array of string values. If the operator + is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the + values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - operator + - scopeName + type: object + type: array + type: object + scopes: + items: + description: A ResourceQuotaScope defines a filter that + must match each object tracked by a quota + type: string + type: array + type: object + type: object + service: + type: object + sync: + description: Sync defines the configuration for sync + properties: + ingresses: + description: Ingresses defines the configuration for ingress + properties: + enabled: + type: boolean + type: object + nodes: + properties: + enabled: + description: Enabled controls whether the nodes are Fake or + Real + type: boolean + nodeSelector: + type: string + type: object + type: object + syncer: + properties: + env: + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be a + C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. If + a variable cannot be resolved, the reference in the input + string will be unchanged. Double $$ are reduced to a single + $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or its + key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its key + must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + extraArgs: + items: + type: string + type: array + type: object + type: object + status: + description: VirtualClusterStatus defines the observed state of VirtualCluster + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index e6320d9..d067cf9 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -4,4 +4,5 @@ resources: - bases/storage.loft.sh_virtualclusterinstances.yaml - bases/storage.loft.sh_virtualclustertemplates.yaml +- bases/storage.openloft.cn_clusterdomains.yaml #+kubebuilder:scaffold:crdkustomizeresource diff --git a/config/crd/patches/cainjection_in_clusterdomains.yaml b/config/crd/patches/cainjection_in_clusterdomains.yaml new file mode 100644 index 0000000..05fa393 --- /dev/null +++ b/config/crd/patches/cainjection_in_clusterdomains.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: clusterdomains.storage.openloft.cn diff --git a/config/crd/patches/webhook_in_clusterdomains.yaml b/config/crd/patches/webhook_in_clusterdomains.yaml new file mode 100644 index 0000000..bce1a17 --- /dev/null +++ b/config/crd/patches/webhook_in_clusterdomains.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: clusterdomains.storage.openloft.cn +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/clusterdomain_editor_role.yaml b/config/rbac/clusterdomain_editor_role.yaml new file mode 100644 index 0000000..6a2d6b9 --- /dev/null +++ b/config/rbac/clusterdomain_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit clusterdomains. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: clusterdomain-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: openloft + app.kubernetes.io/part-of: openloft + app.kubernetes.io/managed-by: kustomize + name: clusterdomain-editor-role +rules: +- apiGroups: + - storage.openloft.cn + resources: + - clusterdomains + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - storage.openloft.cn + resources: + - clusterdomains/status + verbs: + - get diff --git a/config/rbac/clusterdomain_viewer_role.yaml b/config/rbac/clusterdomain_viewer_role.yaml new file mode 100644 index 0000000..3eb592a --- /dev/null +++ b/config/rbac/clusterdomain_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view clusterdomains. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: clusterdomain-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: openloft + app.kubernetes.io/part-of: openloft + app.kubernetes.io/managed-by: kustomize + name: clusterdomain-viewer-role +rules: +- apiGroups: + - storage.openloft.cn + resources: + - clusterdomains + verbs: + - get + - list + - watch +- apiGroups: + - storage.openloft.cn + resources: + - clusterdomains/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index ac1a26c..c74ce47 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -92,6 +92,32 @@ rules: - get - patch - update +- apiGroups: + - storage.openloft.cn + resources: + - clusterdomains + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - storage.openloft.cn + resources: + - clusterdomains/finalizers + verbs: + - update +- apiGroups: + - storage.openloft.cn + resources: + - clusterdomains/status + verbs: + - get + - patch + - update - apiGroups: - storage.openloft.cn resources: diff --git a/config/samples/default/storage_v1_virtualclusterinstance.yaml b/config/samples/default/storage_v1_virtualclusterinstance.yaml index f52de05..86dd0c5 100644 --- a/config/samples/default/storage_v1_virtualclusterinstance.yaml +++ b/config/samples/default/storage_v1_virtualclusterinstance.yaml @@ -1,7 +1,7 @@ apiVersion: storage.loft.sh/v1 kind: VirtualClusterInstance metadata: - name: sample.openloft.cn + name: sample spec: templateRef: name: vcluster-template diff --git a/config/samples/isolated/storage_v1_virtualclusterinstance.yaml b/config/samples/isolated/storage_v1_virtualclusterinstance.yaml index 9f9dcdd..cbd392b 100644 --- a/config/samples/isolated/storage_v1_virtualclusterinstance.yaml +++ b/config/samples/isolated/storage_v1_virtualclusterinstance.yaml @@ -1,7 +1,7 @@ apiVersion: storage.loft.sh/v1 kind: VirtualClusterInstance metadata: - name: isolated-sample.openloft.cn + name: isolated-sample spec: templateRef: name: vcluster-isolated-template diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 38042f3..5dc341b 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -4,4 +4,5 @@ resources: - default/storage_v1_virtualclusterinstance.yaml - isolated/storage_v1_virtualclustertemplate.yaml - isolated/storage_v1_virtualclusterinstance.yaml +- storage_v1_clusterdomain.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/storage_v1_clusterdomain.yaml b/config/samples/storage_v1_clusterdomain.yaml new file mode 100644 index 0000000..19c23af --- /dev/null +++ b/config/samples/storage_v1_clusterdomain.yaml @@ -0,0 +1,12 @@ +apiVersion: storage.openloft.cn/v1 +kind: ClusterDomain +metadata: + labels: + app.kubernetes.io/name: clusterdomain + app.kubernetes.io/instance: clusterdomain-sample + app.kubernetes.io/part-of: openloft + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: openloft + name: clusterdomain-sample +spec: + domain: "openloft.cn" diff --git a/controllers/clusterdomain/controller.go b/controllers/clusterdomain/controller.go new file mode 100644 index 0000000..609cc66 --- /dev/null +++ b/controllers/clusterdomain/controller.go @@ -0,0 +1,54 @@ +/* +Copyright 2023. + +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 clusterdomain + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/go-logr/logr" + openloftv1 "github.com/openloft/openloft/api/v1" +) + +// ClusterDomainReconciler reconciles a ClusterDomain object +type ClusterDomainReconciler struct { + client.Client + Scheme *runtime.Scheme + Log logr.Logger + Recorder record.EventRecorder +} + +//+kubebuilder:rbac:groups=storage.openloft.cn,resources=clusterdomains,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=storage.openloft.cn,resources=clusterdomains/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=storage.openloft.cn,resources=clusterdomains/finalizers,verbs=update + +func (r *ClusterDomainReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Log.Info("Reconciling VirtualClusterTemplateReconciler", "req.NamespacedName", req.NamespacedName) + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ClusterDomainReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&openloftv1.ClusterDomain{}). + Complete(r) +} diff --git a/controllers/suite_test.go b/controllers/suite_test.go index e978106..c151e4c 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -29,6 +29,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + + openloftv1 "github.com/openloft/openloft/api/v1" //+kubebuilder:scaffold:imports ) @@ -60,6 +62,9 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) + err = openloftv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + //+kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) diff --git a/controllers/virtualclusterinstance/clusterdomain.go b/controllers/virtualclusterinstance/clusterdomain.go new file mode 100644 index 0000000..a466a2d --- /dev/null +++ b/controllers/virtualclusterinstance/clusterdomain.go @@ -0,0 +1,23 @@ +package virtualclusterinstance + +import ( + "context" + + openloftv1 "github.com/openloft/openloft/api/v1" +) + +func (r *Reconciler) getClusterDomain(ctx context.Context) *openloftv1.ClusterDomain { + clusterDomainList := &openloftv1.ClusterDomainList{} + err := r.List(ctx, clusterDomainList) + if err != nil { + r.Log.Error(err, "Failed to list ClusterDomains") + return nil + } + + if len(clusterDomainList.Items) == 0 { + r.Log.Error(err, "No ClusterDomain found") + return nil + } + + return &clusterDomainList.Items[0] +} diff --git a/controllers/virtualclusterinstance/virtualclustertemplate.go b/controllers/virtualclusterinstance/virtualclustertemplate.go index 0038649..601b3b9 100644 --- a/controllers/virtualclusterinstance/virtualclustertemplate.go +++ b/controllers/virtualclusterinstance/virtualclustertemplate.go @@ -36,6 +36,8 @@ func (r *Reconciler) getVirtualClusterSpec( return nil, err } + clusterDomain := r.getClusterDomain(ctx) + // https://www.vcluster.com/docs/using-vclusters/access#via-ingress pathType := networkingv1.PathTypeImplementationSpecific @@ -52,8 +54,9 @@ func (r *Reconciler) getVirtualClusterSpec( vcSpec.Ingress.Annotations = annotations vcSpec.Ingress.TLS = []networkingv1.IngressTLS{} - // XXX: HOST - vcSpec.Ingress.Host = vci.Name + if clusterDomain != nil && clusterDomain.Spec.Domain != "" { + vcSpec.Ingress.Host = fmt.Sprintf("%s.%s", normalizedName(vci), clusterDomain.Spec.Domain) + } if vcSpec.Ingress.Host == "" { host, err := r.discoverHostFromService(ctx, vci) if err != nil { diff --git a/deploy/manifests.yaml b/deploy/manifests.yaml index 85d2422..be71812 100644 --- a/deploy/manifests.yaml +++ b/deploy/manifests.yaml @@ -13,6 +13,50 @@ metadata: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: clusterdomains.storage.openloft.cn +spec: + group: storage.openloft.cn + names: + kind: ClusterDomain + listKind: ClusterDomainList + plural: clusterdomains + singular: clusterdomain + scope: Cluster + versions: + - name: v1 + schema: + openAPIV3Schema: + description: ClusterDomain is the Schema for the clusterdomains 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: ClusterDomainSpec defines the desired state of ClusterDomain + properties: + domain: + type: string + type: object + status: + description: ClusterDomainStatus defines the observed state of ClusterDomain + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.11.1 @@ -2122,6 +2166,32 @@ rules: - get - patch - update +- apiGroups: + - storage.openloft.cn + resources: + - clusterdomains + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - storage.openloft.cn + resources: + - clusterdomains/finalizers + verbs: + - update +- apiGroups: + - storage.openloft.cn + resources: + - clusterdomains/status + verbs: + - get + - patch + - update - apiGroups: - storage.openloft.cn resources: diff --git a/main.go b/main.go index 8b720eb..102991c 100644 --- a/main.go +++ b/main.go @@ -33,6 +33,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" openloftv1 "github.com/openloft/openloft/api/v1" + "github.com/openloft/openloft/controllers/clusterdomain" "github.com/openloft/openloft/controllers/virtualclusterinstance" "github.com/openloft/openloft/controllers/virtualclustertemplate" //+kubebuilder:scaffold:imports @@ -110,6 +111,15 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "VirtualClusterTemplate") os.Exit(1) } + if err = (&clusterdomain.ClusterDomainReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Log: ctrlLog.WithName("ClusterDomain"), + Recorder: mgr.GetEventRecorderFor("clusterdomain-controller"), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "ClusterDomain") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/testdata/mpijob/README.md b/testdata/mpijob/README.md index 2562ee2..6844da5 100644 --- a/testdata/mpijob/README.md +++ b/testdata/mpijob/README.md @@ -1,9 +1,9 @@ # MPI Operator ```bash -kubectl get secret vc-isolated-sample-openloft-cn -n vc-isolated-sample-openloft-cn --template={{.data.config}} | base64 -D > kubeconfig.yaml +kubectl get secret vc-isolated-sample -n vc-isolated-sample --template={{.data.config}} | base64 -D > kubeconfig.yaml kubectl --kubeconfig=kubeconfig.yaml apply -f https://raw.githubusercontent.com/kubeflow/mpi-operator/v0.4.0/deploy/v2beta1/mpi-operator.yaml # https://github.com/kubeflow/mpi-operator/blob/master/examples/v2beta1/tensorflow-benchmarks/tensorflow-benchmarks.yaml -# XXX: change image/command and resource limits +# XXX: image/command and resource limits have been modified for testing kubectl --kubeconfig=kubeconfig.yaml apply -f tensorflow-benchmarks.yaml ``` \ No newline at end of file