Skip to content

Commit e2559f6

Browse files
authored
2.3.11 - Snapshot/Volume capabilities, GroovyBuilderDeployment, TimeMachine fixes etc (#31)
* GroovyContainer.groovy * Added createGroovyBuildContainer() * Added setUseLocalM2Cache(), installMaven(), installGit(), cloneGitRepo(), resolveMavenDependencies(), generateEffectivePom(), installMavenProject() Container.groovy * Added prepareVolumeMount() * Improved createTar(), added ignorePaths parameter, now supports long file paths GroovyBuilderDeployment.groovy WIP * New deployment, intended for building and optionally hosting groovy jars DockerClientDS.groovy * added getVolumesWithLabel(), getVolumesWithName(), getContainersUsingVolume(), createVolume(), cloneVolume() * Method for getting time from external, modified TravelToNow to allow external time (cherry picked from commit fc28cdf) * added constructor to TimeMachine (cherry picked from commit aa467b0) * shading kotlinx (cherry picked from commit ed9002f) * TimeMachine.groovy * Improved getExternalTime(), removed unused unirest field TimeMachineSpec.groovy * WIP proved traveling forward in time works for a companion container * Container.groovy * !NOTE! potentially breaking change: mounts field renamed preparedMounts * Added getMounts() JsmContainer.groovy * Added cloneJiraHome, snapshotJiraHome, restoreJiraHomeSnapshot, getJiraHomeMountPoint DockerClientDS.groovy * Added overwriteVolume * JsmContainer.groovy * Added getSnapshotVolume() TimeMachineSpec.groovy * Improved testing for JSM time travel * JsmContainer.groovy * Solved bug that caused the DB to become corrupt during snapshot due to killing container forcefully. Container.groovy * stopContainer(), * Added parameter for timeout and improved logging JsmH2Deployment.groovy * The map appsToInstall can now have keys that are either URLs to apps or MarketplaceApp.Version * setupDeployment() * Added parameters to setupDeployment() to allow creation and reuse of snapshots * pom.xml * Bumped to 2.3.11 * Bumped jirainstancemanager to 2.0.3 --------- Co-authored-by: spatil <Serenad12#nacka>
1 parent ed98b0b commit e2559f6

23 files changed

+1235
-140
lines changed

.github/buildScripts/shadingConf.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@
5353
<pattern>okhttp3</pattern>
5454
<shadedPattern>com.eficode.shaded.okhttp3</shadedPattern>
5555
</relocation>
56+
<relocation>
57+
<pattern>kotlinx.coroutines</pattern>
58+
<shadedPattern>com.eficode.shaded.kotlinx.coroutines</shadedPattern>
59+
</relocation>
5660
</relocations>
5761

5862
<!-- NOTE: Any dependencies of the project will not show up in the standalone pom.

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.eficode</groupId>
88
<artifactId>devstack</artifactId>
9-
<version>2.3.9-SNAPSHOT</version>
9+
<version>2.3.11-SNAPSHOT</version>
1010
<packaging>jar</packaging>
1111

1212
<name>DevStack</name>
@@ -100,7 +100,7 @@
100100
<dependency>
101101
<groupId>com.eficode.atlassian</groupId>
102102
<artifactId>jirainstancemanager</artifactId>
103-
<version>2.0.1-SNAPSHOT</version>
103+
<version>2.0.3-SNAPSHOT</version>
104104
</dependency>
105105

106106

src/main/groovy/com/eficode/devstack/container/Container.groovy

Lines changed: 105 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import de.gesellix.docker.remote.api.ContainerSummary
1313
import de.gesellix.docker.remote.api.EndpointSettings
1414
import de.gesellix.docker.remote.api.ExecConfig
1515
import de.gesellix.docker.remote.api.HostConfig
16-
import de.gesellix.docker.remote.api.IdResponse
1716
import de.gesellix.docker.remote.api.Mount
17+
import de.gesellix.docker.remote.api.MountPoint
1818
import de.gesellix.docker.remote.api.Network
1919
import de.gesellix.docker.remote.api.NetworkCreateRequest
2020
import de.gesellix.docker.remote.api.PortBinding
@@ -52,7 +52,7 @@ trait Container {
5252
ArrayList<String> customEnvVar = []
5353
String defaultShell = "/bin/bash"
5454
String containerId
55-
ArrayList<Mount> mounts = []
55+
ArrayList<Mount> preparedMounts = [] //Mounts that will be added at creation
5656

5757

5858
/**
@@ -70,11 +70,40 @@ trait Container {
7070
m.type = Mount.Type.Bind
7171
}
7272

73-
if (!self.mounts.find { it.source == sourceAbs && it.target == target }) {
74-
self.mounts.add(newMount)
73+
if (!self.preparedMounts.find { it.source == sourceAbs && it.target == target }) {
74+
self.preparedMounts.add(newMount)
7575
}
7676
}
7777

78+
/**
79+
* Prepare mounting of an existing or new volume
80+
* @param volumeName The name of the volume to create, or an existing one to mount
81+
* @param target Where to mount it in the container
82+
* @param readOnly If it should be read only or not
83+
*/
84+
void prepareVolumeMount(String volumeName, String target, boolean readOnly = true) {
85+
86+
Mount newMount = new Mount().tap { m ->
87+
m.source = volumeName
88+
m.target = target
89+
m.readOnly = readOnly
90+
m.type = Mount.Type.Volume
91+
}
92+
93+
if (!self.preparedMounts.find { it.source == volumeName && it.target == target }) {
94+
self.preparedMounts.add(newMount)
95+
}
96+
}
97+
98+
/**
99+
* Get MountPoints currently attached to container
100+
* @return
101+
*/
102+
ArrayList<MountPoint> getMounts() {
103+
104+
ContainerInspectResponse response = inspectContainer()
105+
return response.mounts
106+
}
78107

79108
ContainerCreateRequest setupContainerCreateRequest() {
80109

@@ -91,7 +120,7 @@ trait Container {
91120
if (self.containerMainPort) {
92121
h.portBindings = [(self.containerMainPort + "/tcp"): [new PortBinding("0.0.0.0", (self.containerMainPort))]]
93122
}
94-
h.mounts = self.mounts
123+
h.mounts = self.preparedMounts
95124
}
96125
c.hostname = self.containerName
97126
c.env = self.customEnvVar
@@ -124,7 +153,7 @@ trait Container {
124153
ContainerCreateRequest containerCreateRequest = setupContainerCreateRequest()
125154

126155
if (cmd.size()) {
127-
containerCreateRequest.cmd = cmd.collect {it.toString()}
156+
containerCreateRequest.cmd = cmd.collect { it.toString() }
128157
}
129158

130159
if (entrypoint.size()) {
@@ -201,8 +230,16 @@ trait Container {
201230

202231
}
203232

233+
/**
234+
* Returnes the common short form of the container ID
235+
* @return
236+
*/
237+
String getShortId() {
238+
return getContainerId().substring(0,12)
239+
}
240+
204241
String getId() {
205-
return containerId
242+
return getContainerId()
206243
}
207244

208245
Container getSelf() {
@@ -329,29 +366,33 @@ trait Container {
329366

330367
}
331368

332-
boolean stopContainer() {
369+
boolean stopContainer(Integer timeoutS = 15) {
333370
log.info("Stopping container:" + self.containerId)
334-
running ? dockerClient.stop(self.containerId, 15) : ""
371+
long start = System.currentTimeSeconds()
372+
running ? dockerClient.stop(self.containerId, timeoutS) : ""
373+
335374
if (running) {
336375
log.warn("\tFailed to stop container" + self.containerId)
376+
log.warn("Gave up waiting to shutdown ${shortId} after ${System.currentTimeSeconds() - start} seconds")
337377
return false
338378
} else {
339379
log.info("\tContainer stopped")
380+
log.debug("\t\tContainer ${shortId} stopped after ${System.currentTimeSeconds() - start} seconds")
340381
return true
341382
}
342383
}
343384

344385

345-
File createTar(ArrayList<String> filePaths, String outputPath) {
386+
File createTar(ArrayList<String> filePaths, String outputPath, ArrayList<String> ignorePaths = []) {
346387

347388

348389
log.info("Creating tar file:" + outputPath)
349390
log.debug("\tUsing source paths:")
350391
filePaths.each { log.debug("\t\t$it") }
351392

352-
353393
File outputFile = new File(outputPath)
354394
TarArchiveOutputStream tarArchive = new TarArchiveOutputStream(Files.newOutputStream(outputFile.toPath()))
395+
tarArchive.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX)
355396

356397
log.info("\tProcessing files")
357398
filePaths.each { filePath ->
@@ -368,28 +409,40 @@ trait Container {
368409

369410
String path = ResourceGroovyMethods.relativePath(newEntryFile, subFile)
370411
log.trace("\t" * 4 + "Processing sub file:" + path)
371-
TarArchiveEntry entry = new TarArchiveEntry(subFile, path)
372-
entry.setSize(subFile.size())
373-
tarArchive.putArchiveEntry(entry)
374-
tarArchive.write(subFile.bytes)
375-
tarArchive.closeArchiveEntry()
376-
log.trace("\t" * 5 + "Added to archive")
412+
if (ignorePaths.any { subFile.absolutePath.matches(it) }) {
413+
log.trace("\t" * 5 + "File matches a path that is to be ignored, will not process further")
414+
} else {
415+
TarArchiveEntry entry = new TarArchiveEntry(subFile, path)
416+
entry.setSize(subFile.size())
417+
tarArchive.putArchiveEntry(entry)
418+
tarArchive.write(subFile.bytes)
419+
tarArchive.closeArchiveEntry()
420+
log.trace("\t" * 5 + "Added to archive")
421+
}
422+
423+
377424
}
378425
} else {
379426
log.trace("\t" * 4 + "Processing file:" + newEntryFile.name)
380-
TarArchiveEntry entry = new TarArchiveEntry(newEntryFile, newEntryFile.name)
381-
entry.setSize(newEntryFile.size())
382-
tarArchive.putArchiveEntry(entry)
383-
tarArchive.write(newEntryFile.bytes)
384-
tarArchive.closeArchiveEntry()
385-
log.trace("\t" * 5 + "Added to archive")
386427

428+
if (ignorePaths.any { newEntryFile.absolutePath.matches(it) }) {
429+
log.trace("\t" * 5 + "File matches a path that is to be ignored, will not process further")
430+
}else {
431+
TarArchiveEntry entry = new TarArchiveEntry(newEntryFile, newEntryFile.name)
432+
entry.setSize(newEntryFile.size())
433+
tarArchive.putArchiveEntry(entry)
434+
tarArchive.write(newEntryFile.bytes)
435+
tarArchive.closeArchiveEntry()
436+
log.trace("\t" * 5 + "Added to archive")
437+
}
387438
}
388439

389440

390441
}
391442

392443
tarArchive.finish()
444+
log.info("\tFinished creating TAR file:" + outputFile.absolutePath)
445+
log.debug("\t\t" + (outputFile.size() / (1024 * 1024)).round() + "MB")
393446

394447
return outputFile
395448

@@ -429,6 +482,15 @@ trait Container {
429482
}
430483

431484

485+
/**
486+
* Gets the home path for the containers default user
487+
* @return ex: /home/user
488+
*/
489+
String getHomePath() {
490+
runBashCommandInContainer("pwd").find {true}
491+
}
492+
493+
432494
/**
433495
* Creates a network of the type bridge, or returns an existing one if one with the same name exists
434496
* @param networkName name of the network
@@ -637,12 +699,12 @@ trait Container {
637699
boolean replaceFileInContainer(String content, String filePath, boolean verify = false) {
638700
ArrayList<String> out = runBashCommandInContainer("cat > $filePath <<- 'EOF'\n" + content + "\nEOF")
639701

640-
assert out.isEmpty() : "Unexpected output when replacing file $filePath: " + out.join("\n")
702+
assert out.isEmpty(): "Unexpected output when replacing file $filePath: " + out.join("\n")
641703

642704
if (verify) {
643-
ArrayList<String>rawOut = runBashCommandInContainer("cat " + filePath)
705+
ArrayList<String> rawOut = runBashCommandInContainer("cat " + filePath)
644706
String readOut = rawOut.join()
645-
assert readOut.trim() == content.trim() : "Error when verifying that the file $filePath was replaced"
707+
assert readOut.trim() == content.trim(): "Error when verifying that the file $filePath was replaced"
646708
return true
647709
}
648710

@@ -672,10 +734,18 @@ trait Container {
672734

673735
}
674736

675-
boolean copyFileToContainer(String srcFilePath, String destinationDirectory) {
737+
/**
738+
* Creates a temporary tar, copies it to the container and extracts it there
739+
* @param srcFilePath Local path to copy, will find directories/files recursively
740+
* @param destinationDirectory The destination path in the container, must already exist and be absolut
741+
* @param ignorePaths If these regex patterns matches the path/name of a file it wont be copied over.
742+
* ex: [".*\\.git.*"]
743+
* @return true on success
744+
*/
745+
boolean copyFileToContainer(String srcFilePath, String destinationDirectory, ArrayList<String> ignorePaths = []) {
676746

677747

678-
File tarFile = createTar([srcFilePath], Files.createTempFile("docker_upload", ".tar").toString())
748+
File tarFile = createTar([srcFilePath], Files.createTempFile("docker_upload", ".tar").toString(), ignorePaths)
679749
dockerClient.putArchive(self.containerId, destinationDirectory, tarFile.newDataInputStream())
680750

681751
return tarFile.delete()
@@ -684,6 +754,8 @@ trait Container {
684754

685755
static class ContainerCallback<T> implements StreamCallback<T> {
686756

757+
758+
Logger log = LoggerFactory.getLogger(ContainerCallback.class)
687759
ArrayList<String> output = []
688760

689761
@Override
@@ -693,6 +765,7 @@ trait Container {
693765
} else {
694766
output.add(o.toString())
695767
}
768+
log.debug(output.last())
696769

697770
}
698771
}
@@ -759,7 +832,6 @@ trait Container {
759832
}
760833

761834

762-
763835
/**
764836
* Creates a temporary container, runs a command, exits and removes container
765837
* @param container a container object that hasnt yet been created
@@ -774,20 +846,18 @@ trait Container {
774846
* @param dockerCertPath
775847
* @return An array of the container logs, or just an array containing container id if timeoutMs == 0
776848
*/
777-
static ArrayList<String> runCmdAndRm( ArrayList<String> cmd, long timeoutMs, ArrayList<Map> mounts = [], String dockerHost = "", String dockerCertPath = "") {
849+
static ArrayList<String> runCmdAndRm(ArrayList<String> cmd, long timeoutMs, ArrayList<Map> mounts = [], String dockerHost = "", String dockerCertPath = "") {
778850

779851

780852
Container container = this.getConstructor(String, String).newInstance(dockerHost, dockerCertPath)
781853

782854
Logger log = LoggerFactory.getLogger(this.class)
783855

784856

785-
786857
log.info("Creating a $container.class.simpleName and running:")
787858
log.info("\tCmd:" + cmd)
788859

789860

790-
791861
try {
792862

793863
container.containerName = container.containerName + "-cmd-" + System.currentTimeMillis().toString()[-5..-1]
@@ -821,15 +891,14 @@ trait Container {
821891
log.info("\tContainer finisehd or timed out after ${System.currentTimeMillis() - start}ms")
822892

823893
if (container.running) {
824-
log.info("\t"*2 + "Stopping container forcefully.")
894+
log.info("\t" * 2 + "Stopping container forcefully.")
825895
ArrayList<String> containerOut = container.containerLogs
826896
assert container.stopAndRemoveContainer(1): "Error stopping and removing CMD container after it timed out"
827897

828898
throw new TimeoutException("CMD container timed out, was forcefully stopped and removed. Container logs:" + containerOut?.join("\n"))
829899
}
830900

831901

832-
833902
ArrayList<String> containerOut = container.containerLogs
834903

835904
log.info("\tReturning ${containerOut.size()} log lines")
@@ -843,8 +912,8 @@ trait Container {
843912

844913
try {
845914
container.stopAndRemoveContainer(1)
846-
} catch (ignored){}
847-
915+
} catch (ignored) {
916+
}
848917

849918

850919
throw ex

src/main/groovy/com/eficode/devstack/container/impl/BitbucketContainer.groovy

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package com.eficode.devstack.container.impl
22

33
import com.eficode.devstack.container.Container
44
import com.eficode.devstack.util.ImageBuilder
5-
import de.gesellix.docker.client.EngineResponseContent
65
import de.gesellix.docker.remote.api.ContainerCreateRequest
76
import de.gesellix.docker.remote.api.HostConfig
87
import de.gesellix.docker.remote.api.ImageSummary
@@ -71,7 +70,7 @@ class BitbucketContainer implements Container {
7170
c.exposedPorts = [(containerMainPort + "/tcp"): [:]]
7271
c.hostConfig = new HostConfig().tap { h ->
7372
h.portBindings = [(containerMainPort + "/tcp"): [new PortBinding("0.0.0.0", (containerMainPort))]]
74-
h.mounts = this.mounts
73+
h.mounts = this.preparedMounts
7574
}
7675
c.hostname = containerName
7776
c.env = ["JVM_MAXIMUM_MEMORY=" + jvmMaxRam + "m", "JVM_MINIMUM_MEMORY=" + ((jvmMaxRam / 2) as String) + "m", "SETUP_BASEURL=" + baseUrl, "SERVER_PORT=" + containerMainPort] + customEnvVar

0 commit comments

Comments
 (0)