From 8c0aaeb71de2ceaade687c97eb0d145b3b03b379 Mon Sep 17 00:00:00 2001 From: robert-bor Date: Thu, 25 Jan 2018 11:24:37 +0100 Subject: [PATCH 1/2] Issue #29 by default the Docker run will make use of in-memory mounts for the application and data path in the standard Postgres Docker image. If in-memory must be disabled, it can be done in the properties. Custom paths can be passed as a list under the inMemoryMountDestinations list. The init process has been revamped to make it less reliable on the first lazy call of getProperties(). The in-memory solution requires a dynamic number of mount mappings. The solution is to insert a template (IN_MEMORY_TEMPLATE), which is transformed into the required number of --mount mappings for Docker. IN_MEMORY_TEMPLATE is hardcoded for now. Not sure if it is sensible to make this customizable. --- CHANGELOG.md | 11 ++ .../postgres/DockerPostgresBootSequence.java | 28 ++--- .../postgres/DockerPostgresProperties.java | 117 +++++++++++++++++- .../postgres/DockerStartContainerCommand.java | 2 +- 4 files changed, 133 insertions(+), 25 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f76e9c3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## Unreleased + +## [0.7.1] - 2018-01-24 +### Fixed +- Issue [#29](https://github.com/42BV/spring-boot-docker-postgres/issues/29), **By default use in-memory destination mounts for Docker run**; by default the Docker run will make use of in-memory mounts for the application and data path in the standard Postgres Docker image. If in-memory must be disabled, it can be done in the properties. Custom paths can be passed as a list under the inMemoryMountDestinations list. diff --git a/src/main/java/nl/_42/boot/docker/postgres/DockerPostgresBootSequence.java b/src/main/java/nl/_42/boot/docker/postgres/DockerPostgresBootSequence.java index 690fdb0..73da61f 100644 --- a/src/main/java/nl/_42/boot/docker/postgres/DockerPostgresBootSequence.java +++ b/src/main/java/nl/_42/boot/docker/postgres/DockerPostgresBootSequence.java @@ -12,7 +12,6 @@ public class DockerPostgresBootSequence { - private static final Integer DEFAULT_PORT = 5432; private static final Logger LOGGER = LoggerFactory.getLogger(DockerPostgresBootSequence.class); private final DockerPostgresProperties properties; @@ -25,6 +24,7 @@ public DockerPostgresBootSequence(DockerPostgresProperties properties, DataSourc public DockerStartContainerCommand execute() throws IOException, InterruptedException { + properties.init(dataSourceProperties.getUrl()); LOGGER.info("| Docker Postgres Properties"); LOGGER.info("| * Image name: " + properties.getImageName()); LOGGER.info("| * Image version: " + properties.getImageVersion()); @@ -32,24 +32,22 @@ public DockerStartContainerCommand execute() throws IOException, InterruptedExce LOGGER.info("| * Stop port occupying container: " + properties.isStopPortOccupyingContainer()); LOGGER.info("| * Timeout: " + properties.getTimeout()); LOGGER.info("| * Container name: " + properties.getContainerName()); - if (properties.getPort() == null) { - if (dataSourceProperties.getUrl() != null) { - properties.setPort(determinePort(dataSourceProperties.getUrl())); - // Scrap the port from the JDBC URL - } else { - properties.setPort(DEFAULT_PORT); - } - } LOGGER.info("| * Port: " + properties.getPort()); LOGGER.info("| * Password: " + properties.getPassword()); LOGGER.info("| * Startup Verification Text: [" + properties.getStartupVerificationText() + "]"); LOGGER.info("| * Times expected verification text: " + properties.getTimesExpectedVerificationText() + "x"); LOGGER.info("| * After verification wait: " + properties.getAfterVerificationWait() + "ms"); LOGGER.info("| * Docker command: [" + properties.getDockerCommand() + "]"); + LOGGER.info("| * Use Docker command: [" + properties.getUseDockerCommand() + "]"); LOGGER.info("| * Custom variables (" + properties.getCustomVariables().size() + ")"); for (String key : properties.getCustomVariables().keySet()) { LOGGER.info("| - " + key + ": " + properties.getCustomVariables().get(key)); } + LOGGER.info("| * In memory: " + properties.isInMemory()); + LOGGER.info("| * In memory mount destinations"); + for (String inMemoryMountDestination : properties.getInMemoryMountDestinations()) { + LOGGER.info("| - " + inMemoryMountDestination); + } LOGGER.info("| * Std out: " + properties.getStdOutFilename()); LOGGER.info("| * Std err: " + properties.getStdErrFilename()); @@ -106,18 +104,6 @@ public DockerStartContainerCommand execute() throws IOException, InterruptedExce return postgresContainer; } - private Integer determinePort(String url) { - if (url == null || url.length() == 0) { - throw new ExceptionInInitializerError("spring.datasource.url is empty. No port could be derived."); - } - int lastColonPos = url.lastIndexOf(':'); - int slashAfterPortPos = url.indexOf('/', lastColonPos); - if (lastColonPos == -1 || slashAfterPortPos == -1 || slashAfterPortPos < lastColonPos + 2) { - throw new ExceptionInInitializerError("spring.datasource.url does not have port information: [" + url + "]. No port could be derived."); - } - return Integer.parseInt(url.substring(lastColonPos + 1, slashAfterPortPos)); - } - private void applyAfterVerificationWait(Integer afterVerificationWait) throws InterruptedException { if (afterVerificationWait > 0) { LOGGER.info("| Applying after verification wait of " + afterVerificationWait + "ms"); diff --git a/src/main/java/nl/_42/boot/docker/postgres/DockerPostgresProperties.java b/src/main/java/nl/_42/boot/docker/postgres/DockerPostgresProperties.java index 7545267..00b4a34 100644 --- a/src/main/java/nl/_42/boot/docker/postgres/DockerPostgresProperties.java +++ b/src/main/java/nl/_42/boot/docker/postgres/DockerPostgresProperties.java @@ -1,13 +1,22 @@ package nl._42.boot.docker.postgres; +import org.apache.commons.lang3.StringUtils; import org.springframework.boot.context.properties.ConfigurationProperties; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; @ConfigurationProperties(prefix = "docker.postgres", ignoreUnknownFields = false) public class DockerPostgresProperties { + private static final String DEFAULT_PORT = "5432"; + private static final String POSTGRES_BASE_FOLDER_APPLICATION = "/app"; + private static final String POSTGRES_BASE_FOLDER_DATA = "/var/lib/postgresql/data"; + private static final String IN_MEMORY_TEMPLATE = "[IN_MEMORY_TEMPLATE]"; + private static final String VAR_IN_MEMORY_MOUNT_DESTINATION = "inMemoryMountDestination"; + private boolean enabled = true; private String stdOutFilename = "docker-std-out.log"; @@ -28,7 +37,9 @@ public class DockerPostgresProperties { private Integer timesExpectedVerificationText = 2; - private String dockerCommand = "docker run --rm --tty -e POSTGRES_PASSWORD=${password} -p ${port}:5432 --name ${containerName} ${imageName}:${imageVersion}"; + private String dockerCommand = "docker run --rm --tty -e POSTGRES_PASSWORD=${password} -p ${port}:5432 --name ${containerName} " + IN_MEMORY_TEMPLATE + " ${imageName}:${imageVersion}"; + + private String useDockerCommand; private Integer timeout = 300000; // 5 minutes because of time required for downloading? @@ -42,8 +53,22 @@ public class DockerPostgresProperties { private String containerOccupyingPort = null; + private boolean inMemory = true; + + private String inMemoryMountDestinationCommand = + "--mount type=tmpfs,destination=${" + VAR_IN_MEMORY_MOUNT_DESTINATION + "_@N}"; + + private List inMemoryMountDestinations = new ArrayList<>(); + private Map customVariables = new HashMap<>(); + Map properties = null; + + public DockerPostgresProperties() { + inMemoryMountDestinations.add(POSTGRES_BASE_FOLDER_APPLICATION); + inMemoryMountDestinations.add(POSTGRES_BASE_FOLDER_DATA); + } + public boolean isEnabled() { return enabled; } @@ -188,8 +213,57 @@ public void setForceCleanAfterwards(boolean forceCleanAfterwards) { this.forceCleanAfterwards = forceCleanAfterwards; } + public boolean isInMemory() { + return inMemory; + } + + public void setInMemory(boolean inMemory) { + this.inMemory = inMemory; + } + + public List getInMemoryMountDestinations() { + return inMemoryMountDestinations; + } + + public void setInMemoryMountDestinations(List inMemoryMountDestinations) { + this.inMemoryMountDestinations = inMemoryMountDestinations; + } + + public String getInMemoryMountDestinationCommand() { + return inMemoryMountDestinationCommand; + } + + public void setInMemoryMountDestinationCommand(String inMemoryMountDestinationCommand) { + this.inMemoryMountDestinationCommand = inMemoryMountDestinationCommand; + } + + public String getUseDockerCommand() { + if (useDockerCommand == null) { + getProperties(); + } + return useDockerCommand; + } + public Map getProperties() { - Map properties = new HashMap<>(); + return properties; + } + + public void init(String datasourceUrl) { + initPort(datasourceUrl); + properties = new HashMap<>(); + this.useDockerCommand = replaceInMemoryTemplate(properties, getDockerCommand()); + initProperties(); + } + + private void initPort(String datasourceUrl) { + if (getPort() != null) { + return; + } + // Scrape the port from the JDBC URL + setPort(determinePort(datasourceUrl != null ? datasourceUrl : DEFAULT_PORT)); + } + + private void initProperties() { properties.put("stdOutFilename", getStdOutFilename()); properties.put("stdErrFilename", getStdErrFilename()); properties.put("timeout", getTimeout().toString()); @@ -202,13 +276,50 @@ public Map getProperties() { properties.put("timesExpectedVerificationText", getTimesExpectedVerificationText().toString()); properties.put("afterVerificationWait", getAfterVerificationWait().toString()); properties.put("dockerCommand", getDockerCommand()); + properties.put("useDockerCommand", getUseDockerCommand()); properties.put("forceClean", Boolean.toString(isForceClean())); properties.put("forceCleanAfterwards", Boolean.toString(isForceCleanAfterwards())); properties.put("stopPortOccupyingContainer", Boolean.toString(isStopPortOccupyingContainer())); properties.put("afterVerificationWait", Boolean.toString(isForceClean())); properties.put("containerOccupyingPort", getContainerOccupyingPort()); + properties.put("inMemory", Boolean.toString(isInMemory())); properties.putAll(getCustomVariables()); - return properties; + } + + private String replaceInMemoryTemplate(Map properties, String dockerCommand) { + int templatePosition = dockerCommand.indexOf(IN_MEMORY_TEMPLATE); + if (templatePosition == -1) { + return dockerCommand; + } + List inMemoryCommands = new ArrayList<>(); + if (inMemory) { + Integer destinationNumber = 1; + for (String inMemoryMountDestination : getInMemoryMountDestinations()) { + inMemoryCommands.add(inMemoryMountDestinationCommand.replace( + "@N", + destinationNumber.toString())); + properties.put( + VAR_IN_MEMORY_MOUNT_DESTINATION + "_" + destinationNumber, + inMemoryMountDestination); + destinationNumber++; + } + } + return + dockerCommand.substring(0, templatePosition) + + StringUtils.join(inMemoryCommands, ' ') + + dockerCommand.substring(templatePosition + IN_MEMORY_TEMPLATE.length()); + } + + private Integer determinePort(String url) { + if (url == null || url.length() == 0) { + throw new ExceptionInInitializerError("spring.datasource.url is empty. No port could be derived."); + } + int lastColonPos = url.lastIndexOf(':'); + int slashAfterPortPos = url.indexOf('/', lastColonPos); + if (lastColonPos == -1 || slashAfterPortPos == -1 || slashAfterPortPos < lastColonPos + 2) { + throw new ExceptionInInitializerError("spring.datasource.url does not have port information: [" + url + "]. No port could be derived."); + } + return Integer.parseInt(url.substring(lastColonPos + 1, slashAfterPortPos)); } } diff --git a/src/main/java/nl/_42/boot/docker/postgres/DockerStartContainerCommand.java b/src/main/java/nl/_42/boot/docker/postgres/DockerStartContainerCommand.java index 6a2c364..767cd05 100644 --- a/src/main/java/nl/_42/boot/docker/postgres/DockerStartContainerCommand.java +++ b/src/main/java/nl/_42/boot/docker/postgres/DockerStartContainerCommand.java @@ -9,7 +9,7 @@ public class DockerStartContainerCommand extends DockerInfiniteProcessRunner { private static final Logger LOGGER = LoggerFactory.getLogger(DockerStartContainerCommand.class); public DockerStartContainerCommand(DockerPostgresProperties properties, boolean imageDownloaded) { - super(properties.getDockerCommand(), properties, imageDownloaded); + super(properties.getUseDockerCommand(), properties, imageDownloaded); if (!imageDownloaded) { LOGGER.info("| Process will download (no visual feedback, please be patient)..."); From d7379f32fca8642c4a6bc560874eb132ca510178 Mon Sep 17 00:00:00 2001 From: robert-bor Date: Thu, 25 Jan 2018 14:01:40 +0100 Subject: [PATCH 2/2] v0.7.1 --- CHANGELOG.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f76e9c3..5a1ca30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased -## [0.7.1] - 2018-01-24 +## [0.7.1] - 2018-01-25 ### Fixed - Issue [#29](https://github.com/42BV/spring-boot-docker-postgres/issues/29), **By default use in-memory destination mounts for Docker run**; by default the Docker run will make use of in-memory mounts for the application and data path in the standard Postgres Docker image. If in-memory must be disabled, it can be done in the properties. Custom paths can be passed as a list under the inMemoryMountDestinations list. diff --git a/pom.xml b/pom.xml index e1f1ce2..4e4c21d 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,7 @@ 4.0.0 spring-boot-docker-postgres - 0.7.1-SNAPSHOT + 0.7.1 jar nl.42