feat: add remote trigger token support for Jenkins job execution

- Add support for passing a remote trigger token to Jenkins jobs
- Update the Jenkins constructor to accept a token parameter
- Ensure the token is included as a query parameter when triggering jobs
- Improve error reporting by including response body in error messages
- Remove unnecessary logging and refactor build parameter logic
- Update tests to use the new Jenkins constructor and token handling
- Add CLI option for specifying a remote trigger token
- Extend plugin configuration to support remote token injection

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
This commit is contained in:
Bo-Yi Wu
2025-12-01 17:16:39 +08:00
parent a5469c939e
commit 3dd86f956c
4 changed files with 55 additions and 30 deletions
+30 -12
View File
@@ -6,7 +6,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
@@ -23,12 +22,13 @@ type (
Jenkins struct { Jenkins struct {
Auth *Auth Auth *Auth
BaseURL string BaseURL string
Token string // Remote trigger token
Client *http.Client Client *http.Client
} }
) )
// NewJenkins is initial Jenkins object // NewJenkins is initial Jenkins object
func NewJenkins(auth *Auth, url string, insecure bool) *Jenkins { func NewJenkins(auth *Auth, url string, token string, insecure bool) *Jenkins {
url = strings.TrimRight(url, "/") url = strings.TrimRight(url, "/")
client := http.DefaultClient client := http.DefaultClient
@@ -44,6 +44,7 @@ func NewJenkins(auth *Auth, url string, insecure bool) *Jenkins {
return &Jenkins{ return &Jenkins{
Auth: auth, Auth: auth,
BaseURL: url, BaseURL: url,
Token: token,
Client: client, Client: client,
} }
} }
@@ -70,12 +71,12 @@ func (jenkins *Jenkins) sendRequest(req *http.Request) (*http.Response, error) {
func (jenkins *Jenkins) parseResponse(resp *http.Response, body interface{}) (err error) { func (jenkins *Jenkins) parseResponse(resp *http.Response, body interface{}) (err error) {
defer resp.Body.Close() defer resp.Body.Close()
if body == nil { data, err := io.ReadAll(resp.Body)
if err != nil {
return return
} }
data, err := io.ReadAll(resp.Body) if body == nil {
if err != nil {
return return
} }
@@ -84,6 +85,7 @@ func (jenkins *Jenkins) parseResponse(resp *http.Response, body interface{}) (er
func (jenkins *Jenkins) post(path string, params url.Values, body interface{}) (err error) { func (jenkins *Jenkins) post(path string, params url.Values, body interface{}) (err error) {
requestURL := jenkins.buildURL(path, params) requestURL := jenkins.buildURL(path, params)
req, err := http.NewRequestWithContext(context.Background(), "POST", requestURL, nil) req, err := http.NewRequestWithContext(context.Background(), "POST", requestURL, nil)
if err != nil { if err != nil {
return return
@@ -95,7 +97,7 @@ func (jenkins *Jenkins) post(path string, params url.Values, body interface{}) (
} }
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected response code: %d", resp.StatusCode) return fmt.Errorf("unexpected response code: %d, body: %s", resp.StatusCode, string(data))
} }
return jenkins.parseResponse(resp, body) return jenkins.parseResponse(resp, body)
@@ -119,14 +121,30 @@ func (jenkins *Jenkins) parseJobPath(job string) string {
} }
func (jenkins *Jenkins) trigger(job string, params url.Values) error { func (jenkins *Jenkins) trigger(job string, params url.Values) error {
var urlPath string // Add remote trigger token to params
if len(params) == 0 { if jenkins.Token != "" {
urlPath = jenkins.parseJobPath(job) + "/build" if params == nil {
} else { params = url.Values{}
urlPath = jenkins.parseJobPath(job) + "/buildWithParameters" }
params.Set("token", jenkins.Token)
} }
log.Println(urlPath) var urlPath string
// Check if params contains build parameters (excluding 'token')
hasBuildParams := false
for key := range params {
if key != "token" {
hasBuildParams = true
break
}
}
if hasBuildParams {
urlPath = jenkins.parseJobPath(job) + "/buildWithParameters"
} else {
urlPath = jenkins.parseJobPath(job) + "/build"
}
// All params (including token) are passed as query parameters
return jenkins.post(urlPath, params, nil) return jenkins.post(urlPath, params, nil)
} }
+4 -4
View File
@@ -12,7 +12,7 @@ func TestParseJobPath(t *testing.T) {
Username: "appleboy", Username: "appleboy",
Token: "1234", Token: "1234",
} }
jenkins := NewJenkins(auth, "http://example.com", false) jenkins := NewJenkins(auth, "http://example.com", "", false)
assert.Equal(t, "/job/foo", jenkins.parseJobPath("/foo/")) assert.Equal(t, "/job/foo", jenkins.parseJobPath("/foo/"))
assert.Equal(t, "/job/foo", jenkins.parseJobPath("foo/")) assert.Equal(t, "/job/foo", jenkins.parseJobPath("foo/"))
@@ -25,7 +25,7 @@ func TestUnSupportProtocol(t *testing.T) {
Username: "foo", Username: "foo",
Token: "bar", Token: "bar",
} }
jenkins := NewJenkins(auth, "example.com", false) jenkins := NewJenkins(auth, "example.com", "", false)
err := jenkins.trigger("drone-jenkins", nil) err := jenkins.trigger("drone-jenkins", nil)
assert.NotNil(t, err) assert.NotNil(t, err)
@@ -36,8 +36,8 @@ func TestTriggerBuild(t *testing.T) {
Username: "foo", Username: "foo",
Token: "bar", Token: "bar",
} }
jenkins := NewJenkins(auth, "http://example.com", false) jenkins := NewJenkins(auth, "http://example.com", "remote-token", false)
err := jenkins.trigger("drone-jenkins", url.Values{"token": []string{"bar"}}) err := jenkins.trigger("drone-jenkins", url.Values{"param": []string{"value"}})
assert.Nil(t, err) assert.Nil(t, err)
} }
+13 -7
View File
@@ -48,9 +48,14 @@ func main() {
&cli.StringFlag{ &cli.StringFlag{
Name: "token", Name: "token",
Aliases: []string{"t"}, Aliases: []string{"t"},
Usage: "jenkins token", Usage: "jenkins API token for authentication",
EnvVars: []string{"PLUGIN_TOKEN", "JENKINS_TOKEN", "INPUT_TOKEN"}, EnvVars: []string{"PLUGIN_TOKEN", "JENKINS_TOKEN", "INPUT_TOKEN"},
}, },
&cli.StringFlag{
Name: "remote-token",
Usage: "jenkins remote trigger token",
EnvVars: []string{"PLUGIN_REMOTE_TOKEN", "JENKINS_REMOTE_TOKEN", "INPUT_REMOTE_TOKEN"},
},
&cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: "job", Name: "job",
Aliases: []string{"j"}, Aliases: []string{"j"},
@@ -112,12 +117,13 @@ REPOSITORY:
func run(c *cli.Context) error { func run(c *cli.Context) error {
plugin := Plugin{ plugin := Plugin{
BaseURL: c.String("host"), BaseURL: c.String("host"),
Username: c.String("user"), Username: c.String("user"),
Token: c.String("token"), Token: c.String("token"),
Job: c.StringSlice("job"), RemoteToken: c.String("remote-token"),
Insecure: c.Bool("insecure"), Job: c.StringSlice("job"),
Parameters: c.StringSlice("parameters"), Insecure: c.Bool("insecure"),
Parameters: c.StringSlice("parameters"),
} }
return plugin.Exec() return plugin.Exec()
+8 -7
View File
@@ -10,12 +10,13 @@ import (
type ( type (
// Plugin values. // Plugin values.
Plugin struct { Plugin struct {
BaseURL string BaseURL string
Username string Username string
Token string Token string
Job []string RemoteToken string
Insecure bool Job []string
Parameters []string Insecure bool
Parameters []string
} }
) )
@@ -50,7 +51,7 @@ func (p Plugin) Exec() error {
Token: p.Token, Token: p.Token,
} }
jenkins := NewJenkins(auth, p.BaseURL, p.Insecure) jenkins := NewJenkins(auth, p.BaseURL, p.RemoteToken, p.Insecure)
params := url.Values{} params := url.Values{}
for _, v := range p.Parameters { for _, v := range p.Parameters {