diff --git a/pkg/apis/devops/v1alpha2/register.go b/pkg/apis/devops/v1alpha2/register.go index 2dcc80c83..c2d6eefeb 100644 --- a/pkg/apis/devops/v1alpha2/register.go +++ b/pkg/apis/devops/v1alpha2/register.go @@ -633,30 +633,30 @@ The last one is encrypted info, such as the password of the username-password ty Param(webservice.PathParameter("file", "the name of binary file")). Returns(http.StatusOK, RespOK, nil)) - // TODO are not used in this version. will be added in 2.1.0 - //// match /job/init-job/descriptorByName/org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile - //webservice.Route(webservice.POST("/devops/check/scriptcompile"). - // To(devopsapi.CheckScriptCompile). - // Metadata(restfulspec.KeyOpenAPITags, tags). - // Consumes("application/x-www-form-urlencoded", "charset=utf-8"). - // Produces("application/json", "charset=utf-8"). - // Doc("Check pipeline script compile."). - // Reads(devops.ReqScript{}). - // Returns(http.StatusOK, RespOK, devops.CheckScript{}). - // Writes(devops.CheckScript{})) + webservice.Route(webservice.POST("/devops/{devops}/pipelines/{pipeline}/checkScriptCompile"). + To(devopsapi.CheckScriptCompile). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}). + Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")). + Param(webservice.QueryParameter("pipeline", "the name of the CI/CD pipeline"). + Required(false). + DataFormat("pipeline=%s")). + Consumes("application/x-www-form-urlencoded", "charset=utf-8"). + Produces("application/json", "charset=utf-8"). + Doc("Check pipeline script compile."). + Reads(devops.ReqScript{}). + Returns(http.StatusOK, RespOK, devops.CheckScript{}). + Writes(devops.CheckScript{})) - // match /job/init-job/descriptorByName/hudson.triggers.TimerTrigger/checkSpec - //webservice.Route(webservice.GET("/devops/check/cron"). - // To(devopsapi.CheckCron). - // Metadata(restfulspec.KeyOpenAPITags, tags). - // Produces("application/json", "charset=utf-8"). - // Doc("Check cron script compile."). - // Param(webservice.QueryParameter("value", "string of cron script."). - // Required(true). - // DataFormat("value=%s")). - // Returns(http.StatusOK, RespOK, []devops.QueuedBlueRun{}). - // Returns(http.StatusOK, RespOK, devops.CheckCronRes{}). - // Writes(devops.CheckCronRes{})) + webservice.Route(webservice.POST("/devops/{devops}/checkCron"). + To(devopsapi.CheckCron). + Metadata(restfulspec.KeyOpenAPITags, []string{constants.DevOpsPipelineTag}). + Param(webservice.PathParameter("devops", "DevOps project's ID, e.g. project-RRRRAzLBlLEm")). + Param(webservice.PathParameter("pipeline", "the name of the CI/CD pipeline")). + Produces("application/json", "charset=utf-8"). + Doc("Check cron script compile."). + Reads(devops.CronData{}). + Returns(http.StatusOK, RespOK, devops.CheckCronRes{}). + Writes(devops.CheckCronRes{})) // match /blue/rest/organizations/jenkins/pipelines/{devops}/{pipeline}/runs/{run}/ webservice.Route(webservice.GET("/devops/{devops}/pipelines/{pipeline}/runs/{run}"). diff --git a/pkg/apiserver/devops/devops.go b/pkg/apiserver/devops/devops.go index aab712342..1b0c15ba8 100644 --- a/pkg/apiserver/devops/devops.go +++ b/pkg/apiserver/devops/devops.go @@ -443,7 +443,10 @@ func GetCrumb(req *restful.Request, resp *restful.Response) { } func CheckScriptCompile(req *restful.Request, resp *restful.Response) { - resBody, err := devops.CheckScriptCompile(req.Request) + projectName := req.PathParameter("devops") + pipelineName := req.PathParameter("pipeline") + + resBody, err := devops.CheckScriptCompile(projectName, pipelineName, req.Request) if err != nil { parseErr(err, resp) return @@ -467,7 +470,9 @@ func CheckScriptCompile(req *restful.Request, resp *restful.Response) { } func CheckCron(req *restful.Request, resp *restful.Response) { - res, err := devops.CheckCron(req.Request) + projectName := req.PathParameter("devops") + + res, err := devops.CheckCron(projectName, req.Request) if err != nil { parseErr(err, resp) return diff --git a/pkg/models/devops/devops.go b/pkg/models/devops/devops.go index 663ee31a2..ebf31a5bf 100644 --- a/pkg/models/devops/devops.go +++ b/pkg/models/devops/devops.go @@ -30,11 +30,15 @@ import ( "kubesphere.io/kubesphere/pkg/simple/client/admin_jenkins" "net/http" "net/url" + "strings" "sync" "time" ) -const channelMaxCapacity = 100 +const ( + channelMaxCapacity = 100 + cronJobLayout = "Monday, January 2, 2006 15:04:05 PM" +) var jenkins *gojenkins.Jenkins @@ -485,10 +489,9 @@ func GetCrumb(req *http.Request) ([]byte, error) { return res, err } -func CheckScriptCompile(req *http.Request) ([]byte, error) { - baseUrl := jenkins.Server + CheckScriptCompileUrl +func CheckScriptCompile(projectName, pipelineName string, req *http.Request) ([]byte, error) { + baseUrl := fmt.Sprintf(jenkins.Server+CheckScriptCompileUrl, projectName, pipelineName) log.Info("Jenkins-url: " + baseUrl) - req.SetBasicAuth(jenkins.Requester.BasicAuth.Username, jenkins.Requester.BasicAuth.Password) resBody, err := sendJenkinsRequest(baseUrl, req) if err != nil { @@ -499,38 +502,96 @@ func CheckScriptCompile(req *http.Request) ([]byte, error) { return resBody, err } -func CheckCron(req *http.Request) (*CheckCronRes, error) { - newurl, err := url.Parse(jenkins.Server + CheckCronUrl + req.URL.RawQuery) +func CheckCron(projectName string, req *http.Request) (*CheckCronRes, error) { + var res = new(CheckCronRes) + var cron = new(CronData) + var reader io.ReadCloser + var baseUrl string + + reader = req.Body + cronData, err := ioutil.ReadAll(reader) + json.Unmarshal(cronData, cron) + + if cron.PipelineName != "" { + baseUrl = fmt.Sprintf(jenkins.Server+CheckPipelienCronUrl, projectName, cron.PipelineName, cron.Cron) + } else { + baseUrl = fmt.Sprintf(jenkins.Server+CheckCronUrl, projectName, cron.Cron) + } + + log.Info("Jenkins-url: " + baseUrl) + newurl, err := url.Parse(baseUrl) + if err != nil { + log.Error(err) + return nil, err + } + newurl.RawQuery = newurl.Query().Encode() reqJenkins := &http.Request{ Method: http.MethodGet, URL: newurl, - Header: http.Header{}, + Header: req.Header, } - var res = new(CheckCronRes) + client := &http.Client{Timeout: 30 * time.Second} - reqJenkins.SetBasicAuth(jenkins.Requester.BasicAuth.Username, jenkins.Requester.BasicAuth.Password) - resp, err := client.Do(reqJenkins) + if resp.StatusCode != http.StatusOK { + resBody, _ := getRespBody(resp) + return &CheckCronRes{ + Result: "error", + Message: string(resBody), + }, err + } if err != nil { log.Error(err) - return res, err + return nil, err } defer resp.Body.Close() doc, err := goquery.NewDocumentFromReader(resp.Body) if err != nil { log.Error(err) - return res, err + return nil, err } doc.Find("div").Each(func(i int, selection *goquery.Selection) { res.Message = selection.Text() res.Result, _ = selection.Attr("class") }) + if res.Result == "ok" { + res.LastTime, res.NextTime, err = parseCronJobTime(res.Message) + if err != nil { + log.Error(err) + return nil, err + } + } + return res, err } +func parseCronJobTime(msg string) (string, string, error) { + times := strings.Split(msg, ";") + + lastTmp := strings.SplitN(times[0], " ", 2) + lastTime := strings.SplitN(lastTmp[1], " UTC", 2) + lastUinx, err := time.Parse(cronJobLayout, lastTime[0]) + if err != nil { + log.Error(err) + return "","",err + } + last := lastUinx.Format(time.RFC3339) + + nextTmp := strings.SplitN(times[1], " ", 3) + nextTime := strings.SplitN(nextTmp[2], " UTC", 2) + nextUinx, err := time.Parse(cronJobLayout, nextTime[0]) + if err != nil { + log.Error(err) + return "","",err + } + next := nextUinx.Format(time.RFC3339) + + return last, next, nil +} + func GetPipelineRun(projectName, pipelineName, runId string, req *http.Request) ([]byte, error) { baseUrl := fmt.Sprintf(jenkins.Server+GetPipelineRunUrl, projectName, pipelineName, runId) log.Info("Jenkins-url: " + baseUrl) diff --git a/pkg/models/devops/devops_test.go b/pkg/models/devops/devops_test.go new file mode 100644 index 000000000..d16c5f864 --- /dev/null +++ b/pkg/models/devops/devops_test.go @@ -0,0 +1,36 @@ +package devops + +import "testing" + +func Test_parseCronJobTime(t *testing.T) { + type Except struct { + Last string + Next string + } + + Items := []struct { + Input string + Expected Except + }{ + {"上次运行的时间 Tuesday, September 10, 2019 8:59:09 AM UTC; 下次运行的时间 Tuesday, September 10, 2019 9:14:09 AM UTC.", Except{Last: "2019-09-10T08:59:09Z", Next: "2019-09-10T09:14:09Z"}}, + {"上次运行的时间 Thursday, January 3, 2019 11:56:30 PM UTC; 下次运行的时间 Friday, January 3, 2020 12:11:30 AM UTC.", Except{Last: "2019-01-03T23:56:30Z", Next: "2020-01-03T00:11:30Z"}}, + {"上次运行的时间 Tuesday, September 10, 2019 8:41:34 AM UTC; 下次运行的时间 Tuesday, September 10, 2019 9:41:34 AM UTC.", Except{Last: "2019-09-10T08:41:34Z", Next: "2019-09-10T09:41:34Z"}}, + {"上次运行的时间 Tuesday, September 10, 2019 9:15:26 AM UTC; 下次运行的时间 Tuesday, September 10, 2019 10:03:26 AM UTC.", Except{Last: "2019-09-10T09:15:26Z", Next: "2019-09-10T10:03:26Z"}}, + } + + for _, item:=range Items { + last, next, err := parseCronJobTime(item.Input) + if err != nil { + t.Fatalf("should not get error %+v", err) + } + + if last != item.Expected.Last { + t.Errorf("got %#v, expected %#v", last, item.Expected.Last) + } + + if next != item.Expected.Next { + t.Errorf("got %#v, expected %#v", next, item.Expected.Next) + } + + } +} diff --git a/pkg/models/devops/json.go b/pkg/models/devops/json.go index ab0520eaf..91a24b7a8 100644 --- a/pkg/models/devops/json.go +++ b/pkg/models/devops/json.go @@ -755,14 +755,21 @@ type Crumb struct { type CheckScript struct { Column int `json:"column,omitempty" description:"column e.g. 0"` Line int `json:"line,omitempty" description:"line e.g. 0"` - Message string `json:"message,omitempty" description:"message e.g. success"` - Status string `json:"status,omitempty" description:"status e.g. success"` + Message string `json:"message,omitempty" description:"message e.g. unexpected char: '#'"` + Status string `json:"status,omitempty" description:"status e.g. fail"` } // CheckCron +type CronData struct { + PipelineName string `json:"pipelineName,omitempty" description:"Pipeline name, if pipeline haven't created, not required'"` + Cron string `json:"cron" description:"Cron script data."` +} + type CheckCronRes struct { - Result string `json:"result,omitempty" description:"result"` - Message string `json:"message,omitempty" description:"message"` + Result string `json:"result,omitempty" description:"result e.g. ok, error"` + Message string `json:"message,omitempty" description:"message"` + LastTime string `json:"lastTime,omitempty" description:"last run time."` + NextTime string `json:"nextTime,omitempty" description:"next run time."` } // GetPipelineRun @@ -1035,7 +1042,7 @@ type NodeSteps struct { // CheckScriptCompile type ReqScript struct { - Value string `json:"value,omitempty" description:"check value"` + Value string `json:"value,omitempty" description:"Pipeline script data"` } // ToJenkinsfile requests diff --git a/pkg/models/devops/urlconfig.go b/pkg/models/devops/urlconfig.go index a42a05648..222cc07ab 100644 --- a/pkg/models/devops/urlconfig.go +++ b/pkg/models/devops/urlconfig.go @@ -52,10 +52,11 @@ const ( GetConsoleLogUrl = "/job/%s/job/%s/indexing/consoleText" ScanBranchUrl = "/job/%s/job/%s/build?" GetCrumbUrl = "/crumbIssuer/api/json/" - CheckScriptCompileUrl = "/job/init-job/descriptorByName/org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile" - CheckCronUrl = "/job/init-job/descriptorByName/hudson.triggers.TimerTrigger/checkSpec?" ToJenkinsfileUrl = "/pipeline-model-converter/toJenkinsfile" ToJsonUrl = "/pipeline-model-converter/toJson" GetNotifyCommitUrl = "/git/notifyCommit/?" GithubWebhookUrl = "/github-webhook/" + CheckScriptCompileUrl = "/job/%s/job/%s/descriptorByName/org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile" + CheckPipelienCronUrl = "/job/%s/job/%s/descriptorByName/hudson.triggers.TimerTrigger/checkSpec?value=%s" + CheckCronUrl = "/job/%s/descriptorByName/hudson.triggers.TimerTrigger/checkSpec?value=%s" )