Skip to content

Commit 1dfa318

Browse files
committed
Added NginxContainer.groovy and basic tests for it
1 parent 5581327 commit 1dfa318

4 files changed

Lines changed: 255 additions & 3 deletions

File tree

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import de.gesellix.docker.engine.DockerClientConfig
55
import de.gesellix.docker.engine.DockerEnv
66
import de.gesellix.docker.engine.EngineResponse
77
import de.gesellix.docker.remote.api.IdResponse
8+
import de.gesellix.docker.remote.api.Mount
89
import de.gesellix.docker.remote.api.core.ClientException
910
import de.gesellix.docker.remote.api.core.Frame
1011
import de.gesellix.docker.remote.api.core.StreamCallback
@@ -30,6 +31,23 @@ trait Container {
3031
abstract String containerName
3132
abstract String containerMainPort
3233
String containerId
34+
ArrayList<Mount> mounts = []
35+
36+
37+
void prepareBindMount(String sourceAbs, String target, boolean readOnly = true){
38+
assert !isCreated() : "Bind mounts cant be prepared for already created container"
39+
40+
this.mounts.add(
41+
new Mount().tap{m ->
42+
m.source = sourceAbs
43+
m.target = target
44+
m.readOnly = readOnly
45+
m.type = Mount.Type.Bind
46+
}
47+
)
48+
}
49+
50+
3351

3452

3553
abstract String createContainer()

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ class JsmContainer implements Container{
3535

3636
String createJsmContainer(String jsmContainerName = containerName, String imageName = containerImage, String imageTag = containerImageTag, long jsmMaxRamMB = jvmMaxRam, String jsmPort = containerMainPort) {
3737

38-
assert dockerClient.ping().content as String == "OK", "Error Connecting to docker service"
38+
assert ping(), "Error Connecting to docker engine"
3939

4040

4141
ContainerCreateRequest containerCreateRequest = new ContainerCreateRequest().tap { c ->
4242

4343
c.image = imageName + ":" + imageTag
4444
c.env = ["JVM_MAXIMUM_MEMORY=" + jsmMaxRamMB.toString() + "m", "JVM_MINIMUM_MEMORY=" + ((jsmMaxRamMB / 2) as String) + "m"]
45-
c.exposedPorts = [("8080/tcp"): [:]]
46-
c.hostConfig = new HostConfig().tap { h -> h.portBindings = [("8080/tcp"): [new PortBinding("0.0.0.0", (jsmPort.toString()))]] }
45+
c.exposedPorts = [(jsmPort + "/tcp"): [:]]
46+
c.hostConfig = new HostConfig().tap { h -> h.portBindings = [(jsmPort+"/tcp"): [new PortBinding("0.0.0.0", (jsmPort))]] }
4747

4848
}
4949

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.eficode.devstack.container.impl
2+
3+
import com.eficode.devstack.container.Container
4+
import de.gesellix.docker.client.EngineResponseContent
5+
import de.gesellix.docker.remote.api.ContainerCreateRequest
6+
import de.gesellix.docker.remote.api.HostConfig
7+
import de.gesellix.docker.remote.api.Mount
8+
import de.gesellix.docker.remote.api.PortBinding
9+
10+
class NginxContainer implements Container{
11+
12+
String containerName = "Nginx"
13+
String containerMainPort= "80"
14+
String containerImage = "nginx"
15+
String containerImageTag ="alpine"
16+
17+
18+
NginxContainer() {}
19+
20+
/**
21+
* Setup a secure connection to a remote docker
22+
* @param dockerHost ex: https://docker.domain.com:2376
23+
* @param dockerCertPath ex: src/test/resources/dockerCert
24+
*/
25+
NginxContainer(String dockerHost, String dockerCertPath) {
26+
assert setupSecureRemoteConnection(dockerHost, dockerCertPath) : "Error setting up secure remote docker connection"
27+
}
28+
29+
String createContainer() {
30+
containerId = createNginxContainer()
31+
return containerId
32+
}
33+
34+
35+
/**
36+
* Bind local dir to nginx default root dir /usr/share/nginx/html
37+
* Must be called before creating container
38+
* @param sourceAbs absolut path to local dir
39+
* @param readOnly
40+
*/
41+
void bindHtmlRoot(String sourceAbs, boolean readOnly = true) {
42+
prepareBindMount(sourceAbs,"/usr/share/nginx/html" , readOnly)
43+
}
44+
45+
String createNginxContainer(){
46+
47+
48+
assert ping(), "Error Connecting to docker engine"
49+
50+
ContainerCreateRequest containerCreateRequest = new ContainerCreateRequest().tap { c ->
51+
52+
c.image = containerImage + ":" + containerImageTag
53+
c.exposedPorts = [(containerMainPort+"/tcp"): [:]]
54+
c.hostConfig = new HostConfig().tap { h ->
55+
h.portBindings = [(containerMainPort+"/tcp"): [new PortBinding("0.0.0.0", (containerMainPort))]]
56+
h.mounts = this.mounts
57+
}
58+
59+
60+
}
61+
62+
63+
64+
EngineResponseContent response = dockerClient.createContainer(containerCreateRequest, containerName)
65+
assert response.content.warnings.isEmpty(): "Error when creating $containerName container:" + response.content.warnings.join(",")
66+
67+
containerId = response.content.id
68+
return containerId
69+
70+
71+
}
72+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package com.eficode.devstack.container.impl
2+
3+
import de.gesellix.docker.client.DockerClientImpl
4+
import de.gesellix.docker.engine.DockerClientConfig
5+
import de.gesellix.docker.engine.DockerEnv
6+
import de.gesellix.docker.remote.api.ContainerInspectResponse
7+
import kong.unirest.Unirest
8+
import org.apache.commons.io.FileUtils
9+
import org.apache.commons.io.filefilter.FileFileFilter
10+
import org.slf4j.Logger
11+
import org.slf4j.LoggerFactory
12+
import spock.lang.Shared
13+
import spock.lang.Specification
14+
15+
16+
class NginxContainerTest extends Specification {
17+
18+
@Shared
19+
static Logger log = LoggerFactory.getLogger(NginxContainerTest.class)
20+
21+
@Shared
22+
DockerClientImpl dockerClient
23+
24+
@Shared
25+
File localNginxRoot = new File("examples")
26+
27+
28+
@Shared
29+
String dockerRemoteHost //= "https://docker.domain.se:2376"
30+
@Shared
31+
String dockerCertPath //= "resources/dockerCert"
32+
@Shared
33+
String nginxUrl = "http://localhost"
34+
35+
36+
def setupSpec() {
37+
dockerClient = resolveDockerClient()
38+
dockerClient.stop("Nginx")
39+
dockerClient.rm("Nginx")
40+
41+
assert localNginxRoot.listFiles().findAll{it.isFile()}.size() > 0 : "localNginxRoot must contain at least one file"
42+
43+
}
44+
45+
46+
def "test default setup"() {
47+
48+
setup:
49+
NginxContainer nginxC = new NginxContainer()
50+
51+
expect:
52+
nginxC.createContainer()
53+
nginxC.startContainer()
54+
55+
cleanup:
56+
nginxC.stopAndRemoveContainer()
57+
58+
}
59+
60+
def "test setting nginx root bind dir"() {
61+
62+
setup:
63+
log.info("Testing setting nginx root dir")
64+
log.info("\tWill use dir:" + localNginxRoot.absolutePath)
65+
66+
NginxContainer nginxC = new NginxContainer()
67+
68+
when: "bindHtmlRoot should add a mount to the mounts array"
69+
nginxC.bindHtmlRoot(localNginxRoot.absolutePath, true)
70+
71+
then:
72+
nginxC.mounts.size() == 1
73+
74+
when: "After creating the container, the inspect result should confirm the mount"
75+
nginxC.createContainer()
76+
nginxC.startContainer()
77+
ContainerInspectResponse inspectResponse = dockerClient.inspectContainer(nginxC.id).getContent()
78+
log.info("\tContainer created")
79+
80+
then:
81+
inspectResponse.mounts.find {it.source == localNginxRoot.absolutePath}
82+
log.info("\tDocker API confirms mount was created")
83+
84+
expect: "Nginx should return the expected files"
85+
log.info("\tConfirming Nginx returns files found in the bind directory")
86+
localNginxRoot.listFiles().findAll {it.isFile()}.every {file ->
87+
log.debug("\t"*2 + "Requesting file:" + file.name)
88+
int status = Unirest.get(nginxUrl + "/" + file.name).asEmpty().status
89+
log.debug("\t"*3 + "HTTP Status:" + status)
90+
return status == 200
91+
}
92+
93+
Unirest.get(nginxUrl + "/MISSINGFILE").asEmpty().status == 404
94+
95+
96+
97+
98+
}
99+
100+
101+
DockerClientImpl resolveDockerClient() {
102+
103+
if (this.dockerClient) {
104+
return this.dockerClient
105+
}
106+
107+
log.info("Getting Docker client")
108+
109+
if (!dockerRemoteHost) {
110+
log.info("\tNo remote host configured, returning local docker connection")
111+
return new DockerClientImpl()
112+
}
113+
114+
File certDir = new File(dockerCertPath)
115+
116+
if (!certDir.isDirectory()) {
117+
log.info("\tNo valid Docker Cert Path given, returning local docker connection")
118+
return new DockerClientImpl()
119+
}
120+
log.info("\tLooking for docker certs in:" + certDir.absolutePath)
121+
ArrayList<File> pemFiles = FileUtils.listFiles(certDir, ["pem"] as String[], false)
122+
log.debug("\t\tFound pem files:" + pemFiles.name.join(","))
123+
124+
125+
if (!pemFiles.empty && pemFiles.every { pemFile -> ["ca.pem", "cert.pem", "key.pem"].find { it == pemFile.name } }) {
126+
log.info("\tFound Docker certs, returning Secure remote Docker connection")
127+
try {
128+
DockerClientImpl dockerClient = setupSecureRemoteConnection(dockerRemoteHost, dockerCertPath)
129+
assert dockerClient.ping().content as String == "OK": "Error pinging remote Docker engine"
130+
return dockerClient
131+
} catch (ex) {
132+
log.error("\tError setting up connection to remote Docker engine:" + ex.message)
133+
log.info("\tReturning local Docker connection")
134+
return new DockerClientImpl()
135+
}
136+
137+
}
138+
139+
log.info("\tMissing Docker certs, returning local docker connection")
140+
141+
return new DockerClientImpl()
142+
143+
}
144+
145+
/**
146+
* Replaced the default docker connection (local) with a remote, secure one
147+
* @param host ex: "https://docker.domain.se:2376"
148+
* @param certPath folder containing ca.pem, cert.pem, key.pem
149+
*/
150+
static DockerClientImpl setupSecureRemoteConnection(String host, String certPath) {
151+
152+
DockerClientConfig dockerConfig = new DockerClientConfig(host)
153+
DockerEnv dockerEnv = new DockerEnv(host)
154+
dockerEnv.setCertPath(certPath)
155+
dockerEnv.setTlsVerify("1")
156+
dockerConfig.apply(dockerEnv)
157+
158+
return new DockerClientImpl(dockerConfig)
159+
160+
}
161+
162+
}

0 commit comments

Comments
 (0)