Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify using Localstack with ParameterStore and SecretsManager #1080

Open
maciejwalkowiak opened this issue Mar 9, 2024 · 6 comments
Open
Assignees
Milestone

Comments

@maciejwalkowiak
Copy link
Contributor

maciejwalkowiak commented Mar 9, 2024

@ServiceConnection support that's being added in #1075 does not help with ParameterStore and SecretsManager, as those integration are initiated in the bootstrap phase, before service connection related factories kick in.

We can provide a BootstrapRegistryInitializer implementation for Localstack:

import io.awspring.cloud.autoconfigure.core.AwsProperties;
import io.awspring.cloud.autoconfigure.core.CredentialsProperties;
import io.awspring.cloud.autoconfigure.core.RegionProperties;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.BootstrapRegistryInitializer;
import org.springframework.util.Assert;
import org.testcontainers.containers.localstack.LocalStackContainer;

public class LocalstackBootstrapInitializer implements BootstrapRegistryInitializer {

	private final AwsProperties awsProperties;
	private final RegionProperties regionProperties;
	private final CredentialsProperties credentialsProperties;

	public LocalstackBootstrapInitializer(LocalStackContainer localStackContainer) {
		Assert.notNull(localStackContainer, "localstack container cannot be null");
		this.awsProperties = awsProperties(localStackContainer);
		this.regionProperties = regionProperties(localStackContainer);
		this.credentialsProperties = credentialsProperties(localStackContainer);
	}

	@Override
	public void initialize(BootstrapRegistry registry) {
		registry.register(AwsProperties.class, context -> awsProperties);
		registry.register(RegionProperties.class, context -> regionProperties);
		registry.register(CredentialsProperties.class, context -> credentialsProperties);
	}

	private static CredentialsProperties credentialsProperties(LocalStackContainer localStackContainer) {
		CredentialsProperties properties = new CredentialsProperties();
		properties.setAccessKey(localStackContainer.getAccessKey());
		properties.setSecretKey(localStackContainer.getSecretKey());
		return properties;
	}

	private static RegionProperties regionProperties(LocalStackContainer localStackContainer) {
		RegionProperties properties = new RegionProperties();
		properties.setStatic(localStackContainer.getRegion());
		return properties;
	}

	private static AwsProperties awsProperties(LocalStackContainer localStackContainer) {
		AwsProperties properties = new AwsProperties();
		properties.setEndpoint(localStackContainer.getEndpoint());
		return properties;
	}
}

that can be used like this:

@SpringBootTest(classes = SpringCloudAwsParameterStoreSampleTest.TestApp.class, useMainMethod = SpringBootTest.UseMainMethod.ALWAYS)
@Testcontainers
class SpringCloudAwsParameterStoreSampleTest {

	@Container
	private static LocalStackContainer localStackContainer = new LocalStackContainer(DockerImageName.parse("localstack/localstack:3.2.0"));

	@Test
	void foo() {

	}

	@SpringBootApplication
	public static class TestApp {

		public static void main(String[] args) {
			var app = new SpringApplication(SpringCloudAwsParameterStoreSampleTest.class);
			app.addBootstrapRegistryInitializer(new LocalstackBootstrapInitializer(localStackContainer));
			app.run(args);
		}
	}
}
@maciejwalkowiak maciejwalkowiak modified the milestones: 3.2.0 M2, 3.2.0 M1 Mar 10, 2024
@maciejwalkowiak
Copy link
Contributor Author

maciejwalkowiak commented Mar 19, 2024

@rieckpil @spencergibb perhaps you have some ideas how this can be done in a better way?

The issue is, Parameter Store and Secrets Manager integrations run in the bootstrap phase - for spring.config.import. Service connections kick in too late in the process and @SpringBootTest does not pick up BootstrapRegistryInitializers created through spring.factory. So having custom "main" class and @SpringBootTest with useMainMethod was the only way to get it working.

@rieckpil
Copy link

Good point, I guess I'm lacking some in-depth knowledge of the Spring bootstrap phase here, maybe @sbrannen has a quick idea

@spencergibb
Copy link
Contributor

I don't have anything else to add. Maybe @philwebb might.

@eddumelendez
Copy link
Contributor

eddumelendez commented Mar 21, 2024

Hi, @ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class) should be added at class level. I have some examples running for parameter store and secrets manager working with @DynamicPropertySource and it should work with @ServiceConnection as well. Looking forward for the release :)

So far there is no issues with spring-cloud-aws but if ConfigDataMissingEnvironmentPostProcessor has been implemented for those integrations recently look at this issue in other spring-cloud projects.

@maciejwalkowiak
Copy link
Contributor Author

@eddumelendez thanks for chiming in. With ConfigDataApplicationContextInitializer, service connections are initialized indeed before the config data loaders, BUT the connection details beans are not available in the bootstrap context.

Considering that the amount of boilerplate required to use bootstrap context initializers in tests is so high, I think sticking to @DynamicPropertySource provides better dev experience.

Perhaps we can provide a static method like this just to make it a bit simpler:

static void configureLocalStack(DynamicPropertyRegistry registry, LocalStackContainer localstack) {
	registry.add("spring.cloud.aws.credentials.access-key", localstack::getAccessKey);
	registry.add("spring.cloud.aws.credentials.secret-key", localstack::getSecretKey);
	registry.add("spring.cloud.aws.region.static", localstack::getRegion);
	registry.add("spring.cloud.aws.endpoint", localstack::getEndpoint);
}

Sample usage:

@Container
private static LocalStackContainer localstack = new LocalStackContainer(
		DockerImageName.parse("localstack/localstack:3.2.0"));

@DynamicPropertySource
static void properties(DynamicPropertyRegistry registry) {
	configureLocalStack(registry, localstack);
}

@philwebb
Copy link

I don't have anything else to add. Maybe @philwebb might.

Sorry, not off the top of my head. The bootstrap logic is always quite difficult to get right.

@maciejwalkowiak maciejwalkowiak modified the milestones: 3.2.0 M1, 3.2.0 M2 Mar 29, 2024
@maciejwalkowiak maciejwalkowiak self-assigned this Mar 30, 2024
@maciejwalkowiak maciejwalkowiak modified the milestones: 3.2.0 M2, 3.3.x Sep 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants