diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/Command.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/Command.java index d6a08c6d9c..c6669b0d54 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/Command.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/Command.java @@ -30,6 +30,7 @@ import java.util.stream.Stream; * A command that can be launched from the layertools jarmode. * * @author Phillip Webb + * @author Scott Frederick */ abstract class Command { @@ -192,6 +193,7 @@ abstract class Command { return candidate; } } + throw new UnknownOptionException(name); } return null; } @@ -285,7 +287,13 @@ abstract class Command { } private String claimArg(Deque args) { - return (this.valueDescription != null) ? args.removeFirst() : null; + if (this.valueDescription != null) { + if (args.isEmpty()) { + throw new MissingValueException(this.name); + } + return args.removeFirst(); + } + return null; } @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/LayerToolsJarMode.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/LayerToolsJarMode.java index 55ac681f02..7ac5c2d8ae 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/LayerToolsJarMode.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/LayerToolsJarMode.java @@ -29,6 +29,7 @@ import org.springframework.boot.loader.jarmode.JarMode; * {@link JarMode} providing {@code "layertools"} support. * * @author Phillip Webb + * @author Scott Frederick * @since 2.3.0 */ public class LayerToolsJarMode implements JarMode { @@ -63,22 +64,48 @@ public class LayerToolsJarMode implements JarMode { } private void run(String[] args) { - run(new ArrayDeque<>(Arrays.asList(args))); + run(dequeOf(args)); } private void run(Deque args) { if (!args.isEmpty()) { - Command command = Command.find(this.commands, args.removeFirst()); + String commandName = args.removeFirst(); + Command command = Command.find(this.commands, commandName); if (command != null) { - command.run(args); + runCommand(command, args); return; } + printError("Unknown command \"" + commandName + "\""); } this.help.run(args); } + private void runCommand(Command command, Deque args) { + try { + command.run(args); + } + catch (UnknownOptionException ex) { + printError("Unknown option \"" + ex.getMessage() + "\" for the " + command.getName() + " command"); + this.help.run(dequeOf(command.getName())); + } + catch (MissingValueException ex) { + printError("Option \"" + ex.getMessage() + "\" for the " + command.getName() + + " command requires a value"); + this.help.run(dequeOf(command.getName())); + } + } + + private void printError(String errorMessage) { + System.out.println("Error: " + errorMessage); + System.out.println(); + } + + private Deque dequeOf(String... args) { + return new ArrayDeque<>(Arrays.asList(args)); + } + static List getCommands(Context context) { - List commands = new ArrayList(); + List commands = new ArrayList<>(); commands.add(new ListCommand(context)); commands.add(new ExtractCommand(context)); return Collections.unmodifiableList(commands); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/MissingValueException.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/MissingValueException.java new file mode 100644 index 0000000000..f5fa8570e9 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/MissingValueException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.jarmode.layertools; + +/** + * Exception thrown when a required value is not provided for an option. + * + * @author Scott Frederick + */ +class MissingValueException extends RuntimeException { + + private final String optionName; + + MissingValueException(String optionName) { + this.optionName = optionName; + } + + @Override + public String getMessage() { + return "--" + this.optionName; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/UnknownOptionException.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/UnknownOptionException.java new file mode 100644 index 0000000000..65d5541307 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/main/java/org/springframework/boot/jarmode/layertools/UnknownOptionException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.jarmode.layertools; + +/** + * Exception thrown when an unrecognized option is encountered. + * + * @author Scott Frederick + */ +class UnknownOptionException extends RuntimeException { + + private final String optionName; + + UnknownOptionException(String optionName) { + this.optionName = optionName; + } + + @Override + public String getMessage() { + return "--" + this.optionName; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/CommandTests.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/CommandTests.java index a7c353e55b..14fdf76c34 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/CommandTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/CommandTests.java @@ -30,11 +30,13 @@ import org.springframework.boot.jarmode.layertools.Command.Parameters; import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link Command}. * * @author Phillip Webb + * @author Scott Frederick */ class CommandTests { @@ -77,6 +79,20 @@ class CommandTests { assertThat(command.getRunParameters()).containsExactly("test2", "test3"); } + @Test + void runWithUnknownOptionThrowsException() { + TestCommand command = new TestCommand("test", VERBOSE_FLAG, LOG_LEVEL_OPTION); + assertThatExceptionOfType(UnknownOptionException.class).isThrownBy(() -> run(command, "--invalid")) + .withMessage("--invalid"); + } + + @Test + void runWithOptionMissingRequiredValueThrowsException() { + TestCommand command = new TestCommand("test", VERBOSE_FLAG, LOG_LEVEL_OPTION); + assertThatExceptionOfType(MissingValueException.class) + .isThrownBy(() -> run(command, "--verbose", "--log-level")).withMessage("--log-level"); + } + @Test void findWhenNameMatchesReturnsCommand() { TestCommand test1 = new TestCommand("test1"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/LayerToolsJarModeTests.java b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/LayerToolsJarModeTests.java index a352b01ff0..6e4c95fe5a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/LayerToolsJarModeTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/java/org/springframework/boot/jarmode/layertools/LayerToolsJarModeTests.java @@ -39,6 +39,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link LayerToolsJarMode}. * * @author Phillip Webb + * @author Scott Frederick */ class LayerToolsJarModeTests { @@ -79,6 +80,24 @@ class LayerToolsJarModeTests { assertThat(this.out).hasSameContentAsResource("list-output.txt"); } + @Test + void mainWithUnknownCommandShowsErrorAndHelp() { + new LayerToolsJarMode().run("layertools", new String[] { "invalid" }); + assertThat(this.out).hasSameContentAsResource("error-command-unknown-output.txt"); + } + + @Test + void mainWithUnknownOptionShowsErrorAndCommandHelp() { + new LayerToolsJarMode().run("layertools", new String[] { "extract", "--invalid" }); + assertThat(this.out).hasSameContentAsResource("error-option-unknown-output.txt"); + } + + @Test + void mainWithOptionMissingRequiredValueShowsErrorAndCommandHelp() { + new LayerToolsJarMode().run("layertools", new String[] { "extract", "--destination" }); + assertThat(this.out).hasSameContentAsResource("error-option-missing-value-output.txt"); + } + private File createJarFile(String name) throws IOException { File file = new File(this.temp, name); try (ZipOutputStream jarOutputStream = new ZipOutputStream(new FileOutputStream(file))) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-command-unknown-output.txt b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-command-unknown-output.txt new file mode 100644 index 0000000000..20a2ca2098 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-command-unknown-output.txt @@ -0,0 +1,9 @@ +Error: Unknown command "invalid" + +Usage: + java -Djarmode=layertools -jar test.jar + +Available commands: + list List layers from the jar that can be extracted + extract Extracts layers from the jar for image creation + help Help about any command diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-option-missing-value-output.txt b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-option-missing-value-output.txt new file mode 100644 index 0000000000..6a5034cedd --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-option-missing-value-output.txt @@ -0,0 +1,9 @@ +Error: Option "--destination" for the extract command requires a value + +Extracts layers from the jar for image creation + +Usage: + java -Djarmode=layertools -jar test.jar extract [options] [...] + +Options: + --destination string The destination to extract files to diff --git a/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-option-unknown-output.txt b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-option-unknown-output.txt new file mode 100644 index 0000000000..a207b3b20a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-jarmode-layertools/src/test/resources/org/springframework/boot/jarmode/layertools/error-option-unknown-output.txt @@ -0,0 +1,9 @@ +Error: Unknown option "--invalid" for the extract command + +Extracts layers from the jar for image creation + +Usage: + java -Djarmode=layertools -jar test.jar extract [options] [...] + +Options: + --destination string The destination to extract files to