This is a common design, so the functionality is included in the Kaholo Plugin Library and re-use of this library is encouraged to develop docker-enabled Kaholo plugins. The Kaholo Plugin Library provides a lot of benefit in terms of rapid development, security, validation, and consistent results. In this section we’ll outline the details of how to make this work.
Reference Kaholo Plugin Library
The Kaholo Plugin Libarary is maintained as a Github repository and published as an npm package for easy reference in your own plugins. We’ll use the Maven plugin as an example because it is a very simple CLI that will allow us to focus on the Docker aspect with minimal maven-related distractions.
The first step is to include this library in your npm configuration, package.json
.
"dependencies": { "@kaholo/plugin-library": "^2.0.0" }
Next, within your code file, e.g. app.js
or in the case of Maven mvn-cli.js
, declare a constant requiring the library. In this case name of the docker image to use and the command are declared as constants in consts.json
.
in mvn-cli.js:
const { docker } = require("@kaholo/plugin-library"); const { MAVEN_DOCKER_IMAGE, MAVEN_CLI_NAME, } = require("./consts.json");
consts.json:
{ "MAVEN_DOCKER_IMAGE": "maven", "MAVEN_CLI_NAME": "mvn" }
Now you are ready to use the necessary library functions in code.
Using the Kaholo Plugin Library
The relevant functions in the Kaholo Plugin library are in file docker.js
.
function sanitizeCommand(command, commandPrefix)
This function ensures only the intended command line executable gets used, and also allows the user to optionally omit the name of that executable, returning a string sh -c command options
.
function createVolumeDefinition(path)
This function allows for the mounting of paths in the Kaholo Agent to make them available within the docker container executing the command. Maven for example builds a Java project based on configuration in a file pom.xml
. The source files and pom.xml
are put on the Kaholo Agent (e.g. using Git plugin to clone a repository) and the JAR file result will need to remain on the Kaholo Agent after the Maven container is destroyed so the next step in the pipeline can access it. Therefore a directory on the Kaholo Agent (path
) is passed to this function and gets mounted within the docker container as a docker volume.
Environment variable names and the mount point within the docker image are randomized to minimize the potential of discovery/recovery during and after the lifetime of the docker container. These details are returned as a JSON object.
return { path: { name: pathEnvironmentVariableName, value: path, }, mountPoint: { name: mountPointEnvironmentVariableName, value: mountPoint, }, };
In the case more than one mount is required, this function can be used multiple times to create an array of volume definitions for the buildDockerCommand()
function. Note that even if there is only one, it needs to be a single-element array.
function buildDockerCommand({Object})
Function buildDockerCommand requires a JSON object containing the command, docker image, environment variables, volume definitions, additional arguments, and working directory. There’s a parameter for user but this is most commonly left to default to root. This JSON object is built up using the results from sanitizeCommand()
, createVolumeDefinition()
, and normal method parameters if any are required.
function buildDockerCommand({ command, image, environmentVariables = {}, volumeDefinitionsArray = [], additionalArguments = [], workingDirectory, user, })
This function validates and builds up the complete docker command line string, including image, environment variables, mounts and such that will start the container, run the command, direct output to the Kaholo Activity Log and Final Result, and then destroy the docker container.
Run the Docker Command
Finally it’s time to run the command string built by the buildDockerCommand()
function. Here’s how the Maven plugin does this.
const util = require("util"); const childProcess = require("child_process"); const { access } = require("fs/promises"); const exec = util.promisify(childProcess.exec); return exec(dockerCommand, { env: shellEnvironmentalVariables, }).catch((error) => { throw new Error(error.stderr || error.stdout || error.message || error); });