From 122b072dd893068b37d31c6f5a0bb67e6d07d9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Ho=C3=9F?= Date: Tue, 22 Sep 2020 15:16:19 +0200 Subject: [PATCH] WIP #85 powershell support --- pom.xml | 1 + .../wtf/metio/ilo/{utils => os}/Bash.java | 47 ++++++------ src/main/java/wtf/metio/ilo/os/Cmd.java | 27 +++++++ .../java/wtf/metio/ilo/os/NoOpExpansion.java | 22 ++++++ src/main/java/wtf/metio/ilo/os/OS.java | 74 +++++++++++++++++++ .../wtf/metio/ilo/os/ParameterExpansion.java | 16 ++++ .../java/wtf/metio/ilo/os/PowerShell.java | 27 +++++++ .../ilo/tools/DockerComposePodmanCompose.java | 30 ++++---- .../wtf/metio/ilo/tools/DockerPodman.java | 40 +++++----- .../wtf/metio/ilo/{utils => os}/BashTest.java | 53 +++---------- src/test/java/wtf/metio/ilo/os/OSTest.java | 32 ++++++++ 11 files changed, 266 insertions(+), 103 deletions(-) rename src/main/java/wtf/metio/ilo/{utils => os}/Bash.java (68%) create mode 100644 src/main/java/wtf/metio/ilo/os/Cmd.java create mode 100644 src/main/java/wtf/metio/ilo/os/NoOpExpansion.java create mode 100644 src/main/java/wtf/metio/ilo/os/OS.java create mode 100644 src/main/java/wtf/metio/ilo/os/ParameterExpansion.java create mode 100644 src/main/java/wtf/metio/ilo/os/PowerShell.java rename src/test/java/wtf/metio/ilo/{utils => os}/BashTest.java (63%) create mode 100644 src/test/java/wtf/metio/ilo/os/OSTest.java diff --git a/pom.xml b/pom.xml index f191ddd68..874453d6c 100644 --- a/pom.xml +++ b/pom.xml @@ -184,6 +184,7 @@ --add-opens wtf.metio.ilo/wtf.metio.ilo.devcontainer=ALL-UNNAMED --add-opens wtf.metio.ilo/wtf.metio.ilo.errors=ALL-UNNAMED --add-opens wtf.metio.ilo/wtf.metio.ilo.model=ALL-UNNAMED + --add-opens wtf.metio.ilo/wtf.metio.ilo.os=ALL-UNNAMED --add-opens wtf.metio.ilo/wtf.metio.ilo.shell=ALL-UNNAMED --add-opens wtf.metio.ilo/wtf.metio.ilo.test=ALL-UNNAMED --add-opens wtf.metio.ilo/wtf.metio.ilo.tools=ALL-UNNAMED diff --git a/src/main/java/wtf/metio/ilo/utils/Bash.java b/src/main/java/wtf/metio/ilo/os/Bash.java similarity index 68% rename from src/main/java/wtf/metio/ilo/utils/Bash.java rename to src/main/java/wtf/metio/ilo/os/Bash.java index f0513f8c9..bc7f68d61 100644 --- a/src/main/java/wtf/metio/ilo/utils/Bash.java +++ b/src/main/java/wtf/metio/ilo/os/Bash.java @@ -5,13 +5,10 @@ * in the LICENSE file. */ -package wtf.metio.ilo.utils; +package wtf.metio.ilo.os; import wtf.metio.ilo.cli.Executables; -import wtf.metio.ilo.errors.RuntimeIOException; -import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Optional; @@ -22,11 +19,14 @@ import static wtf.metio.ilo.utils.Streams.filter; import static wtf.metio.ilo.utils.Streams.fromList; -public final class Bash { +/** + * Support for GNU Bash + */ +final class Bash implements ParameterExpansion { - private static final Pattern BASH_NEW_STYLE = Pattern.compile("\\$\\((?.+)\\)"); - private static final Pattern BASH_OLD_STYLE = Pattern.compile("`(?.+)`"); - public static final String EXPRESSION = "expression"; + private static final Pattern BASH_COMMAND_NEW_STYLE = Pattern.compile("\\$\\((?.+)\\)"); + private static final Pattern BASH_COMMAND_OLD_STYLE = Pattern.compile("`(?.+)`"); + private static final String EXPRESSION = "expression"; public static List expand(final List values) { return filter(fromList(values)) @@ -55,10 +55,18 @@ public static String bashExpansion(final String value) { .orElse(value); } + @Override + public String substituteCommands(final String value) { + return Executables.of("bash") + .map(Path::toAbsolutePath) + .map(bash -> substituteCommands(bash, value)) + .orElse(value); + } + // https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html private static String substituteCommands(final Path bash, final String value) { - final var newStyle = BASH_NEW_STYLE.matcher(value); - final var oldStyle = BASH_OLD_STYLE.matcher(value); + final var newStyle = BASH_COMMAND_NEW_STYLE.matcher(value); + final var oldStyle = BASH_COMMAND_OLD_STYLE.matcher(value); var current = value; while (newStyle.find()) { current = substitute(bash, newStyle, current); @@ -77,21 +85,10 @@ private static String substitute(final Path bash, final Matcher matcher, final S return new StringBuilder(current).replace(start, end, replacement).toString(); } - public static Path passwdFile(final String runAs) { - try { - final var username = System.getProperty("user.name"); - final var tempFile = Files.createTempFile("ilo", ".passwd"); - tempFile.toFile().deleteOnExit(); - final var content = String.format("%s:x:%s::/home/%s:/bin/bash", username, expand(runAs), username); - Files.writeString(tempFile, content); - return tempFile.toAbsolutePath(); - } catch (final IOException exception) { - throw new RuntimeIOException(exception); - } - } - - private Bash() { - // utility class + @Override + // https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html + public String expandParameters(final String value) { + return null; } } diff --git a/src/main/java/wtf/metio/ilo/os/Cmd.java b/src/main/java/wtf/metio/ilo/os/Cmd.java new file mode 100644 index 000000000..55225fb9e --- /dev/null +++ b/src/main/java/wtf/metio/ilo/os/Cmd.java @@ -0,0 +1,27 @@ +/* + * This file is part of ilo. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://creativecommons.org/publicdomain/zero/1.0/. No part of ilo, + * including this file, may be copied, modified, propagated, or distributed except according to the terms contained + * in the LICENSE file. + */ + +package wtf.metio.ilo.os; + +/** + * Support for Windows CMD + */ +final class Cmd implements ParameterExpansion { + + @Override + public String substituteCommands(final String value) { + // TODO: cmd impl + return null; + } + + @Override + public String expandParameters(final String value) { + // TODO: cmd impl + return null; + } + +} diff --git a/src/main/java/wtf/metio/ilo/os/NoOpExpansion.java b/src/main/java/wtf/metio/ilo/os/NoOpExpansion.java new file mode 100644 index 000000000..3c7510ca6 --- /dev/null +++ b/src/main/java/wtf/metio/ilo/os/NoOpExpansion.java @@ -0,0 +1,22 @@ +/* + * This file is part of ilo. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://creativecommons.org/publicdomain/zero/1.0/. No part of ilo, + * including this file, may be copied, modified, propagated, or distributed except according to the terms contained + * in the LICENSE file. + */ + +package wtf.metio.ilo.os; + +public class NoOpExpansion implements ParameterExpansion { + + @Override + public String substituteCommands(final String value) { + return value; + } + + @Override + public String expandParameters(final String value) { + return value; + } + +} diff --git a/src/main/java/wtf/metio/ilo/os/OS.java b/src/main/java/wtf/metio/ilo/os/OS.java new file mode 100644 index 000000000..1f424afcd --- /dev/null +++ b/src/main/java/wtf/metio/ilo/os/OS.java @@ -0,0 +1,74 @@ +/* + * This file is part of ilo. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://creativecommons.org/publicdomain/zero/1.0/. No part of ilo, + * including this file, may be copied, modified, propagated, or distributed except according to the terms contained + * in the LICENSE file. + */ + +package wtf.metio.ilo.os; + +import wtf.metio.ilo.errors.RuntimeIOException; +import wtf.metio.ilo.utils.Strings; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +import static java.util.stream.Collectors.toList; +import static wtf.metio.ilo.utils.Streams.filter; +import static wtf.metio.ilo.utils.Streams.fromList; + +public final class OS { + + public static List expand(final List values) { + return filter(fromList(values)) + .map(OS::expand) + .collect(toList()); + } + + public static String expand(final String value) { + final var expansion = expansion(); + return Optional.ofNullable(value) + .map(expansion::expandParameters) + .map(expansion::substituteCommands) + .orElse(value); + } + + static ParameterExpansion expansion() { + return detectExpansionForOS(System.getProperty("os.name")); + } + + private static ParameterExpansion detectExpansionForOS(final String osName) { + if (Strings.isNotBlank(osName)) { + final var name = osName.toLowerCase(Locale.ENGLISH); + if (name.contains("linux") || name.contains("mac")) { + return new Bash(); + } + if (name.contains("win")) { + return new PowerShell(); + } + } + return new NoOpExpansion(); + } + + public static Path passwdFile(final String runAs) { + try { + final var username = System.getProperty("user.name"); + final var tempFile = Files.createTempFile("ilo", ".passwd"); + tempFile.toFile().deleteOnExit(); + final var content = String.format("%s:x:%s::/home/%s:/bin/bash", username, expand(runAs), username); + Files.writeString(tempFile, content); + return tempFile.toAbsolutePath(); + } catch (final IOException exception) { + throw new RuntimeIOException(exception); + } + } + + private OS() { + // utility class + } + +} diff --git a/src/main/java/wtf/metio/ilo/os/ParameterExpansion.java b/src/main/java/wtf/metio/ilo/os/ParameterExpansion.java new file mode 100644 index 000000000..310c29407 --- /dev/null +++ b/src/main/java/wtf/metio/ilo/os/ParameterExpansion.java @@ -0,0 +1,16 @@ +/* + * This file is part of ilo. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://creativecommons.org/publicdomain/zero/1.0/. No part of ilo, + * including this file, may be copied, modified, propagated, or distributed except according to the terms contained + * in the LICENSE file. + */ + +package wtf.metio.ilo.os; + +interface ParameterExpansion { + + String substituteCommands(String value); + + String expandParameters(String value); + +} diff --git a/src/main/java/wtf/metio/ilo/os/PowerShell.java b/src/main/java/wtf/metio/ilo/os/PowerShell.java new file mode 100644 index 000000000..d53dfff39 --- /dev/null +++ b/src/main/java/wtf/metio/ilo/os/PowerShell.java @@ -0,0 +1,27 @@ +/* + * This file is part of ilo. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://creativecommons.org/publicdomain/zero/1.0/. No part of ilo, + * including this file, may be copied, modified, propagated, or distributed except according to the terms contained + * in the LICENSE file. + */ + +package wtf.metio.ilo.os; + +/** + * Support for Windows PowerShell + */ +final class PowerShell implements ParameterExpansion { + + @Override + public String substituteCommands(final String value) { + // TODO: powershell impl + return null; + } + + @Override + public String expandParameters(final String value) { + // TODO: powershell impl + return null; + } + +} diff --git a/src/main/java/wtf/metio/ilo/tools/DockerComposePodmanCompose.java b/src/main/java/wtf/metio/ilo/tools/DockerComposePodmanCompose.java index 095716700..2843c284e 100644 --- a/src/main/java/wtf/metio/ilo/tools/DockerComposePodmanCompose.java +++ b/src/main/java/wtf/metio/ilo/tools/DockerComposePodmanCompose.java @@ -9,7 +9,7 @@ import wtf.metio.ilo.compose.ComposeCLI; import wtf.metio.ilo.compose.ComposeOptions; -import wtf.metio.ilo.utils.Bash; +import wtf.metio.ilo.os.OS; import java.util.List; @@ -23,10 +23,10 @@ public final List pullArguments(final ComposeOptions options) { if (options.pull) { return flatten( of(name()), - fromList(Bash.expand(options.runtimeOptions)), - withPrefix("--file", Bash.expand(options.file)), + fromList(OS.expand(options.runtimeOptions)), + withPrefix("--file", OS.expand(options.file)), of("pull"), - fromList(Bash.expand(options.runtimePullOptions))); + fromList(OS.expand(options.runtimePullOptions))); } return List.of(); } @@ -36,10 +36,10 @@ public final List buildArguments(final ComposeOptions options) { if (options.build) { return flatten( of(name()), - fromList(Bash.expand(options.runtimeOptions)), - withPrefix("--file", Bash.expand(options.file)), + fromList(OS.expand(options.runtimeOptions)), + withPrefix("--file", OS.expand(options.file)), of("build"), - fromList(Bash.expand(options.runtimeBuildOptions))); + fromList(OS.expand(options.runtimeBuildOptions))); } return List.of(); } @@ -48,13 +48,13 @@ public final List buildArguments(final ComposeOptions options) { public final List runArguments(final ComposeOptions options) { return flatten( of(name()), - fromList(Bash.expand(options.runtimeOptions)), - withPrefix("--file", Bash.expand(options.file)), + fromList(OS.expand(options.runtimeOptions)), + withPrefix("--file", OS.expand(options.file)), of("run"), - fromList(Bash.expand(options.runtimeRunOptions)), + fromList(OS.expand(options.runtimeRunOptions)), maybe(!options.interactive, "-T"), - of(Bash.expand(options.service)), - fromList(Bash.expand(options.arguments))); + of(OS.expand(options.service)), + fromList(OS.expand(options.arguments))); } @Override @@ -63,10 +63,10 @@ public final List cleanupArguments(final ComposeOptions options) { // see https://github.com/docker/compose/issues/2791 return flatten( of(name()), - fromList(Bash.expand(options.runtimeOptions)), - withPrefix("--file", Bash.expand(options.file)), + fromList(OS.expand(options.runtimeOptions)), + withPrefix("--file", OS.expand(options.file)), of("down"), - fromList(Bash.expand(options.runtimeCleanupOptions))); + fromList(OS.expand(options.runtimeCleanupOptions))); } } diff --git a/src/main/java/wtf/metio/ilo/tools/DockerPodman.java b/src/main/java/wtf/metio/ilo/tools/DockerPodman.java index f6b6b88d3..9ce9756bb 100644 --- a/src/main/java/wtf/metio/ilo/tools/DockerPodman.java +++ b/src/main/java/wtf/metio/ilo/tools/DockerPodman.java @@ -7,9 +7,9 @@ package wtf.metio.ilo.tools; +import wtf.metio.ilo.os.OS; import wtf.metio.ilo.shell.ShellCLI; import wtf.metio.ilo.shell.ShellOptions; -import wtf.metio.ilo.utils.Bash; import wtf.metio.ilo.utils.Strings; import java.util.List; @@ -24,10 +24,10 @@ public final List pullArguments(final ShellOptions options) { if (options.pull && Strings.isBlank(options.dockerfile)) { return flatten( of(name()), - fromList(Bash.expand(options.runtimeOptions)), + fromList(OS.expand(options.runtimeOptions)), of("pull"), - fromList(Bash.expand(options.runtimePullOptions)), - of(Bash.expand(options.image))); + fromList(OS.expand(options.runtimePullOptions)), + of(OS.expand(options.image))); } return List.of(); } @@ -37,12 +37,12 @@ public final List buildArguments(final ShellOptions options) { if (Strings.isNotBlank(options.dockerfile)) { return flatten( of(name()), - fromList(Bash.expand(options.runtimeOptions)), + fromList(OS.expand(options.runtimeOptions)), of("build", "--file", options.dockerfile), - fromList(Bash.expand(options.runtimeBuildOptions)), + fromList(OS.expand(options.runtimeBuildOptions)), maybe(options.pull, "--pull"), - of("--tag", Bash.expand(options.image)), - of(Bash.expand(options.context))); + of("--tag", OS.expand(options.image)), + of(OS.expand(options.context))); } return List.of(); } @@ -54,23 +54,23 @@ public final List runArguments(final ShellOptions options) { "--volume", currentDir + ":" + currentDir + ":Z", "--workdir", currentDir); final var user = maybe(Strings.isNotBlank(options.runAs), - "--user", Bash.expand(options.runAs)); + "--user", OS.expand(options.runAs)); final var passwd = maybe(Strings.isNotBlank(options.runAs), - "--volume", Bash.passwdFile(options.runAs) + ":/etc/passwd"); + "--volume", OS.passwdFile(options.runAs) + ":/etc/passwd"); return flatten( of(name()), - fromList(Bash.expand(options.runtimeOptions)), + fromList(OS.expand(options.runtimeOptions)), of("run", "--rm"), - fromList(Bash.expand(options.runtimeRunOptions)), + fromList(OS.expand(options.runtimeRunOptions)), user, passwd, projectDir, maybe(options.interactive, "--interactive", "--tty"), - withPrefix("--env", Bash.expand(options.variables)), - withPrefix("--publish", Bash.expand(options.ports)), - withPrefix("--volume", Bash.expand(options.volumes)), - of(Bash.expand(options.image)), - fromList(Bash.expand(options.commands))); + withPrefix("--env", OS.expand(options.variables)), + withPrefix("--publish", OS.expand(options.ports)), + withPrefix("--volume", OS.expand(options.volumes)), + of(OS.expand(options.image)), + fromList(OS.expand(options.commands))); } @Override @@ -78,10 +78,10 @@ public final List cleanupArguments(final ShellOptions options) { if (options.removeImage) { return flatten( of(name()), - fromList(Bash.expand(options.runtimeOptions)), + fromList(OS.expand(options.runtimeOptions)), of("rmi"), - fromList(Bash.expand(options.runtimeCleanupOptions)), - of(Bash.expand(options.image))); + fromList(OS.expand(options.runtimeCleanupOptions)), + of(OS.expand(options.image))); } return List.of(); } diff --git a/src/test/java/wtf/metio/ilo/utils/BashTest.java b/src/test/java/wtf/metio/ilo/os/BashTest.java similarity index 63% rename from src/test/java/wtf/metio/ilo/utils/BashTest.java rename to src/test/java/wtf/metio/ilo/os/BashTest.java index e9c7f5e99..062622f1e 100644 --- a/src/test/java/wtf/metio/ilo/utils/BashTest.java +++ b/src/test/java/wtf/metio/ilo/os/BashTest.java @@ -5,15 +5,15 @@ * in the LICENSE file. */ -package wtf.metio.ilo.utils; +package wtf.metio.ilo.os; import com.github.stefanbirkner.systemlambda.SystemLambda; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; -import java.nio.file.Files; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -21,15 +21,11 @@ @DisplayName("Bash") class BashTest { - @Test - @DisplayName("expands ~ to the user's home directory") - void expandHomesWithTilde() throws Exception { - final var values = List.of("~/test:/something", ""); - SystemLambda.restoreSystemProperties(() -> { - System.setProperty("user.home", "/home/user"); - final var result = Bash.expand(values); - assertIterableEquals(List.of("/home/user/test:/something"), result); - }); + private Bash bash; + + @BeforeEach + void setUp() { + bash = new Bash(); } @Test @@ -37,28 +33,17 @@ void expandHomesWithTilde() throws Exception { void expandHomeWithTilde() throws Exception { SystemLambda.restoreSystemProperties(() -> { System.setProperty("user.home", "/home/user"); - final var result = Bash.expand("~/test:/something"); + final var result = bash.expandParameters("~/test:/something"); assertEquals("/home/user/test:/something", result); }); } - @Test - @DisplayName("expands $HOME to the user's home directory") - void expandHomes() throws Exception { - final var values = List.of("$HOME/test:/something", ""); - SystemLambda.restoreSystemProperties(() -> { - System.setProperty("user.home", "/home/user"); - final var result = Bash.expand(values); - assertIterableEquals(List.of("/home/user/test:/something"), result); - }); - } - @Test @DisplayName("expands $HOME to the user's home directory") void expandHome() throws Exception { SystemLambda.restoreSystemProperties(() -> { System.setProperty("user.home", "/home/user"); - final var result = Bash.expand("$HOME/test:/something"); + final var result = bash.expandParameters("$HOME/test:/something"); assertEquals("/home/user/test:/something", result); }); } @@ -74,17 +59,10 @@ void expandHomesWithBrackets() throws Exception { }); } - @Test - @DisplayName("returns other values as-is") - void keepOthers() { - final var result = Bash.expand(List.of("something", "")); - assertIterableEquals(List.of("something"), result); - } - @Test @DisplayName("returns constants as-is") void keepOther() { - assertEquals("1000:1000", Bash.expand("1000:1000")); + assertEquals("1000:1000", bash.expandParameters("1000:1000")); } @Test @@ -113,15 +91,4 @@ void substituteCommandsWithConstant() { () -> assertTrue(result.contains("1234"), "group")); } - @Test - @DisplayName("write passwd file") - void passwd() throws Exception { - SystemLambda.restoreSystemProperties(() -> { - System.setProperty("user.name", "testuser"); - final var passwdFile = Bash.passwdFile("1234:5678"); - final var content = Files.readString(passwdFile); - assertEquals(content, "testuser:x:1234:5678::/home/testuser:/bin/bash"); - }); - } - } diff --git a/src/test/java/wtf/metio/ilo/os/OSTest.java b/src/test/java/wtf/metio/ilo/os/OSTest.java new file mode 100644 index 000000000..5207a8f97 --- /dev/null +++ b/src/test/java/wtf/metio/ilo/os/OSTest.java @@ -0,0 +1,32 @@ +/* + * This file is part of ilo. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://creativecommons.org/publicdomain/zero/1.0/. No part of ilo, + * including this file, may be copied, modified, propagated, or distributed except according to the terms contained + * in the LICENSE file. + */ + +package wtf.metio.ilo.os; + +import com.github.stefanbirkner.systemlambda.SystemLambda; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@DisplayName("OS") +class OSTest { + + @Test + @DisplayName("write passwd file") + void passwd() throws Exception { + SystemLambda.restoreSystemProperties(() -> { + System.setProperty("user.name", "testuser"); + final var passwdFile = OS.passwdFile("1234:5678"); + final var content = Files.readString(passwdFile); + assertEquals(content, "testuser:x:1234:5678::/home/testuser:/bin/bash"); + }); + } + +}