diff --git a/README.md b/README.md index f2fb227..b2cc022 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,15 @@ This is a Spring Shell tool that allows managing AWS Parameter Store with simple Posix-like commands. -It is currently rudimentary; this was written specifically to facilitate a mass-migration of parameters from one +It is currently rudimentary; this was written specifically to facilitate a mass-migration of parameters from one path layout to another using a "copy, deploy updated application, delete" flow in batches. For this, we scripted the batches for review and pasted the scripts into the tool. +# Warning + +Use at your own risk! While I have used this intensively, several times for production migrations, it's not currently +under daily use nor covered by exhaustive tests. A great contribution would be adding backup and restore. 😁 + ## Commands * `cat ` @@ -33,16 +38,16 @@ AWS_PROFILE=myprofile ./mvnw spring-boot:run This project suffers from well-known problems with Intellij's terminal and jline. Most notably, normal input controls don't work, and tab completion is broken: https://youtrack.jetbrains.com/issue/IDEA-183619 -`rm` supports -f / --force, but Spring Shell doesn't seem to be able to handle a boolean flag without args combined with -standalone args, so one has to specify `rm -f true `. - +`rm` supports -f / --force, but Spring Shell doesn't seem to be able to handle a boolean flag without args combined with +standalone args, so one has to specify `rm -f true `. ## TODO + * allow cp/mv to overwrite with confirmation * cd * create * ed * tab completion for parameters -* caching (only practical way to do tab completion) +* caching (tab completion performance?) * release artifact * backup / restore diff --git a/pom.xml b/pom.xml index ae971d9..074532a 100644 --- a/pom.xml +++ b/pom.xml @@ -17,10 +17,22 @@ 25 - 1.18.42 - 2.38.7 + 1.18.38 + 2.38.7 + + + + software.amazon.awssdk + bom + ${aws.sdk.version} + pom + import + + + + org.projectlombok @@ -36,7 +48,6 @@ software.amazon.awssdk ssm - ${aws.sdk.verrsion} commons-logging @@ -47,7 +58,6 @@ software.amazon.awssdk sso - ${aws.sdk.verrsion} org.springframework.shell diff --git a/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Cd.java b/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Cd.java index f86c4bb..cb328a4 100644 --- a/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Cd.java +++ b/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Cd.java @@ -11,7 +11,7 @@ public class Cd { private final SetCurrentDirectoryUseCase setCurrentDirectoryUseCase; @Command(description = "Change directory", group = "Parameter Store") - public void Cd( + public void cd( @Option(description = "Path") String path ) { setCurrentDirectoryUseCase.setCurrentDirectory(path); diff --git a/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Copy.java b/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Cp.java similarity index 98% rename from src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Copy.java rename to src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Cp.java index f9ba359..fc552e4 100644 --- a/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Copy.java +++ b/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Cp.java @@ -7,7 +7,7 @@ @Command @RequiredArgsConstructor -class Copy { +class Cp { private final ParameterStore parameterStore; @Command(description = "Copy Parameter", group = "Parameter Store") diff --git a/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/List.java b/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Ls.java similarity index 99% rename from src/main/java/com/internetstaff/parameterstore/adapter/in/shell/List.java rename to src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Ls.java index 5d52b06..30c1c41 100644 --- a/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/List.java +++ b/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Ls.java @@ -11,7 +11,7 @@ @Command @RequiredArgsConstructor -class List { +class Ls { private final ParameterStore parameterStore; private int maxLength(java.util.List parameters) { diff --git a/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Move.java b/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Mv.java similarity index 98% rename from src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Move.java rename to src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Mv.java index 7599c81..3f7c6a1 100644 --- a/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Move.java +++ b/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Mv.java @@ -7,7 +7,7 @@ @Command @RequiredArgsConstructor -class Move { +class Mv { private final ParameterStore parameterStore; @Command(description = "Move Parameter", group = "Parameter Store") diff --git a/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Remove.java b/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Rm.java similarity index 86% rename from src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Remove.java rename to src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Rm.java index 9c61c5f..559d7db 100644 --- a/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Remove.java +++ b/src/main/java/com/internetstaff/parameterstore/adapter/in/shell/Rm.java @@ -4,11 +4,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.shell.command.annotation.Command; import org.springframework.shell.command.annotation.Option; -import org.springframework.shell.standard.AbstractShellComponent; @Command @RequiredArgsConstructor -class Remove extends AbstractShellComponent { +class Rm { private final ParameterStore parameterStore; @Command(description = "Remove parameter", group = "Parameter Store") diff --git a/src/test/java/com/internetstaff/parameterstore/adapter/out/ssm/ParameterStoreAdapterTest.java b/src/test/java/com/internetstaff/parameterstore/adapter/out/ssm/ParameterStoreAdapterTest.java index ad25074..5d202bc 100644 --- a/src/test/java/com/internetstaff/parameterstore/adapter/out/ssm/ParameterStoreAdapterTest.java +++ b/src/test/java/com/internetstaff/parameterstore/adapter/out/ssm/ParameterStoreAdapterTest.java @@ -147,6 +147,40 @@ void copyParameter() { .isEqualTo(expected); } + @Test + void copyParameter_doesNotDoubleQualifyDestination() { + // Simulate being in a non-root current directory and realistic qualifyName behavior + var base = "/current"; + lenient().when(currentDirectoryUseCase.getCurrentDirectory()).thenReturn(base); + lenient().when(currentDirectoryUseCase.qualifyName(anyString())).thenAnswer(inv -> { + var name = inv.getArgument(0, String.class); + return name.startsWith("/") ? name : base + "/" + name; + }); + + var src = "src-param-" + RandomStringUtils.insecure().nextAlphabetic(8); + var dst = "dst-param-" + RandomStringUtils.insecure().nextAlphabetic(8); + var value = RandomStringUtils.insecure().nextAlphabetic(16); + + // Create the source parameter using relative name (should be stored under /current/src) + parameterStoreAdapter.createParameter(src, value, "SecureString"); + + // Perform copy using relative destination (should land at /current/dst, not /current/current/dst) + var copied = parameterStoreAdapter.copyParameter(src, dst); + assertThat(copied).isTrue(); + + var expectedQualifiedDst = base + "/" + dst; + var wrongDoublyQualifiedDst = base + base + "/" + dst; + + // Verify the correctly qualified destination exists with the expected value + assertThat(parameterStoreAdapter.getParameter(expectedQualifiedDst)) + .get() + .extracting(ParameterStore.Parameter::value) + .isEqualTo(value); + + // And verify there is no parameter at a doubly-qualified path + assertThat(parameterStoreAdapter.getParameter(wrongDoublyQualifiedDst)).isEmpty(); + } + @Test void createParameter() { var name = q(RandomStringUtils.insecure().nextAlphabetic(24));