未验证 提交 0451f153 编写于 作者: Z zryfish 提交者: GitHub

Merge pull request #362 from runzexia/devops2

add devops project & members & pipeline api
......@@ -17,6 +17,14 @@
revision = "1a8911d1ed007260465c3bfbbc785ac6915a0bb8"
version = "v0.4.12"
[[projects]]
digest = "1:d043e5ae59276188d876090c261c311e146792c0908e08e67e766b1020a72d00"
name = "github.com/PuerkitoBio/goquery"
packages = ["."]
pruneopts = "NUT"
revision = "2d2796f41742ece03e8086188fa4db16a3a0b458"
version = "v1.5.0"
[[projects]]
digest = "1:0a111edd8693fd977f42a0c4f199a0efb13c20aec9da99ad8830c7bb6a87e8d6"
name = "github.com/PuerkitoBio/purell"
......@@ -52,6 +60,14 @@
pruneopts = "NUT"
revision = "8b13a72661dae6e9e5dea04f344f0dc95ea29547"
[[projects]]
digest = "1:fc86904a62ac4bfff8cfbe94f42231ce3d8cea8fe2506d5293eaef468f8eaecf"
name = "github.com/andybalholm/cascadia"
packages = ["."]
pruneopts = "NUT"
revision = "901648c87902174f774fac311d7f176f8647bdaa"
version = "v1.0.0"
[[projects]]
digest = "1:680b63a131506e668818d630d3ca36123ff290afa0afc9f4be21940adca3f27d"
name = "github.com/appscode/jsonpatch"
......@@ -68,6 +84,14 @@
revision = "ccb8e960c48f04d6935e72476ae4a51028f9e22f"
version = "v9"
[[projects]]
digest = "1:15e3271f463f2f40d98bf426aabb86941fc66b10272ccfdfebe548683e37acb1"
name = "github.com/beevik/etree"
packages = ["."]
pruneopts = "NUT"
revision = "8aee6516be3b1163bb6450c35c50e4969e3a3aa8"
version = "v1.1.0"
[[projects]]
branch = "master"
digest = "1:707ebe952a8b3d00b343c01536c79c73771d100f63ec6babeaed5c79e2b8a8dd"
......@@ -562,22 +586,6 @@
pruneopts = "NUT"
revision = "3eca13d6893afd7ecabe15f4445f5d2872a1b012"
[[projects]]
digest = "1:2ddfc1382a659966038282873c9e33e7694fa503130d445e97c4fdc3b8c5db66"
name = "github.com/jinzhu/gorm"
packages = ["."]
pruneopts = "NUT"
revision = "472c70caa40267cb89fd8facb07fe6454b578626"
version = "v1.9.2"
[[projects]]
branch = "master"
digest = "1:802f75230c29108e787d40679f9bf5da1a5673eaf5c10eb89afd993e18972909"
name = "github.com/jinzhu/inflection"
packages = ["."]
pruneopts = "NUT"
revision = "04140366298a54a039076d798123ffa108fff46c"
[[projects]]
digest = "1:da62aa6632d04e080b8a8b85a59ed9ed1550842a0099a55f3ae3a20d02a3745a"
name = "github.com/joho/godotenv"
......@@ -2010,7 +2018,9 @@
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/PuerkitoBio/goquery",
"github.com/asaskevich/govalidator",
"github.com/beevik/etree",
"github.com/dgrijalva/jwt-go",
"github.com/docker/docker/api/types",
"github.com/docker/docker/client",
......@@ -2026,7 +2036,6 @@
"github.com/golang/example/stringutil",
"github.com/golang/glog",
"github.com/google/uuid",
"github.com/jinzhu/gorm",
"github.com/json-iterator/go",
"github.com/kiali/kiali/config",
"github.com/kiali/kiali/handlers",
......@@ -2059,7 +2068,6 @@
"gopkg.in/src-d/go-git.v4/storage/memory",
"gopkg.in/yaml.v2",
"k8s.io/api/apps/v1",
"k8s.io/api/apps/v1beta2",
"k8s.io/api/batch/v1",
"k8s.io/api/batch/v1beta1",
"k8s.io/api/core/v1",
......
/*
Copyright 2019 The KubeSphere Authors.
Copyright 2019 The KubeSphere Authors.
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
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
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.
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 install
import (
......@@ -28,6 +29,6 @@ func init() {
Install(runtime.Container)
}
func Install(c *restful.Container) {
urlruntime.Must(devopsv1alpha2.AddToContainer(c))
func Install(container *restful.Container) {
urlruntime.Must(devopsv1alpha2.AddToContainer(container))
}
......@@ -15,6 +15,7 @@
limitations under the License.
*/
package v1alpha2
import (
......@@ -24,12 +25,14 @@ import (
devopsapi "kubesphere.io/kubesphere/pkg/apiserver/devops"
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/models/devops"
"kubesphere.io/kubesphere/pkg/params"
"net/http"
)
const (
GroupName = "devops.kubesphere.io"
RespMessage = "ok"
GroupName = "devops.kubesphere.io"
RespOK = "ok"
)
var GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha2"}
......@@ -40,9 +43,187 @@ var (
)
func addWebService(c *restful.Container) error {
webservice := runtime.NewWebService(GroupVersion)
tags := []string{"devops"}
tags := []string{"DevOps"}
webservice.Route(webservice.GET("/devops/{devops}").
To(devopsapi.GetDevOpsProjectHandler).
Doc("get devops project").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Returns(http.StatusOK, RespOK, devops.DevOpsProject{}).
Writes(devops.DevOpsProject{}))
webservice.Route(webservice.PATCH("/devops/{devops}").
To(devopsapi.UpdateProjectHandler).
Doc("get devops project").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Returns(http.StatusOK, RespOK, devops.DevOpsProject{}).
Writes(devops.DevOpsProject{}))
webservice.Route(webservice.GET("/devops/{devops}/defaultroles").
To(devopsapi.GetDevOpsProjectDefaultRoles).
Doc("get devops project defaultroles").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Returns(http.StatusOK, RespOK, []devops.Role{}).
Writes([]devops.Role{}))
webservice.Route(webservice.GET("/devops/{devops}/members").
To(devopsapi.GetDevOpsProjectMembersHandler).
Doc("get devops project members").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Param(webservice.QueryParameter(params.PagingParam, "page").
Required(false).
DataFormat("limit=%d,page=%d").
DefaultValue("limit=10,page=1")).
Param(webservice.QueryParameter(params.ConditionsParam, "query conditions").
Required(false).
DataFormat("key=%s,key~%s")).
Returns(http.StatusOK, RespOK, []devops.DevOpsProjectMembership{}).
Writes([]devops.DevOpsProjectMembership{}))
webservice.Route(webservice.GET("/devops/{devops}/members/{members}").
To(devopsapi.GetDevOpsProjectMemberHandler).
Doc("get devops project member").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Param(webservice.PathParameter("members", "member's username")).
Returns(http.StatusOK, RespOK, devops.DevOpsProjectMembership{}).
Writes(devops.DevOpsProjectMembership{}))
webservice.Route(webservice.POST("/devops/{devops}/members").
To(devopsapi.AddDevOpsProjectMemberHandler).
Doc("add devops project members").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Returns(http.StatusOK, RespOK, devops.DevOpsProjectMembership{}).
Writes(devops.DevOpsProjectMembership{}))
webservice.Route(webservice.PATCH("/devops/{devops}/members/{members}").
To(devopsapi.UpdateDevOpsProjectMemberHandler).
Doc("update devops project members").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Param(webservice.PathParameter("members", "member's username")).
Reads(devops.DevOpsProjectMembership{}).
Writes(devops.DevOpsProjectMembership{}))
webservice.Route(webservice.DELETE("/devops/{devops}/members/{members}").
To(devopsapi.DeleteDevOpsProjectMemberHandler).
Doc("delete devops project members").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Param(webservice.PathParameter("members", "member's username")).
Writes(devops.DevOpsProjectMembership{}))
webservice.Route(webservice.POST("/devops/{devops}/pipelines").
To(devopsapi.CreateDevOpsProjectPipelineHandler).
Doc("add devops project pipeline").
Param(webservice.PathParameter("devops", "devops project's Id")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(http.StatusOK, RespOK, devops.ProjectPipeline{}).
Writes(devops.ProjectPipeline{}).
Reads(devops.ProjectPipeline{}))
webservice.Route(webservice.PUT("/devops/{devops}/pipelines/{pipelines}").
To(devopsapi.UpdateDevOpsProjectPipelineHandler).
Doc("update devops project pipeline").
Param(webservice.PathParameter("devops", "devops project's Id")).
Param(webservice.PathParameter("pipelines", "pipeline name")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(http.StatusOK, RespOK, devops.ProjectPipeline{}).
Writes(devops.ProjectPipeline{}).
Reads(devops.ProjectPipeline{}))
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipelines}/config").
To(devopsapi.GetDevOpsProjectPipelineHandler).
Doc("get devops project pipeline config").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Param(webservice.PathParameter("pipelines", "pipeline name")).
Returns(http.StatusOK, RespOK, devops.ProjectPipeline{}).
Writes(devops.ProjectPipeline{}))
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipelines}/sonarStatus").
To(devopsapi.GetPipelineSonarStatusHandler).
Doc("get devops project pipeline sonarStatus").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Param(webservice.PathParameter("pipelines", "pipeline name")).
Returns(http.StatusOK, RespOK, []devops.SonarStatus{}).
Writes([]devops.SonarStatus{}))
webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipelines}/branches/{branches}/sonarStatus").
To(devopsapi.GetMultiBranchesPipelineSonarStatusHandler).
Doc("get devops project pipeline sonarStatus").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Param(webservice.PathParameter("pipelines", "pipeline name")).
Param(webservice.PathParameter("branches", "branch name")).
Returns(http.StatusOK, RespOK, []devops.SonarStatus{}).
Writes([]devops.SonarStatus{}))
webservice.Route(webservice.DELETE("/devops/{devops}/pipelines/{pipelines}").
To(devopsapi.DeleteDevOpsProjectPipelineHandler).
Doc("delete devops project pipeline").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Param(webservice.PathParameter("pipelines", "pipeline name")))
webservice.Route(webservice.PUT("/devops/{devops}/pipelines").
To(devopsapi.CreateDevOpsProjectPipelineHandler).
Doc("update devops project pipeline").
Param(webservice.PathParameter("devops", "devops project's Id")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(devops.ProjectPipeline{}))
webservice.Route(webservice.POST("/devops/{devops}/credentials").
To(devopsapi.CreateDevOpsProjectCredentialHandler).
Doc("add project credential pipeline").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Reads(devops.JenkinsCredential{}))
webservice.Route(webservice.PUT("/devops/{devops}/credentials/{credentials}").
To(devopsapi.UpdateDevOpsProjectCredentialHandler).
Doc("update project credential pipeline").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Param(webservice.PathParameter("credentials", "credential's Id")).
Reads(devops.JenkinsCredential{}))
webservice.Route(webservice.DELETE("/devops/{devops}/credentials/{credentials}").
To(devopsapi.DeleteDevOpsProjectCredentialHandler).
Doc("delete project credential pipeline").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Param(webservice.PathParameter("credentials", "credential's Id")))
webservice.Route(webservice.GET("/devops/{devops}/credentials/{credentials}").
To(devopsapi.GetDevOpsProjectCredentialHandler).
Doc("get project credential pipeline").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Param(webservice.PathParameter("credentials", "credential's Id")).
Param(webservice.QueryParameter("domain", "credential's domain")).
Param(webservice.QueryParameter("content", "get additional content")).
Returns(http.StatusOK, RespOK, devops.JenkinsCredential{}).
Reads(devops.JenkinsCredential{}))
webservice.Route(webservice.GET("/devops/{devops}/credentials").
To(devopsapi.GetDevOpsProjectCredentialsHandler).
Doc("get project credential pipeline").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(webservice.PathParameter("devops", "devops project's Id")).
Param(webservice.PathParameter("credentials", "credential's Id")).
Param(webservice.QueryParameter("domain", "credential's domain")).
Returns(http.StatusOK, RespOK, []devops.JenkinsCredential{}).
Reads([]devops.JenkinsCredential{}))
// match Jenkisn api "/blue/rest/organizations/jenkins/pipelines/{projectName}/{pipelineName}"
webservice.Route(webservice.GET("/devops/{projectName}/pipelines/{pipelineName}").
......@@ -51,7 +232,7 @@ func addWebService(c *restful.Container) error {
Doc("Get DevOps Pipelines.").
Param(webservice.PathParameter("pipelineName", "pipeline name")).
Param(webservice.PathParameter("projectName", "devops project name")).
Returns(http.StatusOK, RespMessage, devops.Pipeline{}).
Returns(http.StatusOK, RespOK, devops.Pipeline{}).
Writes(devops.Pipeline{}))
// match Jenkisn api: "jenkins_api/blue/rest/search"
......@@ -71,7 +252,7 @@ func addWebService(c *restful.Container) error {
Param(webservice.QueryParameter("limit", "limit count").
Required(false).
DataFormat("limit=%d")).
Returns(http.StatusOK, RespMessage, []devops.Pipeline{}).
Returns(http.StatusOK, RespOK, []devops.Pipeline{}).
Writes([]devops.Pipeline{}))
// match Jenkisn api "/blue/rest/organizations/jenkins/pipelines/{projectName}/{pipelineName}/runs/"
......@@ -90,7 +271,7 @@ func addWebService(c *restful.Container) error {
Param(webservice.QueryParameter("branch", "branch ").
Required(false).
DataFormat("branch=%s")).
Returns(http.StatusOK, RespMessage, []devops.PipelineRun{}).
Returns(http.StatusOK, RespOK, []devops.PipelineRun{}).
Writes([]devops.PipelineRun{}))
// match Jenkins api "/blue/rest/organizations/jenkins/pipelines/{projectName}/{pipelineName}/branches/{branchName}/runs/{runId}/"
......@@ -105,7 +286,7 @@ func addWebService(c *restful.Container) error {
Param(webservice.QueryParameter("start", "start").
Required(false).
DataFormat("start=%d")).
Returns(http.StatusOK, RespMessage, devops.PipelineRun{}).
Returns(http.StatusOK, RespOK, devops.PipelineRun{}).
Writes(devops.PipelineRun{}))
// match Jenkins api "/blue/rest/organizations/jenkins/pipelines/{projectName}/{pipelineName}/branches/{branchName}/runs/{runId}/nodes"
......@@ -121,7 +302,7 @@ func addWebService(c *restful.Container) error {
Required(false).
DataFormat("limit=%d").
DefaultValue("limit=10000")).
Returns(http.StatusOK, RespMessage, []devops.Nodes{}).
Returns(http.StatusOK, RespOK, []devops.Nodes{}).
Writes([]devops.Nodes{}))
// match "/blue/rest/organizations/jenkins/pipelines/{projectName}/{pipelineName}/branches/{branchName}/runs/{runId}/nodes/{nodeId}/steps/{stepId}/log/?start=0"
......@@ -147,7 +328,7 @@ func addWebService(c *restful.Container) error {
Metadata(restfulspec.KeyOpenAPITags, tags).
Doc("Validate Github personal access token.").
Param(webservice.PathParameter("scmId", "SCM id")).
Returns(http.StatusOK, RespMessage, devops.Validates{}).
Returns(http.StatusOK, RespOK, devops.Validates{}).
Writes(devops.Validates{}))
// match "/blue/rest/organizations/jenkins/scm/{scmId}/organizations/?credentialId=github"
......@@ -159,7 +340,7 @@ func addWebService(c *restful.Container) error {
Param(webservice.QueryParameter("credentialId", "credential id for SCM").
Required(true).
DataFormat("credentialId=%s")).
Returns(http.StatusOK, RespMessage, []devops.SCMOrg{}).
Returns(http.StatusOK, RespOK, []devops.SCMOrg{}).
Writes([]devops.SCMOrg{}))
// match "/blue/rest/organizations/jenkins/scm/{scmId}/organizations/{organizationId}/repositories/?credentialId=&pageNumber&pageSize="
......@@ -178,7 +359,7 @@ func addWebService(c *restful.Container) error {
Param(webservice.QueryParameter("pageSize", "page size").
Required(true).
DataFormat("pageSize=%d")).
Returns(http.StatusOK, RespMessage, []devops.OrgRepo{}).
Returns(http.StatusOK, RespOK, []devops.OrgRepo{}).
Writes([]devops.OrgRepo{}))
// match /blue/rest/organizations/jenkins/pipelines/{projectName}/pipelines/{pipelineName}/branches/{branchName}/runs/{runId}/stop/
......@@ -198,7 +379,7 @@ func addWebService(c *restful.Container) error {
Required(false).
DataFormat("timeOutInSecs=%d").
DefaultValue("timeOutInSecs=10")).
Returns(http.StatusOK, RespMessage, devops.StopPipe{}).
Returns(http.StatusOK, RespOK, devops.StopPipe{}).
Writes(devops.StopPipe{}))
// match /blue/rest/organizations/jenkins/pipelines/{projectName}/pipelines/{pipelineName}/branches/{branchName}/runs/{runId}/replay/
......@@ -210,7 +391,7 @@ func addWebService(c *restful.Container) error {
Param(webservice.PathParameter("pipelineName", "pipeline name")).
Param(webservice.PathParameter("branchName", "pipeline branch name")).
Param(webservice.PathParameter("runId", "pipeline runs id")).
Returns(http.StatusOK, RespMessage, devops.ReplayPipe{}).
Returns(http.StatusOK, RespOK, devops.ReplayPipe{}).
Writes(devops.ReplayPipe{}))
// match /blue/rest/organizations/jenkins/pipelines/{projectName}/{pipelineName}/branches/{branchName}/runs/{runId}/log/?start=0
......@@ -262,7 +443,7 @@ func addWebService(c *restful.Container) error {
Param(webservice.QueryParameter("limit", "limit count").
Required(true).
DataFormat("limit=%d")).
Returns(http.StatusOK, RespMessage, []devops.PipeBranch{}).
Returns(http.StatusOK, RespOK, []devops.PipeBranch{}).
Writes([]devops.PipeBranch{}))
// /blue/rest/organizations/jenkins/pipelines/{projectName}/pipelines/{pipelineName}/branches/{branchName}/runs/{runId}/nodes/{nodeId}/steps/{stepId}
......@@ -309,7 +490,7 @@ func addWebService(c *restful.Container) error {
Param(webservice.PathParameter("projectName", "devops project name")).
Param(webservice.PathParameter("pipelineName", "pipeline name")).
Param(webservice.PathParameter("branchName", "pipeline branch name")).
Returns(http.StatusOK, RespMessage, devops.QueuedBlueRun{}).
Returns(http.StatusOK, RespOK, devops.QueuedBlueRun{}).
Writes(devops.QueuedBlueRun{}))
// match /pipeline_status/blue/rest/organizations/jenkins/pipelines/{projectName}/{pipelineName}/branches/{branchName}/runs/{runId}/nodes/?limit=
......@@ -325,7 +506,7 @@ func addWebService(c *restful.Container) error {
Param(webservice.QueryParameter("limit", "limit count").
Required(true).
DataFormat("limit=%d")).
Returns(http.StatusOK, RespMessage, []devops.QueuedBlueRun{}).
Returns(http.StatusOK, RespOK, []devops.QueuedBlueRun{}).
Writes([]devops.QueuedBlueRun{}))
// match /crumbIssuer/api/json/
......@@ -333,7 +514,7 @@ func addWebService(c *restful.Container) error {
To(devopsapi.GetCrumb).
Metadata(restfulspec.KeyOpenAPITags, tags).
Doc("Get crumb").
Returns(http.StatusOK, RespMessage, devops.Crumb{}).
Returns(http.StatusOK, RespOK, devops.Crumb{}).
Writes(devops.Crumb{}))
c.Add(webservice)
......
/*
Copyright 2019 The KubeSphere Authors.
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 devops
import (
"fmt"
"github.com/asaskevich/govalidator"
"github.com/emicklei/go-restful"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/errors"
"kubesphere.io/kubesphere/pkg/models/devops"
"kubesphere.io/kubesphere/pkg/params"
"kubesphere.io/kubesphere/pkg/utils/reflectutils"
"net/http"
)
func GetDevOpsProjectMembersHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
err := devops.CheckProjectUserInRole(username, projectId, devops.AllRoleSlice)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
orderBy := request.QueryParameter(params.OrderByParam)
reverse := params.ParseReverse(request)
limit, offset := params.ParsePaging(request.QueryParameter(params.PagingParam))
conditions, err := params.ParseConditions(request.QueryParameter(params.ConditionsParam))
project, err := devops.GetProjectMembers(projectId, conditions, orderBy, reverse, limit, offset)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(project)
return
}
func GetDevOpsProjectMemberHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
member := request.PathParameter("members")
err := devops.CheckProjectUserInRole(username, projectId, devops.AllRoleSlice)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
project, err := devops.GetProjectMember(projectId, member)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(project)
return
}
func AddDevOpsProjectMemberHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
member := &devops.DevOpsProjectMembership{}
err := request.ReadEntity(&member)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
if govalidator.IsNull(member.Username) {
err := fmt.Errorf("error need username")
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
if !reflectutils.In(member.Role, devops.AllRoleSlice) {
err := fmt.Errorf("err role [%s] not in [%s]", member.Role,
devops.AllRoleSlice)
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
err = devops.CheckProjectUserInRole(username, projectId, []string{devops.ProjectOwner})
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
project, err := devops.AddProjectMember(projectId, username, member)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(project)
return
}
func UpdateDevOpsProjectMemberHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
member := &devops.DevOpsProjectMembership{}
err := request.ReadEntity(&member)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
member.Username = request.PathParameter("members")
if govalidator.IsNull(member.Username) {
err := fmt.Errorf("error need username")
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
if username == member.Username {
err := fmt.Errorf("you can not change your role")
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
if !reflectutils.In(member.Role, devops.AllRoleSlice) {
err := fmt.Errorf("err role [%s] not in [%s]", member.Role,
devops.AllRoleSlice)
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
err = devops.CheckProjectUserInRole(username, projectId, []string{devops.ProjectOwner})
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
project, err := devops.UpdateProjectMember(projectId, username, member)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(project)
return
}
func DeleteDevOpsProjectMemberHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
member := request.PathParameter("members")
err := devops.CheckProjectUserInRole(username, projectId, []string{devops.ProjectOwner})
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
username, err = devops.DeleteProjectMember(projectId, member)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(struct {
Username string `json:"username"`
}{Username: username})
return
}
/*
Copyright 2019 The KubeSphere Authors.
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 devops
import (
"github.com/emicklei/go-restful"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/errors"
"kubesphere.io/kubesphere/pkg/models/devops"
"net/http"
)
func GetDevOpsProjectHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
err := devops.CheckProjectUserInRole(username, projectId, devops.AllRoleSlice)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
project, err := devops.GetProject(projectId)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(project)
return
}
func UpdateProjectHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
var project *devops.DevOpsProject
err := request.ReadEntity(&project)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
project.ProjectId = projectId
err = devops.CheckProjectUserInRole(username, projectId, []string{devops.ProjectOwner})
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
project, err = devops.UpdateProject(project)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(project)
return
}
func GetDevOpsProjectDefaultRoles(request *restful.Request, resp *restful.Response) {
resp.WriteAsJson(devops.DefaultRoles)
return
}
/*
Copyright 2019 The KubeSphere Authors.
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 devops
import (
"github.com/emicklei/go-restful"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/errors"
"kubesphere.io/kubesphere/pkg/models/devops"
"net/http"
)
func CreateDevOpsProjectCredentialHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
var credential *devops.JenkinsCredential
err := request.ReadEntity(&credential)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
err = devops.CheckProjectUserInRole(username, projectId, []string{devops.ProjectOwner, devops.ProjectMaintainer})
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
credentialId, err := devops.CreateProjectCredential(projectId, username, credential)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(struct {
Name string `json:"name"`
}{Name: credentialId})
return
}
func UpdateDevOpsProjectCredentialHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
credentialId := request.PathParameter("credentials")
var credential *devops.JenkinsCredential
err := request.ReadEntity(&credential)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
err = devops.CheckProjectUserInRole(username, projectId, []string{devops.ProjectOwner, devops.ProjectMaintainer})
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
credentialId, err = devops.UpdateProjectCredential(projectId, credentialId, credential)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(struct {
Name string `json:"name"`
}{Name: credentialId})
return
}
func DeleteDevOpsProjectCredentialHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
credentialId := request.PathParameter("credentials")
var credential *devops.JenkinsCredential
err := request.ReadEntity(&credential)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
err = devops.CheckProjectUserInRole(username, projectId, []string{devops.ProjectOwner, devops.ProjectMaintainer})
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
credentialId, err = devops.DeleteProjectCredential(projectId, credentialId, credential)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(struct {
Name string `json:"name"`
}{Name: credentialId})
return
}
func GetDevOpsProjectCredentialHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
credentialId := request.PathParameter("credentials")
getContent := request.QueryParameter("content")
domain := request.QueryParameter("domain")
err := devops.CheckProjectUserInRole(username, projectId, []string{devops.ProjectOwner, devops.ProjectMaintainer})
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
response, err := devops.GetProjectCredential(projectId, credentialId, domain, getContent)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(response)
return
}
func GetDevOpsProjectCredentialsHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
domain := request.QueryParameter("domain")
err := devops.CheckProjectUserInRole(username, projectId, []string{devops.ProjectOwner, devops.ProjectMaintainer})
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
jenkinsCredentials, err := devops.GetProjectCredentials(projectId, domain)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(jenkinsCredentials)
return
}
/*
Copyright 2019 The KubeSphere Authors.
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 devops
import (
"github.com/emicklei/go-restful"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/errors"
"kubesphere.io/kubesphere/pkg/models/devops"
"net/http"
)
func CreateDevOpsProjectPipelineHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
var pipeline *devops.ProjectPipeline
err := request.ReadEntity(&pipeline)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
err = devops.CheckProjectUserInRole(username, projectId, []string{devops.ProjectOwner, devops.ProjectMaintainer})
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
pipelineName, err := devops.CreateProjectPipeline(projectId, pipeline)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(struct {
Name string `json:"name"`
}{Name: pipelineName})
return
}
func DeleteDevOpsProjectPipelineHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
pipelineId := request.PathParameter("pipelines")
err := devops.CheckProjectUserInRole(username, projectId, []string{devops.ProjectOwner, devops.ProjectMaintainer})
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
pipelineName, err := devops.DeleteProjectPipeline(projectId, pipelineId)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(struct {
Name string `json:"name"`
}{Name: pipelineName})
return
}
func UpdateDevOpsProjectPipelineHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
pipelineId := request.PathParameter("pipelines")
var pipeline *devops.ProjectPipeline
err := request.ReadEntity(&pipeline)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
err = devops.CheckProjectUserInRole(username, projectId, []string{devops.ProjectOwner, devops.ProjectMaintainer})
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
pipelineName, err := devops.UpdateProjectPipeline(projectId, pipelineId, pipeline)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(struct {
Name string `json:"name"`
}{Name: pipelineName})
return
}
func GetDevOpsProjectPipelineHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
pipelineId := request.PathParameter("pipelines")
err := devops.CheckProjectUserInRole(username, projectId, []string{devops.ProjectOwner, devops.ProjectMaintainer})
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
pipeline, err := devops.GetProjectPipeline(projectId, pipelineId)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(pipeline)
return
}
func GetPipelineSonarStatusHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
pipelineId := request.PathParameter("pipelines")
err := devops.CheckProjectUserInRole(username, projectId, devops.AllRoleSlice)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
sonarStatus, err := devops.GetPipelineSonar(projectId, pipelineId)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(sonarStatus)
}
func GetMultiBranchesPipelineSonarStatusHandler(request *restful.Request, resp *restful.Response) {
projectId := request.PathParameter("devops")
username := request.HeaderParameter(constants.UserNameHeader)
pipelineId := request.PathParameter("pipelines")
branchId := request.PathParameter("branches")
err := devops.CheckProjectUserInRole(username, projectId, devops.AllRoleSlice)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(restful.NewError(http.StatusForbidden, err.Error()), resp)
return
}
sonarStatus, err := devops.GetMultiBranchPipelineSonar(projectId, pipelineId, branchId)
if err != nil {
glog.Errorf("%+v", err)
errors.ParseSvcErr(err, resp)
return
}
resp.WriteAsJson(sonarStatus)
}
......@@ -217,7 +217,7 @@ func ListDevopsProjects(req *restful.Request, resp *restful.Response) {
if err != nil {
glog.Errorf("%+v", err)
resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err))
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
......@@ -225,7 +225,7 @@ func ListDevopsProjects(req *restful.Request, resp *restful.Response) {
if err != nil {
glog.Errorf("%+v", err)
resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err))
errors.ParseSvcErr(err, resp)
return
}
......@@ -233,7 +233,7 @@ func ListDevopsProjects(req *restful.Request, resp *restful.Response) {
}
func DeleteDevopsProject(req *restful.Request, resp *restful.Response) {
devops := req.PathParameter("id")
projectId := req.PathParameter("id")
workspaceName := req.PathParameter("workspace")
username := req.HeaderParameter(constants.UserNameHeader)
......@@ -241,15 +241,15 @@ func DeleteDevopsProject(req *restful.Request, resp *restful.Response) {
if err != nil {
glog.Errorf("%+v", err)
resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err))
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
err, code := tenant.DeleteDevOpsProject(devops, username)
err = tenant.DeleteDevOpsProject(projectId, username)
if err != nil {
glog.Errorf("%+v", err)
resp.WriteHeaderAndEntity(code, errors.Wrap(err))
errors.ParseSvcErr(err, resp)
return
}
......@@ -267,16 +267,16 @@ func CreateDevopsProject(req *restful.Request, resp *restful.Response) {
if err != nil {
glog.Infof("%+v", err)
resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err))
errors.ParseSvcErr(restful.NewError(http.StatusBadRequest, err.Error()), resp)
return
}
glog.Infoln("create workspace", username, workspaceName, devops)
project, err, code := tenant.CreateDevopsProject(username, workspaceName, &devops)
project, err := tenant.CreateDevopsProject(username, workspaceName, &devops)
if err != nil {
glog.Errorf("%+v", err)
resp.WriteHeaderAndEntity(code, errors.Wrap(err))
errors.ParseSvcErr(err, resp)
return
}
......@@ -302,11 +302,11 @@ func ListDevopsRules(req *restful.Request, resp *restful.Response) {
devops := req.PathParameter("devops")
username := req.HeaderParameter(constants.UserNameHeader)
rules, err, code := tenant.GetUserDevopsSimpleRules(username, devops)
rules, err := tenant.GetUserDevopsSimpleRules(username, devops)
if err != nil {
glog.Errorf("%+v", err)
resp.WriteError(code, err)
errors.ParseSvcErr(err, resp)
return
}
......
......@@ -20,6 +20,8 @@ package errors
import (
"encoding/json"
"errors"
"github.com/emicklei/go-restful"
"net/http"
)
type Error struct {
......@@ -53,3 +55,11 @@ func Parse(data []byte) error {
return errors.New(string(data))
}
}
func ParseSvcErr(err error, resp *restful.Response) {
if svcErr, ok := err.(restful.ServiceError); ok {
resp.WriteServiceError(svcErr.Code, svcErr)
} else {
resp.WriteHeaderAndEntity(http.StatusInternalServerError, Wrap(err))
}
}
......@@ -93,13 +93,13 @@ type CredentialResponse struct {
Name string `json:"name,omitempty"`
Ranges struct {
Ranges []*struct {
Start int `json:"start"`
End int `json:"end"`
} `json:"ranges"`
} `json:"ranges"`
Start int `json:"start,omitempty"`
End int `json:"end,omitempty"`
} `json:"ranges,omitempty"`
} `json:"ranges,omitempty"`
} `json:"usage,omitempty"`
} `json:"fingerprint,omitempty"`
Description string `json:"description"`
Description string `json:"description,omitempty"`
Domain string `json:"domain"`
}
......
/*
Copyright 2019 The KubeSphere Authors.
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 devops
import (
"fmt"
"github.com/fatih/structs"
"kubesphere.io/kubesphere/pkg/db"
"kubesphere.io/kubesphere/pkg/gojenkins"
"kubesphere.io/kubesphere/pkg/simple/client/devops_mysql"
"kubesphere.io/kubesphere/pkg/utils/reflectutils"
"kubesphere.io/kubesphere/pkg/utils/stringutils"
)
......@@ -59,3 +77,270 @@ const (
const (
JenkinsAllUserRoleName = "kubesphere-user"
)
type Role struct {
Name string `json:"name"`
Description string `json:"description"`
}
var DefaultRoles = []*Role{
{
Name: ProjectOwner,
Description: "Owner have access to do all the operations of a DevOps project and own the highest permissions as well.",
},
{
Name: ProjectMaintainer,
Description: "Maintainer have access to manage pipeline and credential configuration in a DevOps project.",
},
{
Name: ProjectDeveloper,
Description: "Developer is able to view and trigger the pipeline.",
},
{
Name: ProjectReporter,
Description: "Reporter is only allowed to view the status of the pipeline.",
},
}
var AllRoleSlice = []string{ProjectDeveloper, ProjectReporter, ProjectMaintainer, ProjectOwner}
var JenkinsOwnerProjectPermissionIds = &gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: true,
ItemCreate: true,
ItemDelete: true,
ItemDiscover: true,
ItemMove: true,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
}
var JenkinsProjectPermissionMap = map[string]gojenkins.ProjectPermissionIds{
ProjectOwner: gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: true,
ItemCreate: true,
ItemDelete: true,
ItemDiscover: true,
ItemMove: true,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
},
ProjectMaintainer: gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: false,
ItemCreate: true,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
},
ProjectDeveloper: gojenkins.ProjectPermissionIds{
CredentialCreate: false,
CredentialDelete: false,
CredentialManageDomains: false,
CredentialUpdate: false,
CredentialView: false,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: false,
ItemCreate: false,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: false,
},
ProjectReporter: gojenkins.ProjectPermissionIds{
CredentialCreate: false,
CredentialDelete: false,
CredentialManageDomains: false,
CredentialUpdate: false,
CredentialView: false,
ItemBuild: false,
ItemCancel: false,
ItemConfigure: false,
ItemCreate: false,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: false,
RunDelete: false,
RunReplay: false,
RunUpdate: false,
SCMTag: false,
},
}
var JenkinsPipelinePermissionMap = map[string]gojenkins.ProjectPermissionIds{
ProjectOwner: gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: true,
ItemCreate: true,
ItemDelete: true,
ItemDiscover: true,
ItemMove: true,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
},
ProjectMaintainer: gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: true,
ItemCreate: true,
ItemDelete: true,
ItemDiscover: true,
ItemMove: true,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
},
ProjectDeveloper: gojenkins.ProjectPermissionIds{
CredentialCreate: false,
CredentialDelete: false,
CredentialManageDomains: false,
CredentialUpdate: false,
CredentialView: false,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: false,
ItemCreate: false,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: false,
},
ProjectReporter: gojenkins.ProjectPermissionIds{
CredentialCreate: false,
CredentialDelete: false,
CredentialManageDomains: false,
CredentialUpdate: false,
CredentialView: false,
ItemBuild: false,
ItemCancel: false,
ItemConfigure: false,
ItemCreate: false,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: false,
RunDelete: false,
RunReplay: false,
RunUpdate: false,
SCMTag: false,
},
}
func GetProjectRoleName(projectId, role string) string {
return fmt.Sprintf("%s-%s-project", projectId, role)
}
func GetPipelineRoleName(projectId, role string) string {
return fmt.Sprintf("%s-%s-pipeline", projectId, role)
}
func GetProjectRolePattern(projectId string) string {
return fmt.Sprintf("^%s$", projectId)
}
func GetPipelineRolePattern(projectId string) string {
return fmt.Sprintf("^%s/.*", projectId)
}
func CheckProjectUserInRole(username, projectId string, roles []string) error {
if username == KS_ADMIN {
return nil
}
dbconn := devops_mysql.OpenDatabase()
membership := &DevOpsProjectMembership{}
err := dbconn.Select(DevOpsProjectMembershipColumns...).
From(DevOpsProjectMembershipTableName).
Where(db.And(
db.Eq(DevOpsProjectMembershipUsernameColumn, username),
db.Eq(DevOpsProjectMembershipProjectIdColumn, projectId))).LoadOne(membership)
if err != nil {
return err
}
if !reflectutils.In(membership.Role, roles) {
return fmt.Errorf("user [%s] in project [%s] role is not in %s", username, projectId, roles)
}
return nil
}
func GetProjectUserRole(username, projectId string) (string, error) {
if username == KS_ADMIN {
return ProjectOwner, nil
}
dbconn := devops_mysql.OpenDatabase()
membership := &DevOpsProjectMembership{}
err := dbconn.Select(DevOpsProjectMembershipColumns...).
From(DevOpsProjectMembershipTableName).
Where(db.And(
db.Eq(DevOpsProjectMembershipUsernameColumn, username),
db.Eq(DevOpsProjectMembershipProjectIdColumn, projectId))).LoadOne(membership)
if err != nil {
return "", err
}
return membership.Role, nil
}
/*
Copyright 2019 The KubeSphere Authors.
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 devops
const (
......
/*
Copyright 2019 The KubeSphere Authors.
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 devops
import (
......@@ -21,12 +34,12 @@ const (
type DevOpsProject struct {
ProjectId string `json:"project_id" db:"project_id"`
Name string `json:"name"`
Description string `json:"description"`
Description string `json:"description,omitempty"`
Creator string `json:"creator"`
CreateTime time.Time `json:"create_time"`
Status string `json:"status"`
Visibility string `json:"visibility"`
Extra string `json:"extra"`
Visibility string `json:"visibility,omitempty"`
Extra string `json:"extra,omitempty"`
Workspace string `json:"workspace"`
}
......
/*
Copyright 2019 The KubeSphere Authors.
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 devops
import (
"github.com/asaskevich/govalidator"
"time"
)
const (
CredentialTypeUsernamePassword = "username_password"
CredentialTypeSsh = "ssh"
CredentialTypeSecretText = "secret_text"
CredentialTypeKubeConfig = "kubeconfig"
)
type JenkinsCredential struct {
Id string `json:"id"`
Type string `json:"type"`
DisplayName string `json:"display_name,omitempty"`
Fingerprint *struct {
FileName string `json:"file_name,omitempty"`
Hash string `json:"hash,omitempty"`
Usage []*struct {
Name string `json:"name,omitempty"`
Ranges struct {
Ranges []*struct {
Start int `json:"start,omitempty"`
End int `json:"end,omitempty"`
} `json:"ranges,omitempty"`
} `json:"ranges,omitempty"`
} `json:"usage,omitempty"`
} `json:"fingerprint,omitempty"`
Description string `json:"description,omitempty"`
Domain string `json:"domain,omitempty"`
CreateTime *time.Time `json:"create_time,omitempty"`
Creator string `json:"creator,omitempty"`
UsernamePasswordCredential *UsernamePasswordCredential `json:"username_password,omitempty"`
SshCredential *SshCredential `json:"ssh,omitempty"`
SecretTextCredential *SecretTextCredential `json:"secret_text,omitempty"`
KubeconfigCredential *KubeconfigCredential `json:"kubeconfig,omitempty"`
}
type UsernamePasswordCredential struct {
Id string `json:"id"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Description string `json:"description,omitempty"`
}
type SshCredential struct {
Id string `json:"id"`
Username string `json:"username,omitempty"`
Passphrase string `json:"passphrase,omitempty"`
PrivateKey string `json:"private_key,omitempty" mapstructure:"private_key"`
Description string `json:"description,omitempty"`
}
type SecretTextCredential struct {
Id string `json:"id"`
Secret string `json:"secret,omitempty"`
Description string `json:"description,omitempty"`
}
type KubeconfigCredential struct {
Id string `json:"id"`
Content string `json:"content,omitempty"`
Description string `json:"description,omitempty"`
}
const (
ProjectCredentialTableName = "project_credential"
ProjectCredentialIdColumn = "credential_id"
ProjectCredentialDomainColumn = "domain"
ProjectCredentialProjectIdColumn = "project_id"
)
var CredentialTypeMap = map[string]string{
"SSH Username with private key": CredentialTypeSsh,
"Username with password": CredentialTypeUsernamePassword,
"Secret text": CredentialTypeSecretText,
"Kubernetes configuration (kubeconfig)": CredentialTypeKubeConfig,
}
type ProjectCredential struct {
ProjectId string `json:"project_id"`
CredentialId string `json:"credential_id"`
Domain string `json:"domain"`
Creator string `json:"creator"`
CreateTime time.Time `json:"create_time"`
}
var ProjectCredentialColumns = GetColumnsFromStruct(&ProjectCredential{})
func NewProjectCredential(projectId, credentialId, domain, creator string) *ProjectCredential {
if govalidator.IsNull(domain) {
domain = "_"
}
return &ProjectCredential{
ProjectId: projectId,
CredentialId: credentialId,
Domain: domain,
Creator: creator,
CreateTime: time.Now(),
}
}
/*
Copyright 2019 The KubeSphere Authors.
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 devops
import (
"fmt"
"github.com/PuerkitoBio/goquery"
"github.com/asaskevich/govalidator"
"github.com/emicklei/go-restful"
"github.com/gocraft/dbr"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/db"
"kubesphere.io/kubesphere/pkg/gojenkins"
"kubesphere.io/kubesphere/pkg/gojenkins/utils"
"kubesphere.io/kubesphere/pkg/simple/client/admin_jenkins"
"kubesphere.io/kubesphere/pkg/simple/client/devops_mysql"
"net/http"
"strings"
)
func CreateProjectCredential(projectId, username string, credentialRequest *JenkinsCredential) (string, error) {
jenkinsClient := admin_jenkins.Client()
switch credentialRequest.Type {
case CredentialTypeUsernamePassword:
err := checkJenkinsCredentialExists(projectId, credentialRequest.Domain, credentialRequest.UsernamePasswordCredential.Id)
if err != nil {
glog.Errorf("%+v", err)
return "", err
}
if credentialRequest.UsernamePasswordCredential == nil {
err := fmt.Errorf("usename_password should not be nil")
glog.Error(err)
return "", restful.NewError(http.StatusBadRequest, err.Error())
}
credentialId, err := jenkinsClient.CreateUsernamePasswordCredentialInFolder(credentialRequest.Domain,
credentialRequest.UsernamePasswordCredential.Id,
credentialRequest.UsernamePasswordCredential.Username,
credentialRequest.UsernamePasswordCredential.Password,
credentialRequest.UsernamePasswordCredential.Description,
projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = insertCredentialToDb(projectId, *credentialId, credentialRequest.Domain, username)
if err != nil {
glog.Errorf("%+v", err)
return "", err
}
return *credentialId, nil
case CredentialTypeSsh:
err := checkJenkinsCredentialExists(projectId, credentialRequest.Domain, credentialRequest.SshCredential.Id)
if err != nil {
glog.Errorf("%+v", err)
return "", err
}
if credentialRequest.SshCredential == nil {
err := fmt.Errorf("ssh should not be nil")
glog.Error(err)
return "", restful.NewError(http.StatusBadRequest, err.Error())
}
credentialId, err := jenkinsClient.CreateSshCredentialInFolder(credentialRequest.Domain,
credentialRequest.SshCredential.Id,
credentialRequest.SshCredential.Username,
credentialRequest.SshCredential.Passphrase,
credentialRequest.SshCredential.PrivateKey,
credentialRequest.SshCredential.Description,
projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = insertCredentialToDb(projectId, *credentialId, credentialRequest.Domain, username)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusInternalServerError, err.Error())
}
return *credentialId, nil
case CredentialTypeSecretText:
err := checkJenkinsCredentialExists(projectId, credentialRequest.Domain, credentialRequest.SecretTextCredential.Id)
if err != nil {
glog.Errorf("%+v", err)
return "", err
}
if credentialRequest.SecretTextCredential == nil {
err := fmt.Errorf("secret_text should not be nil")
glog.Error(err)
return "", restful.NewError(http.StatusBadRequest, err.Error())
}
credentialId, err := jenkinsClient.CreateSecretTextCredentialInFolder(credentialRequest.Domain,
credentialRequest.SecretTextCredential.Id,
credentialRequest.SecretTextCredential.Secret,
credentialRequest.SecretTextCredential.Description,
projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = insertCredentialToDb(projectId, *credentialId, credentialRequest.Domain, username)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusInternalServerError, err.Error())
}
return *credentialId, nil
case CredentialTypeKubeConfig:
err := checkJenkinsCredentialExists(projectId, credentialRequest.Domain, credentialRequest.KubeconfigCredential.Id)
if err != nil {
glog.Errorf("%+v", err)
return "", err
}
if credentialRequest.KubeconfigCredential == nil {
err := fmt.Errorf("kubeconfig should not be nil")
glog.Error(err)
return "", restful.NewError(http.StatusBadRequest, err.Error())
}
credentialId, err := jenkinsClient.CreateKubeconfigCredentialInFolder(credentialRequest.Domain,
credentialRequest.KubeconfigCredential.Id,
credentialRequest.KubeconfigCredential.Content,
credentialRequest.KubeconfigCredential.Description,
projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = insertCredentialToDb(projectId, *credentialId, credentialRequest.Domain, username)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusInternalServerError, err.Error())
}
return *credentialId, nil
default:
err := fmt.Errorf("error unsupport credential type")
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusBadRequest, err.Error())
}
}
func UpdateProjectCredential(projectId, credentialId string, credentialRequest *JenkinsCredential) (string, error) {
jenkinsClient := admin_jenkins.Client()
jenkinsCredential, err := jenkinsClient.GetCredentialInFolder(credentialRequest.Domain,
credentialId,
projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
credentialType := CredentialTypeMap[jenkinsCredential.TypeName]
switch credentialType {
case CredentialTypeUsernamePassword:
if credentialRequest.UsernamePasswordCredential == nil {
err := fmt.Errorf("usename_password should not be nil")
glog.Error(err)
return "", restful.NewError(http.StatusBadRequest, err.Error())
}
credentialId, err := jenkinsClient.UpdateUsernamePasswordCredentialInFolder(credentialRequest.Domain,
credentialId,
credentialRequest.UsernamePasswordCredential.Username,
credentialRequest.UsernamePasswordCredential.Password,
credentialRequest.UsernamePasswordCredential.Description,
projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
return *credentialId, nil
case CredentialTypeSsh:
if credentialRequest.SshCredential == nil {
err := fmt.Errorf("ssh should not be nil")
glog.Error(err)
return "", restful.NewError(http.StatusBadRequest, err.Error())
}
credentialId, err := jenkinsClient.UpdateSshCredentialInFolder(credentialRequest.Domain,
credentialId,
credentialRequest.SshCredential.Username,
credentialRequest.SshCredential.Passphrase,
credentialRequest.SshCredential.PrivateKey,
credentialRequest.SshCredential.Description,
projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
return *credentialId, nil
case CredentialTypeSecretText:
if credentialRequest.SecretTextCredential == nil {
err := fmt.Errorf("secret_text should not be nil")
glog.Error(err)
return "", restful.NewError(http.StatusBadRequest, err.Error())
}
credentialId, err := jenkinsClient.UpdateSecretTextCredentialInFolder(credentialRequest.Domain,
credentialId,
credentialRequest.SecretTextCredential.Secret,
credentialRequest.SecretTextCredential.Description,
projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
return *credentialId, nil
case CredentialTypeKubeConfig:
if credentialRequest.KubeconfigCredential == nil {
err := fmt.Errorf("kubeconfig should not be nil")
glog.Error(err)
return "", restful.NewError(http.StatusBadRequest, err.Error())
}
credentialId, err := jenkinsClient.UpdateKubeconfigCredentialInFolder(credentialRequest.Domain,
credentialId,
credentialRequest.KubeconfigCredential.Content,
credentialRequest.KubeconfigCredential.Description,
projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
return *credentialId, nil
default:
err := fmt.Errorf("error unsupport credential type")
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusBadRequest, err.Error())
}
}
func DeleteProjectCredential(projectId, credentialId string, credentialRequest *JenkinsCredential) (string, error) {
jenkinsClient := admin_jenkins.Client()
dbClient := devops_mysql.OpenDatabase()
_, err := jenkinsClient.GetCredentialInFolder(credentialRequest.Domain,
credentialId,
projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
id, err := jenkinsClient.DeleteCredentialInFolder(credentialRequest.Domain, credentialId, projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
deleteConditions := append(make([]dbr.Builder, 0), db.Eq(ProjectCredentialProjectIdColumn, projectId))
deleteConditions = append(deleteConditions, db.Eq(ProjectCredentialIdColumn, credentialId))
if !govalidator.IsNull(credentialRequest.Domain) {
deleteConditions = append(deleteConditions, db.Eq(ProjectCredentialDomainColumn, credentialRequest.Domain))
} else {
deleteConditions = append(deleteConditions, db.Eq(ProjectCredentialDomainColumn, "_"))
}
_, err = dbClient.DeleteFrom(ProjectCredentialTableName).
Where(db.And(deleteConditions...)).Exec()
if err != nil && err != db.ErrNotFound {
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusInternalServerError, err.Error())
}
return *id, nil
}
func GetProjectCredential(projectId, credentialId, domain, getContent string) (*JenkinsCredential, error) {
jenkinsClient := admin_jenkins.Client()
dbClient := devops_mysql.OpenDatabase()
jenkinsResponse, err := jenkinsClient.GetCredentialInFolder(domain,
credentialId,
projectId)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
projectCredential := &ProjectCredential{}
err = dbClient.Select(ProjectCredentialColumns...).
From(ProjectCredentialTableName).Where(
db.And(db.Eq(ProjectCredentialProjectIdColumn, projectId),
db.Eq(ProjectCredentialIdColumn, credentialId),
db.Eq(ProjectCredentialDomainColumn, jenkinsResponse.Domain))).LoadOne(projectCredential)
if err != nil && err != db.ErrNotFound {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
response := formatCredentialResponse(jenkinsResponse, projectCredential)
if getContent != "" {
stringBody, err := jenkinsClient.GetCredentialContentInFolder(jenkinsResponse.Domain, credentialId, projectId)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
stringReader := strings.NewReader(stringBody)
doc, err := goquery.NewDocumentFromReader(stringReader)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
switch response.Type {
case CredentialTypeKubeConfig:
content := &KubeconfigCredential{}
doc.Find("textarea[name*=content]").Each(func(i int, selection *goquery.Selection) {
value := selection.Text()
content.Content = value
})
doc.Find("input[name*=id][type=text]").Each(func(i int, selection *goquery.Selection) {
value, _ := selection.Attr("value")
content.Id = value
})
doc.Find("input[name*=description]").Each(func(i int, selection *goquery.Selection) {
value, _ := selection.Attr("value")
content.Description = value
})
response.KubeconfigCredential = content
case CredentialTypeUsernamePassword:
content := &UsernamePasswordCredential{}
doc.Find("input[name*=username]").Each(func(i int, selection *goquery.Selection) {
value, _ := selection.Attr("value")
content.Username = value
})
doc.Find("input[name*=id][type=text]").Each(func(i int, selection *goquery.Selection) {
value, _ := selection.Attr("value")
content.Id = value
})
doc.Find("input[name*=description]").Each(func(i int, selection *goquery.Selection) {
value, _ := selection.Attr("value")
content.Description = value
})
response.UsernamePasswordCredential = content
case CredentialTypeSsh:
content := &SshCredential{}
doc.Find("input[name*=username]").Each(func(i int, selection *goquery.Selection) {
value, _ := selection.Attr("value")
content.Username = value
})
doc.Find("input[name*=id][type=text]").Each(func(i int, selection *goquery.Selection) {
value, _ := selection.Attr("value")
content.Id = value
})
doc.Find("input[name*=description]").Each(func(i int, selection *goquery.Selection) {
value, _ := selection.Attr("value")
content.Description = value
})
doc.Find("textarea[name*=privateKey]").Each(func(i int, selection *goquery.Selection) {
value := selection.Text()
content.PrivateKey = value
})
response.SshCredential = content
}
}
return response, nil
}
func GetProjectCredentials(projectId, domain string) ([]*JenkinsCredential, error) {
jenkinsClient := admin_jenkins.Client()
dbClient := devops_mysql.OpenDatabase()
jenkinsCredentialResponses, err := jenkinsClient.GetCredentialsInFolder(domain, projectId)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
selectCondition := db.Eq(ProjectCredentialProjectIdColumn, projectId)
if !govalidator.IsNull(domain) {
selectCondition = db.And(selectCondition, db.Eq(ProjectCredentialDomainColumn, domain))
}
projectCredentials := make([]*ProjectCredential, 0)
_, err = dbClient.Select(ProjectCredentialColumns...).
From(ProjectCredentialTableName).Where(selectCondition).Load(&projectCredentials)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
response := formatCredentialsResponse(jenkinsCredentialResponses, projectCredentials)
return response, nil
}
func insertCredentialToDb(projectId, credentialId, domain, username string) error {
dbClient := devops_mysql.OpenDatabase()
projectCredential := NewProjectCredential(projectId, credentialId, domain, username)
_, err := dbClient.InsertInto(ProjectCredentialTableName).Columns(ProjectCredentialColumns...).
Record(projectCredential).Exec()
if err != nil {
glog.Errorf("%+v", err)
return restful.NewError(http.StatusInternalServerError, err.Error())
}
return nil
}
func checkJenkinsCredentialExists(projectId, domain, credentialId string) error {
jenkinsClient := admin_jenkins.Client()
credential, err := jenkinsClient.GetCredentialInFolder(domain, credentialId, projectId)
if credential != nil {
err := fmt.Errorf("credential id [%s] has been used", credential.Id)
glog.Warning(err.Error())
return restful.NewError(http.StatusConflict, err.Error())
}
if err != nil && utils.GetJenkinsStatusCode(err) != http.StatusNotFound {
glog.Errorf("%+v", err)
return restful.NewError(http.StatusBadRequest, err.Error())
}
return nil
}
func formatCredentialResponse(
jenkinsCredentialResponse *gojenkins.CredentialResponse,
dbCredentialResponse *ProjectCredential) *JenkinsCredential {
response := &JenkinsCredential{}
response.Id = jenkinsCredentialResponse.Id
response.Description = jenkinsCredentialResponse.Description
response.DisplayName = jenkinsCredentialResponse.DisplayName
if jenkinsCredentialResponse.Fingerprint != nil && jenkinsCredentialResponse.Fingerprint.Hash != "" {
response.Fingerprint = &struct {
FileName string `json:"file_name,omitempty"`
Hash string `json:"hash,omitempty"`
Usage []*struct {
Name string `json:"name,omitempty"`
Ranges struct {
Ranges []*struct {
Start int `json:"start,omitempty"`
End int `json:"end,omitempty"`
} `json:"ranges,omitempty"`
} `json:"ranges,omitempty"`
} `json:"usage,omitempty"`
}{}
response.Fingerprint.FileName = jenkinsCredentialResponse.Fingerprint.FileName
response.Fingerprint.Hash = jenkinsCredentialResponse.Fingerprint.Hash
for _, usage := range jenkinsCredentialResponse.Fingerprint.Usage {
response.Fingerprint.Usage = append(response.Fingerprint.Usage, usage)
}
}
response.Domain = jenkinsCredentialResponse.Domain
if dbCredentialResponse != nil {
response.CreateTime = &dbCredentialResponse.CreateTime
response.Creator = dbCredentialResponse.Creator
}
credentialType, ok := CredentialTypeMap[jenkinsCredentialResponse.TypeName]
if ok {
response.Type = credentialType
return response
}
response.Type = jenkinsCredentialResponse.TypeName
return response
}
func formatCredentialsResponse(jenkinsCredentialsResponse []*gojenkins.CredentialResponse,
projectCredentials []*ProjectCredential) []*JenkinsCredential {
responseSlice := make([]*JenkinsCredential, 0)
for _, jenkinsCredential := range jenkinsCredentialsResponse {
var dbCredential *ProjectCredential = nil
for _, projectCredential := range projectCredentials {
if projectCredential.CredentialId == jenkinsCredential.Id &&
projectCredential.Domain == jenkinsCredential.Domain {
dbCredential = projectCredential
}
}
responseSlice = append(responseSlice, formatCredentialResponse(jenkinsCredential, dbCredential))
}
return responseSlice
}
/*
Copyright 2019 The KubeSphere Authors.
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 devops
import (
"github.com/asaskevich/govalidator"
"github.com/emicklei/go-restful"
"github.com/gocraft/dbr"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/db"
"kubesphere.io/kubesphere/pkg/simple/client/devops_mysql"
"net/http"
)
func GetProject(projectId string) (*DevOpsProject, error) {
dbconn := devops_mysql.OpenDatabase()
project := &DevOpsProject{}
err := dbconn.Select(DevOpsProjectColumns...).
From(DevOpsProjectTableName).
Where(db.Eq(DevOpsProjectIdColumn, projectId)).
LoadOne(project)
if err != nil && err != dbr.ErrNotFound {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
if err == dbr.ErrNotFound {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusNotFound, err.Error())
}
return project, nil
}
func UpdateProject(project *DevOpsProject) (*DevOpsProject, error) {
dbconn := devops_mysql.OpenDatabase()
query := dbconn.Update(DevOpsProjectTableName)
if !govalidator.IsNull(project.Description) {
query.Set(DevOpsProjectDescriptionColumn, project.Description)
}
if !govalidator.IsNull(project.Extra) {
query.Set(DevOpsProjectExtraColumn, project.Extra)
}
if !govalidator.IsNull(project.Name) {
query.Set(DevOpsProjectNameColumn, project.Extra)
}
if len(query.UpdateStmt.Value) > 0 {
_, err := query.
Where(db.Eq(DevOpsProjectIdColumn, project.ProjectId)).Exec()
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
}
newProject := &DevOpsProject{}
err := dbconn.Select(DevOpsProjectColumns...).
From(DevOpsProjectTableName).
Where(db.Eq(DevOpsProjectIdColumn, project.ProjectId)).
LoadOne(newProject)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
return newProject, nil
}
/*
Copyright 2018 The KubeSphere Authors.
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 devops
import (
"fmt"
"net/http"
"github.com/emicklei/go-restful"
"github.com/gocraft/dbr"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/db"
"kubesphere.io/kubesphere/pkg/gojenkins"
"kubesphere.io/kubesphere/pkg/gojenkins/utils"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/params"
"kubesphere.io/kubesphere/pkg/simple/client/admin_jenkins"
"kubesphere.io/kubesphere/pkg/simple/client/devops_mysql"
)
func GetProjectMembers(projectId string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error) {
dbconn := devops_mysql.OpenDatabase()
memberships := make([]*DevOpsProjectMembership, 0)
var sqconditions []dbr.Builder
sqconditions = append(sqconditions, db.Eq(DevOpsProjectMembershipProjectIdColumn, projectId))
if keyword := conditions.Match["keyword"]; keyword != "" {
sqconditions = append(sqconditions, db.Like(DevOpsProjectMembershipUsernameColumn, keyword))
}
query := dbconn.Select(DevOpsProjectMembershipColumns...).
From(DevOpsProjectMembershipTableName)
switch orderBy {
case "name":
if reverse {
query.OrderDesc(DevOpsProjectMembershipUsernameColumn)
} else {
query.OrderAsc(DevOpsProjectMembershipUsernameColumn)
}
default:
if reverse {
query.OrderDesc(DevOpsProjectMembershipRoleColumn)
} else {
query.OrderAsc(DevOpsProjectMembershipRoleColumn)
}
}
query.Limit(uint64(limit))
query.Offset(uint64(offset))
if len(sqconditions) > 1 {
query.Where(db.And(sqconditions...))
} else {
query.Where(sqconditions[0])
}
_, err := query.Load(&memberships)
if err != nil && err != dbr.ErrNotFound {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
count, err := query.Count()
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
result := make([]interface{}, 0)
for _, v := range memberships {
result = append(result, v)
}
return &models.PageableResponse{Items: result, TotalCount: int(count)}, nil
}
func GetProjectMember(projectId, username string) (*DevOpsProjectMembership, error) {
dbconn := devops_mysql.OpenDatabase()
member := &DevOpsProjectMembership{}
err := dbconn.Select(DevOpsProjectMembershipColumns...).
From(DevOpsProjectMembershipTableName).
Where(db.And(db.Eq(DevOpsProjectMembershipProjectIdColumn, projectId),
db.Eq(DevOpsProjectMembershipUsernameColumn, username))).
LoadOne(&member)
if err != nil && err != dbr.ErrNotFound {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
if err == dbr.ErrNotFound {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusNotFound, err.Error())
}
return member, nil
}
func AddProjectMember(projectId, operator string, member *DevOpsProjectMembership) (*DevOpsProjectMembership, error) {
dbconn := devops_mysql.OpenDatabase()
jenkinsClinet := admin_jenkins.Client()
membership := &DevOpsProjectMembership{}
err := dbconn.Select(DevOpsProjectMembershipColumns...).
From(DevOpsProjectMembershipTableName).
Where(db.And(
db.Eq(DevOpsProjectMembershipUsernameColumn, member.Username),
db.Eq(DevOpsProjectMembershipProjectIdColumn, projectId))).LoadOne(membership)
// if user could be founded in db, user have been added to project
if err == nil {
err = fmt.Errorf("user [%s] have been added to project", member.Username)
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusBadRequest, err.Error())
}
if err != nil && err != db.ErrNotFound {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
globalRole, err := jenkinsClinet.GetGlobalRole(JenkinsAllUserRoleName)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
if globalRole == nil {
_, err := jenkinsClinet.AddGlobalRole(JenkinsAllUserRoleName, gojenkins.GlobalPermissionIds{
GlobalRead: true,
}, true)
if err != nil {
glog.Errorf("failed to create jenkins global role %+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
}
err = globalRole.AssignRole(member.Username)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
projectRole, err := jenkinsClinet.GetProjectRole(GetProjectRoleName(projectId, member.Role))
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = projectRole.AssignRole(member.Username)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
pipelineRole, err := jenkinsClinet.GetProjectRole(GetPipelineRoleName(projectId, member.Role))
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = pipelineRole.AssignRole(member.Username)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
projectMembership := NewDevOpsProjectMemberShip(member.Username, projectId, member.Role, operator)
_, err = dbconn.
InsertInto(DevOpsProjectMembershipTableName).
Columns(DevOpsProjectMembershipColumns...).
Record(projectMembership).Exec()
if err != nil {
glog.Errorf("%+v", err)
err = projectRole.UnAssignRole(member.Username)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = pipelineRole.UnAssignRole(member.Username)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
return projectMembership, nil
}
func UpdateProjectMember(projectId, operator string, member *DevOpsProjectMembership) (*DevOpsProjectMembership, error) {
dbconn := devops_mysql.OpenDatabase()
jenkinsClinet := admin_jenkins.Client()
oldMembership := &DevOpsProjectMembership{}
err := dbconn.Select(DevOpsProjectMembershipColumns...).
From(DevOpsProjectMembershipTableName).
Where(db.And(
db.Eq(DevOpsProjectMembershipUsernameColumn, member.Username),
db.Eq(DevOpsProjectMembershipProjectIdColumn, projectId),
)).LoadOne(oldMembership)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusBadRequest, err.Error())
}
oldProjectRole, err := jenkinsClinet.GetProjectRole(GetProjectRoleName(projectId, oldMembership.Role))
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = oldProjectRole.UnAssignRole(member.Username)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
oldPipelineRole, err := jenkinsClinet.GetProjectRole(GetPipelineRoleName(projectId, oldMembership.Role))
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = oldPipelineRole.UnAssignRole(member.Username)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
projectRole, err := jenkinsClinet.GetProjectRole(GetProjectRoleName(projectId, member.Role))
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = projectRole.AssignRole(member.Username)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
pipelineRole, err := jenkinsClinet.GetProjectRole(GetPipelineRoleName(projectId, member.Role))
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = pipelineRole.AssignRole(member.Username)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
_, err = dbconn.Update(DevOpsProjectMembershipTableName).
Set(DevOpsProjectMembershipRoleColumn, member.Role).
Where(db.And(
db.Eq(DevOpsProjectMembershipProjectIdColumn, projectId),
db.Eq(DevOpsProjectMembershipUsernameColumn, member.Username),
)).Exec()
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
responseMembership := &DevOpsProjectMembership{}
err = dbconn.Select(DevOpsProjectMembershipColumns...).
From(DevOpsProjectMembershipTableName).
Where(db.And(
db.Eq(DevOpsProjectMembershipUsernameColumn, member.Username),
db.Eq(DevOpsProjectMembershipProjectIdColumn, projectId),
)).LoadOne(responseMembership)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
return responseMembership, nil
}
func DeleteProjectMember(projectId, username string) (string, error) {
dbconn := devops_mysql.OpenDatabase()
jenkinsClient := admin_jenkins.Client()
oldMembership := &DevOpsProjectMembership{}
err := dbconn.Select(DevOpsProjectMembershipColumns...).
From(DevOpsProjectMembershipTableName).
Where(db.And(
db.Eq(DevOpsProjectMembershipUsernameColumn, username),
db.Eq(DevOpsProjectMembershipProjectIdColumn, projectId),
)).LoadOne(oldMembership)
if err != nil && err != db.ErrNotFound {
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusInternalServerError, err.Error())
}
if err == db.ErrNotFound {
glog.Warningf("user [%s] not found in project", username)
return username, nil
}
if oldMembership.Role == ProjectOwner {
count, err := dbconn.Select(DevOpsProjectMembershipProjectIdColumn).
From(DevOpsProjectMembershipTableName).
Where(db.And(
db.Eq(DevOpsProjectMembershipProjectIdColumn, projectId),
db.Eq(DevOpsProjectMembershipRoleColumn, ProjectOwner))).Count()
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusInternalServerError, err.Error())
}
if count == 1 {
err = fmt.Errorf("project must has at least one admin")
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusBadRequest, err.Error())
}
}
oldProjectRole, err := jenkinsClient.GetProjectRole(GetProjectRoleName(projectId, oldMembership.Role))
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = oldProjectRole.UnAssignRole(username)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
oldPipelineRole, err := jenkinsClient.GetProjectRole(GetPipelineRoleName(projectId, oldMembership.Role))
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = oldPipelineRole.UnAssignRole(username)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
_, err = dbconn.DeleteFrom(DevOpsProjectMembershipTableName).
Where(db.And(
db.Eq(DevOpsProjectMembershipProjectIdColumn, projectId),
db.Eq(DevOpsProjectMembershipUsernameColumn, username),
)).Exec()
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusInternalServerError, err.Error())
}
return username, nil
}
此差异已折叠。
/*
Copyright 2019 The KubeSphere Authors.
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 devops
import (
"fmt"
"github.com/emicklei/go-restful"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/gojenkins/utils"
"kubesphere.io/kubesphere/pkg/simple/client/admin_jenkins"
"net/http"
)
func CreateProjectPipeline(projectId string, pipeline *ProjectPipeline) (string, error) {
jenkinsClient := admin_jenkins.Client()
switch pipeline.Type {
case NoScmPipelineType:
config, err := createPipelineConfigXml(pipeline.Pipeline)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusInternalServerError, err.Error())
}
job, err := jenkinsClient.GetJob(pipeline.Pipeline.Name, projectId)
if job != nil {
err := fmt.Errorf("job name [%s] has been used", job.GetName())
glog.Warning(err.Error())
return "", restful.NewError(http.StatusConflict, err.Error())
}
if err != nil && utils.GetJenkinsStatusCode(err) != http.StatusNotFound {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
_, err = jenkinsClient.CreateJobInFolder(config, pipeline.Pipeline.Name, projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
return pipeline.Pipeline.Name, nil
case MultiBranchPipelineType:
config, err := createMultiBranchPipelineConfigXml(projectId, pipeline.MultiBranchPipeline)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusInternalServerError, err.Error())
}
job, err := jenkinsClient.GetJob(pipeline.MultiBranchPipeline.Name, projectId)
if job != nil {
err := fmt.Errorf("job name [%s] has been used", job.GetName())
glog.Warning(err.Error())
return "", restful.NewError(http.StatusConflict, err.Error())
}
if err != nil && utils.GetJenkinsStatusCode(err) != http.StatusNotFound {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
_, err = jenkinsClient.CreateJobInFolder(config, pipeline.MultiBranchPipeline.Name, projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
return pipeline.MultiBranchPipeline.Name, nil
default:
err := fmt.Errorf("error unsupport job type")
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusBadRequest, err.Error())
}
}
func DeleteProjectPipeline(projectId string, pipelineId string) (string, error) {
jenkinsClient := admin_jenkins.Client()
_, err := jenkinsClient.DeleteJob(pipelineId, projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
return pipelineId, nil
}
func UpdateProjectPipeline(projectId, pipelineId string, pipeline *ProjectPipeline) (string, error) {
jenkinsClient := admin_jenkins.Client()
switch pipeline.Type {
case NoScmPipelineType:
config, err := createPipelineConfigXml(pipeline.Pipeline)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusInternalServerError, err.Error())
}
job, err := jenkinsClient.GetJob(pipelineId, projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = job.UpdateConfig(config)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
return pipeline.Pipeline.Name, nil
case MultiBranchPipelineType:
config, err := createMultiBranchPipelineConfigXml(projectId, pipeline.MultiBranchPipeline)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusInternalServerError, err.Error())
}
job, err := jenkinsClient.GetJob(pipelineId, projectId)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = job.UpdateConfig(config)
if err != nil {
glog.Errorf("%+v", err)
return "", restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
return pipeline.MultiBranchPipeline.Name, nil
default:
err := fmt.Errorf("error unsupport job type")
glog.Errorf("%+v", err)
return "", restful.NewError(http.StatusBadRequest, err.Error())
}
}
func GetProjectPipeline(projectId, pipelineId string) (*ProjectPipeline, error) {
jenkinsClient := admin_jenkins.Client()
job, err := jenkinsClient.GetJob(pipelineId, projectId)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
switch job.Raw.Class {
case "org.jenkinsci.plugins.workflow.job.WorkflowJob":
config, err := job.GetConfig()
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
pipeline, err := parsePipelineConfigXml(config)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
pipeline.Name = pipelineId
return &ProjectPipeline{
Type: NoScmPipelineType,
Pipeline: pipeline,
}, nil
case "org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject":
config, err := job.GetConfig()
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
pipeline, err := parseMultiBranchPipelineConfigXml(config)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
pipeline.Name = pipelineId
return &ProjectPipeline{
Type: MultiBranchPipelineType,
MultiBranchPipeline: pipeline,
}, nil
default:
err := fmt.Errorf("error unsupport job type")
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusBadRequest, err.Error())
}
}
func GetPipelineSonar(projectId, pipelineId string) ([]*SonarStatus, error) {
jenkinsClient := admin_jenkins.Client()
job, err := jenkinsClient.GetJob(pipelineId, projectId)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
build, err := job.GetLastBuild()
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
sonarStatus, err := getBuildSonarResults(build)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusBadRequest, err.Error())
}
if len(sonarStatus) == 0 {
build, err := job.GetLastCompletedBuild()
if err != nil && utils.GetJenkinsStatusCode(err) != http.StatusNotFound {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
sonarStatus, err = getBuildSonarResults(build)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusBadRequest, err.Error())
}
}
return sonarStatus, nil
}
func GetMultiBranchPipelineSonar(projectId, pipelineId, branchId string) ([]*SonarStatus, error) {
jenkinsClient := admin_jenkins.Client()
job, err := jenkinsClient.GetJob(branchId, projectId, pipelineId)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
build, err := job.GetLastBuild()
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
sonarStatus, err := getBuildSonarResults(build)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusBadRequest, err.Error())
}
if len(sonarStatus) == 0 {
build, err := job.GetLastCompletedBuild()
if err != nil && utils.GetJenkinsStatusCode(err) != http.StatusNotFound {
glog.Errorf("%+v", err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
sonarStatus, err = getBuildSonarResults(build)
if err != nil {
glog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusBadRequest, err.Error())
}
}
return sonarStatus, nil
}
/*
Copyright 2019 The KubeSphere Authors.
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 devops
import (
"reflect"
"testing"
)
func Test_NoScmPipelineConfig(t *testing.T) {
inputs := []*NoScmPipeline{
&NoScmPipeline{
Name: "",
Description: "for test",
Jenkinsfile: "node{echo 'hello'}",
},
&NoScmPipeline{
Name: "",
Description: "",
Jenkinsfile: "node{echo 'hello'}",
},
&NoScmPipeline{
Name: "",
Description: "",
Jenkinsfile: "node{echo 'hello'}",
DisableConcurrent: true,
},
}
for _, input := range inputs {
outputString, err := createPipelineConfigXml(input)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
output, err := parsePipelineConfigXml(outputString)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
if !reflect.DeepEqual(input, output) {
t.Fatalf("input [%+v] output [%+v] should equal ", input, output)
}
}
}
func Test_NoScmPipelineConfig_Discarder(t *testing.T) {
inputs := []*NoScmPipeline{
&NoScmPipeline{
Name: "",
Description: "for test",
Jenkinsfile: "node{echo 'hello'}",
Discarder: &DiscarderProperty{
"3", "5",
},
},
&NoScmPipeline{
Name: "",
Description: "for test",
Jenkinsfile: "node{echo 'hello'}",
Discarder: &DiscarderProperty{
"3", "",
},
},
&NoScmPipeline{
Name: "",
Description: "for test",
Jenkinsfile: "node{echo 'hello'}",
Discarder: &DiscarderProperty{
"", "21321",
},
},
&NoScmPipeline{
Name: "",
Description: "for test",
Jenkinsfile: "node{echo 'hello'}",
Discarder: &DiscarderProperty{
"", "",
},
},
}
for _, input := range inputs {
outputString, err := createPipelineConfigXml(input)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
output, err := parsePipelineConfigXml(outputString)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
if !reflect.DeepEqual(input, output) {
t.Fatalf("input [%+v] output [%+v] should equal ", input, output)
}
}
}
func Test_NoScmPipelineConfig_Param(t *testing.T) {
inputs := []*NoScmPipeline{
&NoScmPipeline{
Name: "",
Description: "for test",
Jenkinsfile: "node{echo 'hello'}",
Parameters: []*Parameter{
&Parameter{
Name: "d",
DefaultValue: "a\nb",
Type: "choice",
Description: "fortest",
},
},
},
&NoScmPipeline{
Name: "",
Description: "for test",
Jenkinsfile: "node{echo 'hello'}",
Parameters: []*Parameter{
&Parameter{
Name: "a",
DefaultValue: "abc",
Type: "string",
Description: "fortest",
},
&Parameter{
Name: "b",
DefaultValue: "false",
Type: "boolean",
Description: "fortest",
},
&Parameter{
Name: "c",
DefaultValue: "password \n aaa",
Type: "text",
Description: "fortest",
},
&Parameter{
Name: "d",
DefaultValue: "a\nb",
Type: "choice",
Description: "fortest",
},
},
},
}
for _, input := range inputs {
outputString, err := createPipelineConfigXml(input)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
output, err := parsePipelineConfigXml(outputString)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
if !reflect.DeepEqual(input, output) {
t.Fatalf("input [%+v] output [%+v] should equal ", input, output)
}
}
}
func Test_NoScmPipelineConfig_Trigger(t *testing.T) {
inputs := []*NoScmPipeline{
&NoScmPipeline{
Name: "",
Description: "for test",
Jenkinsfile: "node{echo 'hello'}",
TimerTrigger: &TimerTrigger{
Cron: "1 1 1 * * *",
},
},
&NoScmPipeline{
Name: "",
Description: "for test",
Jenkinsfile: "node{echo 'hello'}",
RemoteTrigger: &RemoteTrigger{
Token: "abc",
},
},
&NoScmPipeline{
Name: "",
Description: "for test",
Jenkinsfile: "node{echo 'hello'}",
TimerTrigger: &TimerTrigger{
Cron: "1 1 1 * * *",
},
RemoteTrigger: &RemoteTrigger{
Token: "abc",
},
},
}
for _, input := range inputs {
outputString, err := createPipelineConfigXml(input)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
output, err := parsePipelineConfigXml(outputString)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
if !reflect.DeepEqual(input, output) {
t.Fatalf("input [%+v] output [%+v] should equal ", input, output)
}
}
}
func Test_MultiBranchPipelineConfig(t *testing.T) {
inputs := []*MultiBranchPipeline{
&MultiBranchPipeline{
Name: "",
Description: "for test",
ScriptPath: "Jenkinsfile",
SourceType: "git",
GitSource: &GitSource{},
},
&MultiBranchPipeline{
Name: "",
Description: "for test",
ScriptPath: "Jenkinsfile",
SourceType: "github",
GitHubSource: &GithubSource{},
},
&MultiBranchPipeline{
Name: "",
Description: "for test",
ScriptPath: "Jenkinsfile",
SourceType: "single_svn",
SingleSvnSource: &SingleSvnSource{},
},
&MultiBranchPipeline{
Name: "",
Description: "for test",
ScriptPath: "Jenkinsfile",
SourceType: "svn",
SvnSource: &SvnSource{},
},
}
for _, input := range inputs {
outputString, err := createMultiBranchPipelineConfigXml("", input)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
output, err := parseMultiBranchPipelineConfigXml(outputString)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
if !reflect.DeepEqual(input, output) {
t.Fatalf("input [%+v] output [%+v] should equal ", input, output)
}
}
}
func Test_MultiBranchPipelineConfig_Discarder(t *testing.T) {
inputs := []*MultiBranchPipeline{
&MultiBranchPipeline{
Name: "",
Description: "for test",
ScriptPath: "Jenkinsfile",
SourceType: "git",
Discarder: &DiscarderProperty{
DaysToKeep: "1",
NumToKeep: "2",
},
GitSource: &GitSource{},
},
}
for _, input := range inputs {
outputString, err := createMultiBranchPipelineConfigXml("", input)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
output, err := parseMultiBranchPipelineConfigXml(outputString)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
if !reflect.DeepEqual(input, output) {
t.Fatalf("input [%+v] output [%+v] should equal ", input, output)
}
}
}
func Test_MultiBranchPipelineConfig_TimerTrigger(t *testing.T) {
inputs := []*MultiBranchPipeline{
&MultiBranchPipeline{
Name: "",
Description: "for test",
ScriptPath: "Jenkinsfile",
SourceType: "git",
TimerTrigger: &TimerTrigger{
Interval: "12345566",
},
GitSource: &GitSource{},
},
}
for _, input := range inputs {
outputString, err := createMultiBranchPipelineConfigXml("", input)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
output, err := parseMultiBranchPipelineConfigXml(outputString)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
if !reflect.DeepEqual(input, output) {
t.Fatalf("input [%+v] output [%+v] should equal ", input, output)
}
}
}
func Test_MultiBranchPipelineConfig_Source(t *testing.T) {
inputs := []*MultiBranchPipeline{
&MultiBranchPipeline{
Name: "",
Description: "for test",
ScriptPath: "Jenkinsfile",
SourceType: "git",
TimerTrigger: &TimerTrigger{
Interval: "12345566",
},
GitSource: &GitSource{
Url: "https://github.com/kubesphere/devops",
CredentialId: "git",
DiscoverBranches: true,
},
},
&MultiBranchPipeline{
Name: "",
Description: "for test",
ScriptPath: "Jenkinsfile",
SourceType: "github",
TimerTrigger: &TimerTrigger{
Interval: "12345566",
},
GitHubSource: &GithubSource{
Owner: "kubesphere",
Repo: "devops",
CredentialId: "github",
ApiUri: "https://api.github.com",
DiscoverBranches: 1,
DiscoverPRFromOrigin: 2,
DiscoverPRFromForks: &GithubDiscoverPRFromForks{
Strategy: 1,
Trust: 1,
},
},
},
&MultiBranchPipeline{
Name: "",
Description: "for test",
ScriptPath: "Jenkinsfile",
SourceType: "svn",
TimerTrigger: &TimerTrigger{
Interval: "12345566",
},
SvnSource: &SvnSource{
Remote: "https://api.svn.com/bcd",
CredentialId: "svn",
Excludes: "truck",
Includes: "tag/*",
},
},
&MultiBranchPipeline{
Name: "",
Description: "for test",
ScriptPath: "Jenkinsfile",
SourceType: "single_svn",
TimerTrigger: &TimerTrigger{
Interval: "12345566",
},
SingleSvnSource: &SingleSvnSource{
Remote: "https://api.svn.com/bcd",
CredentialId: "svn",
},
},
}
for _, input := range inputs {
outputString, err := createMultiBranchPipelineConfigXml("", input)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
output, err := parseMultiBranchPipelineConfigXml(outputString)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
if !reflect.DeepEqual(input, output) {
t.Fatalf("input [%+v] output [%+v] should equal ", input, output)
}
}
}
func Test_MultiBranchPipelineCloneConfig(t *testing.T) {
inputs := []*MultiBranchPipeline{
&MultiBranchPipeline{
Name: "",
Description: "for test",
ScriptPath: "Jenkinsfile",
SourceType: "git",
GitSource: &GitSource{
Url: "https://github.com/kubesphere/devops",
CredentialId: "git",
DiscoverBranches: true,
CloneOption: &GitCloneOption{
Shallow: false,
Depth: 3,
Timeout: 20,
},
},
},
&MultiBranchPipeline{
Name: "",
Description: "for test",
ScriptPath: "Jenkinsfile",
SourceType: "github",
GitHubSource: &GithubSource{
Owner: "kubesphere",
Repo: "devops",
CredentialId: "github",
ApiUri: "https://api.github.com",
DiscoverBranches: 1,
DiscoverPRFromOrigin: 2,
DiscoverPRFromForks: &GithubDiscoverPRFromForks{
Strategy: 1,
Trust: 1,
},
CloneOption: &GitCloneOption{
Shallow: false,
Depth: 3,
Timeout: 20,
},
},
},
}
for _, input := range inputs {
outputString, err := createMultiBranchPipelineConfigXml("", input)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
output, err := parseMultiBranchPipelineConfigXml(outputString)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
if !reflect.DeepEqual(input, output) {
t.Fatalf("input [%+v] output [%+v] should equal ", input, output)
}
}
}
func Test_MultiBranchPipelineRegexFilter(t *testing.T) {
inputs := []*MultiBranchPipeline{
&MultiBranchPipeline{
Name: "",
Description: "for test",
ScriptPath: "Jenkinsfile",
SourceType: "git",
GitSource: &GitSource{
Url: "https://github.com/kubesphere/devops",
CredentialId: "git",
DiscoverBranches: true,
RegexFilter: ".*",
},
},
&MultiBranchPipeline{
Name: "",
Description: "for test",
ScriptPath: "Jenkinsfile",
SourceType: "github",
GitHubSource: &GithubSource{
Owner: "kubesphere",
Repo: "devops",
CredentialId: "github",
ApiUri: "https://api.github.com",
DiscoverBranches: 1,
DiscoverPRFromOrigin: 2,
DiscoverPRFromForks: &GithubDiscoverPRFromForks{
Strategy: 1,
Trust: 1,
},
RegexFilter: ".*",
},
},
}
for _, input := range inputs {
outputString, err := createMultiBranchPipelineConfigXml("", input)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
output, err := parseMultiBranchPipelineConfigXml(outputString)
if err != nil {
t.Fatalf("should not get error %+v", err)
}
if !reflect.DeepEqual(input, output) {
t.Fatalf("input [%+v] output [%+v] should equal ", input, output)
}
}
}
......@@ -19,6 +19,7 @@ package tenant
import (
"fmt"
"github.com/emicklei/go-restful"
"github.com/gocraft/dbr"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/db"
......@@ -29,248 +30,15 @@ import (
"kubesphere.io/kubesphere/pkg/params"
"kubesphere.io/kubesphere/pkg/simple/client/admin_jenkins"
"kubesphere.io/kubesphere/pkg/simple/client/devops_mysql"
"kubesphere.io/kubesphere/pkg/utils/reflectutils"
"net/http"
"sync"
)
const (
ProjectOwner = "owner"
ProjectMaintainer = "maintainer"
ProjectDeveloper = "developer"
ProjectReporter = "reporter"
)
var AllRoleSlice = []string{ProjectDeveloper, ProjectReporter, ProjectMaintainer, ProjectOwner}
var JenkinsOwnerProjectPermissionIds = &gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: true,
ItemCreate: true,
ItemDelete: true,
ItemDiscover: true,
ItemMove: true,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
}
var JenkinsProjectPermissionMap = map[string]gojenkins.ProjectPermissionIds{
ProjectOwner: gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: true,
ItemCreate: true,
ItemDelete: true,
ItemDiscover: true,
ItemMove: true,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
},
ProjectMaintainer: gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: false,
ItemCreate: true,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
},
ProjectDeveloper: gojenkins.ProjectPermissionIds{
CredentialCreate: false,
CredentialDelete: false,
CredentialManageDomains: false,
CredentialUpdate: false,
CredentialView: false,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: false,
ItemCreate: false,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: false,
},
ProjectReporter: gojenkins.ProjectPermissionIds{
CredentialCreate: false,
CredentialDelete: false,
CredentialManageDomains: false,
CredentialUpdate: false,
CredentialView: false,
ItemBuild: false,
ItemCancel: false,
ItemConfigure: false,
ItemCreate: false,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: false,
RunDelete: false,
RunReplay: false,
RunUpdate: false,
SCMTag: false,
},
}
var JenkinsPipelinePermissionMap = map[string]gojenkins.ProjectPermissionIds{
ProjectOwner: gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: true,
ItemCreate: true,
ItemDelete: true,
ItemDiscover: true,
ItemMove: true,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
},
ProjectMaintainer: gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: true,
ItemCreate: true,
ItemDelete: true,
ItemDiscover: true,
ItemMove: true,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
},
ProjectDeveloper: gojenkins.ProjectPermissionIds{
CredentialCreate: false,
CredentialDelete: false,
CredentialManageDomains: false,
CredentialUpdate: false,
CredentialView: false,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: false,
ItemCreate: false,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: false,
},
ProjectReporter: gojenkins.ProjectPermissionIds{
CredentialCreate: false,
CredentialDelete: false,
CredentialManageDomains: false,
CredentialUpdate: false,
CredentialView: false,
ItemBuild: false,
ItemCancel: false,
ItemConfigure: false,
ItemCreate: false,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: false,
RunDelete: false,
RunReplay: false,
RunUpdate: false,
SCMTag: false,
},
}
func GetProjectRoleName(projectId, role string) string {
return fmt.Sprintf("%s-%s-project", projectId, role)
}
func GetPipelineRoleName(projectId, role string) string {
return fmt.Sprintf("%s-%s-pipeline", projectId, role)
}
func GetProjectRolePattern(projectId string) string {
return fmt.Sprintf("^%s$", projectId)
}
func GetPipelineRolePattern(projectId string) string {
return fmt.Sprintf("^%s/.*", projectId)
}
type DevOpsProjectRoleResponse struct {
ProjectRole *gojenkins.ProjectRole
Err error
}
func CheckProjectUserInRole(username, projectId string, roles []string) error {
if username == devops.KS_ADMIN {
return nil
}
dbconn := devops_mysql.OpenDatabase()
membership := &devops.DevOpsProjectMembership{}
err := dbconn.Select(devops.DevOpsProjectMembershipColumns...).
From(devops.DevOpsProjectMembershipTableName).
Where(db.And(
db.Eq(devops.DevOpsProjectMembershipUsernameColumn, username),
db.Eq(devops.DevOpsProjectMembershipProjectIdColumn, projectId))).LoadOne(membership)
if err != nil {
return err
}
if !reflectutils.In(membership.Role, roles) {
return fmt.Errorf("user [%s] in project [%s] role is not in %s", username, projectId, roles)
}
return nil
}
func ListDevopsProjects(workspace, username string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error) {
dbconn := devops_mysql.OpenDatabase()
......@@ -321,12 +89,12 @@ func ListDevopsProjects(workspace, username string, conditions *params.Condition
_, err := query.Load(&projects)
if err != nil {
glog.Errorf("%+v", err)
return nil, err
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
count, err := query.Count()
if err != nil {
glog.Errorf("%+v", err)
return nil, err
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
result := make([]interface{}, 0)
......@@ -337,11 +105,11 @@ func ListDevopsProjects(workspace, username string, conditions *params.Condition
return &models.PageableResponse{Items: result, TotalCount: int(count)}, nil
}
func DeleteDevOpsProject(projectId, username string) (error, int) {
err := CheckProjectUserInRole(username, projectId, []string{ProjectOwner})
func DeleteDevOpsProject(projectId, username string) error {
err := devops.CheckProjectUserInRole(username, projectId, []string{devops.ProjectOwner})
if err != nil {
glog.Errorf("%+v", err)
return err, http.StatusForbidden
return restful.NewError(http.StatusForbidden, err.Error())
}
gojenkins := admin_jenkins.Client()
devopsdb := devops_mysql.OpenDatabase()
......@@ -349,31 +117,31 @@ func DeleteDevOpsProject(projectId, username string) (error, int) {
if err != nil && utils.GetJenkinsStatusCode(err) != http.StatusNotFound {
glog.Errorf("%+v", err)
return err, utils.GetJenkinsStatusCode(err)
return restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
roleNames := make([]string, 0)
for role := range JenkinsProjectPermissionMap {
roleNames = append(roleNames, GetProjectRoleName(projectId, role))
roleNames = append(roleNames, GetPipelineRoleName(projectId, role))
for role := range devops.JenkinsProjectPermissionMap {
roleNames = append(roleNames, devops.GetProjectRoleName(projectId, role))
roleNames = append(roleNames, devops.GetPipelineRoleName(projectId, role))
}
err = gojenkins.DeleteProjectRoles(roleNames...)
if err != nil {
glog.Errorf("%+v", err)
return err, utils.GetJenkinsStatusCode(err)
return restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
_, err = devopsdb.DeleteFrom(devops.DevOpsProjectMembershipTableName).
Where(db.Eq(devops.DevOpsProjectMembershipProjectIdColumn, projectId)).Exec()
if err != nil {
glog.Errorf("%+v", err)
return err, http.StatusInternalServerError
return restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
_, err = devopsdb.Update(devops.DevOpsProjectTableName).
Set(devops.StatusColumn, devops.StatusDeleted).
Where(db.Eq(devops.DevOpsProjectIdColumn, projectId)).Exec()
if err != nil {
glog.Errorf("%+v", err)
return err, http.StatusInternalServerError
return restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
project := &devops.DevOpsProject{}
err = devopsdb.Select(devops.DevOpsProjectColumns...).
......@@ -382,12 +150,12 @@ func DeleteDevOpsProject(projectId, username string) (error, int) {
LoadOne(project)
if err != nil {
glog.Errorf("%+v", err)
return err, http.StatusInternalServerError
return restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
return nil, http.StatusOK
return nil
}
func CreateDevopsProject(username string, workspace string, req *devops.DevOpsProject) (*devops.DevOpsProject, error, int) {
func CreateDevopsProject(username string, workspace string, req *devops.DevOpsProject) (*devops.DevOpsProject, error) {
jenkinsClient := admin_jenkins.Client()
devopsdb := devops_mysql.OpenDatabase()
......@@ -395,25 +163,25 @@ func CreateDevopsProject(username string, workspace string, req *devops.DevOpsPr
_, err := jenkinsClient.CreateFolder(project.ProjectId, project.Description)
if err != nil {
glog.Errorf("%+v", err)
return nil, err, utils.GetJenkinsStatusCode(err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
var addRoleCh = make(chan *DevOpsProjectRoleResponse, 8)
var addRoleWg sync.WaitGroup
for role, permission := range JenkinsProjectPermissionMap {
for role, permission := range devops.JenkinsProjectPermissionMap {
addRoleWg.Add(1)
go func(role string, permission gojenkins.ProjectPermissionIds) {
_, err := jenkinsClient.AddProjectRole(GetProjectRoleName(project.ProjectId, role),
GetProjectRolePattern(project.ProjectId), permission, true)
_, err := jenkinsClient.AddProjectRole(devops.GetProjectRoleName(project.ProjectId, role),
devops.GetProjectRolePattern(project.ProjectId), permission, true)
addRoleCh <- &DevOpsProjectRoleResponse{nil, err}
addRoleWg.Done()
}(role, permission)
}
for role, permission := range JenkinsPipelinePermissionMap {
for role, permission := range devops.JenkinsPipelinePermissionMap {
addRoleWg.Add(1)
go func(role string, permission gojenkins.ProjectPermissionIds) {
_, err := jenkinsClient.AddProjectRole(GetPipelineRoleName(project.ProjectId, role),
GetPipelineRolePattern(project.ProjectId), permission, true)
_, err := jenkinsClient.AddProjectRole(devops.GetPipelineRoleName(project.ProjectId, role),
devops.GetPipelineRolePattern(project.ProjectId), permission, true)
addRoleCh <- &DevOpsProjectRoleResponse{nil, err}
addRoleWg.Done()
}(role, permission)
......@@ -423,14 +191,14 @@ func CreateDevopsProject(username string, workspace string, req *devops.DevOpsPr
for addRoleResponse := range addRoleCh {
if addRoleResponse.Err != nil {
glog.Errorf("%+v", addRoleResponse.Err)
return nil, addRoleResponse.Err, utils.GetJenkinsStatusCode(addRoleResponse.Err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(addRoleResponse.Err), addRoleResponse.Err.Error())
}
}
globalRole, err := jenkinsClient.GetGlobalRole(devops.JenkinsAllUserRoleName)
if err != nil {
glog.Errorf("%+v", err)
return nil, err, utils.GetJenkinsStatusCode(err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
if globalRole == nil {
_, err := jenkinsClient.AddGlobalRole(devops.JenkinsAllUserRoleName, gojenkins.GlobalPermissionIds{
......@@ -438,74 +206,60 @@ func CreateDevopsProject(username string, workspace string, req *devops.DevOpsPr
}, true)
if err != nil {
glog.Error("failed to create jenkins global role")
return nil, err, utils.GetJenkinsStatusCode(err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
}
err = globalRole.AssignRole(username)
if err != nil {
glog.Errorf("%+v", err)
return nil, err, utils.GetJenkinsStatusCode(err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
projectRole, err := jenkinsClient.GetProjectRole(GetProjectRoleName(project.ProjectId, ProjectOwner))
projectRole, err := jenkinsClient.GetProjectRole(devops.GetProjectRoleName(project.ProjectId, devops.ProjectOwner))
if err != nil {
glog.Errorf("%+v", err)
return nil, err, utils.GetJenkinsStatusCode(err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = projectRole.AssignRole(username)
if err != nil {
glog.Errorf("%+v", err)
return nil, err, utils.GetJenkinsStatusCode(err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
pipelineRole, err := jenkinsClient.GetProjectRole(GetPipelineRoleName(project.ProjectId, ProjectOwner))
pipelineRole, err := jenkinsClient.GetProjectRole(devops.GetPipelineRoleName(project.ProjectId, devops.ProjectOwner))
if err != nil {
glog.Errorf("%+v", err)
return nil, err, utils.GetJenkinsStatusCode(err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
err = pipelineRole.AssignRole(username)
if err != nil {
glog.Errorf("%+v", err)
return nil, err, utils.GetJenkinsStatusCode(err)
return nil, restful.NewError(utils.GetJenkinsStatusCode(err), err.Error())
}
_, err = devopsdb.InsertInto(devops.DevOpsProjectTableName).
Columns(devops.DevOpsProjectColumns...).Record(project).Exec()
if err != nil {
glog.Errorf("%+v", err)
return nil, err, http.StatusInternalServerError
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
projectMembership := devops.NewDevOpsProjectMemberShip(username, project.ProjectId, ProjectOwner, username)
projectMembership := devops.NewDevOpsProjectMemberShip(username, project.ProjectId, devops.ProjectOwner, username)
_, err = devopsdb.InsertInto(devops.DevOpsProjectMembershipTableName).
Columns(devops.DevOpsProjectMembershipColumns...).Record(projectMembership).Exec()
if err != nil {
glog.Errorf("%+v", err)
return nil, err, http.StatusInternalServerError
return nil, restful.NewError(http.StatusInternalServerError, err.Error())
}
return project, nil, http.StatusOK
return project, nil
}
func GetUserDevopsSimpleRules(username, projectId string) ([]models.SimpleRule, error, int) {
err := CheckProjectUserInRole(username, projectId, AllRoleSlice)
if err != nil {
glog.Errorf("%+v", err)
return nil, err, http.StatusForbidden
}
dbconn := devops_mysql.OpenDatabase()
memberships := &devops.DevOpsProjectMembership{}
err = dbconn.Select(devops.DevOpsProjectMembershipColumns...).
From(devops.DevOpsProjectMembershipTableName).
Where(db.And(
db.Eq(devops.DevOpsProjectMembershipProjectIdColumn, projectId),
db.Eq(devops.DevOpsProjectMembershipUsernameColumn, username))).
LoadOne(&memberships)
func GetUserDevopsSimpleRules(username, projectId string) ([]models.SimpleRule, error) {
role, err := devops.GetProjectUserRole(username, projectId)
if err != nil {
glog.Errorf("%+v", err)
return nil, err, http.StatusInternalServerError
return nil, restful.NewError(http.StatusForbidden, err.Error())
}
return GetDevopsRoleSimpleRules(memberships.Role), nil, http.StatusOK
return GetDevopsRoleSimpleRules(role), nil
}
func GetDevopsRoleSimpleRules(role string) []models.SimpleRule {
......
/*
Copyright 2018 The KubeSphere Authors.
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 admin_jenkins
import (
......
Copyright (c) 2012-2016, Martin Angers & Contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package goquery
import (
"golang.org/x/net/html"
)
const (
maxUint = ^uint(0)
maxInt = int(maxUint >> 1)
// ToEnd is a special index value that can be used as end index in a call
// to Slice so that all elements are selected until the end of the Selection.
// It is equivalent to passing (*Selection).Length().
ToEnd = maxInt
)
// First reduces the set of matched elements to the first in the set.
// It returns a new Selection object, and an empty Selection object if the
// the selection is empty.
func (s *Selection) First() *Selection {
return s.Eq(0)
}
// Last reduces the set of matched elements to the last in the set.
// It returns a new Selection object, and an empty Selection object if
// the selection is empty.
func (s *Selection) Last() *Selection {
return s.Eq(-1)
}
// Eq reduces the set of matched elements to the one at the specified index.
// If a negative index is given, it counts backwards starting at the end of the
// set. It returns a new Selection object, and an empty Selection object if the
// index is invalid.
func (s *Selection) Eq(index int) *Selection {
if index < 0 {
index += len(s.Nodes)
}
if index >= len(s.Nodes) || index < 0 {
return newEmptySelection(s.document)
}
return s.Slice(index, index+1)
}
// Slice reduces the set of matched elements to a subset specified by a range
// of indices. The start index is 0-based and indicates the index of the first
// element to select. The end index is 0-based and indicates the index at which
// the elements stop being selected (the end index is not selected).
//
// The indices may be negative, in which case they represent an offset from the
// end of the selection.
//
// The special value ToEnd may be specified as end index, in which case all elements
// until the end are selected. This works both for a positive and negative start
// index.
func (s *Selection) Slice(start, end int) *Selection {
if start < 0 {
start += len(s.Nodes)
}
if end == ToEnd {
end = len(s.Nodes)
} else if end < 0 {
end += len(s.Nodes)
}
return pushStack(s, s.Nodes[start:end])
}
// Get retrieves the underlying node at the specified index.
// Get without parameter is not implemented, since the node array is available
// on the Selection object.
func (s *Selection) Get(index int) *html.Node {
if index < 0 {
index += len(s.Nodes) // Negative index gets from the end
}
return s.Nodes[index]
}
// Index returns the position of the first element within the Selection object
// relative to its sibling elements.
func (s *Selection) Index() int {
if len(s.Nodes) > 0 {
return newSingleSelection(s.Nodes[0], s.document).PrevAll().Length()
}
return -1
}
// IndexSelector returns the position of the first element within the
// Selection object relative to the elements matched by the selector, or -1 if
// not found.
func (s *Selection) IndexSelector(selector string) int {
if len(s.Nodes) > 0 {
sel := s.document.Find(selector)
return indexInSlice(sel.Nodes, s.Nodes[0])
}
return -1
}
// IndexMatcher returns the position of the first element within the
// Selection object relative to the elements matched by the matcher, or -1 if
// not found.
func (s *Selection) IndexMatcher(m Matcher) int {
if len(s.Nodes) > 0 {
sel := s.document.FindMatcher(m)
return indexInSlice(sel.Nodes, s.Nodes[0])
}
return -1
}
// IndexOfNode returns the position of the specified node within the Selection
// object, or -1 if not found.
func (s *Selection) IndexOfNode(node *html.Node) int {
return indexInSlice(s.Nodes, node)
}
// IndexOfSelection returns the position of the first node in the specified
// Selection object within this Selection object, or -1 if not found.
func (s *Selection) IndexOfSelection(sel *Selection) int {
if sel != nil && len(sel.Nodes) > 0 {
return indexInSlice(s.Nodes, sel.Nodes[0])
}
return -1
}
// Copyright (c) 2012-2016, Martin Angers & Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
// * Neither the name of the author nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
// WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/*
Package goquery implements features similar to jQuery, including the chainable
syntax, to manipulate and query an HTML document.
It brings a syntax and a set of features similar to jQuery to the Go language.
It is based on Go's net/html package and the CSS Selector library cascadia.
Since the net/html parser returns nodes, and not a full-featured DOM
tree, jQuery's stateful manipulation functions (like height(), css(), detach())
have been left off.
Also, because the net/html parser requires UTF-8 encoding, so does goquery: it is
the caller's responsibility to ensure that the source document provides UTF-8 encoded HTML.
See the repository's wiki for various options on how to do this.
Syntax-wise, it is as close as possible to jQuery, with the same method names when
possible, and that warm and fuzzy chainable interface. jQuery being the
ultra-popular library that it is, writing a similar HTML-manipulating
library was better to follow its API than to start anew (in the same spirit as
Go's fmt package), even though some of its methods are less than intuitive (looking
at you, index()...).
It is hosted on GitHub, along with additional documentation in the README.md
file: https://github.com/puerkitobio/goquery
Please note that because of the net/html dependency, goquery requires Go1.1+.
The various methods are split into files based on the category of behavior.
The three dots (...) indicate that various "overloads" are available.
* array.go : array-like positional manipulation of the selection.
- Eq()
- First()
- Get()
- Index...()
- Last()
- Slice()
* expand.go : methods that expand or augment the selection's set.
- Add...()
- AndSelf()
- Union(), which is an alias for AddSelection()
* filter.go : filtering methods, that reduce the selection's set.
- End()
- Filter...()
- Has...()
- Intersection(), which is an alias of FilterSelection()
- Not...()
* iteration.go : methods to loop over the selection's nodes.
- Each()
- EachWithBreak()
- Map()
* manipulation.go : methods for modifying the document
- After...()
- Append...()
- Before...()
- Clone()
- Empty()
- Prepend...()
- Remove...()
- ReplaceWith...()
- Unwrap()
- Wrap...()
- WrapAll...()
- WrapInner...()
* property.go : methods that inspect and get the node's properties values.
- Attr*(), RemoveAttr(), SetAttr()
- AddClass(), HasClass(), RemoveClass(), ToggleClass()
- Html()
- Length()
- Size(), which is an alias for Length()
- Text()
* query.go : methods that query, or reflect, a node's identity.
- Contains()
- Is...()
* traversal.go : methods to traverse the HTML document tree.
- Children...()
- Contents()
- Find...()
- Next...()
- Parent[s]...()
- Prev...()
- Siblings...()
* type.go : definition of the types exposed by goquery.
- Document
- Selection
- Matcher
* utilities.go : definition of helper functions (and not methods on a *Selection)
that are not part of jQuery, but are useful to goquery.
- NodeName
- OuterHtml
*/
package goquery
此差异已折叠。
此差异已折叠。
package goquery
// Each iterates over a Selection object, executing a function for each
// matched element. It returns the current Selection object. The function
// f is called for each element in the selection with the index of the
// element in that selection starting at 0, and a *Selection that contains
// only that element.
func (s *Selection) Each(f func(int, *Selection)) *Selection {
for i, n := range s.Nodes {
f(i, newSingleSelection(n, s.document))
}
return s
}
// EachWithBreak iterates over a Selection object, executing a function for each
// matched element. It is identical to Each except that it is possible to break
// out of the loop by returning false in the callback function. It returns the
// current Selection object.
func (s *Selection) EachWithBreak(f func(int, *Selection) bool) *Selection {
for i, n := range s.Nodes {
if !f(i, newSingleSelection(n, s.document)) {
return s
}
}
return s
}
// Map passes each element in the current matched set through a function,
// producing a slice of string holding the returned values. The function
// f is called for each element in the selection with the index of the
// element in that selection starting at 0, and a *Selection that contains
// only that element.
func (s *Selection) Map(f func(int, *Selection) string) (result []string) {
for i, n := range s.Nodes {
result = append(result, f(i, newSingleSelection(n, s.document)))
}
return result
}
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册