Compare commits

..

7 Commits

Author SHA1 Message Date
Bo-Yi Wu 069e6455cc ci: update build workflow and dependencies for Go 1.24 compatibility
- Update Go version requirement from 1.22 to 1.24.0
- Add github.com/appleboy/com as a dependency
- Set GitHub Actions output with build result and URL after Jenkins build completion
- Log a warning if setting GitHub output fails

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-12-02 19:42:44 +08:00
Bo-Yi Wu eb51e55e81 build: enable detailed build info logging in debug mode
- Add debug logging to display final build information when debug mode is enabled

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-12-02 19:40:30 +08:00
Bo-Yi Wu da87ddb86b docs: document and illustrate debug mode configuration options
- Document the new debug mode, including its purpose and usage
- Add examples showing how to enable debug mode via CLI, Docker, and YAML configuration
- Update the configuration table to describe the debug option and its environment variables

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-12-02 17:25:40 +08:00
Bo-Yi Wu 60874908e6 docs: document and standardize multi-line build parameter configurations
- Add example configurations for build parameters and waiting for job completion
- Document new settings: wait, poll_interval, timeout, insecure, and remote_token
- Update parameters format to require multi-line key=value pairs (one per line)
- Clarify handling of whitespace, empty lines, and multiple equals signs in parameter values
- Revise CLI and Docker usage examples to use multi-line parameters format
- Update YAML configuration example to use multi-line parameters block

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-12-02 17:09:52 +08:00
Bo-Yi Wu f6e62d9c49 feat: switch Jenkins build parameters to multi-line string format
- Change the Jenkins build parameters input from a string slice to a multi-line string format
- Update parameter parsing to handle multi-line strings, skipping empty and whitespace-only lines
- Adjust all usages and tests to support the new string-based parameter format
- Add test cases for multiple empty lines and lines containing only whitespace in parameters

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-12-02 16:55:46 +08:00
Bo-Yi Wu cf9b9a0a0d feat: add debug mode with enhanced logging and token masking (#41)
* feat: add debug mode with enhanced logging and token masking

- Add godump library dependency for improved debug output
- Introduce a debug mode flag to the Jenkins and Plugin structs
- Update NewJenkins constructor and all usages to support the debug flag
- Mask sensitive tokens in debug output for security
- Log detailed parameter and configuration information when debug mode is enabled
- Add CLI flag to enable debug mode via environment variables

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

* fix: add error handling and logging for godump.Dump failures

- Add error handling for godump.Dump calls, logging a warning if dumping fails

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

---------

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-12-02 15:11:39 +08:00
Bo-Yi Wu 4c54d13899 docs: simplify Jenkins Docker setup and improve reliability
- Update Jenkins Docker run instructions to use a named volume and the slim image
- Change container restart policy to on-failure
- Remove note about creating a host directory for Jenkins data

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-12-02 14:36:48 +08:00
9 changed files with 314 additions and 62 deletions
+49
View File
@@ -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)
+84 -27
View File
@@ -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
+3 -1
View File
@@ -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 (
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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",
+55 -3
View File
@@ -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()
+21 -8
View File
@@ -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
View File
@@ -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()