Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow running under root on Linux when unshare is available #39

Merged
merged 2 commits into from
Aug 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
java: [8, 11, 13]
java: [8, 11, 13, 14]
steps:
- name: Checkout project
uses: actions/checkout@v1
Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,17 +150,21 @@ Since `PostgreSQL 10.0`, there are additional artifacts with `alpine-lite` suffi

### Process [/tmp/embedded-pg/PG-XYZ/bin/initdb, ...] failed

Try to remove `/tmp/embedded-pg/PG-XYZ` directory containing temporary binaries of the embedded postgres database. That should solve the problem.
Check the console output for an `initdb: cannot be run as root` message. If the error is present, try to upgrade to a newer version of the library (1.2.8+), or ensure the build process to be running as a non-root user.

If the error is not present, try to clean up the `/tmp/embedded-pg/PG-XYZ` directory containing temporary binaries of the embedded database.

### Running tests on Windows does not work

You probably need to install the [Microsoft Visual C++ 2013 Redistributable Package](https://support.microsoft.com/en-us/help/3179560/update-for-visual-c-2013-and-visual-c-redistributable-package). The version 2013 is important, installation of other versions will not help. More detailed is the problem discussed [here](https://github.com/opentable/otj-pg-embedded/issues/65).
You probably need to install [Microsoft Visual C++ 2013 Redistributable Package](https://support.microsoft.com/en-us/help/3179560/update-for-visual-c-2013-and-visual-c-redistributable-package). The version 2013 is important, installation of other versions will not help. More detailed is the problem discussed [here](https://github.com/opentable/otj-pg-embedded/issues/65).

### Running tests in Docker does not work

### Running tests inside Docker does not work
Running builds inside a Docker container is fully supported, including Alpine Linux. However, PostgreSQL has a restriction the database process must run under a non-root user. Otherwise, the database does not start and fails with an error.

Running build inside Docker is fully supported, including Alpine Linux. But you must keep in mind that the **PostgreSQL database must be run under a non-root user**. Otherwise, the database does not start and fails with an error.
So be sure to use a docker image that uses a non-root user. Or, since version `1.2.8` you can run the docker container with `--privileged` option, which allows taking advantage of `unshare` command to run the database process in a separate namespace.

So be sure to use a docker image that uses a non-root user, or you can use any of the following Dockerfiles to prepare your own image.
Below are some examples of how to prepare a docker image running with a non-root user:

<details>
<summary>Standard Dockerfile</summary>
Expand Down
32 changes: 16 additions & 16 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
<version>3.10</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.19</version>
<version>1.20</version>
</dependency>
<dependency>
<groupId>org.tukaani</groupId>
Expand All @@ -124,29 +124,29 @@
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
<version>2.7</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
<version>1.14</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>6.0.8</version>
<version>6.5.1</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>3.6.3</version>
<version>4.0.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.5</version>
<version>42.2.14</version>
</dependency>
<dependency>
<groupId>junit</groupId>
Expand All @@ -158,21 +158,21 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.2</version>
<version>5.6.2</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
<version>1.7.30</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.13.0</version>
<version>3.4.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand All @@ -181,7 +181,7 @@
<plugins>
<plugin>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.8</version>
<version>3.13.0</version>
<executions>
<execution>
<phase>verify</phase>
Expand All @@ -194,13 +194,13 @@
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-core</artifactId>
<version>5.6.1</version>
<version>6.25.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-java</artifactId>
<version>5.6.1</version>
<version>6.25.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
Expand All @@ -216,7 +216,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
Expand All @@ -229,7 +229,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.4</version>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
Expand Down Expand Up @@ -263,7 +263,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.5</version>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
*/
package io.zonky.test.db.postgres.embedded;


import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
Expand Down Expand Up @@ -55,7 +54,6 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

import javax.sql.DataSource;

Expand All @@ -72,6 +70,8 @@
import org.slf4j.LoggerFactory;
import org.tukaani.xz.XZInputStream;

import io.zonky.test.db.postgres.util.LinuxUtils;

import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.WRITE;
import static java.util.Collections.unmodifiableMap;
Expand Down Expand Up @@ -241,12 +241,12 @@ private void initdb()
{
final StopWatch watch = new StopWatch();
watch.start();
List<String> command = new ArrayList<>();
command.addAll(Arrays.asList(
pgBin("initdb"), "-A", "trust", "-U", PG_SUPERUSER,
List<String> args = new ArrayList<>();
args.addAll(Arrays.asList(
"-A", "trust", "-U", PG_SUPERUSER,
"-D", dataDirectory.getPath(), "-E", "UTF-8"));
command.addAll(createLocaleOptions());
system(command.toArray(new String[command.size()]));
args.addAll(createLocaleOptions());
system(INIT_DB, args);
LOG.info("{} initdb completed in {}", instanceId, watch);
}

Expand All @@ -259,23 +259,19 @@ private void startPostmaster() throws IOException
}

final List<String> args = new ArrayList<>();
args.addAll(Arrays.asList(
pgBin("pg_ctl"),
"-D", dataDirectory.getPath(),
"-o", createInitOptions().stream().collect(Collectors.joining(" ")),
"-w",
"start"
));
args.addAll(Arrays.asList("-D", dataDirectory.getPath()));
args.addAll(createInitOptions());

final ProcessBuilder builder = new ProcessBuilder(args);
final ProcessBuilder builder = new ProcessBuilder();
POSTGRES.applyTo(builder, args);

builder.redirectErrorStream(true);
builder.redirectError(errorRedirector);
builder.redirectOutput(outputRedirector);
final Process postmaster = builder.start();

if (outputRedirector.type() == ProcessBuilder.Redirect.Type.PIPE) {
ProcessOutputLogger.logOutput(LOG, postmaster, "pg_ctl");
ProcessOutputLogger.logOutput(LOG, postmaster, POSTGRES.processName());
}

LOG.info("{} postmaster started as {} on port {}. Waiting up to {} for server startup to finish.", instanceId, postmaster.toString(), port, pgStartupWait);
Expand Down Expand Up @@ -414,7 +410,13 @@ public void close() throws IOException

private void pgCtl(File dir, String action)
{
system(pgBin("pg_ctl"), "-D", dir.getPath(), action, "-m", PG_STOP_MODE, "-t", PG_STOP_WAIT_S, "-w");
final List<String> args = new ArrayList<>();
args.addAll(Arrays.asList(
"-D", dir.getPath(), action,
"-m", PG_STOP_MODE, "-t",
PG_STOP_WAIT_S, "-w"
));
system(PG_CTL, args);
}

private void cleanOldDataDirectories(File parentDirectory)
Expand Down Expand Up @@ -461,12 +463,6 @@ private void cleanOldDataDirectories(File parentDirectory)
}
}

private String pgBin(String binaryName)
{
final String extension = SystemUtils.IS_OS_WINDOWS ? ".exe" : "";
return new File(pgDir, "bin/" + binaryName + extension).getPath();
}

private static File getWorkingDirectory()
{
final File tempWorkingDirectory = new File(System.getProperty("java.io.tmpdir"), "embedded-pg");
Expand Down Expand Up @@ -614,21 +610,23 @@ public int hashCode() {
}
}

private void system(String... command)
private void system(Command command, List<String> args)
{
try {
final ProcessBuilder builder = new ProcessBuilder(command);
final ProcessBuilder builder = new ProcessBuilder();

command.applyTo(builder, args);
builder.redirectErrorStream(true);
builder.redirectError(errorRedirector);
builder.redirectOutput(outputRedirector);

final Process process = builder.start();

if (outputRedirector.type() == ProcessBuilder.Redirect.Type.PIPE) {
String processName = command[0].replaceAll("^.*[\\\\/](\\w+)(\\.exe)?$", "$1");
ProcessOutputLogger.logOutput(LOG, process, processName);
ProcessOutputLogger.logOutput(LOG, process, command.processName());
}
if (0 != process.waitFor()) {
throw new IllegalStateException(String.format("Process %s failed", Arrays.asList(command)));
throw new IllegalStateException(String.format("Process %s failed", builder.command()));
}
} catch (final RuntimeException e) { // NOPMD
throw e;
Expand Down Expand Up @@ -841,4 +839,35 @@ public String toString()
{
return "EmbeddedPG-" + instanceId;
}

private final Command INIT_DB = new Command("initdb");
private final Command POSTGRES = new Command("postgres");
private final Command PG_CTL = new Command("pg_ctl");

private class Command {

private final String commandName;

private Command(String commandName) {
this.commandName = commandName;
}

public String processName() {
return commandName;
}

public void applyTo(ProcessBuilder builder, List<String> arguments) {
List<String> command = new ArrayList<>();

if (LinuxUtils.isUnshareAvailable()) {
command.addAll(Arrays.asList("unshare", "-U"));
}

String extension = SystemUtils.IS_OS_WINDOWS ? ".exe" : "";
command.add(new File(pgDir, "bin/" + commandName + extension).getPath());
command.addAll(arguments);

builder.command(command);
}
}
}
39 changes: 39 additions & 0 deletions src/main/java/io/zonky/test/db/postgres/util/LinuxUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;

Expand All @@ -33,13 +34,18 @@ public final class LinuxUtils {
private static final Logger logger = LoggerFactory.getLogger(LinuxUtils.class);

private static final String DISTRIBUTION_NAME = resolveDistributionName();
private static final boolean UNSHARE_AVAILABLE = unshareAvailable();

private LinuxUtils() {}

public static String getDistributionName() {
return DISTRIBUTION_NAME;
}

public static boolean isUnshareAvailable() {
return UNSHARE_AVAILABLE;
}

private static String resolveDistributionName() {
if (!SystemUtils.IS_OS_LINUX) {
return null;
Expand Down Expand Up @@ -85,4 +91,37 @@ private static String resolveDistributionName() {
return null;
}
}

private static boolean unshareAvailable() {
if (!SystemUtils.IS_OS_LINUX) {
return false;
}

try {
Class<?> clazz = Class.forName("com.sun.security.auth.module.UnixSystem");
Object instance = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getDeclaredMethod("getUid");
int uid = ((Number) method.invoke(instance)).intValue();

if (uid != 0) {
return false;
}

ProcessBuilder builder = new ProcessBuilder();
builder.command("unshare", "-U", "id", "-u");

Process process = builder.start();
process.waitFor();

try (BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream(), UTF_8))) {
if (process.exitValue() == 0 && !"0".equals(outputReader.readLine())) {
return true;
}
}

return false;
} catch (Exception e) {
return false;
}
}
}