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>
This commit is contained in:
Bo-Yi Wu
2025-12-02 15:11:39 +08:00
committed by GitHub
parent 4c54d13899
commit cf9b9a0a0d
6 changed files with 108 additions and 12 deletions
+1
View File
@@ -6,6 +6,7 @@ require (
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/urfave/cli/v2 v2.27.5 github.com/urfave/cli/v2 v2.27.5
github.com/yassinebenaid/godump v0.11.1
) )
require ( require (
+2
View File
@@ -14,6 +14,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/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 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+41 -1
View File
@@ -11,6 +11,8 @@ import (
"net/url" "net/url"
"strings" "strings"
"time" "time"
"github.com/yassinebenaid/godump"
) )
type ( type (
@@ -26,6 +28,7 @@ type (
BaseURL string BaseURL string
Token string // Remote trigger token Token string // Remote trigger token
Client *http.Client Client *http.Client
Debug bool // Enable debug mode to show detailed information
} }
// QueueItem represents a Jenkins queue item response // QueueItem represents a Jenkins queue item response
@@ -53,7 +56,7 @@ type (
) )
// NewJenkins is initial Jenkins object // 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, "/") url = strings.TrimRight(url, "/")
client := http.DefaultClient client := http.DefaultClient
@@ -71,6 +74,7 @@ func NewJenkins(auth *Auth, url string, token string, insecure bool) *Jenkins {
BaseURL: url, BaseURL: url,
Token: token, Token: token,
Client: client, Client: client,
Debug: debug,
} }
} }
@@ -321,6 +325,42 @@ func (jenkins *Jenkins) trigger(job string, params url.Values) (int, error) {
urlPath = jenkins.parseJobPath(job) + "/build" 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 // All params (including token) are passed as query parameters
// Returns the queue item ID for tracking // Returns the queue item ID for tracking
return jenkins.postAndGetLocation(urlPath, params) return jenkins.postAndGetLocation(urlPath, params)
+10 -10
View File
@@ -16,7 +16,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, 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/"))
@@ -29,7 +29,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, false)
queueID, err := jenkins.trigger("drone-jenkins", nil) queueID, err := jenkins.trigger("drone-jenkins", nil)
assert.NotNil(t, err) assert.NotNil(t, err)
@@ -50,7 +50,7 @@ func TestTriggerBuild(t *testing.T) {
Username: "foo", Username: "foo",
Token: "bar", 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"}} params := url.Values{"param": []string{"value"}}
queueID, err := jenkins.trigger("drone-jenkins", params) queueID, err := jenkins.trigger("drone-jenkins", params)
@@ -110,7 +110,7 @@ func TestPostAndGetLocation(t *testing.T) {
Username: "test", Username: "test",
Token: "test", Token: "test",
} }
jenkins := NewJenkins(auth, server.URL, "", false) jenkins := NewJenkins(auth, server.URL, "", false, false)
queueID, err := jenkins.postAndGetLocation("/test", nil) queueID, err := jenkins.postAndGetLocation("/test", nil)
@@ -186,7 +186,7 @@ func TestGetQueueItem(t *testing.T) {
Username: "test", Username: "test",
Token: "test", Token: "test",
} }
jenkins := NewJenkins(auth, server.URL, "", false) jenkins := NewJenkins(auth, server.URL, "", false, false)
queueItem, err := jenkins.getQueueItem(tt.queueID) queueItem, err := jenkins.getQueueItem(tt.queueID)
@@ -274,7 +274,7 @@ func TestGetBuildInfo(t *testing.T) {
Username: "test", Username: "test",
Token: "test", Token: "test",
} }
jenkins := NewJenkins(auth, server.URL, "", false) jenkins := NewJenkins(auth, server.URL, "", false, false)
buildInfo, err := jenkins.getBuildInfo(tt.jobName, tt.buildNumber) buildInfo, err := jenkins.getBuildInfo(tt.jobName, tt.buildNumber)
@@ -332,7 +332,7 @@ func TestWaitForCompletion(t *testing.T) {
Username: "test", Username: "test",
Token: "test", Token: "test",
} }
jenkins := NewJenkins(auth, server.URL, "", false) jenkins := NewJenkins(auth, server.URL, "", false, false)
buildInfo, err := jenkins.waitForCompletion( buildInfo, err := jenkins.waitForCompletion(
"test-job", "test-job",
@@ -364,7 +364,7 @@ func TestWaitForCompletion(t *testing.T) {
Username: "test", Username: "test",
Token: "test", Token: "test",
} }
jenkins := NewJenkins(auth, server.URL, "", false) jenkins := NewJenkins(auth, server.URL, "", false, false)
buildInfo, err := jenkins.waitForCompletion( buildInfo, err := jenkins.waitForCompletion(
"test-job", "test-job",
@@ -404,7 +404,7 @@ func TestWaitForCompletion(t *testing.T) {
Username: "test", Username: "test",
Token: "test", Token: "test",
} }
jenkins := NewJenkins(auth, server.URL, "", false) jenkins := NewJenkins(auth, server.URL, "", false, false)
buildInfo, err := jenkins.waitForCompletion( buildInfo, err := jenkins.waitForCompletion(
"test-job", "test-job",
@@ -449,7 +449,7 @@ func TestWaitForCompletion(t *testing.T) {
Username: "test", Username: "test",
Token: "test", Token: "test",
} }
jenkins := NewJenkins(auth, server.URL, "", false) jenkins := NewJenkins(auth, server.URL, "", false, false)
buildInfo, err := jenkins.waitForCompletion( buildInfo, err := jenkins.waitForCompletion(
"test-job", "test-job",
+52
View File
@@ -8,6 +8,7 @@ import (
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/yassinebenaid/godump"
) )
// Version set at compile-time // Version set at compile-time
@@ -23,6 +24,14 @@ ________ ____. __ .__
version: {{.Version}} version: {{.Version}}
` `
// maskToken masks a token string for secure display
func maskToken(token string) string {
if token == "" {
return ""
}
return "***MASKED***"
}
func main() { func main() {
// Load env-file if it exists first // Load env-file if it exists first
if filename, found := os.LookupEnv("PLUGIN_ENV_FILE"); found { if filename, found := os.LookupEnv("PLUGIN_ENV_FILE"); found {
@@ -110,6 +119,11 @@ func main() {
Value: 30 * time.Minute, Value: 30 * time.Minute,
EnvVars: []string{"PLUGIN_TIMEOUT", "JENKINS_TIMEOUT", "INPUT_TIMEOUT"}, 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 // Override a template
@@ -174,6 +188,44 @@ func run(c *cli.Context) error {
Wait: c.Bool("wait"), Wait: c.Bool("wait"),
PollInterval: c.Duration("poll-interval"), PollInterval: c.Duration("poll-interval"),
Timeout: c.Duration("timeout"), 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() return plugin.Exec()
+2 -1
View File
@@ -23,6 +23,7 @@ type (
Wait bool // Whether to wait for job completion Wait bool // Whether to wait for job completion
PollInterval time.Duration // Interval between status checks (default: 10s) PollInterval time.Duration // Interval between status checks (default: 10s)
Timeout time.Duration // Maximum time to wait for job completion (default: 30m) Timeout time.Duration // Maximum time to wait for job completion (default: 30m)
Debug bool // Enable debug mode to show detailed parameter information
} }
) )
@@ -104,7 +105,7 @@ func (p Plugin) Exec() error {
} }
// Initialize Jenkins client // 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 // Parse job parameters
params := parseParameters(p.Parameters) params := parseParameters(p.Parameters)