From df34ee9978013928d59d3e051e112177643b1172 Mon Sep 17 00:00:00 2001 From: rick Date: Mon, 30 Nov 2020 11:42:41 +0800 Subject: [PATCH] Adding approvable field to indicate if current user can approve a particular step Signed-off-by: rick --- pkg/kapis/devops/v1alpha2/devops.go | 67 +++++++++++++++++----------- pkg/simple/client/devops/pipeline.go | 41 ++++++++++++++++- tools/cmd/doc-gen/main.go | 2 +- 3 files changed, 82 insertions(+), 28 deletions(-) diff --git a/pkg/kapis/devops/v1alpha2/devops.go b/pkg/kapis/devops/v1alpha2/devops.go index 7f8eef92d..8b0cf0033 100644 --- a/pkg/kapis/devops/v1alpha2/devops.go +++ b/pkg/kapis/devops/v1alpha2/devops.go @@ -209,24 +209,49 @@ func (h *ProjectPipelineHandler) GetPipelineRunNodes(req *restful.Request, resp resp.WriteAsJson(res) } -func (h *ProjectPipelineHandler) hasSubmitPermission(req *restful.Request) (hasPermit bool, err error) { - var currentUserName string +func (h *ProjectPipelineHandler) approvableCheck(nodes []clientDevOps.NodesDetail, req *restful.Request) { + currentUserName, roleName := h.getCurrentUser(req) + // check if current user belong to the admin group, grant it if it's true + isAdmin := roleName == iamv1alpha2.PlatformAdmin + + for i, node := range nodes { + if node.State != clientDevOps.StatePaused { + continue + } + + for j, step := range node.Steps { + if step.State != clientDevOps.StatePaused || step.Input == nil { + continue + } + + nodes[i].Steps[j].Approvable = isAdmin || step.Input.Approvable(currentUserName) + } + } +} + +func (h *ProjectPipelineHandler) getCurrentUser(req *restful.Request) (username, roleName string) { var userInfo user.Info var ok bool + var err error + ctx := req.Request.Context() if userInfo, ok = request.UserFrom(ctx); ok { - // check if current user belong to the admin group, grant it if it's true var role *iamv1alpha2.GlobalRole - currentUserName = userInfo.GetName() - if role, err = h.abc.GetGlobalRoleOfUser(currentUserName); err == nil { - if role.Name == iamv1alpha2.PlatformAdmin { - hasPermit = true - return - } - } else { - return + username = userInfo.GetName() + if role, err = h.abc.GetGlobalRoleOfUser(username); err == nil { + roleName = role.Name } } + return +} + +func (h *ProjectPipelineHandler) hasSubmitPermission(req *restful.Request) (hasPermit bool, err error) { + currentUserName, roleName := h.getCurrentUser(req) + // check if current user belong to the admin group, grant it if it's true + if roleName == iamv1alpha2.PlatformAdmin { + hasPermit = true + return + } // step 2, check if current user if was addressed httpReq := &http.Request{ @@ -242,8 +267,7 @@ func (h *ProjectPipelineHandler) hasSubmitPermission(req *restful.Request) (hasP nodeId := req.PathParameter("node") stepId := req.PathParameter("step") - // find the expected submitter list which separated by common - var expectedSubmitter string + // check if current user can approve this input var res []clientDevOps.NodesDetail if res, err = h.devopsOperator.GetNodesDetail(projectName, pipelineName, runId, httpReq); err == nil { for _, node := range res { @@ -256,7 +280,7 @@ func (h *ProjectPipelineHandler) hasSubmitPermission(req *restful.Request) (hasP continue } - expectedSubmitter = fmt.Sprintf("%v", step.Input.Submitter) + hasPermit = step.Input.Approvable(currentUserName) break } break @@ -266,18 +290,6 @@ func (h *ProjectPipelineHandler) hasSubmitPermission(req *restful.Request) (hasP err = errors.New("cannot get the submitters of current pipeline run") return } - - // grant all users if there's no specific one - if expectedSubmitter == "" { - hasPermit = true - } else { - for _, submitter := range strings.Split(expectedSubmitter, ",") { - if strings.TrimSpace(submitter) == currentUserName { - hasPermit = true - break - } - } - } return } @@ -321,6 +333,8 @@ func (h *ProjectPipelineHandler) GetNodesDetail(req *restful.Request, resp *rest parseErr(err, resp) return } + h.approvableCheck(res, req) + resp.WriteAsJson(res) } @@ -548,6 +562,7 @@ func (h *ProjectPipelineHandler) GetBranchNodesDetail(req *restful.Request, resp parseErr(err, resp) return } + h.approvableCheck(res, req) resp.WriteAsJson(res) } diff --git a/pkg/simple/client/devops/pipeline.go b/pkg/simple/client/devops/pipeline.go index 800220d71..0eb78db04 100644 --- a/pkg/simple/client/devops/pipeline.go +++ b/pkg/simple/client/devops/pipeline.go @@ -17,9 +17,11 @@ limitations under the License. package devops import ( + "fmt" "io" "net/http" "net/url" + "strings" ) type PipelineList struct { @@ -979,6 +981,8 @@ type NodeSteps struct { StartTime string `json:"startTime,omitempty" description:"the time of starts"` State string `json:"state,omitempty" description:"run state. e.g. SKIPPED"` Type string `json:"type,omitempty" description:"type"` + // Approvable indicates if this step can be approved by current user + Approvable bool `json:"aprovable" description:"indicate if this step can be approved by current user"` } // CheckScriptCompile @@ -1075,6 +1079,11 @@ type NodesDetail struct { Steps []NodeSteps `json:"steps,omitempty" description:"steps"` } +const ( + // StatePaused indicates a node or a step was paused, for example it's waiting for an iput + StatePaused = "PAUSED" +) + type NodesStepsIndex struct { Id int `json:"id,omitempty" description:"id"` Steps []NodeSteps `json:"steps,omitempty" description:"steps"` @@ -1095,6 +1104,37 @@ type Input struct { Submitter interface{} `json:"submitter,omitempty" description:"check submitter"` } +// GetSubmitters returns the all submitters related to this input +func (i *Input) GetSubmitters() (submitters []string) { + if i.Submitter == nil { + return + } + + submitterArray := strings.Split(fmt.Sprintf("%v", i.Submitter), ",") + submitters = make([]string, len(submitterArray)) + for i, submitter := range submitterArray { + submitters[i] = strings.TrimSpace(submitter) + } + return +} + +// Approvable returns the result if the given identify (username or group name) can approve this input +func (i *Input) Approvable(identify string) (ok bool) { + submitters := i.GetSubmitters() + + // it means anyone can approve this if there's no specific one + if len(submitters) == 0 { + ok = true + } else { + for _, submitter := range submitters { + if submitter == identify { + ok = true + } + } + } + return +} + type HttpParameters struct { Method string `json:"method,omitempty"` Header http.Header `json:"header,omitempty"` @@ -1105,7 +1145,6 @@ type HttpParameters struct { } type PipelineOperator interface { - // Pipelinne operator interface GetPipeline(projectName, pipelineName string, httpParameters *HttpParameters) (*Pipeline, error) ListPipelines(httpParameters *HttpParameters) (*PipelineList, error) diff --git a/tools/cmd/doc-gen/main.go b/tools/cmd/doc-gen/main.go index a0b9c226a..52138cbcc 100644 --- a/tools/cmd/doc-gen/main.go +++ b/tools/cmd/doc-gen/main.go @@ -119,7 +119,7 @@ func generateSwaggerJson() []byte { urlruntime.Must(oauth.AddToContainer(container, nil, nil, nil, nil, nil)) urlruntime.Must(clusterkapisv1alpha1.AddToContainer(container, informerFactory.KubernetesSharedInformerFactory(), informerFactory.KubeSphereSharedInformerFactory(), "", "", "")) - urlruntime.Must(devopsv1alpha2.AddToContainer(container, informerFactory.KubeSphereSharedInformerFactory(), &fakedevops.Devops{}, nil, clientsets.KubeSphere(), fakes3.NewFakeS3(), "")) + urlruntime.Must(devopsv1alpha2.AddToContainer(container, informerFactory.KubeSphereSharedInformerFactory(), &fakedevops.Devops{}, nil, clientsets.KubeSphere(), fakes3.NewFakeS3(), "", am.NewReadOnlyOperator(informerFactory))) urlruntime.Must(devopsv1alpha3.AddToContainer(container, &fakedevops.Devops{}, clientsets.Kubernetes(), clientsets.KubeSphere(), informerFactory.KubeSphereSharedInformerFactory(), informerFactory.KubernetesSharedInformerFactory())) urlruntime.Must(iamv1alpha2.AddToContainer(container, im.NewOperator(clientsets.KubeSphere(), informerFactory, nil), am.NewReadOnlyOperator(informerFactory), group.New(informerFactory, clientsets.KubeSphere(), clientsets.Kubernetes()), authoptions.NewAuthenticateOptions())) urlruntime.Must(monitoringv1alpha3.AddToContainer(container, clientsets.Kubernetes(), nil, informerFactory, nil))