From b865ab1c50729177a9fc84605b3ac1e53fe534b1 Mon Sep 17 00:00:00 2001 From: Joe Lee Date: Fri, 11 Feb 2022 15:27:49 +0800 Subject: [PATCH] add view (#8) * add view --- Makefile | 4 +- go.mod | 2 +- go.sum | 4 +- jenkins/build_test.go | 25 +++++++--- jenkins/jenkins.go | 2 + jenkins/jenkins_test.go | 90 +++++++++++++++++++++++++++++---- jenkins/job.go | 49 +++++++++--------- jenkins/job_test.go | 31 ++++++------ jenkins/plugin.go | 5 ++ jenkins/types.go | 21 +++++--- jenkins/view.go | 107 ++++++++++++++++++++++++++++++++++++++++ jenkins/view_test.go | 73 +++++++++++++++++++++++++++ 12 files changed, 343 insertions(+), 70 deletions(-) create mode 100644 jenkins/plugin.go create mode 100644 jenkins/view.go create mode 100644 jenkins/view_test.go diff --git a/Makefile b/Makefile index 54e8188..22a65d0 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,8 @@ build: ## build package go build -v . test: ## run test - env | sort - go test -race -coverprofile=coverage.out -covermode=atomic -v ./... | tee test.log + env | sort && \ + go test -race -coverprofile=coverage.out -covermode=atomic -v ./... && \ go tool cover -html=coverage.out -o cover.html fmt: ## format code diff --git a/go.mod b/go.mod index 7fadc2c..fef06a4 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,6 @@ module github.com/joelee2012/go-jenkins go 1.15 require ( - github.com/imroc/req v0.3.0 + github.com/imroc/req v0.3.2 github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index dea600b..81d5999 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/imroc/req v0.3.0 h1:3EioagmlSG+z+KySToa+Ylo3pTFZs+jh3Brl7ngU12U= -github.com/imroc/req v0.3.0/go.mod h1:F+NZ+2EFSo6EFXdeIbpfE9hcC233id70kf0byW97Caw= +github.com/imroc/req v0.3.2 h1:M/JkeU6RPmX+WYvT2vaaOL0K+q8ufL5LxwvJc4xeB4o= +github.com/imroc/req v0.3.2/go.mod h1:F+NZ+2EFSo6EFXdeIbpfE9hcC233id70kf0byW97Caw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/jenkins/build_test.go b/jenkins/build_test.go index 93d2a6b..af92eb2 100644 --- a/jenkins/build_test.go +++ b/jenkins/build_test.go @@ -33,7 +33,6 @@ func setupBuild(t *testing.T) *BuildItem { return nil }) assert.Nil(t, err) - assert.Nil(t, err) assert.Contains(t, strings.Join(output, ""), os.Getenv("JENKINS_VERSION")) return build } @@ -63,8 +62,8 @@ func TestBuildItemGetDescription(t *testing.T) { } func TestBuildItemDelete(t *testing.T) { - t.Skip() build := setupBuild(t) + assert.NotNil(t, build) assert.Nil(t, build.Delete()) build, err := pipeline.GetBuild(build.ID) assert.Nil(t, err) @@ -72,7 +71,7 @@ func TestBuildItemDelete(t *testing.T) { } func TestStopBuildItem(t *testing.T) { - t.Skip() + // change config conf := ` @@ -81,11 +80,15 @@ func TestStopBuildItem(t *testing.T) { false ` + assert.Nil(t, pipeline.SetConfigure(conf)) - assert.Nil(t, folder.Create("sleep-pipeline", conf)) - qitem, err := client.BuildJob("folder/sleep-pipeline", ReqParams{}) - var build *BuildItem + // start build to sleep 20s + qitem, err := pipeline.Build(ReqParams{}) assert.Nil(t, err) + job, err := qitem.GetJob() + assert.Nil(t, err) + assert.Equal(t, job.FullName, pipeline.FullName) + var build *BuildItem for { time.Sleep(1 * time.Second) build, err = qitem.GetBuild() @@ -98,5 +101,13 @@ func TestStopBuildItem(t *testing.T) { assert.Nil(t, err) assert.True(t, building) assert.Nil(t, build.Stop()) - + building, err = build.IsBuilding() + assert.Nil(t, err) + assert.False(t, building) + result, err := build.GetResult() + assert.Nil(t, err) + assert.Equal(t, result, "ABORTED") + // delete build and revert configure + assert.Nil(t, build.Delete()) + assert.Nil(t, pipeline.SetConfigure(jobConf)) } diff --git a/jenkins/jenkins.go b/jenkins/jenkins.go index 50bffbd..c96b075 100644 --- a/jenkins/jenkins.go +++ b/jenkins/jenkins.go @@ -23,6 +23,7 @@ type Client struct { Credentials *CredentialService Nodes *NodeService Queue *QueueService + Views *ViewService } type Crumb struct { @@ -48,6 +49,7 @@ func NewClient(url, user, password string) (*Client, error) { c.Credentials = NewCredentialService(c) c.Nodes = NewNodeService(c) c.Queue = NewQueueService(c) + c.Views = NewViewService(c) return c, nil } diff --git a/jenkins/jenkins_test.go b/jenkins/jenkins_test.go index d94e877..3c2d6c2 100644 --- a/jenkins/jenkins_test.go +++ b/jenkins/jenkins_test.go @@ -6,15 +6,17 @@ import ( "os" "strings" "testing" + "time" "github.com/stretchr/testify/assert" ) var ( - client *Client - folder *JobItem - pipeline *JobItem - jobConf = ` + client *Client + folder *JobItem + pipeline *JobItem + pipeline2 *JobItem + jobConf = ` @@ -22,10 +24,30 @@ var ( false ` + paramsJobConf = ` + + + false + + + + + ARG1 + false + + + + + + + true + + + false + ` folderConf = ` - @@ -37,6 +59,29 @@ var ( user-password user id for testing ` + + viewConf = ` + + test + false + false + + + + + + + + + + + + + + + + false +` ) func setup() error { @@ -48,7 +93,7 @@ func setup() error { } jobConf = strings.ReplaceAll(jobConf, "JENKINS_VERSION", os.Getenv("JENKINS_VERSION")) - confs := []string{folderConf, folderConf, jobConf, jobConf} + confs := []string{folderConf, folderConf, jobConf, paramsJobConf} names := []string{"folder", "folder/folder1", "folder/pipeline", "folder/pipeline2"} for index, name := range names { @@ -67,6 +112,11 @@ func setup() error { if err != nil { return err } + + pipeline2, err = client.GetJob("folder/pipeline2") + if err != nil { + return err + } return nil } @@ -128,7 +178,7 @@ func TestUrl2Name(t *testing.T) { func TestGetJob(t *testing.T) { // check job exist - job, err := client.GetJob("folder/pipeline2") + job, err := client.GetJob(pipeline.FullName) assert.Nil(t, err) assert.Equal(t, job.Class, "WorkflowJob") @@ -137,7 +187,7 @@ func TestGetJob(t *testing.T) { assert.Nil(t, err) assert.Nil(t, job) // wrong path - job, err = client.GetJob("folder/pipeline2/notexist") + job, err = client.GetJob(pipeline.FullName + "/notexist") assert.Nil(t, err) assert.Nil(t, job) } @@ -180,7 +230,7 @@ func TestBuildJob(t *testing.T) { assert.Contains(t, output, os.Getenv("JENKINS_VERSION")) // test job.GetBuild - build1, err := pipeline.GetBuild(1) + build1, err := pipeline.GetBuild(build.ID) assert.Nil(t, err) assert.Equal(t, build, build1) @@ -195,6 +245,28 @@ func TestBuildJob(t *testing.T) { assert.Equal(t, build, build1) } +func TestBuildJobWithParameters(t *testing.T) { + qitem, err := client.BuildJob(pipeline2.FullName, ReqParams{"ARG1": "ARG1_VALUE"}) + var build *BuildItem + assert.Nil(t, err) + for { + time.Sleep(1 * time.Second) + build, err = qitem.GetBuild() + assert.Nil(t, err) + if build != nil { + break + } + } + var output []string + err = build.LoopProgressiveLog("text", func(line string) error { + output = append(output, line) + time.Sleep(1 * time.Second) + return nil + }) + assert.Nil(t, err) + assert.Contains(t, strings.Join(output, ""), "ARG1_VALUE") +} + func TestSystemCredentials(t *testing.T) { cm := client.Credentials creds, err := cm.List() diff --git a/jenkins/job.go b/jenkins/job.go index 56e9ef3..03529c8 100644 --- a/jenkins/job.go +++ b/jenkins/job.go @@ -11,12 +11,18 @@ import ( type JobItem struct { *Item - Credentials *CredentialService + Credentials *CredentialService + Views *ViewService + Name string + FullName string + FullDisplayName string } func NewJobItem(url, class string, client *Client) *JobItem { j := &JobItem{Item: NewItem(url, class, client)} j.Credentials = NewCredentialService(j) + j.Views = NewViewService(j) + j.setName() return j } @@ -27,6 +33,7 @@ func (j *JobItem) Rename(name string) error { } url, _ := resp.Response().Location() j.URL = appendSlash(url.String()) + j.setName() return nil } @@ -38,6 +45,7 @@ func (j *JobItem) Move(path string) error { } url, _ := resp.Response().Location() j.URL = appendSlash(url.String()) + j.setName() return nil } @@ -56,12 +64,12 @@ func (j *JobItem) GetParent() (*JobItem, error) { } func (j *JobItem) GetConfigure() (string, error) { - resp, err := j.Request("GET", "/config.xml") + resp, err := j.Request("GET", "config.xml") return resp.String(), err } func (j *JobItem) SetConfigure(xml string) error { - _, err := j.Request("POST", "/config.xml", req.BodyXML(xml)) + _, err := j.Request("POST", "config.xml", req.BodyXML(xml)) return err } @@ -84,19 +92,10 @@ func (j *JobItem) IsBuildable() (bool, error) { return job.Buildable, err } -func (j *JobItem) GetName() string { - _, name := path.Split(strings.Trim(j.URL, "/")) - return name -} - -func (j *JobItem) GetFullName() string { - fullname, _ := j.client.URL2Name(j.URL) - return fullname -} - -func (j *JobItem) GetFullDisplayName() string { - fullname, _ := j.client.URL2Name(j.URL) - return strings.ReplaceAll(fullname, "/", " » ") +func (j *JobItem) setName() { + j.FullName, _ = j.client.URL2Name(j.URL) + _, j.Name = path.Split(j.FullName) + j.FullDisplayName = strings.ReplaceAll(j.FullName, "/", " » ") } func (j *JobItem) GetDescription() (string, error) { @@ -202,31 +201,31 @@ func (j *JobItem) List(depth int) ([]*JobItem, error) { } func (j *JobItem) GetFirstBuild() (*BuildItem, error) { - return j.getBuildByName("firstBuild") + return j.GetBuildByName("firstBuild") } func (j *JobItem) GetLastBuild() (*BuildItem, error) { - return j.getBuildByName("lastBuild") + return j.GetBuildByName("lastBuild") } func (j *JobItem) GetLastCompleteBuild() (*BuildItem, error) { - return j.getBuildByName("lastCompletedBuild") + return j.GetBuildByName("lastCompletedBuild") } func (j *JobItem) GetLastFailedBuild() (*BuildItem, error) { - return j.getBuildByName("lastFailedBuild") + return j.GetBuildByName("lastFailedBuild") } func (j *JobItem) GetLastStableBuild() (*BuildItem, error) { - return j.getBuildByName("lastStableBuild") + return j.GetBuildByName("lastStableBuild") } func (j *JobItem) GetLastUnstableBuild() (*BuildItem, error) { - return j.getBuildByName("lastUnstableBuild") + return j.GetBuildByName("lastUnstableBuild") } func (j *JobItem) GetLastSuccessfulBuild() (*BuildItem, error) { - return j.getBuildByName("lastSuccessfulBuild") + return j.GetBuildByName("lastSuccessfulBuild") } func (j *JobItem) GetLastUnsucessfulBuild() (*BuildItem, error) { - return j.getBuildByName("lastUnsuccessfulBuild") + return j.GetBuildByName("lastUnsuccessfulBuild") } -func (j *JobItem) getBuildByName(name string) (*BuildItem, error) { +func (j *JobItem) GetBuildByName(name string) (*BuildItem, error) { if j.Class == "Folder" || j.Class == "WorkflowMultiBranchProject" { return nil, fmt.Errorf("%s have no builds", j) } diff --git a/jenkins/job_test.go b/jenkins/job_test.go index 6db3fc2..e893908 100644 --- a/jenkins/job_test.go +++ b/jenkins/job_test.go @@ -7,19 +7,14 @@ import ( "github.com/stretchr/testify/assert" ) -func TestGetName(t *testing.T) { - assert.Equal(t, "folder", folder.GetName()) - assert.Equal(t, "pipeline", pipeline.GetName()) -} - -func TestGetFullDisplayName(t *testing.T) { - assert.Equal(t, "folder", folder.GetFullDisplayName()) - assert.Equal(t, "folder » pipeline", pipeline.GetFullDisplayName()) -} - -func TestGetFullName(t *testing.T) { - assert.Equal(t, "folder", folder.GetFullName()) - assert.Equal(t, "folder/pipeline", pipeline.GetFullName()) +func TestName(t *testing.T) { + assert.Equal(t, "folder", folder.Name) + assert.Equal(t, "folder", folder.FullName) + assert.Equal(t, "folder", folder.FullDisplayName) + + assert.Equal(t, "pipeline", pipeline.Name) + assert.Equal(t, "folder/pipeline", pipeline.FullName) + assert.Equal(t, "folder » pipeline", pipeline.FullDisplayName) } func TestRename(t *testing.T) { @@ -28,6 +23,7 @@ func TestRename(t *testing.T) { newPipeline, err := folder.Get("pipeline1") assert.Nil(t, err) assert.Equal(t, pipeline.URL, newPipeline.URL) + assert.Equal(t, pipeline.Name, newPipeline.Name) // old job 'pipeline' should not exist old, err := folder.Get("pipeline") @@ -126,20 +122,21 @@ func TestSetDescription(t *testing.T) { } func TestGetBuildFunctions(t *testing.T) { + expect_build := setupBuild(t) // test job.GetBuild - build, err := pipeline.GetBuild(1) + build, err := pipeline.GetBuild(expect_build.ID) assert.Nil(t, err) - assert.Equal(t, 1, build.ID) + assert.Equal(t, expect_build.ID, build.ID) // test job.GetLastBuild build, err = pipeline.GetLastBuild() assert.Nil(t, err) - assert.Equal(t, 1, build.ID) + assert.Equal(t, expect_build.ID, build.ID) // test job.GetLastBuild build, err = pipeline.GetFirstBuild() assert.Nil(t, err) - assert.Equal(t, 1, build.ID) + assert.Equal(t, expect_build.ID, build.ID) // test for folder build, err = folder.GetFirstBuild() diff --git a/jenkins/plugin.go b/jenkins/plugin.go new file mode 100644 index 0000000..80c2f2b --- /dev/null +++ b/jenkins/plugin.go @@ -0,0 +1,5 @@ +package jenkins + +type Plugin struct { + *Item +} diff --git a/jenkins/types.go b/jenkins/types.go index 4968339..96b6ec0 100644 --- a/jenkins/types.go +++ b/jenkins/types.go @@ -33,7 +33,7 @@ type Job struct { ResumeBlocked bool `json:"resumeBlocked"` Jobs []*Job `json:"jobs"` PrimaryView *PrimaryView `json:"primaryView"` - Views []*Views `json:"views"` + Views []*View `json:"views"` } type Build struct { @@ -181,10 +181,17 @@ type PrimaryView struct { Name string `json:"name"` URL string `json:"url"` } -type Views struct { - Class string `json:"_class"` - Name string `json:"name"` - URL string `json:"url"` +type View struct { + Class string `json:"_class"` + Name string `json:"name"` + URL string `json:"url"` + Description string `json:"description"` + Jobs []*Job `json:"jobs"` + Property []string `json:"property"` +} + +func (v View) String() string { + return fmt.Sprintf("<%s: %s>", parseClass(v.Class), v.URL) } type HealthReport struct { @@ -235,7 +242,7 @@ type Nodes struct { URL string `json:"url"` UseCrumbs bool `json:"useCrumbs"` UseSecurity bool `json:"useSecurity"` - Views []Views `json:"views"` + Views []*View `json:"views"` } type AssignedLabels struct { @@ -368,7 +375,7 @@ type Computer struct { } func (c Computer) String() string { - return fmt.Sprintf("<%s: %s>", c.Class, c.DisplayName) + return fmt.Sprintf("<%s: %s>", parseClass(c.Class), c.DisplayName) } type Queue struct { diff --git a/jenkins/view.go b/jenkins/view.go new file mode 100644 index 0000000..bab9334 --- /dev/null +++ b/jenkins/view.go @@ -0,0 +1,107 @@ +package jenkins + +import ( + "github.com/imroc/req" +) + +type ViewService struct { + *Item +} + +func NewViewService(v interface{}) *ViewService { + if c, ok := v.(*Client); ok { + return &ViewService{Item: NewItem(c.URL, "Views", c)} + } + if c, ok := v.(*JobItem); ok { + return &ViewService{Item: NewItem(c.URL, "Views", c.client)} + } + return nil +} + +func (v *ViewService) Get(name string) (*View, error) { + jobJson := &Job{} + if err := v.BindAPIJson(ReqParams{"tree": "views[name,url,description]"}, jobJson); err != nil { + return nil, err + } + for _, view := range jobJson.Views { + if view.Name == name { + return view, nil + } + } + return nil, nil +} + +func (v *ViewService) Create(name, xml string) error { + _, err := v.Request("POST", "createView", ReqParams{"name": name}, req.BodyXML(xml)) + return err +} +func (v *ViewService) Delete(name string) error { + _, err := v.Request("POST", "view/"+name+"/doDelete") + return err +} + +func (v *ViewService) AddJobToView(name, jobName string) error { + _, err := v.Request("POST", "view/"+name+"/addJobToView", ReqParams{"name": jobName}) + return err +} + +func (v *ViewService) RemoveJobFromView(name, jobName string) error { + _, err := v.Request("POST", "view/"+name+"/removeJobFromView", ReqParams{"name": jobName}) + return err +} + +func (v *ViewService) GetConfigure(name string) (string, error) { + resp, err := v.Request("GET", "view/"+name+"/config.xml") + return resp.String(), err +} + +func (v *ViewService) SetConfigure(name, xml string) error { + _, err := v.Request("POST", "view/"+name+"/config.xml", req.BodyXML(xml)) + return err +} + +func (v *ViewService) SetDescription(name, description string) error { + _, err := v.Request("POST", "view/"+name+"/submitDescription", ReqParams{"description": description}) + return err +} + +func (v *ViewService) List() ([]*View, error) { + jobJson := &Job{} + if err := v.BindAPIJson(ReqParams{"tree": "views[name,url,description]"}, jobJson); err != nil { + return nil, err + } + return jobJson.Views, nil +} + +func (v *ViewService) bindViewAPIJson(name string, view interface{}) error { + resp, err := v.Request("GET", "view/"+name+"/api/json") + if err != nil { + return err + } + return resp.ToJSON(view) +} + +func (v *ViewService) GetJobFromView(name, jobName string) (*JobItem, error) { + view := &View{} + if err := v.bindViewAPIJson(name, view); err != nil { + return nil, err + } + for _, job := range view.Jobs { + if job.Name == jobName { + return NewJobItem(job.URL, job.Class, v.client), nil + } + } + return nil, nil +} + +func (v *ViewService) ListJobInView(name string) ([]*JobItem, error) { + view := &View{} + if err := v.bindViewAPIJson(name, view); err != nil { + return nil, err + } + var jobs []*JobItem + for _, job := range view.Jobs { + jobs = append(jobs, NewJobItem(job.URL, job.Class, v.client)) + } + return jobs, nil +} diff --git a/jenkins/view_test.go b/jenkins/view_test.go new file mode 100644 index 0000000..bc19f4d --- /dev/null +++ b/jenkins/view_test.go @@ -0,0 +1,73 @@ +package jenkins + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestViewServiceGet(t *testing.T) { + v, err := client.Views.Get("all") + assert.Nil(t, err) + assert.NotNil(t, v) + assert.Equal(t, v.Name, "all") +} + +func TestViewServiceCreate(t *testing.T) { + v, err := folder.Views.Get("testview") + assert.Nil(t, err) + assert.Empty(t, v) + + // create view + assert.Nil(t, folder.Views.Create("testview", viewConf)) + v, err = folder.Views.Get("testview") + assert.Nil(t, err) + assert.NotNil(t, v) + assert.Equal(t, v.Name, "testview") + assert.Equal(t, v.Description, "test") + + // list views + views, err := folder.Views.List() + assert.Nil(t, err) + assert.Len(t, views, 2) + + // get job from view + job, err := folder.Views.GetJobFromView("testview", pipeline.Name) + assert.Nil(t, err) + assert.Nil(t, job) + + // add job to view + assert.Nil(t, folder.Views.AddJobToView("testview", pipeline.Name)) + job, err = folder.Views.GetJobFromView("testview", pipeline.Name) + assert.Nil(t, err) + assert.Equal(t, job.FullName, pipeline.FullName) + + jobs, err := folder.Views.ListJobInView("testview") + assert.Nil(t, err) + assert.Len(t, jobs, 1) + + // remove job from view + assert.Nil(t, folder.Views.RemoveJobFromView("testview", pipeline.Name)) + jobs, err = folder.Views.ListJobInView("testview") + assert.Nil(t, err) + assert.Len(t, jobs, 0) + + // set description + assert.Nil(t, folder.Views.SetDescription("testview", "new description")) + v, err = folder.Views.Get("testview") + assert.Nil(t, err) + assert.Equal(t, v.Description, "new description") + + // set/get configuration + assert.Nil(t, folder.Views.SetConfigure("testview", strings.ReplaceAll(viewConf, "test", "newtest"))) + config, err := folder.Views.GetConfigure("testview") + assert.Nil(t, err) + assert.Contains(t, config, "newtest") + + // delete view + assert.Nil(t, folder.Views.Delete("testview")) + v, err = folder.Views.Get("testview") + assert.Nil(t, err) + assert.Nil(t, v) +}