-
-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathplugin.go
More file actions
184 lines (156 loc) · 5.17 KB
/
plugin.go
File metadata and controls
184 lines (156 loc) · 5.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package main
import (
"context"
"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")
}
// Validate authentication: either (user + token) or remote-token must be provided
hasUserAuth := p.Username != "" && p.Token != ""
hasRemoteToken := p.RemoteToken != ""
if !hasUserAuth && !hasRemoteToken {
return errors.New("authentication 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.
// The context can be used to cancel operations mid-execution.
func (p Plugin) Exec(ctx context.Context) 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 (only if username and token are provided)
var auth *Auth
if p.Username != "" && p.Token != "" {
auth = &Auth{
Username: p.Username,
Token: p.Token,
}
}
// Initialize Jenkins client
jenkins, err := NewJenkins(ctx, 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(ctx, 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(
ctx,
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
}