Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add operationrequest for multi-namespace and update user controller. #3595

Merged
merged 14 commits into from Aug 21, 2023
27 changes: 14 additions & 13 deletions 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))
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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.
Expand Down
17 changes: 17 additions & 0 deletions 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
Expand All @@ -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"
85 changes: 85 additions & 0 deletions 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{})
}
111 changes: 111 additions & 0 deletions 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
}
13 changes: 5 additions & 8 deletions controllers/user/api/v1/user_types.go
Expand Up @@ -23,28 +23,25 @@ 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.
//
// +optional
//+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
Expand Down
3 changes: 3 additions & 0 deletions controllers/user/api/v1/webhook_suite_test.go
Expand Up @@ -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() {
Expand Down