23
23
*/
24
24
package jp .ikedam .jenkins .plugins .jobcopy_builder ;
25
25
26
+ import java .util .ArrayList ;
27
+ import java .util .Collections ;
26
28
import java .util .List ;
29
+ import java .util .StringTokenizer ;
27
30
28
31
import hudson .Extension ;
29
32
import hudson .XmlFile ;
30
33
import hudson .EnvVars ;
31
34
import hudson .Launcher ;
32
35
import hudson .DescriptorExtensionList ;
33
36
import hudson .matrix .MatrixProject ;
37
+ import hudson .model .Item ;
38
+ import hudson .model .ItemGroup ;
34
39
import hudson .model .TopLevelItem ;
35
40
import hudson .model .BuildListener ;
36
- import hudson .model .Job ;
37
41
import hudson .model .AbstractBuild ;
38
42
import hudson .model .AbstractItem ;
39
43
import hudson .model .AbstractProject ;
42
46
import hudson .util .FormValidation ;
43
47
import hudson .tasks .Builder ;
44
48
import hudson .tasks .BuildStepDescriptor ;
49
+ import jenkins .model .ModifiableTopLevelItemGroup ;
45
50
import jenkins .model .Jenkins ;
46
51
47
52
import org .apache .commons .lang .StringUtils ;
53
+ import org .kohsuke .stapler .AncestorInPath ;
48
54
import org .kohsuke .stapler .DataBoundConstructor ;
49
55
import org .kohsuke .stapler .QueryParameter ;
50
56
57
+ import com .google .common .base .Function ;
58
+ import com .google .common .collect .Lists ;
59
+
51
60
import java .io .InputStream ;
52
61
import java .io .ByteArrayInputStream ;
53
62
import java .io .IOException ;
54
63
import java .io .Serializable ;
55
64
65
+ import javax .xml .transform .Source ;
56
66
import javax .xml .transform .stream .StreamSource ;
57
67
58
68
/**
@@ -186,6 +196,7 @@ public JobcopyBuilder(String fromJobName, String toJobName, boolean overwrite, L
186
196
public boolean perform (AbstractBuild <?, ?> build , Launcher launcher , BuildListener listener )
187
197
throws IOException , InterruptedException
188
198
{
199
+ ItemGroup <?> context = build .getProject ().getRootProject ().getParent ();
189
200
EnvVars env = build .getEnvironment (listener );
190
201
191
202
if (StringUtils .isBlank (getFromJobName ()))
@@ -217,21 +228,21 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
217
228
listener .getLogger ().println (String .format ("Copying %s to %s" , fromJobNameExpanded , toJobNameExpanded ));
218
229
219
230
// Reteive the job to be copied from.
220
- TopLevelItem fromJob = Jenkins . getInstance (). getItem ( fromJobNameExpanded );
231
+ TopLevelItem fromJob = getRelative ( fromJobNameExpanded , context , TopLevelItem . class );
221
232
222
233
if (fromJob == null )
223
234
{
224
- listener .getLogger ().println ("Error: Item was not found." );
235
+ listener .getLogger ().println (String . format ( "Error: Item '%s ' was not found." , fromJob ) );
225
236
return false ;
226
237
}
227
- else if (!(fromJob instanceof Job <?,?> ))
238
+ else if (!(fromJob instanceof AbstractItem ))
228
239
{
229
- listener .getLogger ().println ("Error: Item was found, but is not a job." );
240
+ listener .getLogger ().println (String . format ( "Error: Item '%s' was found, but cannot be copied (does not support AbstractItem)." , fromJob ) );
230
241
return false ;
231
242
}
232
243
233
244
// Check whether the job to be copied to is already exists.
234
- TopLevelItem toJob = Jenkins . getInstance (). getItem ( toJobNameExpanded );
245
+ TopLevelItem toJob = getRelative ( toJobNameExpanded , context , TopLevelItem . class );
235
246
if (toJob != null ){
236
247
listener .getLogger ().println (String .format ("Already exists: %s" , toJobNameExpanded ));
237
248
if (!isOverwrite ()){
@@ -245,10 +256,9 @@ else if(!(fromJob instanceof Job<?,?>))
245
256
}
246
257
247
258
// Retrieve the config.xml of the job copied from.
248
- // TODO: what happens if this runs on a slave node?
249
259
listener .getLogger ().println (String .format ("Fetching configuration of %s..." , fromJobNameExpanded ));
250
260
251
- XmlFile file = ((Job <?,?> )fromJob ).getConfigFile ();
261
+ XmlFile file = ((AbstractItem )fromJob ).getConfigFile ();
252
262
String jobConfigXmlString = file .asString ();
253
263
String encoding = file .sniffEncoding ();
254
264
listener .getLogger ().println ("Original xml:" );
@@ -274,7 +284,28 @@ else if(!(fromJob instanceof Job<?,?>))
274
284
// Create the job copied to.
275
285
listener .getLogger ().println (String .format ("Creating %s" , toJobNameExpanded ));
276
286
InputStream is = new ByteArrayInputStream (jobConfigXmlString .getBytes (encoding ));
277
- toJob = Jenkins .getInstance ().createProjectFromXML (toJobNameExpanded , is );
287
+ ItemGroup <?> toContext = context ;
288
+ if (toJobNameExpanded .lastIndexOf ('/' ) >= 0 )
289
+ {
290
+ int pos = toJobNameExpanded .lastIndexOf ('/' );
291
+ String parentName = toJobNameExpanded .substring (0 , pos );
292
+ toJobNameExpanded = toJobNameExpanded .substring (pos + 1 );
293
+
294
+ toContext = getRelative (parentName , context , ItemGroup .class );
295
+ if (toContext == null )
296
+ {
297
+ listener .getLogger ().println (String .format ("Error: Target folder '%s' was not found." , parentName ));
298
+ return false ;
299
+ }
300
+ }
301
+
302
+ if (!(toContext instanceof ModifiableTopLevelItemGroup ))
303
+ {
304
+ listener .getLogger ().println (String .format ("Error: Target folder '%s' does not support ModifiableTopLevelItemGroup" , toContext .getFullName ()));
305
+ return false ;
306
+ }
307
+
308
+ toJob = ((ModifiableTopLevelItemGroup )toContext ).createProjectFromXML (toJobNameExpanded , is );
278
309
if (toJob == null )
279
310
{
280
311
listener .getLogger ().println (String .format ("Failed to create %s" , toJobNameExpanded ));
@@ -302,7 +333,7 @@ else if(!(fromJob instanceof Job<?,?>))
302
333
303
334
try
304
335
{
305
- target .updateByXml (new StreamSource (is ));
336
+ target .updateByXml (( Source ) new StreamSource (is ));
306
337
}
307
338
catch (IOException e )
308
339
{
@@ -331,7 +362,7 @@ else if(!(fromJob instanceof Job<?,?>))
331
362
332
363
// Do null update to reload the configuration.
333
364
AbstractItem target = (AbstractItem )toJob ;
334
- target .updateByXml (new StreamSource (target .getConfigFile ().readRaw ()));
365
+ target .updateByXml (( Source ) new StreamSource (target .getConfigFile ().readRaw ()));
335
366
}
336
367
337
368
// add the information of jobs copied from and to to the build.
@@ -340,6 +371,73 @@ else if(!(fromJob instanceof Job<?,?>))
340
371
return true ;
341
372
}
342
373
374
+ /**
375
+ * Reimplementation of {@link Jenkins#getItem(String, ItemGroup, Class)}
376
+ *
377
+ * Existing implementation has following problems:
378
+ * * Falls back to {@link Jenkins#getItemByFullName(String)}
379
+ * * Cannot get {@link ItemGroup}
380
+ *
381
+ * @param pathName
382
+ * @param context
383
+ * @param klass
384
+ * @return
385
+ */
386
+ public static <T > T getRelative (String pathName , ItemGroup <?> context , Class <T > klass )
387
+ {
388
+ if (context ==null )
389
+ {
390
+ context = Jenkins .getInstance ().getItemGroup ();
391
+ }
392
+ if (pathName ==null )
393
+ {
394
+ return null ;
395
+ }
396
+
397
+ if (pathName .startsWith ("/" ))
398
+ {
399
+ // absolute
400
+ Item item = Jenkins .getInstance ().getItemByFullName (pathName );
401
+ return klass .isInstance (item )?klass .cast (item ):null ;
402
+ }
403
+
404
+ Object /*Item|ItemGroup*/ ctx = context ;
405
+
406
+ StringTokenizer tokens = new StringTokenizer (pathName ,"/" );
407
+ while (tokens .hasMoreTokens ())
408
+ {
409
+ String s = tokens .nextToken ();
410
+ if (s .equals (".." ))
411
+ {
412
+ if (!(ctx instanceof Item ))
413
+ {
414
+ // can't go up further
415
+ return null ;
416
+ }
417
+ ctx = ((Item )ctx ).getParent ();
418
+ continue ;
419
+ }
420
+ if (s .equals ("." ))
421
+ {
422
+ continue ;
423
+ }
424
+
425
+ if (!(ctx instanceof ItemGroup ))
426
+ {
427
+ return null ;
428
+ }
429
+ ItemGroup <?> g = (ItemGroup <?>)ctx ;
430
+ Item i = g .getItem (s );
431
+ if (i == null || !i .hasPermission (Item .READ ))
432
+ {
433
+ return null ;
434
+ }
435
+ ctx =i ;
436
+ }
437
+
438
+ return klass .isInstance (ctx )?klass .cast (ctx ):null ;
439
+ }
440
+
343
441
/**
344
442
* The internal class to work with views.
345
443
*
@@ -399,11 +497,23 @@ public DescriptorExtensionList<JobcopyOperation,Descriptor<JobcopyOperation>> ge
399
497
*
400
498
* Used for the autocomplete of From Job Name.
401
499
*
402
- * @return the list of jobs
500
+ * @return the list of names of jobs
403
501
*/
404
- public ComboBoxModel doFillFromJobNameItems ()
502
+ public ComboBoxModel doFillFromJobNameItems (@ AncestorInPath AbstractProject <?,?> project )
405
503
{
406
- return new ComboBoxModel (Jenkins .getInstance ().getTopLevelItemNames ());
504
+ final ItemGroup <?> context = (project != null )?project .getParent ():Jenkins .getInstance ().getItemGroup ();
505
+ List <String > itemList = new ArrayList <String >(Lists .transform (
506
+ Jenkins .getInstance ().getAllItems (AbstractItem .class ),
507
+ new Function <Item , String >()
508
+ {
509
+ public String apply (Item input )
510
+ {
511
+ return input .getRelativeNameFrom (context );
512
+ }
513
+ }
514
+ ));
515
+ Collections .sort (itemList );
516
+ return new ComboBoxModel (itemList );
407
517
}
408
518
409
519
/**
@@ -476,8 +586,9 @@ private boolean containsVariable(String value)
476
586
* @param warnIfNotExists
477
587
* @return
478
588
*/
479
- public FormValidation doCheckJobName (String jobName , boolean warnIfExists , boolean warnIfNotExists )
589
+ public FormValidation doCheckJobName (AbstractProject <?,?> project , String jobName , boolean warnIfExists , boolean warnIfNotExists )
480
590
{
591
+ ItemGroup <?> context = (project != null )?project .getParent ():Jenkins .getInstance ().getItemGroup ();
481
592
jobName = StringUtils .trim (jobName );
482
593
483
594
if (StringUtils .isBlank (jobName ))
@@ -489,14 +600,18 @@ public FormValidation doCheckJobName(String jobName, boolean warnIfExists, boole
489
600
return FormValidation .ok ();
490
601
}
491
602
492
- TopLevelItem job = Jenkins . getInstance (). getItem ( jobName );
603
+ TopLevelItem job = getRelative ( jobName , context , TopLevelItem . class );
493
604
if (job != null )
494
605
{
495
606
// job exists
496
607
if (warnIfExists )
497
608
{
498
609
return FormValidation .warning (Messages .JobCopyBuilder_JobName_exists ());
499
610
}
611
+ if (!(job instanceof AbstractItem ))
612
+ {
613
+ return FormValidation .warning (Messages .JobCopyBuilder_JobName_notAbstractItem ());
614
+ }
500
615
}
501
616
else
502
617
{
@@ -516,9 +631,9 @@ public FormValidation doCheckJobName(String jobName, boolean warnIfExists, boole
516
631
* @param fromJobName
517
632
* @return
518
633
*/
519
- public FormValidation doCheckFromJobName (@ QueryParameter String fromJobName )
634
+ public FormValidation doCheckFromJobName (@ AncestorInPath AbstractProject <?,?> project , @ QueryParameter String fromJobName )
520
635
{
521
- return doCheckJobName (fromJobName , false , true );
636
+ return doCheckJobName (project , fromJobName , false , true );
522
637
}
523
638
524
639
/**
@@ -528,9 +643,9 @@ public FormValidation doCheckFromJobName(@QueryParameter String fromJobName)
528
643
* @param overwrite
529
644
* @return FormValidation object.
530
645
*/
531
- public FormValidation doCheckToJobName (@ QueryParameter String toJobName , @ QueryParameter boolean overwrite )
646
+ public FormValidation doCheckToJobName (@ AncestorInPath AbstractProject <?,?> project , @ QueryParameter String toJobName , @ QueryParameter boolean overwrite )
532
647
{
533
- return doCheckJobName (toJobName , !overwrite , false );
648
+ return doCheckJobName (project , toJobName , !overwrite , false );
534
649
}
535
650
}
536
651
}
0 commit comments