Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name>`
Expand Down Expand Up @@ -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 <param>`.

`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 <param>`.

## 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
18 changes: 14 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,22 @@

<properties>
<maven.compiler.release>25</maven.compiler.release>
<lombok.version>1.18.42</lombok.version>
<aws.sdk.verrsion>2.38.7</aws.sdk.verrsion>
<lombok.version>1.18.38</lombok.version>
<aws.sdk.version>2.38.7</aws.sdk.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>${aws.sdk.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
Expand All @@ -36,7 +48,6 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>ssm</artifactId>
<version>${aws.sdk.verrsion}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
Expand All @@ -47,7 +58,6 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sso</artifactId>
<version>${aws.sdk.verrsion}</version>
</dependency>
<dependency>
<groupId>org.springframework.shell</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

@Command
@RequiredArgsConstructor
class Copy {
class Cp {
private final ParameterStore parameterStore;

@Command(description = "Copy Parameter", group = "Parameter Store")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

@Command
@RequiredArgsConstructor
class List {
class Ls {
private final ParameterStore parameterStore;

private int maxLength(java.util.List<Metadata> parameters) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

@Command
@RequiredArgsConstructor
class Move {
class Mv {
private final ParameterStore parameterStore;

@Command(description = "Move Parameter", group = "Parameter Store")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down