mirror of
https://github.com/appleboy/drone-jenkins.git
synced 2026-06-04 10:15:02 +08:00
02829360ad
* feat: add support for custom CA certificates for SSL/TLS connections - Add support for custom CA certificates (via PEM content, file path, or HTTP/HTTPS URL) for SSL/TLS connections. - Document the new CA certificate option and usage examples for CLI, Docker, and Drone CI in the README. - Update Jenkins client initialization to load and validate a custom CA certificate if provided, using a priority where insecure mode overrides custom CA. - Introduce comprehensive tests for CA certificate loading and Jenkins client initialization with different CA certificate sources and error scenarios. - Register the new ca-cert command-line flag and propagate its value through configuration and debug output. - Ensure that error handling for certificate loading fully propagates failures. Signed-off-by: appleboy <appleboy.tw@gmail.com> * test: update test CA certificate with new sample - Replace the sample CA certificate used in tests with a new certificate Signed-off-by: appleboy <appleboy.tw@gmail.com> --------- Signed-off-by: appleboy <appleboy.tw@gmail.com>
171 lines
4.8 KiB
Go
171 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type (
|
|
// Plugin represents the configuration for the Jenkins plugin.
|
|
// It contains all necessary credentials and settings to trigger Jenkins jobs.
|
|
Plugin struct {
|
|
BaseURL string // Jenkins server base URL
|
|
Username string // Jenkins username for authentication
|
|
Token string // Jenkins API token for authentication
|
|
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
|
|
CACert string // Custom CA certificate (PEM content, file path, or HTTP URL)
|
|
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
|
|
}
|
|
)
|
|
|
|
// trimWhitespaceFromSlice removes empty and whitespace-only strings from a slice.
|
|
// It returns a new slice containing only non-empty trimmed strings.
|
|
func trimWhitespaceFromSlice(items []string) []string {
|
|
result := make([]string, 0, len(items))
|
|
|
|
for _, item := range items {
|
|
trimmed := strings.TrimSpace(item)
|
|
if trimmed != "" {
|
|
result = append(result, trimmed)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// 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 {
|
|
values := url.Values{}
|
|
|
|
// 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",
|
|
trimmedLine,
|
|
)
|
|
continue
|
|
}
|
|
|
|
key := strings.TrimSpace(parts[0])
|
|
value := parts[1] // Keep value as-is to preserve intentional spaces
|
|
|
|
if key == "" {
|
|
log.Printf("warning: skipping parameter with empty key: %q", trimmedLine)
|
|
continue
|
|
}
|
|
|
|
values.Add(key, value)
|
|
}
|
|
|
|
return values
|
|
}
|
|
|
|
// validateConfig checks that all required plugin configuration is present.
|
|
// It returns a descriptive error if any required field is missing.
|
|
func (p Plugin) validateConfig() error {
|
|
if p.BaseURL == "" {
|
|
return errors.New("jenkins base URL is required")
|
|
}
|
|
if p.Username == "" {
|
|
return errors.New("jenkins username is required")
|
|
}
|
|
if p.Token == "" {
|
|
return errors.New("jenkins API token is required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Exec executes the plugin by triggering the configured Jenkins jobs.
|
|
// It validates the configuration, parses parameters, and triggers each job sequentially.
|
|
// Returns an error if validation fails or any job trigger fails.
|
|
func (p Plugin) Exec() error {
|
|
// Validate required configuration
|
|
if err := p.validateConfig(); err != nil {
|
|
return fmt.Errorf("configuration error: %w", err)
|
|
}
|
|
|
|
// Clean and validate job list
|
|
jobs := trimWhitespaceFromSlice(p.Job)
|
|
if len(jobs) == 0 {
|
|
return errors.New("at least one Jenkins job name is required")
|
|
}
|
|
|
|
// Set up authentication
|
|
auth := &Auth{
|
|
Username: p.Username,
|
|
Token: p.Token,
|
|
}
|
|
|
|
// Initialize Jenkins client
|
|
jenkins, err := NewJenkins(auth, p.BaseURL, p.RemoteToken, p.Insecure, p.CACert, p.Debug)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to initialize Jenkins client: %w", err)
|
|
}
|
|
|
|
// Parse job parameters
|
|
params := parseParameters(p.Parameters)
|
|
|
|
// Set default values for wait configuration
|
|
pollInterval := p.PollInterval
|
|
if pollInterval == 0 {
|
|
pollInterval = 10 * time.Second
|
|
}
|
|
|
|
timeout := p.Timeout
|
|
if timeout == 0 {
|
|
timeout = 30 * time.Minute
|
|
}
|
|
|
|
// Trigger each job
|
|
for _, jobName := range jobs {
|
|
queueID, err := jenkins.trigger(jobName, params)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to trigger job %q: %w", jobName, err)
|
|
}
|
|
log.Printf("successfully triggered job: %s (queue #%d)", jobName, queueID)
|
|
|
|
// Wait for job completion if requested
|
|
if p.Wait {
|
|
buildInfo, err := jenkins.waitForCompletion(jobName, queueID, pollInterval, timeout)
|
|
if err != nil {
|
|
return fmt.Errorf("error waiting for job %q: %w", jobName, err)
|
|
}
|
|
|
|
// Check if build was successful
|
|
if buildInfo.Result != "SUCCESS" {
|
|
return fmt.Errorf(
|
|
"job %q (build #%d) failed with status: %s",
|
|
jobName,
|
|
buildInfo.Number,
|
|
buildInfo.Result,
|
|
)
|
|
}
|
|
|
|
log.Printf("job %s (build #%d) completed successfully", jobName, buildInfo.Number)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|