diff --git a/README.md b/README.md
index e64c0c9..d8f329d 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,28 @@
![GitHub](https://img.shields.io/github/license/joelee2012/go-jenkins)
# go-jenkins
-[Golang](https://golang.org/) client library for [Jenkins API](https://wiki.jenkins.io/display/JENKINS/Remote+access+API).
-> Ported from [api4jenkins](https://github.com/joelee2012/api4jenkins>), a [Python3](https://www.python.org/) client library for [Jenkins API](https://wiki.jenkins.io/display/JENKINS/Remote+access+API).
+[Golang](https://golang.org/) client library for accessing [Jenkins API](https://www.jenkins.io/doc/book/using/remote-access-api/).
+> Ported from [api4jenkins](https://github.com/joelee2012/api4jenkins>), a [Python3](https://www.python.org/) client library for [Jenkins API](https://www.jenkins.io/doc/book/using/remote-access-api/).
+
+# Features
+This API client package covers most of the existing Jenkins API calls and is updated regularly to add new and/or missing endpoints.
+
+Currently, the following are supported:
+
+- Job
+- Build
+- Credential
+- View
+- Queue
+- Node
# Usage
+```go
+import "github.com/joelee2012/go-jenkins"
+```
+
+## Example
```go
package main
@@ -16,37 +33,39 @@ import (
"log"
"time"
- "github.com/joelee2012/go-jenkins/jenkins"
+ "github.com/joelee2012/go-jenkins"
)
func main() {
+ // Construct new client
client, err := jenkins.NewClient("http://localhost:8080/", "admin", "1234")
if err != nil {
log.Fatalln(err)
}
xml := `
-
+
+ pipeline {
+ agent any
+ stages {
+ stage('build'){
+ steps{
+ sh 'echo $JENKINS_VERSION'
+ }
+ }
+ }
+ }
true
-
- false
+
+ false
`
- // create jenkins job
+ // create jenkins job
if err := client.CreateJob("pipeline", xml); err != nil {
log.Fatalln(err)
}
- qitem, err := client.BuildJob("pipeline", jenkins.ReqParams{})
+ // Build Job and get BuildItem
+ qitem, err := client.BuildJob("pipeline", nil)
if err != nil {
log.Fatalln(err)
}
@@ -68,4 +87,7 @@ func main() {
return nil
})
}
-```
\ No newline at end of file
+```
+
+# Documentation
+See https://pkg.go.dev/github.com/joelee2012/go-jenkins
\ No newline at end of file
diff --git a/jenkins/build.go b/build.go
similarity index 100%
rename from jenkins/build.go
rename to build.go
diff --git a/jenkins/build_test.go b/build_test.go
similarity index 99%
rename from jenkins/build_test.go
rename to build_test.go
index af92eb2..cc52216 100644
--- a/jenkins/build_test.go
+++ b/build_test.go
@@ -36,6 +36,7 @@ func setupBuild(t *testing.T) *BuildItem {
assert.Contains(t, strings.Join(output, ""), os.Getenv("JENKINS_VERSION"))
return build
}
+
func TestBuildItemIsBuilding(t *testing.T) {
build := setupBuild(t)
building, err := build.IsBuilding()
diff --git a/jenkins/credential.go b/credential.go
similarity index 100%
rename from jenkins/credential.go
rename to credential.go
diff --git a/jenkins/item.go b/item.go
similarity index 100%
rename from jenkins/item.go
rename to item.go
diff --git a/jenkins/jenkins.go b/jenkins.go
similarity index 63%
rename from jenkins/jenkins.go
rename to jenkins.go
index c96b075..3df67e9 100644
--- a/jenkins/jenkins.go
+++ b/jenkins.go
@@ -11,9 +11,6 @@ import (
"github.com/imroc/req"
)
-type JenkinsError struct {
-}
-
type Client struct {
URL string
Header http.Header
@@ -31,12 +28,65 @@ type Crumb struct {
Value string `json:"crumb"`
}
-// Create new Client
-// client, err = NewClient(os.Getenv("JENKINS_URL"), os.Getenv("JENKINS_USER"), os.Getenv("JENKINS_PASSWORD"))
-// if err != nil {
-// return err
-// }
-// fmt.Println(client)
+// Init Jenkins client and create job to build
+// package main
+//
+// import (
+// "log"
+// "time"
+//
+// "github.com/joelee2012/go-jenkins/jenkins"
+// )
+//
+// func main() {
+// client, err := jenkins.NewClient("http://localhost:8080/", "admin", "1234")
+// if err != nil {
+// log.Fatalln(err)
+// }
+// xml := `
+//
+//
+//
+// true
+//
+// false
+// `
+// // create jenkins job
+// if err := client.CreateJob("pipeline", xml); err != nil {
+// log.Fatalln(err)
+// }
+// qitem, err := client.BuildJob("pipeline", nil)
+// if err != nil {
+// log.Fatalln(err)
+// }
+// var build *Build
+// for {
+// time.Sleep(1 * time.Second)
+// build, err = qitem.GetBuild()
+// if err != nil {
+// log.Fatalln(err)
+// }
+// if build != nil {
+// break
+// }
+// }
+// // tail the build log to end
+// build.LoopProgressiveLog("text", func(line string) error {
+// log.Println(line)
+// time.Sleep(1 * time.Second)
+// return nil
+// })
+// }
func NewClient(url, user, password string) (*Client, error) {
url = appendSlash(url)
c := &Client{URL: url, Header: make(http.Header), Req: req.New()}
@@ -53,6 +103,7 @@ func NewClient(url, user, password string) (*Client, error) {
return c, nil
}
+// Set content type for request, default is 'application/json'
func (c *Client) SetContentType(ctype string) {
if ctype == "" {
c.Header.Set("Accept", "application/json")
@@ -84,11 +135,41 @@ func (c *Client) GetCrumb() (*Crumb, error) {
return c.Crumb, nil
}
+// Get job with fullname:
+// job, err := client.GetJob("path/to/job")
+// if err != nil {
+// return err
+// }
+// fmt.Println(job)
func (c *Client) GetJob(fullName string) (*JobItem, error) {
folder, shortName := c.resolveJob(fullName)
return folder.Get(shortName)
}
+// Create job with given xml config:
+// xml := `
+//
+//
+//
+// true
+//
+// false
+// `
+// // create jenkins job
+// if err := client.CreateJob("path/to/name", xml); err != nil {
+// log.Fatalln(err)
+// }
func (c *Client) CreateJob(fullName, xml string) error {
folder, shortName := c.resolveJob(fullName)
return folder.Create(shortName, xml)
@@ -108,6 +189,8 @@ func (c *Client) resolveJob(fullName string) (*JobItem, string) {
return NewJobItem(url, "Folder", c), name
}
+// Covert fullname to url, eg:
+// path/to/name -> http://jenkins/job/path/job/to/job/name
func (c *Client) Name2URL(fullName string) string {
if fullName == "" {
return c.URL
@@ -116,6 +199,8 @@ func (c *Client) Name2URL(fullName string) string {
return appendSlash(c.URL + "job/" + path)
}
+// Covert url to full name, eg:
+// http://jenkins/job/path/job/to/job/name -> path/to/name
func (c *Client) URL2Name(url string) (string, error) {
if !strings.HasPrefix(url, c.URL) {
return "", fmt.Errorf("%s is not in %s", url, c.URL)
@@ -124,6 +209,7 @@ func (c *Client) URL2Name(url string) (string, error) {
return strings.Trim(strings.ReplaceAll(path, "/job/", "/"), "/"), nil
}
+// Get jenkins version number
func (c *Client) GetVersion() (string, error) {
resp, err := c.Req.Get(c.URL)
if err != nil {
@@ -132,15 +218,25 @@ func (c *Client) GetVersion() (string, error) {
return resp.Response().Header.Get("X-Jenkins"), nil
}
+// Trigger job to build:
+// // without parameters
+// client.BuildJob("your job", nil)
+// client.BuildJob("your job", jenkins.ReqParams{})
+// // with parameters
+// client.BuildJob("your job", jenkins.ReqParams{"ARG1": "ARG1_VALUE"})
func (c *Client) BuildJob(fullName string, params ReqParams) (*OneQueueItem, error) {
return NewJobItem(c.Name2URL(fullName), "Job", c).Build(params)
}
+// List job with depth
func (c *Client) ListJobs(depth int) ([]*JobItem, error) {
job := NewJobItem(c.URL, "Folder", c)
return job.List(depth)
}
+// Send request to jenkins,
+// // send request to get JSON data of jenkins
+// client.Request("GET", "api/json")
func (c *Client) Request(method, entry string, v ...interface{}) (*req.Resp, error) {
return c.Do(method, c.URL+entry, v...)
}
@@ -203,6 +299,11 @@ func (c *Client) ExportJCasC(name string) error {
return resp.ToFile(name)
}
+// Bind jenkins JSON data to interface,
+// // bind json data to map
+// data := make(map[string]string)
+// client.BindAPIJson(jenkins.ReqParams{"tree":"description"}, &data)
+// fmt.Println(data["description"])
func (c *Client) BindAPIJson(params ReqParams, v interface{}) error {
resp, err := c.Request("GET", "api/json", params)
if err != nil {
diff --git a/jenkins/jenkins_test.go b/jenkins_test.go
similarity index 100%
rename from jenkins/jenkins_test.go
rename to jenkins_test.go
diff --git a/jenkins/job.go b/job.go
similarity index 84%
rename from jenkins/job.go
rename to job.go
index 03529c8..b023560 100644
--- a/jenkins/job.go
+++ b/job.go
@@ -3,6 +3,7 @@ package jenkins
import (
"encoding/json"
"fmt"
+ "net/url"
"path"
"strings"
@@ -93,9 +94,10 @@ func (j *JobItem) IsBuildable() (bool, error) {
}
func (j *JobItem) setName() {
- j.FullName, _ = j.client.URL2Name(j.URL)
+ urlPath, _ := j.client.URL2Name(j.URL)
+ j.FullName, _ = url.PathUnescape(urlPath)
_, j.Name = path.Split(j.FullName)
- j.FullDisplayName = strings.ReplaceAll(j.FullName, "/", " » ")
+ j.FullDisplayName, _ = url.PathUnescape(strings.ReplaceAll(j.FullName, "/", " » "))
}
func (j *JobItem) GetDescription() (string, error) {
@@ -146,7 +148,7 @@ func (j *JobItem) GetBuild(number int) (*BuildItem, error) {
for _, build := range jobJson.Builds {
if number == build.Number {
- return NewBuildItem(build.URL, parseClass(build.Class), j.client), nil
+ return NewBuildItem(build.URL, build.Class, j.client), nil
}
}
return nil, nil
@@ -259,7 +261,41 @@ func (j *JobItem) ListBuilds() ([]*BuildItem, error) {
}
for _, build := range jobJson.Builds {
- builds = append(builds, NewBuildItem(build.URL, parseClass(build.Class), j.client))
+ builds = append(builds, NewBuildItem(build.URL, build.Class, j.client))
}
return builds, nil
}
+
+func (j *JobItem) SetNextBuildNumber(number int) error {
+ _, err := j.Request("POST", "nextbuildnumber/submit", ReqParams{"nextBuildNumber": number})
+ return err
+}
+
+func (j *JobItem) GetParameters() ([]*ParameterDefinition, error) {
+ jobJson := &Job{}
+ if err := j.BindAPIJson(nil, jobJson); err != nil {
+ return nil, err
+ }
+ for _, p := range jobJson.Property {
+ if p.Class == "hudson.model.ParametersDefinitionProperty" {
+ return p.ParameterDefinitions, nil
+ }
+ }
+ return nil, nil
+}
+
+func (j *JobItem) SCMPolling() error {
+ _, err := j.Request("POST", "polling")
+ return err
+}
+
+func (j *JobItem) GetMultibranchPipelineScanLog() (string, error) {
+ if j.Class != "WorkflowMultiBranchProject" {
+ return "", fmt.Errorf("%s is not a WorkflowMultiBranchProject", j)
+ }
+ resp, err := j.Request("POST", "indexing/consoleText")
+ if err != nil {
+ return "", err
+ }
+ return resp.String(), nil
+}
diff --git a/jenkins/job_test.go b/job_test.go
similarity index 100%
rename from jenkins/job_test.go
rename to job_test.go
diff --git a/jenkins/node.go b/node.go
similarity index 100%
rename from jenkins/node.go
rename to node.go
diff --git a/jenkins/node_test.go b/node_test.go
similarity index 100%
rename from jenkins/node_test.go
rename to node_test.go
diff --git a/jenkins/plugin.go b/plugin.go
similarity index 100%
rename from jenkins/plugin.go
rename to plugin.go
diff --git a/jenkins/queue.go b/queue.go
similarity index 100%
rename from jenkins/queue.go
rename to queue.go
diff --git a/jenkins/types.go b/types.go
similarity index 99%
rename from jenkins/types.go
rename to types.go
index 96b6ec0..d2f0334 100644
--- a/jenkins/types.go
+++ b/types.go
@@ -27,7 +27,7 @@ type Job struct {
LastUnstableBuild *Build `json:"lastUnstableBuild"`
LastUnsuccessfulBuild *Build `json:"lastUnsuccessfulBuild"`
NextBuildNumber int `json:"nextBuildNumber"`
- Property []Property `json:"property"`
+ Property []*Property `json:"property"`
QueueItem interface{} `json:"queueItem"`
ConcurrentBuild bool `json:"concurrentBuild"`
ResumeBlocked bool `json:"resumeBlocked"`
@@ -206,7 +206,7 @@ type DefaultParameterValue struct {
Name string `json:"name"`
Value string `json:"value"`
}
-type ParameterDefinitions struct {
+type ParameterDefinition struct {
Class string `json:"_class"`
DefaultParameterValue DefaultParameterValue `json:"defaultParameterValue"`
Description string `json:"description"`
@@ -216,7 +216,7 @@ type ParameterDefinitions struct {
}
type Property struct {
Class string `json:"_class"`
- ParameterDefinitions []ParameterDefinitions `json:"parameterDefinitions,omitempty"`
+ ParameterDefinitions []*ParameterDefinition `json:"parameterDefinitions,omitempty"`
}
type ComputerSet struct {
diff --git a/jenkins/view.go b/view.go
similarity index 100%
rename from jenkins/view.go
rename to view.go
diff --git a/jenkins/view_test.go b/view_test.go
similarity index 100%
rename from jenkins/view_test.go
rename to view_test.go