diff --git a/controllers/user/Makefile b/controllers/user/Makefile index 26a51b48df2..4ec917cf9df 100644 --- a/controllers/user/Makefile +++ b/controllers/user/Makefile @@ -1,8 +1,10 @@ # Image URL to use all building/pushing image targets -IMG ?= ghcr.io/labring/sealos-user-controller:latest +IMG ?= ghcr.io/labring/sealos-user-controller:latest +TARGETARCH ?= amd64 + # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. -ENVTEST_K8S_VERSION = 1.24.1 +ENVTEST_K8S_VERSION = 1.25.6 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) @@ -62,16 +64,18 @@ test: manifests generate fmt vet envtest ## Run tests. ##@ Build .PHONY: build -build:## Build manager binary. - CGO_ENABLED=0 go build -o bin/manager main.go +build: ## Build manager binary. + CGO_ENABLED=0 GOOS=linux go build -o bin/manager main.go .PHONY: run run: manifests generate fmt vet ## Run a controller from your host. go run ./main.go .PHONY: docker-build -docker-build: test ## Build docker image with the manager. - docker build -t ${IMG} . +docker-build: test build ## Build docker image with the manager. + mv bin/manager bin/controller-user-${TARGETARCH} + chmod +x bin/controller-user-${TARGETARCH} + docker build -t ${IMG} . --build-arg TARGETARCH=${TARGETARCH} .PHONY: docker-push docker-push: ## Push docker image with the manager. @@ -94,21 +98,18 @@ uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified .PHONY: deploy deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} - $(KUSTOMIZE) build -e SERVICE_NAME=webhook-service -e SERVICE_NAMESPACE=system | kubectl apply -f - + $(KUSTOMIZE) build config/default | kubectl apply -f - -.PHONY: deploy +.PHONY: pre-deploy pre-deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} $(KUSTOMIZE) build config/default > deploy/manifests/deploy.yaml + .PHONY: undeploy undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - -deploy-crd: manifests - kubectl apply -f config/crd/bases - - ##@ Build Dependencies ## Location to install dependencies to @@ -129,7 +130,7 @@ KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/k .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. $(KUSTOMIZE): $(LOCALBIN) - GOBIN=$(LOCALBIN) go install sigs.k8s.io/kustomize/kustomize/v4@latest + curl -s $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN) .PHONY: controller-gen controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. diff --git a/controllers/user/PROJECT b/controllers/user/PROJECT index ddf7fc39c44..8289cf92533 100644 --- a/controllers/user/PROJECT +++ b/controllers/user/PROJECT @@ -1,3 +1,7 @@ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html domain: sealos.io layout: - go.kubebuilder.io/v3 @@ -16,4 +20,17 @@ resources: defaulting: true validation: true webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: sealos.io + group: user + kind: Operationrequest + path: github.com/labring/sealos/controllers/user/api/v1 + version: v1 + webhooks: + defaulting: true + validation: true + webhookVersion: v1 version: "3" diff --git a/controllers/user/api/v1/operationrequest_types.go b/controllers/user/api/v1/operationrequest_types.go new file mode 100644 index 00000000000..481345252e1 --- /dev/null +++ b/controllers/user/api/v1/operationrequest_types.go @@ -0,0 +1,85 @@ +/* +Copyright 2022 labring. + +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" +) + +// OperationrequestSpec defines the desired state of Operationrequest +type OperationrequestSpec struct { + User string `json:"user,omitempty"` + // +kubebuilder:validation:Enum=Owner;Manager;Developer + Role RoleType `json:"role,omitempty"` + // +kubebuilder:validation:Enum=Grant;Update;Deprive + Action ActionType `json:"action,omitempty"` +} + +type ActionType string + +const ( + Grant ActionType = "Grant" + Update ActionType = "Update" + Deprive ActionType = "Deprive" +) + +// OperationrequestStatus defines the observed state of Operationrequest +type OperationrequestStatus struct { + // Phase is the recently observed lifecycle phase of operationrequest. + //+kubebuilder:default:=Pending + //+kubebuilder:validation:Enum=Pending;Processing;Completed;Failed + Phase RequestPhase `json:"phase,omitempty"` +} + +type RequestPhase string + +// These are the valid phases of node. +const ( + RequestPending RequestPhase = "Pending" + RequestProcessing RequestPhase = "Processing" + RequestCompleted RequestPhase = "Completed" + RequestFailed RequestPhase = "Failed" +) + +//+kubebuilder:printcolumn:name="Action",type="string",JSONPath=".spec.action" +//+kubebuilder:printcolumn:name="User",type="string",JSONPath=".spec.user" +//+kubebuilder:printcolumn:name="Role",type="string",JSONPath=".spec.role" +//+kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase" +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Operationrequest is the Schema for the operation requests API +type Operationrequest struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec OperationrequestSpec `json:"spec,omitempty"` + Status OperationrequestStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// OperationrequestList contains a list of Operationrequest +type OperationrequestList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Operationrequest `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Operationrequest{}, &OperationrequestList{}) +} diff --git a/controllers/user/api/v1/operationrequest_webhook.go b/controllers/user/api/v1/operationrequest_webhook.go new file mode 100644 index 00000000000..5e93a8c4f49 --- /dev/null +++ b/controllers/user/api/v1/operationrequest_webhook.go @@ -0,0 +1,111 @@ +/* +Copyright 2022 labring. + +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 ( + "context" + "errors" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" +) + +// log is for logging in this package. +var operationrequestlog = logf.Log.WithName("operationrequest-resource") + +func (r *Operationrequest) SetupWebhookWithManager(mgr ctrl.Manager) error { + m := &ReqMutator{Client: mgr.GetClient()} + v := &ReqValidator{Client: mgr.GetClient()} + return ctrl.NewWebhookManagedBy(mgr). + For(r). + WithDefaulter(m). + WithValidator(v). + Complete() +} + +// +kubebuilder:webhook:path=/mutate-user-sealos-io-v1-operationrequest,mutating=true,failurePolicy=fail,sideEffects=None,groups=user.sealos.io,resources=operationrequests,verbs=create;update,versions=v1,name=moperationrequest.kb.io,admissionReviewVersions=v1 +//+kubebuilder:object:generate=false + +type ReqMutator struct { + client.Client +} + +func (r ReqMutator) Default(_ context.Context, obj runtime.Object) error { + req, ok := obj.(*Operationrequest) + if !ok { + return errors.New("obj convert Operationrequest is error") + } + // mutate the request with an owner label + operationrequestlog.Info("mutate", "name", req.Name) + req.ObjectMeta = initAnnotationAndLabels(req.ObjectMeta) + req.Labels[UserLabelOwnerKey] = req.Spec.User + return nil +} + +//+kubebuilder:webhook:path=/validate-user-sealos-io-v1-operationrequest,mutating=false,failurePolicy=fail,sideEffects=None,groups=user.sealos.io,resources=operationrequests,verbs=create;update,versions=v1,name=voperationrequest.kb.io,admissionReviewVersions=v1 +//+kubebuilder:object:generate=false + +type ReqValidator struct { + client.Client +} + +func (r ReqValidator) ValidateCreate(ctx context.Context, obj runtime.Object) error { + req, ok := obj.(*Operationrequest) + if !ok { + return errors.New("obj convert Operationrequest is error") + } + + // todo check request, _ := admission.RequestFromContext(ctx), request.UserInfo.Username if legal + + // list all requests in the same namespace with a same owner + var reqList OperationrequestList + err := r.List(ctx, &reqList, client.InNamespace(req.Namespace), client.MatchingLabels{UserLabelOwnerKey: req.Spec.User}) + if client.IgnoreNotFound(err) != nil { + operationrequestlog.Error(err, "list operationrequest error") + return err + } + + for _, item := range reqList.Items { + if item.Status.Phase != RequestCompleted { + operationrequestlog.Info("there is a request not completed, can not create new request", "name", item.Name, "phase", item.Status.Phase) + return errors.New("there is a request not completed, can not create new request") + } + } + return nil +} + +func (r ReqValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) error { + // todo check request, _ := admission.RequestFromContext(ctx), request.UserInfo.Username if legal + oldReq, ok := oldObj.(*Operationrequest) + if !ok { + return errors.New("obj convert Operationrequest error") + } + newReq, ok := newObj.(*Operationrequest) + if !ok { + return errors.New("obj convert Operationrequest error") + } + if oldReq.Spec != newReq.Spec { + return errors.New("operation request spec do not support update") + } + return nil +} + +func (r ReqValidator) ValidateDelete(_ context.Context, _ runtime.Object) error { + return nil +} diff --git a/controllers/user/api/v1/user_types.go b/controllers/user/api/v1/user_types.go index ab703a903b3..076a6193f7e 100644 --- a/controllers/user/api/v1/user_types.go +++ b/controllers/user/api/v1/user_types.go @@ -23,15 +23,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. - // UserSpec defines the desired state of User type UserSpec struct { // expirationSeconds is the requested duration of validity of the issued // certificate. The certificate signer may issue a certificate with a different // validity duration so a client must check the delta between the notBefore and - // and notAfter fields in the issued certificate to determine the actual duration. + // notAfter fields in the issued certificate to determine the actual duration. // // The minimum valid value for expirationSeconds is 600, i.e. 10 minutes. // @@ -39,12 +36,12 @@ type UserSpec struct { //+kubebuilder:default:=7200 CSRExpirationSeconds int32 `json:"csrExpirationSeconds,omitempty"` } -type UserRoleType string +type RoleType string const ( - OwnerRoleType UserRoleType = "Owner" - ManagerRoleType UserRoleType = "Manager" - DeveloperRoleType UserRoleType = "Developer" + OwnerRoleType RoleType = "Owner" + ManagerRoleType RoleType = "Manager" + DeveloperRoleType RoleType = "Developer" ) type UserPhase string diff --git a/controllers/user/api/v1/webhook_suite_test.go b/controllers/user/api/v1/webhook_suite_test.go index d2e75faafaf..2e806a5612e 100644 --- a/controllers/user/api/v1/webhook_suite_test.go +++ b/controllers/user/api/v1/webhook_suite_test.go @@ -107,6 +107,9 @@ var _ = BeforeSuite(func() { err = (&User{}).SetupWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) + err = (&Operationrequest{}).SetupWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + //+kubebuilder:scaffold:webhook go func() { diff --git a/controllers/user/api/v1/zz_generated.deepcopy.go b/controllers/user/api/v1/zz_generated.deepcopy.go index ed521d6681a..c5ac0355fa5 100644 --- a/controllers/user/api/v1/zz_generated.deepcopy.go +++ b/controllers/user/api/v1/zz_generated.deepcopy.go @@ -42,6 +42,95 @@ 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 *Operationrequest) DeepCopyInto(out *Operationrequest) { + *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 Operationrequest. +func (in *Operationrequest) DeepCopy() *Operationrequest { + if in == nil { + return nil + } + out := new(Operationrequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Operationrequest) 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 *OperationrequestList) DeepCopyInto(out *OperationrequestList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Operationrequest, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperationrequestList. +func (in *OperationrequestList) DeepCopy() *OperationrequestList { + if in == nil { + return nil + } + out := new(OperationrequestList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OperationrequestList) 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 *OperationrequestSpec) DeepCopyInto(out *OperationrequestSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperationrequestSpec. +func (in *OperationrequestSpec) DeepCopy() *OperationrequestSpec { + if in == nil { + return nil + } + out := new(OperationrequestSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperationrequestStatus) DeepCopyInto(out *OperationrequestStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperationrequestStatus. +func (in *OperationrequestStatus) DeepCopy() *OperationrequestStatus { + if in == nil { + return nil + } + out := new(OperationrequestStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *User) DeepCopyInto(out *User) { *out = *in diff --git a/controllers/user/config/crd/bases/user.sealos.io_operationrequests.yaml b/controllers/user/config/crd/bases/user.sealos.io_operationrequests.yaml new file mode 100644 index 00000000000..4a30470f301 --- /dev/null +++ b/controllers/user/config/crd/bases/user.sealos.io_operationrequests.yaml @@ -0,0 +1,83 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: operationrequests.user.sealos.io +spec: + group: user.sealos.io + names: + kind: Operationrequest + listKind: OperationrequestList + plural: operationrequests + singular: operationrequest + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.action + name: Action + type: string + - jsonPath: .spec.user + name: User + type: string + - jsonPath: .spec.role + name: Role + type: string + - jsonPath: .status.phase + name: Phase + type: string + name: v1 + schema: + openAPIV3Schema: + description: Operationrequest is the Schema for the operation requests 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: OperationrequestSpec defines the desired state of Operationrequest + properties: + action: + enum: + - Grant + - Update + - Deprive + type: string + role: + enum: + - Owner + - Manager + - Developer + type: string + user: + type: string + type: object + status: + description: OperationrequestStatus defines the observed state of Operationrequest + properties: + phase: + default: Pending + description: Phase is the recently observed lifecycle phase of operationrequest. + enum: + - Pending + - Processing + - Completed + - Failed + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/controllers/user/config/crd/bases/user.sealos.io_users.yaml b/controllers/user/config/crd/bases/user.sealos.io_users.yaml index fb49be67013..561a92f20e8 100644 --- a/controllers/user/config/crd/bases/user.sealos.io_users.yaml +++ b/controllers/user/config/crd/bases/user.sealos.io_users.yaml @@ -50,7 +50,7 @@ spec: description: "expirationSeconds is the requested duration of validity of the issued certificate. The certificate signer may issue a certificate with a different validity duration so a client must check the delta - between the notBefore and and notAfter fields in the issued certificate + between the notBefore and notAfter fields in the issued certificate to determine the actual duration. \n The minimum valid value for expirationSeconds is 600, i.e. 10 minutes." format: int32 diff --git a/controllers/user/config/crd/kustomization.yaml b/controllers/user/config/crd/kustomization.yaml index 4357afb0f50..2ebf2fa2cda 100644 --- a/controllers/user/config/crd/kustomization.yaml +++ b/controllers/user/config/crd/kustomization.yaml @@ -3,6 +3,7 @@ # It should be run by config/default resources: - bases/user.sealos.io_users.yaml +- bases/user.sealos.io_operationrequests.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -14,6 +15,7 @@ patchesStrategicMerge: #- patches/webhook_in_usergroupuserbindings.yaml #- patches/webhook_in_usergroupnamespacebindings.yaml #- patches/webhook_in_usergroupbindings.yaml +#- patches/webhook_in_operationrequests.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -24,6 +26,7 @@ patchesStrategicMerge: #- patches/cainjection_in_usergroupuserbindings.yaml #- patches/cainjection_in_usergroupnamespacebindings.yaml #- patches/cainjection_in_usergroupbindings.yaml +#- patches/cainjection_in_operationrequests.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/controllers/user/config/crd/patches/cainjection_in_operationrequests.yaml b/controllers/user/config/crd/patches/cainjection_in_operationrequests.yaml new file mode 100644 index 00000000000..ac1796d9e07 --- /dev/null +++ b/controllers/user/config/crd/patches/cainjection_in_operationrequests.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: operationrequests.user.sealos.io diff --git a/controllers/user/config/crd/patches/webhook_in_operationrequests.yaml b/controllers/user/config/crd/patches/webhook_in_operationrequests.yaml new file mode 100644 index 00000000000..7af050f2e89 --- /dev/null +++ b/controllers/user/config/crd/patches/webhook_in_operationrequests.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: operationrequests.user.sealos.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/controllers/user/config/rbac/operationrequest_editor_role.yaml b/controllers/user/config/rbac/operationrequest_editor_role.yaml new file mode 100644 index 00000000000..bf567cfffcc --- /dev/null +++ b/controllers/user/config/rbac/operationrequest_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit operationrequests. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: operationrequest-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: user + app.kubernetes.io/part-of: user + app.kubernetes.io/managed-by: kustomize + name: operationrequest-editor-role +rules: +- apiGroups: + - user.sealos.io + resources: + - operationrequests + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - user.sealos.io + resources: + - operationrequests/status + verbs: + - get diff --git a/controllers/user/config/rbac/operationrequest_viewer_role.yaml b/controllers/user/config/rbac/operationrequest_viewer_role.yaml new file mode 100644 index 00000000000..8c1c4404e28 --- /dev/null +++ b/controllers/user/config/rbac/operationrequest_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view operationrequests. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: operationrequest-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: user + app.kubernetes.io/part-of: user + app.kubernetes.io/managed-by: kustomize + name: operationrequest-viewer-role +rules: +- apiGroups: + - user.sealos.io + resources: + - operationrequests + verbs: + - get + - list + - watch +- apiGroups: + - user.sealos.io + resources: + - operationrequests/status + verbs: + - get diff --git a/controllers/user/config/rbac/role.yaml b/controllers/user/config/rbac/role.yaml index 30b44cb2700..b10537867dc 100644 --- a/controllers/user/config/rbac/role.yaml +++ b/controllers/user/config/rbac/role.yaml @@ -11,3 +11,29 @@ rules: - '*' verbs: - '*' +- apiGroups: + - user.sealos.io + resources: + - operationrequests + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - user.sealos.io + resources: + - operationrequests/finalizers + verbs: + - update +- apiGroups: + - user.sealos.io + resources: + - operationrequests/status + verbs: + - get + - patch + - update diff --git a/controllers/user/config/samples/user_v1_operationrequest.yaml b/controllers/user/config/samples/user_v1_operationrequest.yaml new file mode 100644 index 00000000000..b3ca0a6f7ed --- /dev/null +++ b/controllers/user/config/samples/user_v1_operationrequest.yaml @@ -0,0 +1,19 @@ +apiVersion: user.sealos.io/v1 +kind: Operationrequest +metadata: + name: request-grant-a-owns-b + namespace: ns-bbbb0001 +spec: + user: aaaa0001 + role: Owner + action: Grant +--- +apiVersion: user.sealos.io/v1 +kind: Operationrequest +metadata: + name: request-deprive-a-owns-b + namespace: ns-bbbb0001 +spec: + user: aaaa0001 + role: Owner + action: Deprive \ No newline at end of file diff --git a/controllers/user/config/samples/user_v1_user.yaml b/controllers/user/config/samples/user_v1_user.yaml index 17387ed4553..c21d7cf7fe2 100644 --- a/controllers/user/config/samples/user_v1_user.yaml +++ b/controllers/user/config/samples/user_v1_user.yaml @@ -1,6 +1,17 @@ +# user apiVersion: user.sealos.io/v1 kind: User metadata: - name: f8699ded-58d3-432b-a9ff-56568b57a38d + annotations: + user.sealos.io/owner: aaaa0001 + name: aaaa0001 spec: csrExpirationSeconds: 1000000000 +--- +# group +apiVersion: user.sealos.io/v1 +kind: User +metadata: + annotations: + user.sealos.io/owner: aaaa0001 + name: bbbb0001 \ No newline at end of file diff --git a/controllers/user/config/samples/user_v1_usergroup.yaml b/controllers/user/config/samples/user_v1_usergroup.yaml deleted file mode 100644 index e6e7cc114c0..00000000000 --- a/controllers/user/config/samples/user_v1_usergroup.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: user.sealos.io/v1 -kind: UserGroup -metadata: - name: usergroup-sample diff --git a/controllers/user/config/samples/user_v1_usergroupbinding.yaml b/controllers/user/config/samples/user_v1_usergroupbinding.yaml deleted file mode 100644 index 36c0424b460..00000000000 --- a/controllers/user/config/samples/user_v1_usergroupbinding.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: user.sealos.io/v1 -kind: UserGroupBinding -metadata: - name: usergroupbinding-sample -spec: - # TODO(user): Add fields here diff --git a/controllers/user/config/webhook/manifests.yaml b/controllers/user/config/webhook/manifests.yaml index 83a0d8e3602..983c7a28c51 100644 --- a/controllers/user/config/webhook/manifests.yaml +++ b/controllers/user/config/webhook/manifests.yaml @@ -5,6 +5,26 @@ metadata: creationTimestamp: null name: mutating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-user-sealos-io-v1-operationrequest + failurePolicy: Fail + name: moperationrequest.kb.io + rules: + - apiGroups: + - user.sealos.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - operationrequests + sideEffects: None - admissionReviewVersions: - v1 clientConfig: @@ -32,6 +52,26 @@ metadata: creationTimestamp: null name: validating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-user-sealos-io-v1-operationrequest + failurePolicy: Fail + name: voperationrequest.kb.io + rules: + - apiGroups: + - user.sealos.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - operationrequests + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/controllers/user/controllers/helper/config/rbac.go b/controllers/user/controllers/helper/config/rbac.go index cc0f2d607c7..702be32d49c 100644 --- a/controllers/user/controllers/helper/config/rbac.go +++ b/controllers/user/controllers/helper/config/rbac.go @@ -21,19 +21,19 @@ import ( "os" userv1 "github.com/labring/sealos/controllers/user/api/v1" - rbacV1 "k8s.io/api/rbac/v1" + rbacv1 "k8s.io/api/rbac/v1" ) func GetDefaultNamespace() string { return os.Getenv("NAMESPACE_NAME") } -func GetUsersSubject(user string) []rbacV1.Subject { +func GetUsersSubject(user string) []rbacv1.Subject { defaultNamespace := GetDefaultNamespace() if defaultNamespace == "" { defaultNamespace = "default" } - return []rbacV1.Subject{ + return []rbacv1.Subject{ { Kind: "ServiceAccount", Name: user, @@ -46,10 +46,14 @@ func GetUsersNamespace(user string) string { return fmt.Sprintf("ns-%s", user) } -func GetUserRole(roleType userv1.UserRoleType) []rbacV1.PolicyRule { +func GetGroupRoleBindingName(user string) string { + return fmt.Sprintf("rb-%s", user) +} + +func GetUserRole(roleType userv1.RoleType) []rbacv1.PolicyRule { switch roleType { case userv1.OwnerRoleType: - return []rbacV1.PolicyRule{ + return []rbacv1.PolicyRule{ { APIGroups: []string{"*"}, Resources: []string{"*"}, @@ -57,7 +61,7 @@ func GetUserRole(roleType userv1.UserRoleType) []rbacV1.PolicyRule { }, } case userv1.ManagerRoleType: - return []rbacV1.PolicyRule{ + return []rbacv1.PolicyRule{ { APIGroups: []string{"*"}, Resources: []string{"*"}, @@ -65,7 +69,7 @@ func GetUserRole(roleType userv1.UserRoleType) []rbacV1.PolicyRule { }, } case userv1.DeveloperRoleType: - return []rbacV1.PolicyRule{ + return []rbacv1.PolicyRule{ { APIGroups: []string{"*"}, Resources: []string{"*"}, @@ -73,16 +77,6 @@ func GetUserRole(roleType userv1.UserRoleType) []rbacV1.PolicyRule { }, } default: - return []rbacV1.PolicyRule{} - } -} - -func GetNewUsersSubject(user string) []rbacV1.Subject { - return []rbacV1.Subject{ - { - Kind: "ServiceAccount", - Name: user, - Namespace: GetUsersNamespace(user), - }, + return []rbacv1.PolicyRule{} } } diff --git a/controllers/user/controllers/operationrequest_controller.go b/controllers/user/controllers/operationrequest_controller.go new file mode 100644 index 00000000000..0b4ea66dd36 --- /dev/null +++ b/controllers/user/controllers/operationrequest_controller.go @@ -0,0 +1,247 @@ +/* +Copyright 2022 labring. + +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 controllers + +import ( + "context" + "fmt" + "time" + + "github.com/go-logr/logr" + + util "github.com/labring/operator-sdk/controller" + userv1 "github.com/labring/sealos/controllers/user/api/v1" + "github.com/labring/sealos/controllers/user/controllers/helper/config" + + v1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + controller "sigs.k8s.io/controller-runtime/pkg/controller" +) + +// OperationReqRequeueDuration is the time interval to reconcile a OperationRequest if no error occurs +const OperationReqRequeueDuration time.Duration = 30 * time.Second + +// OperationReqReconciler reconciles a Operationrequest object +type OperationReqReconciler struct { + client.Client + + Logger logr.Logger + Scheme *runtime.Scheme + Recorder record.EventRecorder + + // expirationTime is the time duration of the request is expired + expirationTime time.Duration + // retentionTime is the time duration of the request is retained after it is isCompleted + retentionTime time.Duration +} + +// SetupWithManager sets up the controller with the Manager. +func (r *OperationReqReconciler) SetupWithManager(mgr ctrl.Manager, opts util.RateLimiterOptions, expTime time.Duration, retTime time.Duration) error { + const controllerName = "operationrequest_controller" + if r.Client == nil { + r.Client = mgr.GetClient() + } + r.Logger = ctrl.Log.WithName(controllerName) + if r.Recorder == nil { + r.Recorder = mgr.GetEventRecorderFor(controllerName) + } + r.Scheme = mgr.GetScheme() + r.expirationTime = expTime + r.retentionTime = retTime + r.Logger.V(1).Info("init reconcile operationrequest controller") + return ctrl.NewControllerManagedBy(mgr). + For(&userv1.Operationrequest{}). + WithOptions(controller.Options{ + MaxConcurrentReconciles: util.GetConcurrent(opts), + RateLimiter: util.GetRateLimiter(opts), + }). + Complete(r) +} + +// +kubebuilder:rbac:groups=user.sealos.io,resources=operationrequests,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=user.sealos.io,resources=operationrequests/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=user.sealos.io,resources=operationrequests/finalizers,verbs=update + +func (r *OperationReqReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Logger.V(1).Info("start create or delete rolebinding for operation requests") + operationRequest := &userv1.Operationrequest{} + if err := r.Get(ctx, req.NamespacedName, operationRequest); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + return r.reconcile(ctx, operationRequest) +} + +func (r *OperationReqReconciler) reconcile(ctx context.Context, request *userv1.Operationrequest) (ctrl.Result, error) { + r.Logger.V(1).Info("update reconcile controller operationRequest", getLog(request)...) + // count the time cost of handling the request + startTime := time.Now() + defer func() { + r.Logger.V(1).Info("complete request handling", getLog(request, "create time", request.CreationTimestamp, "handling cost time", time.Since(startTime))...) + }() + + // delete OperationRequest first if its status is isCompleted and exist for retention time + if r.isRetained(request) { + if err := r.deleteOperationRequest(ctx, request); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + // return early if its status is isCompleted and didn't exist for retention time + if r.isCompleted(request) { + return ctrl.Result{RequeueAfter: OperationReqRequeueDuration}, nil + } + // change OperationRequest status to failed if it is expired + if r.isExpired(request) { + if err := r.updateOperationRequestStatus(ctx, request, userv1.RequestFailed); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + + // update OperationRequest status to processing + err := r.updateOperationRequestStatus(ctx, request, userv1.RequestProcessing) + if err != nil { + return ctrl.Result{}, err + } + + // convert OperationRequest to RoleBinding + rolebinding := conventRequestToRolebinding(request) + r.Logger.V(1).Info("convert OperationRequest to RoleBinding", + "rolebinding.name", rolebinding.Name, + "rolebinding.namespace", rolebinding.Namespace, + "rolebinding.subjects", rolebinding.Subjects, + "rolebinding.roleRef", rolebinding.RoleRef, + ) + + // handle OperationRequest, create or delete rolebinding + switch request.Spec.Action { + case userv1.Grant: + r.Recorder.Eventf(request, v1.EventTypeNormal, "Grant", "Grant role %s to user %s", request.Spec.Role, request.Spec.User) + if _, err := ctrl.CreateOrUpdate(ctx, r.Client, rolebinding, func() error { return nil }); err != nil { + r.Recorder.Eventf(request, v1.EventTypeWarning, "Failed to create/update rolebinding", "Failed to create rolebinding %s/%s", rolebinding.Namespace, rolebinding.Name) + return ctrl.Result{}, err + } + case userv1.Deprive: + r.Recorder.Eventf(request, v1.EventTypeNormal, "Deprive", "Deprive role %s from user %s", request.Spec.Role, request.Spec.User) + if err := r.Delete(ctx, rolebinding); client.IgnoreNotFound(err) != nil { + r.Recorder.Eventf(request, v1.EventTypeWarning, "Failed to delete rolebinding", "Failed to delete rolebinding %s/%s", rolebinding.Namespace, rolebinding.Name) + return ctrl.Result{}, err + } + case userv1.Update: + r.Recorder.Eventf(request, v1.EventTypeNormal, "Update", "Update role %s to user %s", request.Spec.Role, request.Spec.User) + // todo update rolebinding, delete old rolebinding and create new rolebinding + default: + return ctrl.Result{}, fmt.Errorf("invalid action %s", request.Spec.Action) + } + + // update OperationRequest status to completed + err = r.updateOperationRequestStatus(ctx, request, userv1.RequestCompleted) + if err != nil { + return ctrl.Result{}, err + } + + r.Recorder.Eventf(request, v1.EventTypeNormal, "Completed", "Completed operation request %s/%s", request.Namespace, request.Name) + return ctrl.Result{RequeueAfter: OperationReqRequeueDuration}, nil +} + +// isRetained returns true if the request is isCompleted and exist for retention time +func (r *OperationReqReconciler) isRetained(request *userv1.Operationrequest) bool { + if request.Status.Phase == userv1.RequestCompleted && request.CreationTimestamp.Add(r.retentionTime).Before(time.Now()) { + r.Logger.Info("operation request is isCompleted and retained", "name", request.Name) + return true + } + return false +} + +// isCompleted returns true if the request is isCompleted +func (r *OperationReqReconciler) isCompleted(request *userv1.Operationrequest) bool { + return request.Status.Phase == userv1.RequestCompleted +} + +// isExpired returns true if the request is expired +func (r *OperationReqReconciler) isExpired(request *userv1.Operationrequest) bool { + if request.Status.Phase != userv1.RequestCompleted && request.CreationTimestamp.Add(r.expirationTime).Before(time.Now()) { + r.Logger.Info("operation request is expired", "name", request.Name) + return true + } + return false +} + +func (r *OperationReqReconciler) deleteOperationRequest(ctx context.Context, request *userv1.Operationrequest) error { + r.Logger.V(1).Info("deleting OperationRequest", "request", request) + if err := r.Delete(ctx, request); client.IgnoreNotFound(err) != nil { + r.Recorder.Eventf(request, v1.EventTypeWarning, "Failed to delete OperationRequest", "Failed to delete OperationRequest %s/%s", request.Namespace, request.Name) + r.Logger.Error(err, "Failed to delete OperationRequest", getLog(request)...) + return fmt.Errorf("failed to delete OperationRequest %s/%s: %w", request.Namespace, request.Name, err) + } + r.Logger.V(1).Info("delete OperationRequest success", getLog(request)...) + return nil +} + +func (r *OperationReqReconciler) updateOperationRequestStatus(ctx context.Context, request *userv1.Operationrequest, phase userv1.RequestPhase) error { + request.Status.Phase = phase + if err := r.Status().Update(ctx, request); err != nil { + r.Recorder.Eventf(request, v1.EventTypeWarning, "Failed to update OperationRequest status", "Failed to update OperationRequest status %s/%s", request.Namespace, request.Name) + r.Logger.V(1).Info("update OperationRequest status failed", getLog(request)...) + return err + } + r.Logger.V(1).Info("update OperationRequest status success", getLog(request)...) + return nil +} + +func conventRequestToRolebinding(request *userv1.Operationrequest) *rbacv1.RoleBinding { + return &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: config.GetGroupRoleBindingName(request.Spec.User), + Namespace: request.Namespace, + Annotations: map[string]string{ + userAnnotationOwnerKey: request.Spec.User, + }, + Labels: map[string]string{ + userLabelOwnerKey: request.Spec.User, + }, + }, + Subjects: []rbacv1.Subject{ + { + Kind: rbacv1.ServiceAccountKind, + Name: request.Spec.User, + Namespace: config.GetUsersNamespace(request.Spec.User), + }, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "Role", + Name: string(request.Spec.Role), + APIGroup: rbacv1.GroupName, + }, + } +} + +func getLog(request *userv1.Operationrequest, kv ...interface{}) []interface{} { + return append([]interface{}{ + "request.name", request.Name, + "request.namespace", request.Namespace, + "request.user", request.Spec.User, + "request.role", request.Spec.Role, + "request.action", request.Spec.Action, + "request.phase", request.Status.Phase, + }, kv...) +} diff --git a/controllers/user/controllers/suite_test.go b/controllers/user/controllers/suite_test.go index 5aa6027629f..8678577f5b9 100644 --- a/controllers/user/controllers/suite_test.go +++ b/controllers/user/controllers/suite_test.go @@ -31,9 +31,10 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - v1 "github.com/labring/sealos/controllers/user/api/v1" "k8s.io/client-go/tools/clientcmd" + v1 "github.com/labring/sealos/controllers/user/api/v1" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" //+kubebuilder:scaffold:imports @@ -71,6 +72,9 @@ var _ = BeforeSuite(func() { err = csrv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = v1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + //+kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) diff --git a/controllers/user/controllers/user_controller.go b/controllers/user/controllers/user_controller.go index 0153fdcd1d0..43523c10d39 100644 --- a/controllers/user/controllers/user_controller.go +++ b/controllers/user/controllers/user_controller.go @@ -281,7 +281,7 @@ func (r *UserReconciler) syncRole(ctx context.Context, user *userv1.User) contex return ctx } -func (r *UserReconciler) createRole(ctx context.Context, condition *userv1.Condition, user *userv1.User, roleType userv1.UserRoleType) { +func (r *UserReconciler) createRole(ctx context.Context, condition *userv1.Condition, user *userv1.User, roleType userv1.RoleType) { if err := retry.RetryOnConflict(retry.DefaultRetry, func() error { var change controllerutil.OperationResult var err error @@ -341,7 +341,7 @@ func (r *UserReconciler) syncRoleBinding(ctx context.Context, user *userv1.User) Kind: "Role", Name: string(userv1.OwnerRoleType), } - roleBinding.Subjects = config.GetNewUsersSubject(user.Name) + roleBinding.Subjects = config.GetUsersSubject(user.Name) return controllerutil.SetControllerReference(user, roleBinding, r.Scheme) }); err != nil { return fmt.Errorf("unable to create namespace role binding by User: %w", err) diff --git a/controllers/user/deploy/manifests/deploy.yaml.tmpl b/controllers/user/deploy/manifests/deploy.yaml.tmpl index ea1a0e72521..b072fd336ad 100644 --- a/controllers/user/deploy/manifests/deploy.yaml.tmpl +++ b/controllers/user/deploy/manifests/deploy.yaml.tmpl @@ -7,6 +7,89 @@ metadata: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: operationrequests.user.sealos.io +spec: + group: user.sealos.io + names: + kind: Operationrequest + listKind: OperationrequestList + plural: operationrequests + singular: operationrequest + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.action + name: Action + type: string + - jsonPath: .spec.user + name: User + type: string + - jsonPath: .spec.role + name: Role + type: string + - jsonPath: .status.phase + name: Phase + type: string + name: v1 + schema: + openAPIV3Schema: + description: Operationrequest is the Schema for the operation requests 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: OperationrequestSpec defines the desired state of Operationrequest + properties: + action: + enum: + - Grant + - Update + - Deprive + type: string + role: + enum: + - Owner + - Manager + - Developer + type: string + user: + type: string + type: object + status: + description: OperationrequestStatus defines the observed state of Operationrequest + properties: + phase: + default: Pending + description: Phase is the recently observed lifecycle phase of operationrequest. + enum: + - Pending + - Processing + - Completed + - Failed + type: string + 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.10.0 @@ -56,7 +139,7 @@ spec: description: "expirationSeconds is the requested duration of validity of the issued certificate. The certificate signer may issue a certificate with a different validity duration so a client must check the delta - between the notBefore and and notAfter fields in the issued certificate + between the notBefore and notAfter fields in the issued certificate to determine the actual duration. \n The minimum valid value for expirationSeconds is 600, i.e. 10 minutes." format: int32 @@ -178,6 +261,32 @@ rules: - '*' verbs: - '*' +- apiGroups: + - user.sealos.io + resources: + - operationrequests + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - user.sealos.io + resources: + - operationrequests/finalizers + verbs: + - update +- apiGroups: + - user.sealos.io + resources: + - operationrequests/status + verbs: + - get + - patch + - update --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -440,6 +549,26 @@ metadata: cert-manager.io/inject-ca-from: user-system/user-serving-cert name: user-mutating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: user-webhook-service + namespace: user-system + path: /mutate-user-sealos-io-v1-operationrequest + failurePolicy: Fail + name: moperationrequest.kb.io + rules: + - apiGroups: + - user.sealos.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - operationrequests + sideEffects: None - admissionReviewVersions: - v1 clientConfig: @@ -468,6 +597,26 @@ metadata: cert-manager.io/inject-ca-from: user-system/user-serving-cert name: user-validating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: user-webhook-service + namespace: user-system + path: /validate-user-sealos-io-v1-operationrequest + failurePolicy: Fail + name: voperationrequest.kb.io + rules: + - apiGroups: + - user.sealos.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - operationrequests + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/controllers/user/go.sum b/controllers/user/go.sum index bea6d791531..b74252c7871 100644 --- a/controllers/user/go.sum +++ b/controllers/user/go.sum @@ -249,7 +249,6 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -552,7 +551,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= diff --git a/controllers/user/main.go b/controllers/user/main.go index d2713796e4e..e3ffa756f20 100644 --- a/controllers/user/main.go +++ b/controllers/user/main.go @@ -54,13 +54,15 @@ func init() { func main() { var ( - metricsAddr string - enableLeaderElection bool - probeAddr string - rateLimiterOptions utilcontroller.RateLimiterOptions - syncPeriod time.Duration - minRequeueDuration time.Duration - maxRequeueDuration time.Duration + metricsAddr string + enableLeaderElection bool + probeAddr string + rateLimiterOptions utilcontroller.RateLimiterOptions + syncPeriod time.Duration + minRequeueDuration time.Duration + maxRequeueDuration time.Duration + operationReqExpirationTime time.Duration + operationReqRetentionTime time.Duration ) flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") @@ -70,6 +72,8 @@ func main() { flag.DurationVar(&syncPeriod, "sync-period", time.Hour*24*30, "SyncPeriod determines the minimum frequency at which watched resources are reconciled.") flag.DurationVar(&minRequeueDuration, "min-requeue-duration", time.Hour*24, "The minimum duration between requeue options of a resource.") flag.DurationVar(&maxRequeueDuration, "max-requeue-duration", time.Hour*24*2, "The maximum duration between requeue options of a resource.") + flag.DurationVar(&operationReqExpirationTime, "operation-req-expiration-time", time.Minute*3, "Sets the expiration time duration for an operation request. By default, the duration is set to 3 minutes.") + flag.DurationVar(&operationReqRetentionTime, "operation-req-retention-time", time.Minute*3, "Sets the retention time duration for an operation request. By default, the duration is set to 3 minutes.") rateLimiterOptions.BindFlags(flag.CommandLine) opts := zap.Options{ Development: true, @@ -117,6 +121,14 @@ func main() { } } + if err = (&controllers.OperationReqReconciler{}).SetupWithManager(mgr, rateLimiterOptions, operationReqExpirationTime, operationReqRetentionTime); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Operationrequest") + os.Exit(1) + } + if err = (&userv1.Operationrequest{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Operationrequest") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {