Skip to content

Commit

Permalink
Merge pull request #544 from molgenis/feat/248-run-armadillo-container
Browse files Browse the repository at this point in the history
Make docker compose for CI and dev
  • Loading branch information
clemens-tolboom authored Dec 7, 2023
2 parents cef74ac + 26cc8f2 commit 0ec628b
Show file tree
Hide file tree
Showing 21 changed files with 669 additions and 24 deletions.
106 changes: 106 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ jobs:
steps:
- checkout

# https://support.circleci.com/hc/en-us/articles/16164465307931-Using-an-environment-variable-to-define-a-path-when-using-the-Docker-executor
- run:
name: "Fix CIRCLE_WORKING_DIRECTORY"
# replace ~ by $HOME
command: echo 'CIRCLE_WORKING_DIRECTORY="${CIRCLE_WORKING_DIRECTORY/#\~/$HOME}"' >> $BASH_ENV

- run:
name: Test env
command: |
echo "id: `id`"
cd
cd repo
pwd
echo "$CIRCLE_WORKING_DIRECTORY"
- run:
name: update package manager
command: apt-get update
Expand Down Expand Up @@ -49,6 +64,10 @@ jobs:
name: Install Docker client
command: apt-get -y install docker-ce docker-ce-cli containerd.io

- run:
name: Install zip
command: apt-get --yes install zip

- setup_remote_docker:
version: 19.03.13
docker_layer_caching: true
Expand All @@ -73,3 +92,90 @@ jobs:
- store_artifacts:
path: build/libs

- run:
name: Build Armadillo and R CICD image
command: |
./gradlew docker
./docker/bin/prepare.bash ci
- run:
name: Make sure all images declared in docker-compose.yml are available and ready
command: |
cd $CIRCLE_WORKING_DIRECTORY
cd build/docker/armadillo-compose
docker images ls
docker compose up -d
docker images ls
- run:
name: Start docker-compose and wait for `release-test.R` to finish
command: |
# FIXME: make this name not directory dependent
ARMADILLO="armadillo-compose-armadillo-1"
cd $CIRCLE_WORKING_DIRECTORY
cd build/docker/armadillo-compose
docker compose up -d
docker ps
# 1 CircleCI docker in docker workaround
docker cp ./armadillo/config $ARMADILLO:/config
docker cp ./armadillo/data $ARMADILLO:/data
docker cp ./armadillo/logs $ARMADILLO:/logs
# .1 CircleCI docker in docker workaround
# Poll to see Armadillo is up
docker container run --network container:$ARMADILLO \
docker.io/jwilder/dockerize \
-wait http://localhost:8080/ \
-wait-retry-interval 20s \
-timeout 30s || echo "Timed out"
cd $CIRCLE_WORKING_DIRECTORY
cd build/docker/cicd
# Run release-test.R
docker container run \
--network container:$ARMADILLO \
--interactive --tty \
--entrypoint /bin/bash molgenis/r-cicd -c "cd /cicd/scripts/release ; ./armadillo-ready.bash"
cd $CIRCLE_WORKING_DIRECTORY
cd build/docker/armadillo-compose
# 2 CircleCI docker in docker workaround
# See what changed within Armadillo
docker cp $ARMADILLO:/logs ./armadillo/logs
# .2 CircleCI docker in docker workaround
docker container ls
docker compose down
docker images ls
docker compose rm
- store_artifacts:
path: build/libs

- run:
name: Zip armadillo-compose
command: |
cd $CIRCLE_WORKING_DIRECTORY
mkdir build/artifacts
# Workaround for ~ expansion not working
K=`pwd`
cd build/docker/
zip -r $K/build/artifacts/armadillo-compose.zip armadillo-compose
- store_artifacts:
path: build/artifacts/armadillo-compose.zip


workflows:
build-deploy:
jobs:
- build
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ build/
**/audit.log

# during development you can safely create an application.yml but don't commit it!
application.yml
application.yaml
application.properties
/application.yml
/application.yaml
/application.properties
.Rproj.user
26 changes: 26 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,32 @@ List messages to see usage of conventional commits from the past.
git log --pretty=format:"%s" | cut -c -20
```

## Continuous integration

- we test on each PR and merges on master
- we build docker compose set for CI testing and demo purposes.
- [CI testing](./docker/ci/README.md)
- Demo zip file is a delivery you as artifact
- Master build have a armadillo-compose.zip for download

### Local CI build

```
./gradlew docker
./docker/bin/prepare.bash ci
cd build/docker/armadillo-compose
# Follow README.md to see Armadillo and R images run in container
docker compose build
docker compose up
```

then run `./release-test.R` against this.

### Local CI test of armadillo-compose

Follow [docker CI README.md](./docker/ci/README.md) to run `release-test.R` using `molgenis/r-cicd` image

## Profile xenon with resourcer whitelisted returns a host.docker.internal error
When developing locally, it might be possible to come across the container error: `Could not resolve host: host.docker.internal`,
especially when developing on a non-supported operating system when resourcer is whitelisted (such as xenon).
Expand Down
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
FROM eclipse-temurin:17.0.9_9-jdk-focal
FROM --platform=linux/amd64 eclipse-temurin:17.0.9_9-jdk-focal
VOLUME /data
VOLUME /config
VOLUME /logs

ARG JAR_FILE
EXPOSE 8080
COPY ${JAR_FILE} /app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-DSPRING_CONFIG_ADDITIONAL_LOCATION=/config/application.yml", "-jar","/app.jar"]
8 changes: 8 additions & 0 deletions application.template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ armadillo:
# set this false if you DON'T want Armadillo to create/edit/delete profile docker images via the user interface
docker-management-enabled: true

# set this true if you want Armadillo runs as a docker container
# NOTE: this needs "docker-management-enabled" == false
docker-run-in-container: false

# when running the R containers from a docker-compose.yml they get prefixes based on the directory name of the
# docker-compose.yml file ie armadillo-dev-" + profileName + "-1". Same goes for Armadillo ie armadillo-dev-armadillo-1"
container-prefix: ''

# uncomment this to configure an oidc user as admin user
# oidc-admin-user: [email protected]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
public class ProfilesDockerController {

public static final String DOCKER_MANAGEMENT_ENABLED = "armadillo.docker-management-enabled";
public static final String DOCKER_RUN_IN_CONTAINER = "armadillo.docker-run-in-container";

private final DockerService dockerService;
private final AuditEventPublisher auditor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.molgenis.armadillo.metadata.ProfileStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
Expand All @@ -39,6 +40,12 @@ public class DockerService {
private final DockerClient dockerClient;
private final ProfileService profileService;

@Value("${armadillo.docker-run-in-container:true}")
private boolean inContainer;

@Value("${armadillo.container-prefix:''}")
private String containerPrefix;

public DockerService(DockerClient dockerClient, ProfileService profileService) {
this.dockerClient = dockerClient;
this.profileService = profileService;
Expand Down Expand Up @@ -74,12 +81,47 @@ public Map<String, ContainerInfo> getAllProfileStatuses() {
return statuses;
}

// `docker container ps` show these name structure

/**
* The container can run in its own network/compose.
*
* <p>Both the profiles and/or Armadillo can be part of a docker-compose.yml. You can check with
* `docker container ps` to see this name structure.
*
* @param profileName the profile name set by DataManager.
* @return adjusted container name if applicable.
*/
String asContainerName(String profileName) {
if (!inContainer) {
LOG.warn("NO ".repeat(100) + " " + profileName);
return profileName;
}

if (containerPrefix.isEmpty()) {
LOG.error("Running in container without prefix: " + profileName);
return profileName;
}

LOG.warn("YES ".repeat(100) + " " + profileName);
return containerPrefix + profileName + "-1";
}

String asProfileName(String containerName) {
if (inContainer) {
return containerName.replace("armadillo-docker-compose-", "").replace("-1", "");
}
return containerName;
}

public ContainerInfo getProfileStatus(String profileName) {
// check profile exists
profileService.getByName(profileName);

String containerName = asContainerName(profileName);
try {
InspectContainerResponse containerInfo = dockerClient.inspectContainerCmd(profileName).exec();
InspectContainerResponse containerInfo =
dockerClient.inspectContainerCmd(containerName).exec();
var tags = getImageTags(containerInfo.getName());
return ContainerInfo.create(tags, ProfileStatus.of(containerInfo.getState()));
} catch (ProcessingException e) {
Expand All @@ -94,12 +136,15 @@ public ContainerInfo getProfileStatus(String profileName) {
}

public void startProfile(String profileName) {
String containerName = asContainerName(profileName);
LOG.info(profileName + " : " + containerName);

var profileConfig = profileService.getByName(profileName);
pullImage(profileConfig);
stopContainer(profileName);
removeContainer(profileName); // for reinstall
stopContainer(containerName);
removeContainer(containerName); // for reinstall
installImage(profileConfig);
startContainer(profileName);
startContainer(containerName);
}

private void installImage(ProfileConfig profileConfig) {
Expand All @@ -122,21 +167,22 @@ private void installImage(ProfileConfig profileConfig) {
}
}

private void startContainer(String profileName) {
private void startContainer(String containerName) {
try {
dockerClient.startContainerCmd(profileName).exec();
dockerClient.startContainerCmd(containerName).exec();
} catch (DockerException e) {
throw new ImageStartFailedException(profileName, e);
throw new ImageStartFailedException(containerName, e);
}
}

private void stopContainer(String profileName) {
private void stopContainer(String containerName) {
String profileName = "stoppingContainer has not profileName: " + containerName;
try {
dockerClient.stopContainerCmd(profileName).exec();
dockerClient.stopContainerCmd(containerName).exec();
} catch (DockerException e) {
try {
InspectContainerResponse containerInfo =
dockerClient.inspectContainerCmd(profileName).exec();
dockerClient.inspectContainerCmd(containerName).exec();
// should not be a problem if not running
if (TRUE.equals(containerInfo.getState().getRunning())) {
throw new ImageStopFailedException(profileName, e);
Expand Down Expand Up @@ -180,14 +226,14 @@ public void removeProfile(String profileName) {
removeContainer(profileName);
}

private void removeContainer(String profileName) {
private void removeContainer(String containerName) {
try {
dockerClient.removeContainerCmd(profileName).exec();
dockerClient.removeContainerCmd(containerName).exec();
} catch (NotFoundException nfe) {
LOG.info("Couldn't remove container '{}' because it doesn't exist", profileName);
LOG.info("Couldn't remove container '{}' because it doesn't exist", containerName);
// not a problem, wanted to remove anyway
} catch (DockerException e) {
throw new ContainerRemoveFailedException(profileName, e);
throw new ContainerRemoveFailedException(containerName, e);
}
}

Expand Down
11 changes: 11 additions & 0 deletions armadillo/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
armadillo:
# set this to 'true' if you want to accept permissions from oidc provider
oidc-permission-enabled: false

# set this true if you want Armadillo to create/edit/delete profile docker images
docker-management-enabled: true

# set this true if you want Armadillo runs as a docker container
# NOTE: this needs "docker-management-enabled" == false
docker-run-in-container: true

# when running the R containers from a docker-compose.yml they get prefixes based on the directory name of the
# docker-compose.yml file ie armadillo-dev-" + profileName + "-1". Same goes for Armadillo ie armadillo-dev-armadillo-1"
container-prefix: ''


# uncomment this to configure a default admin user
# oidc-admin-user: [email protected]

Expand Down
Loading

0 comments on commit 0ec628b

Please sign in to comment.