-
Notifications
You must be signed in to change notification settings - Fork 581
/
nxrm.groovy
1228 lines (1074 loc) · 42.9 KB
/
nxrm.groovy
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env groovy
/*
* Sonatype Nexus (TM) Open Source Version
* Copyright (c) 2008-present Sonatype, Inc.
* All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.
*
* This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,
* which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks
* of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the
* Eclipse Foundation. All other trademarks are the property of their respective owners.
*/
/**
* Requires:
* - git
* - unbuffer (apt install expect-dev, brew install expect --with-brewed-tk)
* - Takari (optional but recommended. Much quicker builds.) - see http://takari.io/book/30-team-maven.html#takari-smart-builder
* To enable: Add 'takari=true' to .nxrm/nxrmrc.groovy
*/
@Grab(group = 'com.aestasit.infrastructure.sshoogr', module = 'sshoogr', version = '0.9.26')
@Grab(group = 'com.caseyscarborough.colorizer', module = 'groovy-colorizer', version = '1.0.0')
@Grab(group = 'jline', module = 'jline', version = '2.14.2')
@Grab(group = 'org.ajoberstar', module = 'grgit', version = '2.2.1')
@Grab(group = 'org.apache.commons', module = 'commons-compress', version = '1.24.0')
@Grab(group = 'commons-io', module = 'commons-io', version = '2.13.0')
@Grab(group = 'org.apache.maven', module = 'maven-model', version = '3.8.1')
@Grab(group = 'org.rauschig', module = 'jarchivelib', version = '1.2.0')
@Grab(group = 'com.google.guava', module = 'guava', version = '32.1.1-jre')
import java.nio.file.Paths
import java.time.ZonedDateTime
import com.caseyscarborough.colorizer.Colorizer
import org.ajoberstar.grgit.*
import org.ajoberstar.grgit.operation.*
import org.ajoberstar.grgit.service.*
import org.apache.commons.io.FileUtils
import org.apache.maven.model.Model
import org.apache.maven.model.io.xpp3.MavenXpp3Reader
import org.rauschig.jarchivelib.ArchiveFormat
import org.rauschig.jarchivelib.ArchiverFactory
import com.google.common.base.Stopwatch
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import groovy.xml.XmlNodePrinter
import static com.aestasit.infrastructure.ssh.DefaultSsh.*
import static java.time.ZoneId.systemDefault
import static java.time.format.DateTimeFormatter.ofPattern
import java.util.zip.*
ant = new AntBuilder()
ant.project.buildListeners[0].messageOutputLevel = 0
HR = "".padRight(jline.TerminalFactory.get().getWidth() - 7, '-') // terminal line width
TARGET_DIR = "target"
LOCK_FILE = "$TARGET_DIR/sonatype-work/nexus3/lock"
SONATYPE_WORK = "$TARGET_DIR/sonatype-work"
SONATYPE_WORK_BACKUP = System.getProperty("java.io.tmpdir") + "/nxrm-sonatype-work"
TAKARI_SMART_BUILD_VERSION = "0.6.6"
TAKARI_LOCAL_REPO_VERSION = "0.11.3"
TAKARI_FILE_MANAGER_VERSION = "0.8.3"
MAVEN_BUILD_CACHE_VERSION = "1.2.0"
env = System.getenv()
changedProjects = [] as Set
ignoredProjects = [':nexus-buildsupport-scripts'] // always ignore these projects
cliOptions = null
positionalOptions = null
buildLog = new File("build.log")
// test projects - generated with command: for i in `find -name pom.xml`; do cd `dirname $i`; xmllint --xpath "//*[local-name()='project']/*[local-name()='artifactId']/text()" pom.xml; cd -; done
testProjects = [':functional-testsuite', ':nexus-analytics-testsupport', ':nexus-contributedhandler-testsupport', ':nexus-docker-testsupport-internal', ':nexus-ldap-testsupport', ':nexus-migration-testsupport', ':nexus-repository-testsupport-internal', ':nexus-saml-testsupport', ':nexus-stress-testsuite', ':nexus-testlm-edition', ':nexus-testsuite-data', ':nexus-upgrade-testsupport', ':nexuspro-fabric-testsuite', ':nexuspro-migration-testsuite', ':nexuspro-modern-testsuite', ':nexuspro-performance-testsuite', ':nexuspro-sql-fabric-testsuite', ':nexuspro-testsuite', ':nxrm-pro-image', ':pax-exam-spock', ':selenide-functional-tests' ]
/**
Customize these by creating a .nxrm/nxrmrc.groovy. Sample contents:
javaMaxMem="4g"
directMaxMem="4g"
vmOptions="-XX:-MaxFDLimit"
port=8082
sslPort=8444
karafSshPort=8023
javaDebugPort=5006
ssl=true
elastic=false
takari=false
deploy=true
caching=false
//backup=false
//restore=false
//tests="custom Maven test arguments here"
//assemblies="custom Maven assembly arguments here"
//sources="custom Maven sources arguments here"
*/
configDefaults = [
javaMinMem : '2703m',
javaMaxMem : '2703m',
directMaxMem : '2703m',
vmOptions : '',
port : 8081,
sslPort : 8443,
karafSshPort : 8022,
javaDebugPort: 5005,
ssl : true, // SSL is enabled by default
elastic : false, // Elastic is disabled by default
takari : false, // Takari is disabled by default
caching : false, // Maven build caching disabled by default
deploy : true, // Deployment is performed by default
backup : false, // Backup sonatype-work disabled by default
restore : false, // Restore backup of sonatype-work disabled by default
builder : '-T 1C', // default one thread per core
randomPassword: false,
// use the default maven property or change to "install" for faster but less stable build which also updates the lock file
npmInstall: '',
// default to building both debug and production builds with webpack - change to "build" to get the debug build copied as the "production" build (saves around 30s of build time)
npmBuild: ''
]
buildOptions = [
buildMode : "",
buildModeDesc : "",
mavenGoalsAndPhases : "",
mavenGoalsAndPhasesDesc: "",
tests : "",
testsDesc : "",
assemblies : "",
assemblyDesc : "",
sources : "",
sourcesDesc : "",
]
def hr = {
info("$HR")
}
def info(String message) {
println Colorizer.colorize("|[lightBlue]INFO[default]| ${message}")
buildLog << "|INFO| ${message}\n"
}
def warn(String message) {
println Colorizer.colorize("|[lightYellow]WARNING[default]| ${message}")
buildLog << "|WARNING| ${message}\n"
}
def error(String message) {
println Colorizer.colorize("|[lightRed]ERROR[default]| ${message}")
buildLog << "|ERROR| ${message}\n"
}
def debug(String message) {
if (cliOptions.verbose) {
println Colorizer.colorize("|[lightYellow]DEBUG[default]| ${message}")
buildLog << "|DEBUG| ${message}\n"
}
}
File searchUp(File directory) {
if (!directory) {
return null
}
File pom = new File(directory, "pom.xml")
if (pom.exists()) {
return pom
}
else {
return searchUp(directory.getParentFile())
}
}
def getChangedProjects() {
def grgit = Grgit.open()
def projects = [] as Set
try {
def changes = grgit.status().staged.getAllChanges() + grgit.status().unstaged.getAllChanges()
// for all changes, search up for a pom.xml, and get artifactId out
MavenXpp3Reader reader = new MavenXpp3Reader()
changes.each {
File pom = searchUp(new File(it).getParentFile())
// note: this ignores files in the project root
if (pom) {
Model model = reader.read(new FileReader(pom))
projects.add(":" + model.getArtifactId())
}
}
} catch (e) {
debug("Unable to determine changed projects: ${e}")
}
return projects
}
ConfigObject processRcConfigFile() {
// start with defaults
ConfigObject config = new ConfigObject()
config.putAll(configDefaults)
// combine with overrides
def rcFile = new File('.nxrm/nxrmrc.groovy')
if(rcFile.exists()) {
def overrides = new ConfigSlurper().parse(rcFile.toURI().toURL())
config = config.merge(overrides)
}
else {
// create .nxrm folder if it doesn't exist
File nxrmFolder = new File('.nxrm/')
if(!nxrmFolder.exists()) {
nxrmFolder.mkdir()
info("First run. Creating .nxrm folder")
}
}
// assign any CLI options
config.port = cliOptions.'port' ?: config.port
config.sslPort = cliOptions.'ssl-port' ?: config.sslPort
config.sslIp = cliOptions.'ssl-ip' ?: null
config.karafSshPort = cliOptions.'karaf-ssh-port' ?: config.karafSshPort
config.javaDebugPort = cliOptions.'java-debug-port' ?: config.javaDebugPort
config.ssl = assign('ssl', 'no-ssl', config.ssl)
config.elastic = assign('elastic', 'no-elastic', config.elastic)
config.takari = assign('takari', 'no-takari', config.takari)
config.deploy = assign('deploy', 'no-deploy', config.deploy)
config.backup = assign('backup', 'no-backup', config.backup)
config.restore = assign('restore', 'no-restore', config.restore)
config.randomPassword = assign('random-password', 'no-random-password', config.randomPassword)
config.npmInstall = assign('npm-install', 'npm-ci', config.npmInstall)
config.npmBuild = assign('npm-build-all', 'npm-build', config.npmBuild)
debug("config read from RC and merged with defaults: ${config}")
return config
}
def assign(def trueOption, def falseOption, def defaultValue){
debug("assign(${trueOption}, ${falseOption}, ${defaultValue})")
if(cliOptions[trueOption])
return true
else if(cliOptions[falseOption])
return false
else
return defaultValue
}
static ConfigObject processLastBuild() {
def lastBuildFile = new File('.nxrm/last_build')
if (!lastBuildFile.exists()) {
return new ConfigObject()
}
return new ConfigSlurper().parse(lastBuildFile.toURI().toURL())
}
def saveLastBuild() {
def grgit = Grgit.open()
def currentBranch = grgit.branch.getCurrent().name
def currentHead = grgit.head().abbreviatedId
def lastProjects = buildOptions.buildMode == "full" ? [] : changedProjects
ConfigObject configObject = new ConfigObject()
configObject['BRANCH'] = currentBranch
configObject['HEAD'] = currentHead
configObject['LAST_PROJECTS'] = lastProjects
File lastBuild = new File(".nxrm/last_build")
lastBuild.delete()
lastBuild.text = configObject.prettyPrint()
}
def processBuildMode() {
if (cliOptions.full) {
info("Full build selected")
buildOptions.buildMode = "full"
return
}
if (!new File("$TARGET_DIR").exists()) {
warn("No root target folder present. Full build required.")
buildOptions.buildMode = "full"
return
}
if (!new File(".nxrm/last_build").exists()) {
warn("No previous build detected. Full build required.")
buildOptions.buildMode = "full"
return
}
ConfigObject lastBuild = processLastBuild()
debug("Last build info: ${lastBuild}")
def grgit = Grgit.open()
def currentBranch = grgit.branch.getCurrent().name
def currentHead = grgit.head().abbreviatedId
debug("Git current branch: ${currentBranch}")
debug("Git current HEAD: ${currentHead}")
if (lastBuild.BRANCH == null) {
warn("No previous branch detected. Full build required.")
buildOptions.buildMode = "full"
return
}
else if (lastBuild.BRANCH != currentBranch) {
warn("Detected new branch. Full build required.")
buildOptions.buildMode = "full"
return
}
if (lastBuild.HEAD == null) {
warn("No previous HEAD detected. Full build required.")
buildOptions.buildMode = "full"
return
}
else if (lastBuild.HEAD != currentHead) {
warn("Detected new HEAD. Full build required.")
buildOptions.buildMode = "full"
return
}
// default is an incremental build
buildOptions.buildMode = "incremental"
}
def processCliOptions(args) {
CliBuilder cli = new CliBuilder(usage: './nxrm.groovy [options] [-x options to be passed through]',
header: 'Nexus Repository Manager control script')
cli.with {
h longOpt: 'help', 'Show usage information'
v longOpt: 'verbose', 'show verbose debug information'
// modes (exclusive from each other)
b longOpt: 'build', 'Build mode [default]. Intelligently does a full or incremental build.'
r longOpt: 'run', args: 1, 'Run mode. Starts Nexus with the specified assembly. Add \'debug\' for Nexus debug mode (i.e. remote debugging).'
_ longOpt: 'geb', '''Process dependencies for Geb test execution in your IDE.'''
_ longOpt: 'sass', '''Compile Sass files to CSS.'''
e longOpt: 'extract', 'Re-run the assembly extraction'
// build mode options
f longOpt: 'full', 'Force full build'
t longOpt: 'tests', args: 1, '''Control test execution. Options:
skipAll: Skip all building & execution [default]
skip: Build unit & integration but skip execution
unit: Build and execute unit tests only
skipITs: Build unit & integration, only execute unit
all: Build and execute all tests'''
a longOpt: 'assemblies', args: 1, '''Control assembly execution. Options:
skip: Skip all assembly building [default - ignored if full build is triggered]
all: Build all assemblies'''
s longOpt: 'sources', args: 1, '''Control building of sources. Options:
skip: Skip all source creation [default]
all: Build all sources'''
n longOpt: 'deploy', 'Enable automatic deployment for incremental builds (if disabled by config). Note this will enable SSH on Karaf.'
n longOpt: 'no-deploy', 'Disable automatic deployment for incremental builds (enabled by default)'
_ longOpt: 'backup', "Enable backup of any existing target/sonatype-work folder to ${SONATYPE_WORK_BACKUP} (if disabled by config)"
_ longOpt: 'no-backup', "Disable backup (if enabled by config)"
// run mode options
p longOpt: 'port', args: 1, 'Set NXRM port. Defaults to 8081'
_ longOpt: 'ssl-port', args: 1, 'Set NXRM SSL port. Defaults to 8443'
_ longOpt: 'karaf-ssh-port', args: 1, 'Set Karaf SSH port. Defaults to 8022'
_ longOpt: 'java-debug-port', args: 1, 'Set JDWP debug port. Defaults to 5005'
_ longOpt: 'ssl', 'Enable SSL (if disabled by config)'
_ longOpt: 'no-ssl', 'Disable SSL (enabled by default)'
_ longOpt: 'ssl-ip', args: 1, 'Provide the ip address of this machine to generate an SSL certificate for use with Docker'
_ longOpt: 'elastic', 'Enable Elastic plugins (disabled by default)'
_ longOpt: 'no-elastic', 'Disable Elastic plugins (if enabled by config)'
_ longOpt: 'takari', 'Enable Takari (disabled by default)'
_ longOpt: 'no-takari', 'Disable Takari (if enabled by config)'
_ longOpt: 'restore', "Enable restore of backup from ${SONATYPE_WORK_BACKUP} to target/sonatype-work (disabled by default)"
_ longOpt: 'no-restore', "Disable restore of backup (if enabled by config)"
_ longOpt: 'random-password', "Enable generation of random password for admin user on initial start"
_ longOpt: 'no-random-password', "Disable generation of random password (default)"
_ longOpt: 'npm-install', "use `npm install` for npm dependencies (this results in a faster, but less stable build)"
_ longOpt: 'npm-ci', "use `npm ci` for npm dependencies (this results in a slower, but more stable build and is the default)"
o longOpt: 'overwrite-target', args: 1, "Overwrite target dir"
// general options
d longOpt: 'dry-run', 'Dry run, don\'t actually execute anything'
_ longOpt: 'no-docker', 'Disable the docker build'
_ longOpt: 'single-threaded', "Don't build in parallel"
}
cliOptions = cli.parse(args)
if (!cliOptions) {
return false
}
if (cliOptions.h) {
cli.usage()
// CliBuilder automatically wraps ALL text at ~80 chars which doesn't make for good help. Manually print it out
println '''
Examples:
./nxrm.groovy Normal 'should just work' use case. Will perform a full build or incremental build as necessary.
Same as ./nxrm.groovy --build
./nxrm.groovy -f Force a full build.
Same as ./nxrm.groovy --build --full
./nxrm.groovy -r pro debug Run Nexus. Assembly is required. 'debug' is optional and starts Nexus in debug mode.
Same as ./nxrm.groovy --run
If NXRM is running when you do a build, it will automatically be re-deployed and re-started.
./nxrm.groovy -t skip Compile tests but skip execution. Note that the default is to skip all tests, even compilation, to save time.
./nxrm.groovy -e Re-run the assembly extraction. This is for if you nuke your 'target/' folder and want to start your NXRM clean.
Same as ./nxrm.groovy --extract
./nxrm.groovy clean package Maven goals and phases can be passed in directly. Otherwise will default to 'clean install' for full build and 'install' for incremental.
./nxrm.groovy -x -rf :nexus-main Due to a limitation in the Groovy CliBuilder, any positional parameters you wish to pass into the maven build or run commands need to be after the '-x' parameter, and be last.
./nxrm.groovy -x -U Example usage to force Maven snapshot updates.
./nxrm.groovy --geb Enables Geb in your IDE. See https://docs.sonatype.com/display/Nexus/Nexus+Repository+Manager+Developer+Onboarding#NexusRepositoryManagerDeveloperOnboarding-TestingWithGeb
./nxrm.groovy target-overwrite
new-target-dir Run script using artifacts from provided directory
'''
return false
}
// get the positional arguments (i.e. the arguments to pass through to NXRM)
// Have to do this through special unspecified '-x' option which indicates what follows are arguments to be passed to build or run command
// don't need the '-x' itself though, so remove it
positionalOptions = cliOptions.arguments()
positionalOptions.removeAll { it == '-x' }
if(!positionalOptions.isEmpty()) {
debug("Positional options: " + positionalOptions.toString())
}
return true
}
def processMavenGoalsAndPhases() {
if (buildOptions.mavenGoalsAndPhases == "") {
if (buildOptions.buildMode == "full") {
buildOptions.mavenGoalsAndPhases = "clean install"
buildOptions.mavenGoalsAndPhasesDesc = "${buildOptions.mavenGoalsAndPhases} [default full build]"
}
else {
buildOptions.mavenGoalsAndPhases = "install"
buildOptions.mavenGoalsAndPhasesDesc = "${buildOptions.mavenGoalsAndPhases} [default]"
// see if positional options contains any maven phases/goals (anything at the beginning of the list that isn't prefixed with a '-')
if (positionalOptions.size) {
def items = []
positionalOptions.removeAll {
if(!it.startsWith('-')) {
items.add(it)
}
!it.startsWith('-')
}
debug("items: ${items}")
if (items) {
buildOptions.mavenGoalsAndPhases = items.join(" ")
buildOptions.mavenGoalsAndPhasesDesc = "${buildOptions.mavenGoalsAndPhases} [custom]"
}
}
}
}
else {
buildOptions.mavenGoalsAndPhasesDesc = "${buildOptions.mavenGoalsAndPhases}"
}
}
def processTestArgs() {
// Test options
// Precendence: environment variable, parameter, rc
debug("Initial value for 'tests' environment variable: $env.tests")
debug("Initial value for '-tests' CLI: $cliOptions.tests")
debug("Initial value for 'tests' RC config: $rcConfig.tests")
def testsArg
if (env.tests) {
testsArg = env.tests
debug("Using test arguments from environment '$testsArg'")
}
else if (cliOptions.tests) {
testsArg = cliOptions.tests
debug("Using test arguments from CLI '$testsArg'")
}
else if (rcConfig.tests) {
testsArg = rcConfig.tests
debug("Using test arguments from RC '$testsArg'")
}
else {
testsArg = "skipAll"
if (buildOptions.buildMode == "full") {
debug("Detected full build mode. Defaulting to '-t skipAll' to skip all test compilation and execution")
}
else {
debug("No test params specified. Defaulting to '-t skipAll'")
}
}
if (testsArg == "skipAll") {
buildOptions.testsDesc = "Skipping all compiling & execution"
buildOptions.tests = "-DskipTests -Dmaven.test.skip=true"
}
else if (testsArg == "skip") {
buildOptions.testsDesc = "Compiling all (unit & integration) but skipping execution"
buildOptions.tests = "-DskipTests -Dit"
}
else if (testsArg == "unit") {
buildOptions.testsDesc = "Building and executing unit tests only"
buildOptions.tests = ""
}
else if (testsArg == "skipITs") {
buildOptions.testsDesc = "Compiling all (unit & integration), executing unit tests only"
buildOptions.tests = "-DskipITs -Dit"
}
else if (testsArg == "all") {
buildOptions.testsDesc = "Compiling and executing all tests (unit & integration)"
buildOptions.tests = "-Dit"
}
else {
// custom option
buildOptions.testsDesc = "Using custom arguments"
buildOptions.tests = testsArg
}
debug("Final test arguments: $buildOptions.tests")
}
def processAssemblyArgs() {
// Assembly options
// Precendence: environment variable, parameter, rc
debug("Initial value for 'assemblies' environment variable: $env.assemblies")
debug("Initial value for '-assemblies' CLI: $cliOptions.assemblies")
debug("Initial value for 'assemblies' RC config: $rcConfig.assemblies")
if (buildOptions.buildMode == "full") {
debug("Detected full build mode. Assemblies required")
buildOptions.assemblies = ""
buildOptions.assembliesDesc = "Yes (Build mode = full, assemblies required)"
}
else if (buildOptions.buildMode == "incremental" && !isNxrmRunning()) {
debug("Incremental build mode, but Nexus not running or no lock file exists so assemblies required")
buildOptions.assemblies = ""
buildOptions.assembliesDesc = "Yes (Incremental mode, but Nexus not running or no lock file exists so assemblies " +
"required)"
}
else {
def assembliesArg
if (env.assemblies) {
assembliesArg = env.assemblies
debug("Using assembly arguments from environment '$assembliesArg'")
}
else if (cliOptions.assemblies) {
assembliesArg = cliOptions.assemblies
debug("Using assembly arguments from CLI '$assembliesArg'")
}
else if (rcConfig.assemblies) {
assembliesArg = rcConfig.assemblies
debug("Using assembly arguments from RC '$assembliesArg'")
}
else {
assembliesArg = "skip"
}
if (assembliesArg == "skip") {
buildOptions.assembliesDesc = "Skipping all assembly"
buildOptions.assemblies = "-Dassembly.skipAssembly=true"
}
else if (assembliesArg == "all") {
buildOptions.assembliesDesc = "Building all assemblies"
buildOptions.assemblies = ""
}
else {
buildOptions.assembliesDesc = "Using custom assembly arguments"
buildOptions.assemblies = assembliesArg
}
}
debug("Final assembly arguments: $buildOptions.assemblies")
}
def processSourceArgs() {
// Source options
// Precendence: environment variable, parameter, rc
debug("Initial value for 'sources' environment variable: $env.sources")
debug("Initial value for '-sources' CLI: $cliOptions.sources")
debug("Initial value for 'sources' RC config: $rcConfig.sources")
def sourcesArg
if (env.sources) {
sourcesArg = env.sources
debug("Using source arguments from environment '$sourcesArg'")
}
else if (cliOptions.sources) {
sourcesArg = cliOptions.sources
debug("Using source arguments from CLI '$sourcesArg'")
}
else if (rcConfig.sources) {
sourcesArg = rcConfig.sources
debug("Using source arguments from RC '$sourcesArg'")
}
else {
sourcesArg = "skip"
}
if (sourcesArg == "skip") {
buildOptions.sourcesDesc = "Skipping all source building"
buildOptions.sources = "-Dmaven.source.skip=true"
}
else if (sourcesArg == "all") {
buildOptions.sourcesDesc = "Building all sources"
buildOptions.sources = ""
}
else {
buildOptions.sourcesDesc = "Using custom source arguments"
buildOptions.sources = sourcesArg
}
debug("Final source arguments: $buildOptions.sources")
}
def processBuilder() {
def mvnDir = Paths.get('.mvn/')
def extensionsPath = mvnDir.resolve('extensions.xml')
if (Files.notExists(mvnDir)) {
Files.createDirectories(mvnDir)
}
if (Files.notExists(extensionsPath)) {
Files.createFile(extensionsPath).write """<extensions
xmlns="http://maven.apache.org/EXTENSIONS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/EXTENSIONS/1.0.0 http://maven.apache.org/xsd/core-extensions-1.0.0.xsd">
</extensions>"""
}
def extensionsXml = new XmlParser(false, false).parse(extensionsPath.toFile())
if (rcConfig.takari) {
rcConfig.builder = "--builder smart -T 1C"
if (!extensionsXml.'**'.artifactId*.children()*.first()*.trim().contains('takari-smart-builder')) {
error('Takari enabled but not detected in .mvn/extensions.xml')
warn('Installing Takari now')
extensionsXml.children() << new NodeBuilder().extension {
groupId('io.takari.maven')
artifactId('takari-smart-builder')
version(TAKARI_SMART_BUILD_VERSION)
} << new NodeBuilder().extension {
groupId('io.takari.aether')
artifactId('takari-local-repository')
version(TAKARI_LOCAL_REPO_VERSION)
} << new NodeBuilder().extension {
groupId('io.takari')
artifactId('takari-filemanager')
version(TAKARI_FILE_MANAGER_VERSION)
}
new XmlNodePrinter(new PrintWriter(Files.newBufferedWriter(extensionsPath))).print(extensionsXml)
}
}
else if (cliOptions['single-threaded']) {
rcConfig.builder = ''
}
if (rcConfig.caching) {
if (!extensionsXml.'**'.artifactId*.children()*.first()*.trim().contains('maven-build-cache-extension')) {
error('Maven Build Cache Extension enabled but not detected in .mvn/extensions.xml')
warn('Installing Maven Build Cache Extension now')
extensionsXml.children() << new NodeBuilder().extension {
groupId('org.apache.maven.extensions')
artifactId('maven-build-cache-extension')
version(MAVEN_BUILD_CACHE_VERSION)
}
new XmlNodePrinter(new PrintWriter(Files.newBufferedWriter(extensionsPath))).print(extensionsXml)
}
}
}
def processMavenCommand() {
changedProjects = getChangedProjects()
def projects = changedProjects
// include the last projects built as well to avoid issues with git reset/checkout
projects += lastBuild.LAST_PROJECTS
// remove ignored entries
projects -= ignoredProjects
// if tests are not built, don't care about changes on test projects
if(buildOptions.tests == "-Dmaven.test.skip=true") {
projects -= testProjects
}
// if there are no projects, then a full build is needed
if (!projects) {
buildOptions.buildMode = "full"
warn("No projects to build, nothing to do but perform full build")
}
if (buildOptions.buildMode == "full") {
buildOptions.buildModeDesc = "Full"
// Note: Assembly arguments not included here so assemblies will be generated.
buildOptions.mavenCommand = "${buildOptions.mavenGoalsAndPhases} ${rcConfig.builder} ${buildOptions.tests} " +
"${buildOptions.sources}"
}
else if (projects) {
buildOptions.buildModeDesc = "Incremental on ${projects}"
buildOptions.projects = projects.join(',')
buildOptions.mavenCommand = "${buildOptions.mavenGoalsAndPhases} ${rcConfig.builder} ${buildOptions.tests} " +
"${buildOptions.sources} ${buildOptions.assemblies} -pl ${projects.join(',')} -amd"
}
else {
buildOptions.buildModeDesc = "Full (Incremental mode, but no code changes detected)"
buildOptions.mavenCommand = "${buildOptions.mavenGoalsAndPhases} ${rcConfig.builder} ${buildOptions.tests} " +
"${buildOptions.sources} ${buildOptions.assemblies}"
}
if (cliOptions['no-docker']) {
buildOptions.mavenCommand += ' -Dno-docker'
}
if (rcConfig.npmInstall) {
buildOptions.mavenCommand += " -Dnpm.install=${rcConfig.npmInstall}"
}
if (rcConfig.npmBuild) {
buildOptions.mavenCommand += " -Dnpm.build=${rcConfig.npmBuild}"
}
buildOptions.mavenCommand += ' ' + positionalOptions.join(' ')
}
def removeBuildLog() {
if (buildLog.exists()) {
buildLog.delete()
}
}
def isNxrmRunning() {
File lockFile = new File("$LOCK_FILE")
if (!lockFile.exists()) {
debug("No lock file, NXRM is not running")
return false
}
else {
def pid = lockFile.text - ~/@.*/
if ("kill -0 ${pid}".execute().waitFor() == 0) {
debug("NXRM running at pid $pid")
return true
}
else {
debug("Lock file exists, but NXRM not running")
return false
}
}
}
def isNxrmFullyUp() {
try {
def url = "http://localhost:${rcConfig.port}/service/metrics/ping"
String encoded = Base64.getEncoder().encodeToString("admin:admin123".getBytes(StandardCharsets.UTF_8))
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection()
connection.setConnectTimeout(30000)
connection.setReadTimeout(30000)
connection.setRequestMethod("GET")
connection.setRequestProperty("Authorization", "Basic ${encoded}")
int responseCode = connection.getResponseCode()
return (200 <= responseCode && responseCode <= 399)
}
catch (IOException exception) {
debug("Unable to connect to NXRM. Message was ${exception.getMessage()}")
return false
}
}
String getSshHost() {
return "admin:admin123@localhost:${rcConfig.karafSshPort}"
}
def runBuild() {
if (buildOptions.buildMode == "full" && isNxrmRunning()) {
// Full mode means Maven clean which nukes target folder which kills Nexus
warn("Shutting down NXRM due to full build")
try {
remoteSession(getSshHost(), trustUnknownHosts = true) {
exec 'system:shutdown -f'
}
} catch (com.jcraft.jsch.JSchException e) {
e.printStackTrace()
error("Unable to connect to NXRM to shut it down. Attempting to continue.")
}
}
if (buildOptions.buildMode == "full" && rcConfig.backup) {
// perform backup (if enabled) on full build only due to 'clean'
File sonatypeWork = new File(SONATYPE_WORK)
if ((sonatypeWork).exists()) {
def backupFolder = new File(SONATYPE_WORK_BACKUP)
info("Backing up ${sonatypeWork.getCanonicalPath()} to ${backupFolder.getCanonicalPath()}")
if (backupFolder.exists()) {
def newName = new File(SONATYPE_WORK_BACKUP + new Date().format('-yyyyMMddHHmm'))
info("Moving existing backup to ${newName.getCanonicalPath()}")
FileUtils.moveDirectory(backupFolder, newName)
}
FileUtils.moveDirectory(sonatypeWork, backupFolder)
}
else {
info("No target/sonatype-work to backup!")
}
}
// execute command
def exitValue = mvnw(buildOptions.mavenCommand).exitValue()
debug("Build process exit value: $exitValue")
// stop if mvn execution failed
if (exitValue == 0) {
info "Build succeeded"
// TODO - should this be run on build failure still?
saveLastBuild()
return true
}
else {
error "Build failed"
return false
}
}
def runDeploy() {
if (buildOptions.buildMode == "full") {
// only extract the assemblies on full builds
deploy()
}
else if (!rcConfig.deploy) {
info("Skipping deployment (no-deploy=true)")
}
else if (!((new File(LOCK_FILE)).exists())) {
info("No lock file detected at $LOCK_FILE. Performing regular deployment.")
deploy()
}
else if (isNxrmRunning()) {
info("Nexus found running. Restarting...")
try {
remoteSession(getSshHost(), trustUnknownHosts = true) {
exec 'system:shutdown -f -r'
}
} catch (com.jcraft.jsch.JSchException e) {
e.printStackTrace()
error("Unable to connect to NXRM to restart it down. Attempting to continue.")
}
// give it a few to shut down
sleep(3000)
// loop until we can ping it again
def counter = 0
while (!isNxrmFullyUp()) {
counter++
print "."
if (counter >= 60) {
info("Failed to detect Nexus start within 60 seconds. Exiting")
return
}
sleep 1000
}
println "done"
info("Nexus Ready")
}
else {
deploy()
info("Nexus is not running. You may start it now with './nxrm.groovy -r <assembly> [debug]'.")
}
}
def deploy() {
extract("./assemblies/nexus-base-template/target/", "nexus-base-template-*.zip")
extract("./private/assemblies/distributions/nexus-oss/target/", "nexus-*-bundle.zip")
extract("./private/assemblies/distributions/nexus-pro/target/", "nexus-professional-*-bundle.zip")
extract("./private/assemblies/distributions/nexus-pro-starter/target/", "nexus-pro-starter-*-bundle.zip")
// Tell Karaf to load bundles from local .m2 folder
def files = new FileNameFinder().getFileNames("$TARGET_DIR", "nexus*/**/org.ops4j.pax.url.mvn.cfg")
files.each {
// comment out localRepository
ant.replace(file: it, token: "org.ops4j.pax.url.mvn.localRepository",
value: "#org.ops4j.pax.url.mvn.localRepository")
// point defaultRepositories to .m2
ant.replace(file: it, token: 'file:${karaf.base}/${karaf.default.repository}@id=system.repository@snapshots',
value: 'file:\${user.home}/.m2/repository@id=system.repository@snapshots')
}
}
// helper to unzip assemblies
def extract(path, zipRegex) {
List<String> files = new FileNameFinder().getFileNames(path, zipRegex)
if (!files) {
error("MISSING: ${path}${zipRegex}")
}
else {
File file = new File(files.get(0))
info("Extracting: ${file.toString()}")
ArchiverFactory.createArchiver(ArchiveFormat.ZIP).extract(file, new File("$TARGET_DIR"))
}
}
def checkSSL() {
// Assumes if keystore.jks is already copied that it is already enabled
if (rcConfig.ssl) {
debug("Enabling SSL")
// see if keystore.jks is already created
def keystore = new File(".nxrm/keystore.jks")
if (rcConfig.sslIp != null && keystore.exists()) {
// If we have an ssl ip address set, then we need to generate a new keystore
keystore.delete()
}
if (!keystore.exists()) {
info "Generating .nxrm/keystore.jks"
def keytool = ["keytool", "-genkeypair", "-keystore", ".nxrm/keystore.jks", "-storepass",
"password", "-keypass", "password", "-alias", "self-signed-example", "-keyalg", "RSA", "-keysize", "2048",
"-validity", "5000", "-dname", "CN=localhost, OU=Example, O=Example, L=Unspecified, ST=Unspecified, C=US",
"-ext", "BC=ca:true"]
if (rcConfig.sslIp) {
keytool << "-ext"
keytool << "SAN=IP:${rcConfig.sslIp},DNS:localhost"
}
def process = new ProcessBuilder(keytool as String[]).redirectErrorStream(true).start()
process.inputStream.eachLine {
println it
}
process.waitFor()
def exitValue = process.exitValue()
debug("keytool exit value: $exitValue")
}
// Copy to etc/ssl folder
List<String> files = new FileNameFinder().getFileNames("$TARGET_DIR", "nexus*/NOTICE.txt")
files.each {
File dest = new File(new File(it).getParent(), "etc/ssl/")
debug("SSL: Copying keystore.jks to $dest")
ant.copy(file: keystore, todir: dest)
}
// Update nexus.properties (and default files)
files = new FileNameFinder().getFileNames("$TARGET_DIR", "nexus*/**/nexus*.properties")
files.each {
ant.replace(file: it, token: '${jetty.etc}/jetty-http.xml,${jetty.etc}/jetty-requestlog.xml',
value: '${jetty.etc}/jetty-http.xml,${jetty.etc}/jetty-https.xml,${jetty.etc}/jetty-requestlog.xml')
}
}
else {
debug("Skipping SSL")
}
}
def ensurePresentInFile(File file, String line) {
debug("Ensuring '$line' present in $file")
ant.touch(file: file)
if (!file.getText().contains(line)) {
file << "$line\n"
}
}
def checkSSH() {
if (!rcConfig.'deploy') {
debug("TODO no deploy")
return
}
debug("Enabling SSH")
// Add ssh option to all cfg files in target
List<String> files = new FileNameFinder().getFileNames("$TARGET_DIR", "nexus*/**/org.apache.karaf.features.cfg")
files.each {
debug("Updating $it to enable SSH")
ant.replaceregexp(file: it, match: "\\(wrap\\), ", replace: "\\(wrap\\),ssh,")
}
// Karaf ssh port
files = new FileNameFinder().getFileNames("$TARGET_DIR", "nexus*/**/org.apache.karaf.shell.cfg")
files.each {
debug("Updating $it to set Karaf SSH port")
ant.replaceregexp(file: it, match: "sshPort = 8022", replace: "sshPort = ${rcConfig.karafSshPort}")
}
}
def checkElastic() {
if (rcConfig.elastic) {
debug("Enabling Elastic")
// Update elasticsearch.yml (and default files)