5
5
import com .cloudbees .jenkins .GitHubRepositoryName ;
6
6
import com .cloudbees .jenkins .GitHubTrigger ;
7
7
import hudson .model .AbstractProject ;
8
+ import hudson .model .BooleanParameterValue ;
9
+ import hudson .model .Cause ;
10
+ import hudson .model .CauseAction ;
8
11
import hudson .model .Hudson ;
12
+ import hudson .model .Job ;
13
+ import hudson .model .ParametersAction ;
14
+ import hudson .model .ParameterValue ;
15
+ import hudson .model .StringParameterValue ;
16
+ import hudson .model .queue .QueueTaskFuture ;
9
17
import hudson .security .ACL ;
10
18
import hudson .triggers .Trigger ;
19
+ import hudson .triggers .TriggerDescriptor ;
20
+ import jenkins .model .Jenkins ;
21
+ import jenkins .model .ParameterizedJobMixIn ;
22
+ import net .sf .json .JSONArray ;
23
+ import net .sf .json .JSONException ;
11
24
import net .sf .json .JSONObject ;
12
25
import org .acegisecurity .Authentication ;
13
26
import org .acegisecurity .context .SecurityContextHolder ;
14
27
28
+ import java .util .ArrayList ;
29
+ import java .util .List ;
30
+ import java .util .Map ;
15
31
import java .util .logging .Logger ;
16
32
import java .util .regex .Matcher ;
17
33
import java .util .regex .Pattern ;
@@ -27,7 +43,22 @@ public class GitHubTriggerProcessor implements TriggerProcessor {
27
43
private static final Logger LOGGER = Logger .getLogger (GitHubTriggerProcessor .class .getName ());
28
44
29
45
public void trigger (String payload ) {
30
- processGitHubPayload (payload , SqsBuildTrigger .class );
46
+ JSONObject json = extractJsonFromPayload (payload );
47
+ // You can signal through the payload that you will be using a custom format
48
+ // by including a root object named "custom_format" of any type and value.
49
+ if (json .has ("custom_format" )) {
50
+ processCustomPayload (json , SqsBuildTrigger .class );
51
+ }
52
+ // The default format, e.g. payload passed directly from a GitHub webhook,
53
+ // always includes a "repository" object.
54
+ else if (json .has ("repository" )) {
55
+ // Note that the payload will again be extracted from JSON at the start of the processGitHubPayload function.
56
+ // Leaving it this way so as not to change the contract, to be backwards compatible with any integrations.
57
+ processGitHubPayload (payload , SqsBuildTrigger .class );
58
+ }
59
+ else {
60
+ LOGGER .warning ("Unable to determine the format of the SQS message." );
61
+ }
31
62
}
32
63
33
64
public void processGitHubPayload (String payload , Class <? extends Trigger > triggerClass ) {
@@ -71,22 +102,117 @@ public void processGitHubPayload(String payload, Class<? extends Trigger> trigge
71
102
}
72
103
}
73
104
74
- private JSONObject extractJsonFromPayload (String payload ) {
75
- JSONObject repository = null ;
105
+ public JSONObject extractJsonFromPayload (String payload ) {
76
106
JSONObject json = JSONObject .fromObject (payload );
77
107
if (json .has ("Type" )) {
78
108
String msg = json .getString ("Message" );
79
109
if (msg != null ) {
80
110
char ch [] = msg .toCharArray ();
81
111
if ((ch [0 ] == '"' ) && (ch [msg .length ()-1 ]) == '"' ) {
82
- msg = msg .substring (1 ,msg .length ()-1 ); //remove the leading and trailing double quotes
112
+ msg = msg .substring (1 ,msg .length ()-1 ); //remove the leading and trailing double quotes
83
113
}
84
114
return JSONObject .fromObject (msg );
85
115
}
86
- } else if (json .has ("repository" )){
87
- return json ;
116
+ }
117
+ return json ;
118
+ }
119
+
120
+ public void processCustomPayload (JSONObject json , Class <? extends Trigger > triggerClass ) {
121
+ // Note that custom payloads will only trigger jobs that are configured with this SQS trigger.
122
+ // The custom payload must contain a root object named "job" of type string.
123
+ String jobToTrigger = json .getString ("job" );
124
+ if (jobToTrigger == null ) {
125
+ LOGGER .warning ("Custom sqs message payload does not contain information about which job to trigger." );
126
+ return ;
127
+ }
128
+ // The custom payload can contain a root object named "parameters"
129
+ // that contains a list of parameters to pass to the job when scheduled.
130
+ // Each parameter object should contain information about its type, name, and value.
131
+ // TODO this will only work with string or boolean parameters,
132
+ // and you need to pass in ALL parameters for the job,
133
+ // as the scheduled job will not fill in any defaults for you.
134
+ List <ParameterValue > parameters = getParamsFromJson (json );
135
+
136
+ Authentication old = SecurityContextHolder .getContext ().getAuthentication ();
137
+ SecurityContextHolder .getContext ().setAuthentication (ACL .SYSTEM );
138
+ try {
139
+ Jenkins jenkins = Jenkins .getInstance ();
140
+ for (Job job : jenkins .getAllItems (Job .class )) {
141
+ String jobName = job .getDisplayName ();
142
+ if (jobName .equals (jobToTrigger )) {
143
+ // Custom triggers operate on Parameterized jobs only
144
+ if (job instanceof ParameterizedJobMixIn .ParameterizedJob ) {
145
+ // Make sure the job is configured to use the SQS trigger
146
+ ParameterizedJobMixIn .ParameterizedJob pJob = (ParameterizedJobMixIn .ParameterizedJob ) job ;
147
+ final Map <TriggerDescriptor , Trigger <?>> pJobTriggers = pJob .getTriggers ();
148
+ SqsBuildTrigger .DescriptorImpl descriptor = jenkins .getDescriptorByType (SqsBuildTrigger .DescriptorImpl .class );
149
+ if (!pJobTriggers .containsKey (descriptor )) {
150
+ LOGGER .warning ("The job " + jobName + " is not configured to use the SQS trigger." );
151
+ } else {
152
+ final Job theJob = job ;
153
+ ParameterizedJobMixIn mixin = new ParameterizedJobMixIn () {
154
+ @ Override
155
+ protected Job asJob () {
156
+ return theJob ;
157
+ }
158
+ };
159
+ Cause cause = new Cause .RemoteCause ("SQS" , "Triggered by SQS." );
160
+ CauseAction cAction = new CauseAction (cause );
161
+ ParametersAction pAction = new ParametersAction (parameters );
162
+ final QueueTaskFuture queueTaskFuture = mixin .scheduleBuild2 (0 , cAction , pAction );
163
+ if (queueTaskFuture == null ) {
164
+ LOGGER .warning ("Unable to schedule the job " + jobName );
165
+ }
166
+ }
167
+ } else {
168
+ LOGGER .warning ("The job " + jobName + " is not configured as a Parameterized Job." );
169
+ }
170
+ }
171
+ }
172
+ } finally {
173
+ SecurityContextHolder .getContext ().setAuthentication (old );
174
+ }
175
+ }
176
+
177
+ private Boolean isValidParameterJson (JSONObject param ) {
178
+ if (param .has ("name" ) && param .has ("value" ) && param .has ("type" )) {
179
+ if (param .getString ("type" ).matches ("string|boolean" )) {
180
+ return true ;
181
+ } else {
182
+ LOGGER .warning ("'string' and 'boolean' are the only supported parameter types." );
183
+ return false ;
184
+ }
185
+ } else {
186
+ LOGGER .warning ("Parameters must contain key/value pairs for 'name', 'value', and 'type'." );
187
+ return false ;
188
+ }
189
+ }
88
190
191
+ public List <ParameterValue > getParamsFromJson (JSONObject json ) {
192
+ List <ParameterValue > params = new ArrayList <ParameterValue >();
193
+ if (json .has ("parameters" )) {
194
+ try {
195
+ JSONArray parameters = json .getJSONArray ("parameters" );
196
+ for (int i = 0 ; i < parameters .size (); i ++) {
197
+ JSONObject param = (JSONObject ) parameters .get (i );
198
+ if (isValidParameterJson (param )) {
199
+ String name = param .getString ("name" );
200
+ String type = param .getString ("type" );
201
+ if (type .equals ("boolean" )) {
202
+ Boolean value = param .getBoolean ("value" );
203
+ BooleanParameterValue parameterValue = new BooleanParameterValue (name , value );
204
+ params .add (parameterValue );
205
+ } else {
206
+ String value = param .getString ("value" );
207
+ StringParameterValue parameterValue = new StringParameterValue (name , value );
208
+ params .add (parameterValue );
209
+ }
210
+ }
211
+ }
212
+ } catch (JSONException e ) {
213
+ LOGGER .warning ("Parameters must be passed as a JSONArray in the SQS message." );
214
+ }
89
215
}
90
- return null ;
216
+ return params ;
91
217
}
92
218
}
0 commit comments