mirror of
https://github.com/appleboy/drone-jenkins.git
synced 2026-06-16 14:49:16 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 069e6455cc | |||
| eb51e55e81 | |||
| da87ddb86b | |||
| 60874908e6 | |||
| f6e62d9c49 | |||
| cf9b9a0a0d | |||
| 4c54d13899 |
@@ -48,6 +48,37 @@ Example configuration with jobs in the folder:
|
||||
|
||||
It will trigger the URL of Jenkins job like as `http://example.com/job/folder_name/job/job_name/`
|
||||
|
||||
Example configuration with build parameters:
|
||||
|
||||
```yaml
|
||||
- name: trigger jenkins job
|
||||
image: appleboy/drone-jenkins
|
||||
settings:
|
||||
url: http://example.com
|
||||
user: appleboy
|
||||
token: xxxxxxxxxx
|
||||
job: parameterized-job
|
||||
parameters: |
|
||||
ENVIRONMENT=production
|
||||
VERSION=${DRONE_TAG}
|
||||
COMMIT_SHA=${DRONE_COMMIT_SHA}
|
||||
```
|
||||
|
||||
Example configuration with wait for completion:
|
||||
|
||||
```yaml
|
||||
- name: trigger jenkins job and wait
|
||||
image: appleboy/drone-jenkins
|
||||
settings:
|
||||
url: http://example.com
|
||||
user: appleboy
|
||||
token: xxxxxxxxxx
|
||||
job: deploy-job
|
||||
wait: true
|
||||
poll_interval: 15s
|
||||
timeout: 1h
|
||||
```
|
||||
|
||||
## Parameter Reference
|
||||
|
||||
url
|
||||
@@ -61,3 +92,21 @@ token
|
||||
|
||||
job
|
||||
: jenkins job name
|
||||
|
||||
parameters
|
||||
: build parameters in multi-line `key=value` format (one per line)
|
||||
|
||||
wait
|
||||
: wait for job completion (default: false)
|
||||
|
||||
poll_interval
|
||||
: interval between status checks when waiting (default: 10s)
|
||||
|
||||
timeout
|
||||
: maximum time to wait for job completion (default: 30m)
|
||||
|
||||
insecure
|
||||
: allow insecure SSL connections (default: false)
|
||||
|
||||
remote_token
|
||||
: jenkins remote trigger token (alternative to user/token authentication)
|
||||
|
||||
@@ -40,6 +40,7 @@ A [Drone](https://github.com/drone/drone) plugin for triggering [Jenkins](https:
|
||||
- Support for Jenkins build parameters
|
||||
- Multiple authentication methods (API token or remote trigger token)
|
||||
- Wait for job completion with configurable polling and timeout
|
||||
- Debug mode with detailed parameter information and secure token masking
|
||||
- SSL/TLS support with optional insecure mode
|
||||
- Cross-platform support (Linux, macOS, Windows)
|
||||
- Available as binary, Docker image, or Drone plugin
|
||||
@@ -97,16 +98,9 @@ docker pull ghcr.io/appleboy/drone-jenkins
|
||||
Set up a Jenkins server using Docker:
|
||||
|
||||
```sh
|
||||
docker run \
|
||||
--name jenkins \
|
||||
-d --restart always \
|
||||
-p 8080:8080 -p 50000:50000 \
|
||||
-v /data/jenkins:/var/jenkins_home \
|
||||
jenkins/jenkins:lts
|
||||
docker run -d -v jenkins_home:/var/jenkins_home -p 8080:8080 -p 50000:50000 --restart=on-failure jenkins/jenkins:slim
|
||||
```
|
||||
|
||||
**Note**: Create the `/data/jenkins` directory before starting Jenkins.
|
||||
|
||||
### Authentication
|
||||
|
||||
Jenkins API tokens are recommended for authentication. To create an API token:
|
||||
@@ -124,24 +118,35 @@ Alternatively, you can use a remote trigger token configured in your Jenkins job
|
||||
|
||||
### Parameters Reference
|
||||
|
||||
| Parameter | CLI Flag | Environment Variable | Required | Description |
|
||||
| ------------- | -------------------- | ----------------------------------------------- | ------------- | ------------------------------------------------------ |
|
||||
| Host | `--host` | `PLUGIN_URL`, `JENKINS_URL` | Yes | Jenkins base URL (e.g., `http://jenkins.example.com/`) |
|
||||
| User | `--user`, `-u` | `PLUGIN_USER`, `JENKINS_USER` | Conditional\* | Jenkins username |
|
||||
| Token | `--token`, `-t` | `PLUGIN_TOKEN`, `JENKINS_TOKEN` | Conditional\* | Jenkins API token |
|
||||
| Remote Token | `--remote-token` | `PLUGIN_REMOTE_TOKEN`, `JENKINS_REMOTE_TOKEN` | Conditional\* | Jenkins remote trigger token |
|
||||
| Job | `--job`, `-j` | `PLUGIN_JOB`, `JENKINS_JOB` | Yes | Jenkins job name(s) - can specify multiple |
|
||||
| Parameters | `--parameters`, `-p` | `PLUGIN_PARAMETERS`, `JENKINS_PARAMETERS` | No | Build parameters in `key=value` format |
|
||||
| Insecure | `--insecure` | `PLUGIN_INSECURE`, `JENKINS_INSECURE` | No | Allow insecure SSL connections (default: false) |
|
||||
| Wait | `--wait` | `PLUGIN_WAIT`, `JENKINS_WAIT` | No | Wait for job completion (default: false) |
|
||||
| Poll Interval | `--poll-interval` | `PLUGIN_POLL_INTERVAL`, `JENKINS_POLL_INTERVAL` | No | Interval between status checks (default: 10s) |
|
||||
| Timeout | `--timeout` | `PLUGIN_TIMEOUT`, `JENKINS_TIMEOUT` | No | Maximum time to wait for job completion (default: 30m) |
|
||||
| Parameter | CLI Flag | Environment Variable | Required | Description |
|
||||
| ------------- | -------------------- | ----------------------------------------------- | ------------- | ----------------------------------------------------------------- |
|
||||
| Host | `--host` | `PLUGIN_URL`, `JENKINS_URL` | Yes | Jenkins base URL (e.g., `http://jenkins.example.com/`) |
|
||||
| User | `--user`, `-u` | `PLUGIN_USER`, `JENKINS_USER` | Conditional\* | Jenkins username |
|
||||
| Token | `--token`, `-t` | `PLUGIN_TOKEN`, `JENKINS_TOKEN` | Conditional\* | Jenkins API token |
|
||||
| Remote Token | `--remote-token` | `PLUGIN_REMOTE_TOKEN`, `JENKINS_REMOTE_TOKEN` | Conditional\* | Jenkins remote trigger token |
|
||||
| Job | `--job`, `-j` | `PLUGIN_JOB`, `JENKINS_JOB` | Yes | Jenkins job name(s) - can specify multiple |
|
||||
| Parameters | `--parameters`, `-p` | `PLUGIN_PARAMETERS`, `JENKINS_PARAMETERS` | No | Build parameters in multi-line `key=value` format (one per line) |
|
||||
| Insecure | `--insecure` | `PLUGIN_INSECURE`, `JENKINS_INSECURE` | No | Allow insecure SSL connections (default: false) |
|
||||
| Wait | `--wait` | `PLUGIN_WAIT`, `JENKINS_WAIT` | No | Wait for job completion (default: false) |
|
||||
| Poll Interval | `--poll-interval` | `PLUGIN_POLL_INTERVAL`, `JENKINS_POLL_INTERVAL` | No | Interval between status checks (default: 10s) |
|
||||
| Timeout | `--timeout` | `PLUGIN_TIMEOUT`, `JENKINS_TIMEOUT` | No | Maximum time to wait for job completion (default: 30m) |
|
||||
| Debug | `--debug` | `PLUGIN_DEBUG`, `JENKINS_DEBUG` | No | Enable debug mode to show detailed parameter information (default: false) |
|
||||
|
||||
**Authentication Requirements**: You must provide either:
|
||||
|
||||
- `user` + `token` (API token authentication), OR
|
||||
- `remote-token` (remote trigger token authentication)
|
||||
|
||||
**Parameters Format**: The `parameters` field accepts a multi-line string where each line contains one `key=value` pair:
|
||||
|
||||
- Each parameter should be on a separate line
|
||||
- Format: `KEY=VALUE` (one per line)
|
||||
- Empty lines are automatically ignored
|
||||
- Whitespace-only lines are skipped
|
||||
- Keys are trimmed of surrounding whitespace
|
||||
- Values preserve intentional spaces
|
||||
- Values can contain `=` signs (everything after the first `=` is treated as the value)
|
||||
|
||||
## Usage
|
||||
|
||||
### Command Line
|
||||
@@ -175,8 +180,21 @@ drone-jenkins \
|
||||
--user appleboy \
|
||||
--token XXXXXXXX \
|
||||
--job my-jenkins-job \
|
||||
--parameters "ENVIRONMENT=production" \
|
||||
--parameters "VERSION=1.0.0"
|
||||
--parameters $'ENVIRONMENT=production\nVERSION=1.0.0'
|
||||
```
|
||||
|
||||
Or using environment variable:
|
||||
|
||||
```bash
|
||||
export JENKINS_PARAMETERS="ENVIRONMENT=production
|
||||
VERSION=1.0.0
|
||||
BRANCH=main"
|
||||
|
||||
drone-jenkins \
|
||||
--host http://jenkins.example.com/ \
|
||||
--user appleboy \
|
||||
--token XXXXXXXX \
|
||||
--job my-jenkins-job
|
||||
```
|
||||
|
||||
**Using remote token authentication:**
|
||||
@@ -201,6 +219,17 @@ drone-jenkins \
|
||||
--timeout 1h
|
||||
```
|
||||
|
||||
**With debug mode:**
|
||||
|
||||
```bash
|
||||
drone-jenkins \
|
||||
--host http://jenkins.example.com/ \
|
||||
--user appleboy \
|
||||
--token XXXXXXXX \
|
||||
--job my-jenkins-job \
|
||||
--debug
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
**Single job:**
|
||||
@@ -233,7 +262,7 @@ docker run --rm \
|
||||
-e JENKINS_USER=appleboy \
|
||||
-e JENKINS_TOKEN=xxxxxxx \
|
||||
-e JENKINS_JOB=my-jenkins-job \
|
||||
-e JENKINS_PARAMETERS="ENVIRONMENT=production,VERSION=1.0.0" \
|
||||
-e JENKINS_PARAMETERS=$'ENVIRONMENT=production\nVERSION=1.0.0\nBRANCH=main' \
|
||||
ghcr.io/appleboy/drone-jenkins
|
||||
```
|
||||
|
||||
@@ -251,6 +280,18 @@ docker run --rm \
|
||||
ghcr.io/appleboy/drone-jenkins
|
||||
```
|
||||
|
||||
**With debug mode:**
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
-e JENKINS_URL=http://jenkins.example.com/ \
|
||||
-e JENKINS_USER=appleboy \
|
||||
-e JENKINS_TOKEN=xxxxxxx \
|
||||
-e JENKINS_JOB=my-jenkins-job \
|
||||
-e JENKINS_DEBUG=true \
|
||||
ghcr.io/appleboy/drone-jenkins
|
||||
```
|
||||
|
||||
### Drone CI
|
||||
|
||||
Add the plugin to your `.drone.yml`:
|
||||
@@ -284,10 +325,11 @@ steps:
|
||||
job:
|
||||
- deploy-frontend
|
||||
- deploy-backend
|
||||
parameters:
|
||||
- ENVIRONMENT=production
|
||||
- VERSION=${DRONE_TAG}
|
||||
- COMMIT_SHA=${DRONE_COMMIT_SHA}
|
||||
parameters: |
|
||||
ENVIRONMENT=production
|
||||
VERSION=${DRONE_TAG}
|
||||
COMMIT_SHA=${DRONE_COMMIT_SHA}
|
||||
BRANCH=${DRONE_BRANCH}
|
||||
```
|
||||
|
||||
**Using remote token:**
|
||||
@@ -320,6 +362,21 @@ steps:
|
||||
timeout: 1h
|
||||
```
|
||||
|
||||
**With debug mode:**
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: trigger-jenkins
|
||||
image: ghcr.io/appleboy/drone-jenkins
|
||||
settings:
|
||||
url: http://jenkins.example.com/
|
||||
user: appleboy
|
||||
token:
|
||||
from_secret: jenkins_token
|
||||
job: my-jenkins-job
|
||||
debug: true
|
||||
```
|
||||
|
||||
For more detailed examples and advanced configurations, see [DOCS.md](DOCS.md).
|
||||
|
||||
## Development
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
module github.com/appleboy/drone-jenkins
|
||||
|
||||
go 1.22
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/appleboy/com v1.1.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/urfave/cli/v2 v2.27.5
|
||||
github.com/yassinebenaid/godump v0.11.1
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
github.com/appleboy/com v1.1.1 h1:iqu+BzrEcO3Towwi4E0GDRLSEeMBix3gf3LRjn9h8ow=
|
||||
github.com/appleboy/com v1.1.1/go.mod h1:WKU8+CaWcyLkpm0NLhGA8Wl/yGi3KXfTIXsp7T2ceZc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -14,6 +16,8 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
|
||||
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
github.com/yassinebenaid/godump v0.11.1 h1:SPujx/XaYqGDfmNh7JI3dOyCUVrG0bG2duhO3Eh2EhI=
|
||||
github.com/yassinebenaid/godump v0.11.1/go.mod h1:dc/0w8wmg6kVIvNGAzbKH1Oa54dXQx8SNKh4dPRyW44=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
+60
-1
@@ -11,6 +11,9 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/appleboy/com/gh"
|
||||
"github.com/yassinebenaid/godump"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -26,6 +29,7 @@ type (
|
||||
BaseURL string
|
||||
Token string // Remote trigger token
|
||||
Client *http.Client
|
||||
Debug bool // Enable debug mode to show detailed information
|
||||
}
|
||||
|
||||
// QueueItem represents a Jenkins queue item response
|
||||
@@ -53,7 +57,7 @@ type (
|
||||
)
|
||||
|
||||
// NewJenkins is initial Jenkins object
|
||||
func NewJenkins(auth *Auth, url string, token string, insecure bool) *Jenkins {
|
||||
func NewJenkins(auth *Auth, url string, token string, insecure bool, debug bool) *Jenkins {
|
||||
url = strings.TrimRight(url, "/")
|
||||
|
||||
client := http.DefaultClient
|
||||
@@ -71,6 +75,7 @@ func NewJenkins(auth *Auth, url string, token string, insecure bool) *Jenkins {
|
||||
BaseURL: url,
|
||||
Token: token,
|
||||
Client: client,
|
||||
Debug: debug,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,6 +294,24 @@ func (jenkins *Jenkins) waitForCompletion(
|
||||
buildNumber,
|
||||
buildInfo.Result,
|
||||
)
|
||||
|
||||
// Debug: Display final build info
|
||||
if jenkins.Debug {
|
||||
log.Println("=== Debug Mode: Build Result ===")
|
||||
if err := godump.Dump(buildInfo); err != nil {
|
||||
log.Printf("warning: failed to dump build info: %v", err)
|
||||
}
|
||||
log.Println("================================")
|
||||
}
|
||||
|
||||
// Set GitHub Actions output
|
||||
if err := gh.SetOutput(map[string]string{
|
||||
"result": buildInfo.Result,
|
||||
"url": buildInfo.URL,
|
||||
}); err != nil {
|
||||
log.Printf("warning: failed to set GitHub output: %v", err)
|
||||
}
|
||||
|
||||
return buildInfo, nil
|
||||
}
|
||||
|
||||
@@ -321,6 +344,42 @@ func (jenkins *Jenkins) trigger(job string, params url.Values) (int, error) {
|
||||
urlPath = jenkins.parseJobPath(job) + "/build"
|
||||
}
|
||||
|
||||
// Debug: Display parameters being sent
|
||||
if jenkins.Debug {
|
||||
log.Println("=== Debug Mode: Jenkins Job Trigger ===")
|
||||
log.Printf("Job: %s", job)
|
||||
log.Printf("URL Path: %s", urlPath)
|
||||
|
||||
// Build the full URL for display
|
||||
fullURL := jenkins.buildURL(urlPath, params)
|
||||
// Mask token in URL for display
|
||||
if jenkins.Token != "" {
|
||||
fullURL = strings.Replace(fullURL, "token="+jenkins.Token, "token=***MASKED***", 1)
|
||||
}
|
||||
log.Printf("Full URL: %s", fullURL)
|
||||
|
||||
if len(params) > 0 {
|
||||
// Create a copy of params with masked token for display
|
||||
displayParams := url.Values{}
|
||||
for key, values := range params {
|
||||
if key == "token" {
|
||||
// Mask token values for security
|
||||
displayParams[key] = []string{"***MASKED***"}
|
||||
} else {
|
||||
displayParams[key] = values
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Parameters:")
|
||||
if err := godump.Dump(displayParams); err != nil {
|
||||
log.Printf("warning: failed to dump parameters: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Println("Parameters: (none)")
|
||||
}
|
||||
log.Println("======================================")
|
||||
}
|
||||
|
||||
// All params (including token) are passed as query parameters
|
||||
// Returns the queue item ID for tracking
|
||||
return jenkins.postAndGetLocation(urlPath, params)
|
||||
|
||||
+10
-10
@@ -16,7 +16,7 @@ func TestParseJobPath(t *testing.T) {
|
||||
Username: "appleboy",
|
||||
Token: "1234",
|
||||
}
|
||||
jenkins := NewJenkins(auth, "http://example.com", "", false)
|
||||
jenkins := NewJenkins(auth, "http://example.com", "", false, false)
|
||||
|
||||
assert.Equal(t, "/job/foo", jenkins.parseJobPath("/foo/"))
|
||||
assert.Equal(t, "/job/foo", jenkins.parseJobPath("foo/"))
|
||||
@@ -29,7 +29,7 @@ func TestUnSupportProtocol(t *testing.T) {
|
||||
Username: "foo",
|
||||
Token: "bar",
|
||||
}
|
||||
jenkins := NewJenkins(auth, "example.com", "", false)
|
||||
jenkins := NewJenkins(auth, "example.com", "", false, false)
|
||||
|
||||
queueID, err := jenkins.trigger("drone-jenkins", nil)
|
||||
assert.NotNil(t, err)
|
||||
@@ -50,7 +50,7 @@ func TestTriggerBuild(t *testing.T) {
|
||||
Username: "foo",
|
||||
Token: "bar",
|
||||
}
|
||||
jenkins := NewJenkins(auth, server.URL, "remote-token", false)
|
||||
jenkins := NewJenkins(auth, server.URL, "remote-token", false, false)
|
||||
|
||||
params := url.Values{"param": []string{"value"}}
|
||||
queueID, err := jenkins.trigger("drone-jenkins", params)
|
||||
@@ -110,7 +110,7 @@ func TestPostAndGetLocation(t *testing.T) {
|
||||
Username: "test",
|
||||
Token: "test",
|
||||
}
|
||||
jenkins := NewJenkins(auth, server.URL, "", false)
|
||||
jenkins := NewJenkins(auth, server.URL, "", false, false)
|
||||
|
||||
queueID, err := jenkins.postAndGetLocation("/test", nil)
|
||||
|
||||
@@ -186,7 +186,7 @@ func TestGetQueueItem(t *testing.T) {
|
||||
Username: "test",
|
||||
Token: "test",
|
||||
}
|
||||
jenkins := NewJenkins(auth, server.URL, "", false)
|
||||
jenkins := NewJenkins(auth, server.URL, "", false, false)
|
||||
|
||||
queueItem, err := jenkins.getQueueItem(tt.queueID)
|
||||
|
||||
@@ -274,7 +274,7 @@ func TestGetBuildInfo(t *testing.T) {
|
||||
Username: "test",
|
||||
Token: "test",
|
||||
}
|
||||
jenkins := NewJenkins(auth, server.URL, "", false)
|
||||
jenkins := NewJenkins(auth, server.URL, "", false, false)
|
||||
|
||||
buildInfo, err := jenkins.getBuildInfo(tt.jobName, tt.buildNumber)
|
||||
|
||||
@@ -332,7 +332,7 @@ func TestWaitForCompletion(t *testing.T) {
|
||||
Username: "test",
|
||||
Token: "test",
|
||||
}
|
||||
jenkins := NewJenkins(auth, server.URL, "", false)
|
||||
jenkins := NewJenkins(auth, server.URL, "", false, false)
|
||||
|
||||
buildInfo, err := jenkins.waitForCompletion(
|
||||
"test-job",
|
||||
@@ -364,7 +364,7 @@ func TestWaitForCompletion(t *testing.T) {
|
||||
Username: "test",
|
||||
Token: "test",
|
||||
}
|
||||
jenkins := NewJenkins(auth, server.URL, "", false)
|
||||
jenkins := NewJenkins(auth, server.URL, "", false, false)
|
||||
|
||||
buildInfo, err := jenkins.waitForCompletion(
|
||||
"test-job",
|
||||
@@ -404,7 +404,7 @@ func TestWaitForCompletion(t *testing.T) {
|
||||
Username: "test",
|
||||
Token: "test",
|
||||
}
|
||||
jenkins := NewJenkins(auth, server.URL, "", false)
|
||||
jenkins := NewJenkins(auth, server.URL, "", false, false)
|
||||
|
||||
buildInfo, err := jenkins.waitForCompletion(
|
||||
"test-job",
|
||||
@@ -449,7 +449,7 @@ func TestWaitForCompletion(t *testing.T) {
|
||||
Username: "test",
|
||||
Token: "test",
|
||||
}
|
||||
jenkins := NewJenkins(auth, server.URL, "", false)
|
||||
jenkins := NewJenkins(auth, server.URL, "", false, false)
|
||||
|
||||
buildInfo, err := jenkins.waitForCompletion(
|
||||
"test-job",
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/yassinebenaid/godump"
|
||||
)
|
||||
|
||||
// Version set at compile-time
|
||||
@@ -23,6 +24,14 @@ ________ ____. __ .__
|
||||
version: {{.Version}}
|
||||
`
|
||||
|
||||
// maskToken masks a token string for secure display
|
||||
func maskToken(token string) string {
|
||||
if token == "" {
|
||||
return ""
|
||||
}
|
||||
return "***MASKED***"
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Load env-file if it exists first
|
||||
if filename, found := os.LookupEnv("PLUGIN_ENV_FILE"); found {
|
||||
@@ -83,10 +92,10 @@ func main() {
|
||||
Usage: "allow insecure server connections when using SSL",
|
||||
EnvVars: []string{"PLUGIN_INSECURE", "JENKINS_INSECURE", "INPUT_INSECURE"},
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "parameters",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "jenkins build parameters",
|
||||
Usage: "jenkins build parameters (multi-line format: key=value, one per line)",
|
||||
EnvVars: []string{"PLUGIN_PARAMETERS", "JENKINS_PARAMETERS", "INPUT_PARAMETERS"},
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
@@ -110,6 +119,11 @@ func main() {
|
||||
Value: 30 * time.Minute,
|
||||
EnvVars: []string{"PLUGIN_TIMEOUT", "JENKINS_TIMEOUT", "INPUT_TIMEOUT"},
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "enable debug mode to show detailed parameter information",
|
||||
EnvVars: []string{"PLUGIN_DEBUG", "JENKINS_DEBUG", "INPUT_DEBUG"},
|
||||
},
|
||||
}
|
||||
|
||||
// Override a template
|
||||
@@ -170,10 +184,48 @@ func run(c *cli.Context) error {
|
||||
RemoteToken: c.String("remote-token"),
|
||||
Job: c.StringSlice("job"),
|
||||
Insecure: c.Bool("insecure"),
|
||||
Parameters: c.StringSlice("parameters"),
|
||||
Parameters: c.String("parameters"),
|
||||
Wait: c.Bool("wait"),
|
||||
PollInterval: c.Duration("poll-interval"),
|
||||
Timeout: c.Duration("timeout"),
|
||||
Debug: c.Bool("debug"),
|
||||
}
|
||||
|
||||
// Display plugin configuration in debug mode
|
||||
if plugin.Debug {
|
||||
log.Println("=== Debug Mode: Plugin Configuration ===")
|
||||
|
||||
// Create a display copy with masked sensitive data
|
||||
displayPlugin := struct {
|
||||
BaseURL string
|
||||
Username string
|
||||
Token string
|
||||
RemoteToken string
|
||||
Job []string
|
||||
Insecure bool
|
||||
Parameters string
|
||||
Wait bool
|
||||
PollInterval time.Duration
|
||||
Timeout time.Duration
|
||||
Debug bool
|
||||
}{
|
||||
BaseURL: plugin.BaseURL,
|
||||
Username: plugin.Username,
|
||||
Token: maskToken(plugin.Token),
|
||||
RemoteToken: maskToken(plugin.RemoteToken),
|
||||
Job: plugin.Job,
|
||||
Insecure: plugin.Insecure,
|
||||
Parameters: plugin.Parameters,
|
||||
Wait: plugin.Wait,
|
||||
PollInterval: plugin.PollInterval,
|
||||
Timeout: plugin.Timeout,
|
||||
Debug: plugin.Debug,
|
||||
}
|
||||
|
||||
if err := godump.Dump(displayPlugin); err != nil {
|
||||
log.Printf("warning: failed to dump plugin configuration: %v", err)
|
||||
}
|
||||
log.Println("========================================")
|
||||
}
|
||||
|
||||
return plugin.Exec()
|
||||
|
||||
@@ -19,10 +19,11 @@ type (
|
||||
RemoteToken string // Optional remote trigger token for additional security
|
||||
Job []string // List of Jenkins job names to trigger
|
||||
Insecure bool // Whether to skip TLS certificate verification
|
||||
Parameters []string // Job parameters in key=value format
|
||||
Parameters string // Job parameters in key=value format (one per line)
|
||||
Wait bool // Whether to wait for job completion
|
||||
PollInterval time.Duration // Interval between status checks (default: 10s)
|
||||
Timeout time.Duration // Maximum time to wait for job completion (default: 30m)
|
||||
Debug bool // Enable debug mode to show detailed parameter information
|
||||
}
|
||||
)
|
||||
|
||||
@@ -41,15 +42,27 @@ func trimWhitespaceFromSlice(items []string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
// parseParameters converts a slice of key=value strings into url.Values.
|
||||
// parseParameters converts a multi-line string of key=value pairs into url.Values.
|
||||
// Each line should contain one key=value pair.
|
||||
// It logs a warning for any parameters that don't match the expected format.
|
||||
func parseParameters(params []string) url.Values {
|
||||
func parseParameters(params string) url.Values {
|
||||
values := url.Values{}
|
||||
|
||||
for _, param := range params {
|
||||
parts := strings.SplitN(param, "=", 2)
|
||||
// Split by newlines and process each line
|
||||
lines := strings.Split(params, "\n")
|
||||
for _, line := range lines {
|
||||
// Skip empty lines
|
||||
trimmedLine := strings.TrimSpace(line)
|
||||
if trimmedLine == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.SplitN(trimmedLine, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
log.Printf("warning: skipping invalid parameter format (expected key=value): %q", param)
|
||||
log.Printf(
|
||||
"warning: skipping invalid parameter format (expected key=value): %q",
|
||||
trimmedLine,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -57,7 +70,7 @@ func parseParameters(params []string) url.Values {
|
||||
value := parts[1] // Keep value as-is to preserve intentional spaces
|
||||
|
||||
if key == "" {
|
||||
log.Printf("warning: skipping parameter with empty key: %q", param)
|
||||
log.Printf("warning: skipping parameter with empty key: %q", trimmedLine)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -104,7 +117,7 @@ func (p Plugin) Exec() error {
|
||||
}
|
||||
|
||||
// Initialize Jenkins client
|
||||
jenkins := NewJenkins(auth, p.BaseURL, p.RemoteToken, p.Insecure)
|
||||
jenkins := NewJenkins(auth, p.BaseURL, p.RemoteToken, p.Insecure, p.Debug)
|
||||
|
||||
// Parse job parameters
|
||||
params := parseParameters(p.Parameters)
|
||||
|
||||
+28
-12
@@ -122,12 +122,12 @@ func TestTrimWhitespaceFromSlice(t *testing.T) {
|
||||
func TestParseParameters(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []string
|
||||
input string
|
||||
expected url.Values
|
||||
}{
|
||||
{
|
||||
name: "valid parameters",
|
||||
input: []string{"key1=value1", "key2=value2"},
|
||||
input: "key1=value1\nkey2=value2",
|
||||
expected: url.Values{
|
||||
"key1": []string{"value1"},
|
||||
"key2": []string{"value2"},
|
||||
@@ -135,38 +135,38 @@ func TestParseParameters(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "parameter with multiple equals signs",
|
||||
input: []string{"key=value=with=equals"},
|
||||
input: "key=value=with=equals",
|
||||
expected: url.Values{
|
||||
"key": []string{"value=with=equals"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "parameter with spaces in value",
|
||||
input: []string{"key=value with spaces"},
|
||||
input: "key=value with spaces",
|
||||
expected: url.Values{
|
||||
"key": []string{"value with spaces"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "parameter with empty value",
|
||||
input: []string{"key="},
|
||||
input: "key=",
|
||||
expected: url.Values{
|
||||
"key": []string{""},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid parameter format (no equals)",
|
||||
input: []string{"invalid"},
|
||||
input: "invalid",
|
||||
expected: url.Values{},
|
||||
},
|
||||
{
|
||||
name: "parameter with empty key",
|
||||
input: []string{"=value"},
|
||||
input: "=value",
|
||||
expected: url.Values{},
|
||||
},
|
||||
{
|
||||
name: "mixed valid and invalid",
|
||||
input: []string{"valid=yes", "invalid", "also=valid"},
|
||||
input: "valid=yes\ninvalid\nalso=valid",
|
||||
expected: url.Values{
|
||||
"valid": []string{"yes"},
|
||||
"also": []string{"valid"},
|
||||
@@ -174,16 +174,32 @@ func TestParseParameters(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "key with surrounding whitespace",
|
||||
input: []string{" key =value"},
|
||||
input: " key =value",
|
||||
expected: url.Values{
|
||||
"key": []string{"value"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty slice",
|
||||
input: []string{},
|
||||
name: "empty string",
|
||||
input: "",
|
||||
expected: url.Values{},
|
||||
},
|
||||
{
|
||||
name: "multiple empty lines",
|
||||
input: "key1=value1\n\n\nkey2=value2",
|
||||
expected: url.Values{
|
||||
"key1": []string{"value1"},
|
||||
"key2": []string{"value2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lines with whitespace only",
|
||||
input: "key1=value1\n \n\t\nkey2=value2",
|
||||
expected: url.Values{
|
||||
"key1": []string{"value1"},
|
||||
"key2": []string{"value2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -333,7 +349,7 @@ func TestExecWithParameters(t *testing.T) {
|
||||
Username: "foo",
|
||||
Token: "bar",
|
||||
Job: []string{"parameterized-job"},
|
||||
Parameters: []string{"branch=main", "environment=production"},
|
||||
Parameters: "branch=main\nenvironment=production",
|
||||
}
|
||||
|
||||
err := plugin.Exec()
|
||||
|
||||
Reference in New Issue
Block a user