Skip to content

Commit d3f6854

Browse files
authored
Merge pull request #39 from jameshilliard/root-unshare
Allow running under root on Linux when unshare is available
2 parents 5917444 + 97f7211 commit d3f6854

File tree

5 files changed

+122
-50
lines changed

5 files changed

+122
-50
lines changed

.github/workflows/maven.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jobs:
66
runs-on: ubuntu-latest
77
strategy:
88
matrix:
9-
java: [8, 11, 13]
9+
java: [8, 11, 13, 14]
1010
steps:
1111
- name: Checkout project
1212
uses: actions/checkout@v1

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,17 +150,21 @@ Since `PostgreSQL 10.0`, there are additional artifacts with `alpine-lite` suffi
150150

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

153-
Try to remove `/tmp/embedded-pg/PG-XYZ` directory containing temporary binaries of the embedded postgres database. That should solve the problem.
153+
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.
154+
155+
If the error is not present, try to clean up the `/tmp/embedded-pg/PG-XYZ` directory containing temporary binaries of the embedded database.
154156

155157
### Running tests on Windows does not work
156158

157-
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).
159+
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).
160+
161+
### Running tests in Docker does not work
158162

159-
### Running tests inside Docker does not work
163+
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.
160164

161-
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.
165+
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.
162166

163-
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.
167+
Below are some examples of how to prepare a docker image running with a non-root user:
164168

165169
<details>
166170
<summary>Standard Dockerfile</summary>

pom.xml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,12 @@
109109
<dependency>
110110
<groupId>org.apache.commons</groupId>
111111
<artifactId>commons-lang3</artifactId>
112-
<version>3.6</version>
112+
<version>3.10</version>
113113
</dependency>
114114
<dependency>
115115
<groupId>org.apache.commons</groupId>
116116
<artifactId>commons-compress</artifactId>
117-
<version>1.19</version>
117+
<version>1.20</version>
118118
</dependency>
119119
<dependency>
120120
<groupId>org.tukaani</groupId>
@@ -124,29 +124,29 @@
124124
<dependency>
125125
<groupId>commons-io</groupId>
126126
<artifactId>commons-io</artifactId>
127-
<version>2.6</version>
127+
<version>2.7</version>
128128
</dependency>
129129
<dependency>
130130
<groupId>commons-codec</groupId>
131131
<artifactId>commons-codec</artifactId>
132-
<version>1.11</version>
132+
<version>1.14</version>
133133
</dependency>
134134
<dependency>
135135
<groupId>org.flywaydb</groupId>
136136
<artifactId>flyway-core</artifactId>
137-
<version>6.0.8</version>
137+
<version>6.5.1</version>
138138
<optional>true</optional>
139139
</dependency>
140140
<dependency>
141141
<groupId>org.liquibase</groupId>
142142
<artifactId>liquibase-core</artifactId>
143-
<version>3.6.3</version>
143+
<version>4.0.0</version>
144144
<optional>true</optional>
145145
</dependency>
146146
<dependency>
147147
<groupId>org.postgresql</groupId>
148148
<artifactId>postgresql</artifactId>
149-
<version>42.2.5</version>
149+
<version>42.2.14</version>
150150
</dependency>
151151
<dependency>
152152
<groupId>junit</groupId>
@@ -158,21 +158,21 @@
158158
<dependency>
159159
<groupId>org.junit.jupiter</groupId>
160160
<artifactId>junit-jupiter-api</artifactId>
161-
<version>5.3.2</version>
161+
<version>5.6.2</version>
162162
<scope>provided</scope>
163163
<optional>true</optional>
164164
</dependency>
165165

166166
<dependency>
167167
<groupId>org.slf4j</groupId>
168168
<artifactId>slf4j-simple</artifactId>
169-
<version>1.7.25</version>
169+
<version>1.7.30</version>
170170
<scope>test</scope>
171171
</dependency>
172172
<dependency>
173173
<groupId>org.mockito</groupId>
174174
<artifactId>mockito-core</artifactId>
175-
<version>2.13.0</version>
175+
<version>3.4.0</version>
176176
<scope>test</scope>
177177
</dependency>
178178
</dependencies>
@@ -181,7 +181,7 @@
181181
<plugins>
182182
<plugin>
183183
<artifactId>maven-pmd-plugin</artifactId>
184-
<version>3.8</version>
184+
<version>3.13.0</version>
185185
<executions>
186186
<execution>
187187
<phase>verify</phase>
@@ -194,13 +194,13 @@
194194
<dependency>
195195
<groupId>net.sourceforge.pmd</groupId>
196196
<artifactId>pmd-core</artifactId>
197-
<version>5.6.1</version>
197+
<version>6.25.0</version>
198198
<scope>compile</scope>
199199
</dependency>
200200
<dependency>
201201
<groupId>net.sourceforge.pmd</groupId>
202202
<artifactId>pmd-java</artifactId>
203-
<version>5.6.1</version>
203+
<version>6.25.0</version>
204204
<scope>compile</scope>
205205
</dependency>
206206
</dependencies>
@@ -216,7 +216,7 @@
216216
<plugin>
217217
<groupId>org.apache.maven.plugins</groupId>
218218
<artifactId>maven-source-plugin</artifactId>
219-
<version>3.0.1</version>
219+
<version>3.2.1</version>
220220
<executions>
221221
<execution>
222222
<id>attach-sources</id>
@@ -229,7 +229,7 @@
229229
<plugin>
230230
<groupId>org.apache.maven.plugins</groupId>
231231
<artifactId>maven-javadoc-plugin</artifactId>
232-
<version>2.10.4</version>
232+
<version>3.2.0</version>
233233
<executions>
234234
<execution>
235235
<id>attach-javadocs</id>
@@ -263,7 +263,7 @@
263263
<plugin>
264264
<groupId>org.apache.maven.plugins</groupId>
265265
<artifactId>maven-gpg-plugin</artifactId>
266-
<version>1.5</version>
266+
<version>1.6</version>
267267
<executions>
268268
<execution>
269269
<id>sign-artifacts</id>

src/main/java/io/zonky/test/db/postgres/embedded/EmbeddedPostgres.java

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
*/
1414
package io.zonky.test.db.postgres.embedded;
1515

16-
1716
import java.io.ByteArrayInputStream;
1817
import java.io.Closeable;
1918
import java.io.File;
@@ -55,7 +54,6 @@
5554
import java.util.concurrent.atomic.AtomicBoolean;
5655
import java.util.concurrent.locks.Lock;
5756
import java.util.concurrent.locks.ReentrantLock;
58-
import java.util.stream.Collectors;
5957

6058
import javax.sql.DataSource;
6159

@@ -72,6 +70,8 @@
7270
import org.slf4j.LoggerFactory;
7371
import org.tukaani.xz.XZInputStream;
7472

73+
import io.zonky.test.db.postgres.util.LinuxUtils;
74+
7575
import static java.nio.file.StandardOpenOption.CREATE;
7676
import static java.nio.file.StandardOpenOption.WRITE;
7777
import static java.util.Collections.unmodifiableMap;
@@ -241,12 +241,12 @@ private void initdb()
241241
{
242242
final StopWatch watch = new StopWatch();
243243
watch.start();
244-
List<String> command = new ArrayList<>();
245-
command.addAll(Arrays.asList(
246-
pgBin("initdb"), "-A", "trust", "-U", PG_SUPERUSER,
244+
List<String> args = new ArrayList<>();
245+
args.addAll(Arrays.asList(
246+
"-A", "trust", "-U", PG_SUPERUSER,
247247
"-D", dataDirectory.getPath(), "-E", "UTF-8"));
248-
command.addAll(createLocaleOptions());
249-
system(command.toArray(new String[command.size()]));
248+
args.addAll(createLocaleOptions());
249+
system(INIT_DB, args);
250250
LOG.info("{} initdb completed in {}", instanceId, watch);
251251
}
252252

@@ -259,23 +259,19 @@ private void startPostmaster() throws IOException
259259
}
260260

261261
final List<String> args = new ArrayList<>();
262-
args.addAll(Arrays.asList(
263-
pgBin("pg_ctl"),
264-
"-D", dataDirectory.getPath(),
265-
"-o", createInitOptions().stream().collect(Collectors.joining(" ")),
266-
"-w",
267-
"start"
268-
));
262+
args.addAll(Arrays.asList("-D", dataDirectory.getPath()));
263+
args.addAll(createInitOptions());
269264

270-
final ProcessBuilder builder = new ProcessBuilder(args);
265+
final ProcessBuilder builder = new ProcessBuilder();
266+
POSTGRES.applyTo(builder, args);
271267

272268
builder.redirectErrorStream(true);
273269
builder.redirectError(errorRedirector);
274270
builder.redirectOutput(outputRedirector);
275271
final Process postmaster = builder.start();
276272

277273
if (outputRedirector.type() == ProcessBuilder.Redirect.Type.PIPE) {
278-
ProcessOutputLogger.logOutput(LOG, postmaster, "pg_ctl");
274+
ProcessOutputLogger.logOutput(LOG, postmaster, POSTGRES.processName());
279275
}
280276

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

415411
private void pgCtl(File dir, String action)
416412
{
417-
system(pgBin("pg_ctl"), "-D", dir.getPath(), action, "-m", PG_STOP_MODE, "-t", PG_STOP_WAIT_S, "-w");
413+
final List<String> args = new ArrayList<>();
414+
args.addAll(Arrays.asList(
415+
"-D", dir.getPath(), action,
416+
"-m", PG_STOP_MODE, "-t",
417+
PG_STOP_WAIT_S, "-w"
418+
));
419+
system(PG_CTL, args);
418420
}
419421

420422
private void cleanOldDataDirectories(File parentDirectory)
@@ -461,12 +463,6 @@ private void cleanOldDataDirectories(File parentDirectory)
461463
}
462464
}
463465

464-
private String pgBin(String binaryName)
465-
{
466-
final String extension = SystemUtils.IS_OS_WINDOWS ? ".exe" : "";
467-
return new File(pgDir, "bin/" + binaryName + extension).getPath();
468-
}
469-
470466
private static File getWorkingDirectory()
471467
{
472468
final File tempWorkingDirectory = new File(System.getProperty("java.io.tmpdir"), "embedded-pg");
@@ -614,21 +610,23 @@ public int hashCode() {
614610
}
615611
}
616612

617-
private void system(String... command)
613+
private void system(Command command, List<String> args)
618614
{
619615
try {
620-
final ProcessBuilder builder = new ProcessBuilder(command);
616+
final ProcessBuilder builder = new ProcessBuilder();
617+
618+
command.applyTo(builder, args);
621619
builder.redirectErrorStream(true);
622620
builder.redirectError(errorRedirector);
623621
builder.redirectOutput(outputRedirector);
622+
624623
final Process process = builder.start();
625624

626625
if (outputRedirector.type() == ProcessBuilder.Redirect.Type.PIPE) {
627-
String processName = command[0].replaceAll("^.*[\\\\/](\\w+)(\\.exe)?$", "$1");
628-
ProcessOutputLogger.logOutput(LOG, process, processName);
626+
ProcessOutputLogger.logOutput(LOG, process, command.processName());
629627
}
630628
if (0 != process.waitFor()) {
631-
throw new IllegalStateException(String.format("Process %s failed", Arrays.asList(command)));
629+
throw new IllegalStateException(String.format("Process %s failed", builder.command()));
632630
}
633631
} catch (final RuntimeException e) { // NOPMD
634632
throw e;
@@ -841,4 +839,35 @@ public String toString()
841839
{
842840
return "EmbeddedPG-" + instanceId;
843841
}
842+
843+
private final Command INIT_DB = new Command("initdb");
844+
private final Command POSTGRES = new Command("postgres");
845+
private final Command PG_CTL = new Command("pg_ctl");
846+
847+
private class Command {
848+
849+
private final String commandName;
850+
851+
private Command(String commandName) {
852+
this.commandName = commandName;
853+
}
854+
855+
public String processName() {
856+
return commandName;
857+
}
858+
859+
public void applyTo(ProcessBuilder builder, List<String> arguments) {
860+
List<String> command = new ArrayList<>();
861+
862+
if (LinuxUtils.isUnshareAvailable()) {
863+
command.addAll(Arrays.asList("unshare", "-U"));
864+
}
865+
866+
String extension = SystemUtils.IS_OS_WINDOWS ? ".exe" : "";
867+
command.add(new File(pgDir, "bin/" + commandName + extension).getPath());
868+
command.addAll(arguments);
869+
870+
builder.command(command);
871+
}
872+
}
844873
}

src/main/java/io/zonky/test/db/postgres/util/LinuxUtils.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.io.IOException;
2323
import java.io.InputStream;
2424
import java.io.InputStreamReader;
25+
import java.lang.reflect.Method;
2526
import java.nio.file.Files;
2627
import java.nio.file.Path;
2728

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

3536
private static final String DISTRIBUTION_NAME = resolveDistributionName();
37+
private static final boolean UNSHARE_AVAILABLE = unshareAvailable();
3638

3739
private LinuxUtils() {}
3840

3941
public static String getDistributionName() {
4042
return DISTRIBUTION_NAME;
4143
}
4244

45+
public static boolean isUnshareAvailable() {
46+
return UNSHARE_AVAILABLE;
47+
}
48+
4349
private static String resolveDistributionName() {
4450
if (!SystemUtils.IS_OS_LINUX) {
4551
return null;
@@ -85,4 +91,37 @@ private static String resolveDistributionName() {
8591
return null;
8692
}
8793
}
94+
95+
private static boolean unshareAvailable() {
96+
if (!SystemUtils.IS_OS_LINUX) {
97+
return false;
98+
}
99+
100+
try {
101+
Class<?> clazz = Class.forName("com.sun.security.auth.module.UnixSystem");
102+
Object instance = clazz.getDeclaredConstructor().newInstance();
103+
Method method = clazz.getDeclaredMethod("getUid");
104+
int uid = ((Number) method.invoke(instance)).intValue();
105+
106+
if (uid != 0) {
107+
return false;
108+
}
109+
110+
ProcessBuilder builder = new ProcessBuilder();
111+
builder.command("unshare", "-U", "id", "-u");
112+
113+
Process process = builder.start();
114+
process.waitFor();
115+
116+
try (BufferedReader outputReader = new BufferedReader(new InputStreamReader(process.getInputStream(), UTF_8))) {
117+
if (process.exitValue() == 0 && !"0".equals(outputReader.readLine())) {
118+
return true;
119+
}
120+
}
121+
122+
return false;
123+
} catch (Exception e) {
124+
return false;
125+
}
126+
}
88127
}

0 commit comments

Comments
 (0)