Skip to content

Commit 8946aab

Browse files
committed
Initial commit
0 parents  commit 8946aab

16 files changed

+426
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/.idea/
2+
/target/
3+
/dependency-reduce-pom.xml

LICENSE.txt

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Geoff Bourne
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This tool does the complicated bits for [the itzg/minecraft-server Docker image](https://github.com/itzg/docker-minecraft-server).

pom.xml

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>com.github.itzg</groupId>
8+
<artifactId>mc-image-helper</artifactId>
9+
<version>1.0-SNAPSHOT</version>
10+
11+
<properties>
12+
<maven.compiler.source>8</maven.compiler.source>
13+
<maven.compiler.target>8</maven.compiler.target>
14+
<picocli.version>4.6.1</picocli.version>
15+
<lombok.version>1.18.20</lombok.version>
16+
<logback.version>1.2.5</logback.version>
17+
</properties>
18+
19+
<dependencies>
20+
<dependency>
21+
<groupId>info.picocli</groupId>
22+
<artifactId>picocli</artifactId>
23+
<version>${picocli.version}</version>
24+
<scope>compile</scope>
25+
</dependency>
26+
<dependency>
27+
<groupId>info.picocli</groupId>
28+
<artifactId>picocli-codegen</artifactId>
29+
<version>${picocli.version}</version>
30+
<scope>provided</scope>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.projectlombok</groupId>
34+
<artifactId>lombok</artifactId>
35+
<version>${lombok.version}</version>
36+
<scope>provided</scope>
37+
</dependency>
38+
<dependency>
39+
<groupId>ch.qos.logback</groupId>
40+
<artifactId>logback-core</artifactId>
41+
<version>${logback.version}</version>
42+
<scope>compile</scope>
43+
</dependency>
44+
<dependency>
45+
<groupId>ch.qos.logback</groupId>
46+
<artifactId>logback-classic</artifactId>
47+
<version>${logback.version}</version>
48+
<scope>compile</scope>
49+
</dependency>
50+
</dependencies>
51+
52+
<build>
53+
<plugins>
54+
<plugin>
55+
<groupId>org.apache.maven.plugins</groupId>
56+
<artifactId>maven-shade-plugin</artifactId>
57+
<version>3.2.4</version>
58+
<executions>
59+
<execution>
60+
<phase>package</phase>
61+
<goals>
62+
<goal>shade</goal>
63+
</goals>
64+
<configuration>
65+
<transformers>
66+
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
67+
<mainClass>me.itzg.helpers.McImageHelper</mainClass>
68+
</transformer>
69+
</transformers>
70+
</configuration>
71+
</execution>
72+
</executions>
73+
</plugin>
74+
<plugin>
75+
<groupId>org.apache.maven.plugins</groupId>
76+
<artifactId>maven-surefire-plugin</artifactId>
77+
<version>3.0.0-M5</version>
78+
<configuration>
79+
<environmentVariables>
80+
<CFG_TEST1>value1</CFG_TEST1>
81+
<CFG_TEST2>value2</CFG_TEST2>
82+
<CFG_TEST3_FILE>src/test/resources/test3.txt</CFG_TEST3_FILE>
83+
<IGNORE_TEST1>ignored1</IGNORE_TEST1>
84+
</environmentVariables>
85+
</configuration>
86+
</plugin>
87+
</plugins>
88+
</build>
89+
90+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package me.itzg.helpers;
2+
3+
import lombok.ToString;
4+
import lombok.extern.slf4j.Slf4j;
5+
import picocli.CommandLine.ArgGroup;
6+
import picocli.CommandLine.Command;
7+
import picocli.CommandLine.Option;
8+
import picocli.CommandLine.Parameters;
9+
10+
import java.io.IOException;
11+
import java.nio.file.Files;
12+
import java.nio.file.Path;
13+
import java.util.concurrent.Callable;
14+
15+
@Command(name = "copy-and-interpolate", mixinStandardHelpOptions = true,
16+
description = "Copies the contents of one directory to another with conditional variable interpolation.")
17+
@ToString
18+
@Slf4j
19+
public class CopyAndInterpolate implements Callable<Integer> {
20+
@Option(names = "--skip-newer-in-destination",
21+
// this is same as rsync's --update option
22+
description = "Skip any files that exist in the destination and have a newer modification time than the source.")
23+
boolean skipNewerInDestination;
24+
25+
@ArgGroup(multiplicity = "1", exclusive = false)
26+
ReplaceEnvOptions replaceEnv = new ReplaceEnvOptions();
27+
28+
@Parameters(index = "0", description = "source directory")
29+
Path src;
30+
31+
@Parameters(index = "1", description = "destination directory")
32+
Path dest;
33+
34+
@Override
35+
public Integer call() throws Exception {
36+
log.info("CopyAndInterpolate : {}", this);
37+
38+
try {
39+
Files.walkFileTree(src, new InterpolatingFileVisitor(src, dest, skipNewerInDestination, replaceEnv,
40+
new Interpolator(new StandardEnvironmentVariablesProvider(), replaceEnv.prefix)));
41+
} catch (IOException e) {
42+
log.error("Failed to copy and interpolate {} into {} : {}", src, dest, e.getMessage());
43+
log.debug("Details", e);
44+
return 1;
45+
}
46+
47+
return 0;
48+
}
49+
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package me.itzg.helpers;
2+
3+
public interface EnvironmentVariablesProvider {
4+
String get(String name);
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package me.itzg.helpers;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
5+
import java.io.BufferedReader;
6+
import java.io.BufferedWriter;
7+
import java.io.IOException;
8+
import java.nio.file.*;
9+
import java.nio.file.attribute.BasicFileAttributes;
10+
11+
import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
12+
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
13+
14+
@Slf4j
15+
class InterpolatingFileVisitor implements FileVisitor<Path> {
16+
private final Path src;
17+
private final Path dest;
18+
private final boolean skipNewerInDestination;
19+
private final ReplaceEnvOptions replaceEnv;
20+
private final Interpolator interpolator;
21+
22+
public InterpolatingFileVisitor(Path src, Path dest, boolean skipNewerInDestination, ReplaceEnvOptions replaceEnv, Interpolator interpolator) {
23+
this.src = src;
24+
this.dest = dest;
25+
this.skipNewerInDestination = skipNewerInDestination;
26+
this.replaceEnv = replaceEnv;
27+
this.interpolator = interpolator;
28+
}
29+
30+
@Override
31+
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
32+
log.trace("pre visit dir={}", dir);
33+
final Path destPath = dest.resolve(src.relativize(dir));
34+
35+
log.debug("ensuring destinationDirectory={}", destPath);
36+
Files.createDirectories(destPath);
37+
38+
return FileVisitResult.CONTINUE;
39+
}
40+
41+
@Override
42+
public FileVisitResult visitFile(Path srcFile, BasicFileAttributes attrs) throws IOException {
43+
log.trace("visit file={}", srcFile);
44+
final Path destFile = dest.resolve(src.relativize(srcFile));
45+
46+
if (processFile(srcFile, destFile)) {
47+
if (replaceEnv.matches(destFile)) {
48+
log.debug("interpolate from={} to={}", srcFile, destFile);
49+
50+
try (BufferedReader srcReader = Files.newBufferedReader(srcFile)) {
51+
try (BufferedWriter destWriter = Files.newBufferedWriter(destFile, StandardOpenOption.CREATE)) {
52+
interpolator.interpolate(srcReader, destWriter);
53+
}
54+
}
55+
} else {
56+
log.debug("copy from={} to={}", srcFile, destFile);
57+
58+
Files.copy(srcFile, destFile, COPY_ATTRIBUTES, REPLACE_EXISTING);
59+
}
60+
}
61+
else {
62+
log.debug("skipping destFile={}", destFile);
63+
}
64+
65+
return FileVisitResult.CONTINUE;
66+
}
67+
68+
private boolean processFile(Path srcFile, Path destFile) throws IOException {
69+
if (Files.notExists(destFile)) {
70+
return true;
71+
}
72+
73+
if (skipNewerInDestination) {
74+
if (Files.getLastModifiedTime(destFile).compareTo(Files.getLastModifiedTime(srcFile)) > 0) {
75+
return false;
76+
}
77+
}
78+
79+
return Files.size(srcFile) != Files.size(destFile) ||
80+
Files.getLastModifiedTime(srcFile) != Files.getLastModifiedTime(destFile);
81+
}
82+
83+
@Override
84+
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
85+
log.warn("Failed to access {} due to {}", file, exc.getMessage());
86+
log.debug("Details", exc);
87+
return FileVisitResult.CONTINUE;
88+
}
89+
90+
@Override
91+
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
92+
return FileVisitResult.CONTINUE;
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package me.itzg.helpers;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
5+
import java.io.BufferedReader;
6+
import java.io.BufferedWriter;
7+
import java.io.IOException;
8+
import java.nio.charset.StandardCharsets;
9+
import java.nio.file.Files;
10+
import java.nio.file.Paths;
11+
import java.util.Map;
12+
import java.util.regex.Matcher;
13+
import java.util.regex.Pattern;
14+
import java.util.stream.Collectors;
15+
16+
@Slf4j
17+
public class Interpolator {
18+
19+
private final static Pattern VAR_PATTERN = Pattern.compile("\\$\\{([^}]+)}");
20+
private static final String FILE_SUFFIX = "_FILE";
21+
22+
private final EnvironmentVariablesProvider environmentVariablesProvider;
23+
private final String envPrefix;
24+
25+
public Interpolator(EnvironmentVariablesProvider environmentVariablesProvider, String envPrefix) {
26+
this.environmentVariablesProvider = environmentVariablesProvider;
27+
this.envPrefix = envPrefix;
28+
}
29+
30+
public void interpolate(BufferedReader reader, BufferedWriter writer) throws IOException {
31+
String line;
32+
while ((line = reader.readLine()) != null) {
33+
final Matcher matcher = VAR_PATTERN.matcher(line);
34+
final StringBuffer sb = new StringBuffer();
35+
36+
while (matcher.find()) {
37+
final String varName = matcher.group(1);
38+
39+
String value = null;
40+
if (varName.startsWith(envPrefix)) {
41+
value = environmentVariablesProvider.get(varName+FILE_SUFFIX);
42+
if (value != null) {
43+
value = readValueFromFile(value);
44+
}
45+
else {
46+
value = environmentVariablesProvider.get(varName);
47+
}
48+
}
49+
50+
log.debug("Processing varName={} with value={}", varName, value);
51+
matcher.appendReplacement(sb, value != null ? value : Matcher.quoteReplacement(matcher.group()));
52+
}
53+
matcher.appendTail(sb);
54+
55+
writer.write(sb.toString());
56+
writer.write("\n");
57+
}
58+
}
59+
60+
private String readValueFromFile(String filename) throws IOException {
61+
final String content = new String(Files.readAllBytes(Paths.get(filename)), StandardCharsets.UTF_8);
62+
return content.trim();
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package me.itzg.helpers;
2+
3+
import ch.qos.logback.classic.Level;
4+
import ch.qos.logback.classic.Logger;
5+
import org.slf4j.LoggerFactory;
6+
import picocli.CommandLine;
7+
import picocli.CommandLine.Command;
8+
import picocli.CommandLine.Option;
9+
10+
@Command(name = "mc-image-helper",
11+
subcommands = {
12+
CopyAndInterpolate.class
13+
}
14+
)
15+
public class McImageHelper {
16+
@Option(names = "--debug")
17+
void setDebug(boolean value) {
18+
((Logger) LoggerFactory.getLogger("me.itzg.helpers")).setLevel(value ? Level.DEBUG : Level.INFO);
19+
}
20+
21+
public static void main(String[] args) {
22+
System.exit(
23+
new CommandLine(new McImageHelper()).execute(args)
24+
);
25+
}
26+
27+
}

0 commit comments

Comments
 (0)