Skip to content

Commit

Permalink
Comprehensive updates across the codebase
Browse files Browse the repository at this point in the history
This commit reflects a multitude of adjustments across several resources:
1. Added `ContainerBuilderCallback` class that provides an interface for defining a callback to configure a container builder.
2. Extended `Container` interface with string argument variants for `putFile` and `getFile` methods - enhancing versatility & ease of use.
3. Revised `handleJobCompletion` of `K8sJobRuntime` class to bolster error handling by considering job failure scenarios.
4. Enhanced logging message accuracy in test class 'ShowCaseTests'.
5. Added `normalizeVolumeName` method in `K8sUtils`, improving volume name handling.
6. Test class `ContainerVolumeTests` was added, which includes a test case for mounting container file.
7. Updated `handleContainerStatuses` in test class `ContainerTaskTests` to cater for changes in Container state.
8. Edited `GenericContainer` to use the `ExperimentalStdlibApi` annotation, potentially enhancing future development options.
9. Improved `K8sUtils` by introducing a regex constant `INVALID_VOLUME_CHARS_REGEX` and a method `normalizeConfigMapName`.
10. Small formatting tweaks across classes for better code readability and cleanliness, including alterations in README.md
  • Loading branch information
thomas-muller666 committed Apr 17, 2024
1 parent 4bcbf8b commit ac0a92e
Show file tree
Hide file tree
Showing 26 changed files with 418 additions and 191 deletions.
11 changes: 6 additions & 5 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Additional notices:

3. This product includes software developed by the Docker Java project (https://github.com/docker-java/docker-java):
- Docker Java API Client
- Docker Java Transport OkHttp
The Docker Java API Client is used under the terms of the Apache License 2.0.

4. This product includes software from the Apache Commons project (http://commons.apache.org/):
Expand All @@ -33,10 +34,6 @@ Additional notices:
- Logback Classic Module
The logging frameworks are used under the terms of the MIT License and the Eclipse Public License 1.0 respectively.

6. This product includes software developed by Michael Wiede (https://github.com/mwiede):
- JSch
The JSch library is used under the terms of the MIT License.

7. This product includes software developed by Awaitility (https://github.com/awaitility/awaitility):
- Awaitility
The Awaitility library is used under the terms of the Apache License 2.0.
Expand All @@ -45,7 +42,11 @@ Additional notices:
- khttp
The khttp library is used under the terms of the MIT License.

9. This product includes software from the JUnit Team (https://junit.org/):
9. This product includes software developed by Michael Wiede (https://github.com/mwiede):
- JSch
The JSch library is used under the terms of the MIT License.

10. This product includes software from the JUnit Team (https://junit.org/):
- JUnit Jupiter API
- JUnit Jupiter Engine
- JUnit Jupiter Params
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ Born from the practical insights gained from the [Testcontainers](https://www.te

With Easycontainers, developers gain a versatile tool that streamlines container management, freeing them to focus more on development and less on the operational intricacies of containers.


## Getting Started

### Starting a container
Expand Down
5 changes: 0 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,6 @@
<artifactId>netty-handler</artifactId>
</exclusion>

<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
</exclusion>

<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
Expand Down
16 changes: 3 additions & 13 deletions src/main/kotlin/no/acntech/easycontainers/BaseContainerBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package no.acntech.easycontainers
import no.acntech.easycontainers.model.*
import no.acntech.easycontainers.output.OutputLineCallback
import no.acntech.easycontainers.util.collections.prettyPrint
import no.acntech.easycontainers.util.text.EQUALS
import no.acntech.easycontainers.util.text.NEW_LINE
import org.apache.commons.lang3.builder.ToStringBuilder
import org.apache.commons.lang3.builder.ToStringStyle
Expand Down Expand Up @@ -188,19 +189,8 @@ abstract class BaseContainerBuilder<SELF : BaseContainerBuilder<SELF>> : Contain
return self()
}

override fun withContainerFile(
name: ContainerFileName,
path: UnixDir,
data: Map<String, String>,
keyValSeparator: String,
): SELF {
val content = data.entries.joinToString(NEW_LINE) { (key, value) -> "$key$keyValSeparator$value" }
containerFiles[name] = ContainerFile(name, path, content)
return self()
}

override fun withContainerFile(name: ContainerFileName, path: UnixDir, content: String): SELF {
containerFiles[name] = ContainerFile(name, path, content)
override fun withContainerFile(file: ContainerFile): SELF {
containerFiles[file.name] = file
return self()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package no.acntech.easycontainers

import no.acntech.easycontainers.model.ContainerBuilder

/**
* An interface for defining a callback to configure a container builder.
* Implement the [ContainerBuilderCallback] interface to provide custom configuration logic for a container builder.
*
* @param T the type of container builder
*/
interface ContainerBuilderCallback {

/**
* Configures a container builder.
*
* @param builder the container builder to configure
*/
fun configure(builder: ContainerBuilder<*>)

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ open class GenericContainer(

companion object {

@OptIn(ExperimentalStdlibApi::class)
private val LEGAL_STATE_TRANSITIONS: Map<ContainerState, Set<ContainerState>> = mapOf(
ContainerState.UNINITIATED to setOf(ContainerState.INITIALIZING),
ContainerState.INITIALIZING to setOf(ContainerState.RUNNING, ContainerState.FAILED),
Expand Down
65 changes: 53 additions & 12 deletions src/main/kotlin/no/acntech/easycontainers/docker/DockerRuntime.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ import no.acntech.easycontainers.ContainerException
import no.acntech.easycontainers.GenericContainer
import no.acntech.easycontainers.model.*
import no.acntech.easycontainers.util.io.FileUtils
import no.acntech.easycontainers.util.lang.asStringMap
import no.acntech.easycontainers.util.lang.guardedExecution
import no.acntech.easycontainers.util.platform.PlatformUtils
import no.acntech.easycontainers.util.text.EMPTY_STRING
import no.acntech.easycontainers.util.text.SPACE
import no.acntech.easycontainers.util.text.splitOnWhites
import no.acntech.easycontainers.util.text.*
import org.awaitility.Awaitility.await
import org.awaitility.core.ConditionTimeoutException
import java.io.*
Expand Down Expand Up @@ -138,6 +137,10 @@ internal class DockerRuntime(

private var startedAt: Instant? = null

init {
log.debug("DockerRuntime using container builder:$NEW_LINE${container.builder}")
}

override fun getType(): ContainerPlatformType {
return ContainerPlatformType.DOCKER
}
Expand Down Expand Up @@ -464,10 +467,11 @@ internal class DockerRuntime(
{
val image = container.getImage().toFQDN()
val hostConfig = prepareHostConfig()

configureNetwork(hostConfig)

val containerCmd = createContainerCommand(image, hostConfig).also {
log.debug("containerCmd created: $it")
log.debug("containerCmd created:$NEW_LINE${it.asStringMap()}")
}

containerCmd.exec().id.also {
Expand Down Expand Up @@ -624,34 +628,71 @@ internal class DockerRuntime(
}

private fun createDockerVolumes(hostConfig: HostConfig): List<Volume> {
val volumeMap = container.getVolumes().associateWith { Volume(it.mountPath.value) }
val volumes = container.getVolumes()
val volumeMap = volumes.associateWith {
Volume(it.mountPath.value)
}
val dockerVolumeNames = getExistingVolumeNames()

val binds = container.getVolumes().filter { it.memoryBacked }
val volumeBinds = volumes
.filter { !it.memoryBacked }
.map { volume -> createBind(volume, volumeMap[volume], dockerVolumeNames) }

val tmpfsMounts = container.getVolumes().filter { it.memoryBacked }
val fileBinds = container.builder.containerFiles.map { (name, file) ->
createContainerFileBind(file)
}

val tmpfsMounts = volumes
.filter { it.memoryBacked }
.map { volume -> createTmpfsMount(volume) }

configureHostConfigVolumes(hostConfig, binds, tmpfsMounts)
configureHostConfigVolumes(hostConfig, volumeBinds + fileBinds, tmpfsMounts)

return volumeMap.values.toList()
}

private fun createContainerFileBind(containerFile: ContainerFile): Bind {
val hostFile = containerFile.hostFile ?: File.createTempFile(containerFile.name.value, null).toPath()

// If content is not null, write content to this file
containerFile.content?.let { content ->
hostFile.toFile().writeText(content)
}

val actualBindPath = PlatformUtils.convertToDockerPath(hostFile)

// Get the complete path including the filename as the mount point
val mountPath = "${containerFile.mountPath}$FORWARD_SLASH${containerFile.name}"

// Finally, create a Docker bind
val bind = Bind(actualBindPath, Volume(mountPath))

return bind.also {
log.info(
"Using host file '${actualBindPath}' for container file '${containerFile.name}'" +
" with mount-path '$mountPath'"
)
}
}

private fun createBind(
volume: no.acntech.easycontainers.model.Volume,
dockerVolume: Volume?,
dockerVolumeNames: Set<String>,
): Bind {
val volumeName = volume.name.value

return if (dockerVolumeNames.contains(volumeName)) {
log.info("Using existing named Docker volume '$volumeName' with mount-path '${volume.mountPath}'")
Bind(volumeName, dockerVolume)

} else {
log.info("Using hostDir '${volume.hostDir}' for volume '$volumeName'")
val actualHostDir =
getActualHostDir(volume.hostDir ?: throw ContainerException("Volume '$volumeName' must have a hostDir"))
Bind(actualHostDir, dockerVolume)

volume.hostDir?.let { hostDir ->
val actualHostDir = getActualHostDir(hostDir)
Bind(actualHostDir, dockerVolume)
} ?: throw ContainerException("Volume '$volumeName' must have a host-dir")
}
}

Expand Down Expand Up @@ -720,7 +761,7 @@ internal class DockerRuntime(
}

private fun configureVolumes(cmd: CreateContainerCmd, hostConfig: HostConfig) {
if (container.getVolumes().isNotEmpty()) {
if (container.getVolumes().isNotEmpty() || container.builder.containerFiles.isNotEmpty()) {
val volumes = createDockerVolumes(hostConfig)
cmd.withVolumes(*volumes.toTypedArray())
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ class K8sJobRuntime(

private fun handleJobCompletion(condition: JobCondition) {
if ("True" == condition.status) {

val completionDateTimeVal = job.status.completionTime
completionDateTimeVal?.let {
finishedAt = Instant.parse(completionDateTimeVal)
Expand All @@ -198,7 +199,20 @@ class K8sJobRuntime(
log.info("Job '$jobName' took approximately: $duration")
}
}
container.changeState(ContainerState.STOPPED)

job.status.failed?.let { failed ->
if (failed > 0) {
log.error("Job '$jobName' failed with $failed failed pods")
container.changeState(ContainerState.FAILED)
} else {
log.info("Job '$jobName' completed successfully")
container.changeState(ContainerState.STOPPED)
}
} ?: run {
log.info("Job '$jobName' completed successfully")
container.changeState(ContainerState.STOPPED)
}

completionLatch.countDown()
log.trace("Latch decremented, job '$jobName' completed")
}
Expand Down
Loading

0 comments on commit ac0a92e

Please sign in to comment.