Add optional seeding of pet types on startup from S3 (#3)

This commit is contained in:
Taylor Becker 2024-02-29 22:43:05 -05:00 committed by GitHub
parent 2ae3474248
commit 161183f27e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 148 additions and 3 deletions

View file

@ -50,6 +50,14 @@ dependencies {
testImplementation 'org.testcontainers:mysql' testImplementation 'org.testcontainers:mysql'
checkstyle 'io.spring.javaformat:spring-javaformat-checkstyle:0.0.41' checkstyle 'io.spring.javaformat:spring-javaformat-checkstyle:0.0.41'
checkstyle 'com.puppycrawl.tools:checkstyle:10.12.5' checkstyle 'com.puppycrawl.tools:checkstyle:10.12.5'
implementation platform('io.awspring.cloud:spring-cloud-aws-dependencies:3.1.0')
implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3'
implementation platform("software.amazon.awssdk:bom:2.24.13")
implementation 'software.amazon.awssdk:kms'
implementation 'software.amazon.awssdk:sso'
implementation 'software.amazon.awssdk:ssooidc'
} }
tasks.named('test') { tasks.named('test') {

45
pom.xml
View file

@ -35,8 +35,28 @@
<nohttp-checkstyle.version>0.0.11</nohttp-checkstyle.version> <nohttp-checkstyle.version>0.0.11</nohttp-checkstyle.version>
<spring-format.version>0.0.40</spring-format.version> <spring-format.version>0.0.40</spring-format.version>
<aws.java.sdk.version>2.24.13</aws.java.sdk.version>
</properties> </properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-dependencies</artifactId>
<version>3.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>${aws.java.sdk.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies> <dependencies>
<!-- Spring and Spring Boot dependencies --> <!-- Spring and Spring Boot dependencies -->
<dependency> <dependency>
@ -139,6 +159,25 @@
<artifactId>jakarta.xml.bind-api</artifactId> <artifactId>jakarta.xml.bind-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-s3</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>kms</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sso</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>ssooidc</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
@ -220,7 +259,7 @@
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<executions> <executions>
<execution> <execution>
<!-- Spring Boot Actuator displays build-related information <!-- Spring Boot Actuator displays build-related information
if a META-INF/build-info.properties file is present --> if a META-INF/build-info.properties file is present -->
<goals> <goals>
<goal>build-info</goal> <goal>build-info</goal>
@ -378,7 +417,7 @@
<build> <build>
<pluginManagement> <pluginManagement>
<plugins> <plugins>
<!-- This plugin's configuration is used to store Eclipse m2e settings <!-- This plugin's configuration is used to store Eclipse m2e settings
only. It has no influence on the Maven build itself. --> only. It has no influence on the Maven build itself. -->
<plugin> <plugin>
<groupId>org.eclipse.m2e</groupId> <groupId>org.eclipse.m2e</groupId>
@ -436,4 +475,4 @@
</profile> </profile>
</profiles> </profiles>
</project> </project>

View file

@ -0,0 +1,87 @@
package org.springframework.samples.petclinic.pettypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.samples.petclinic.owner.PetType;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.kms.KmsClient;
import software.amazon.awssdk.services.kms.model.DecryptResponse;
import software.amazon.awssdk.services.s3.S3Client;
import java.util.List;
import java.util.Optional;
/**
* Perform some initializing of the supported pet types on startup by downloading them
* from S3, if enabled
*/
@Component
public class InitPetTypes implements InitializingBean {
private final Logger logger = LoggerFactory.getLogger(InitPetTypes.class);
private final PetTypesRepository petTypesRepository;
private final KmsClient kmsClient;
private final S3Client s3Client;
@Value("${app.init.pet-types.key:petclinic-pettypes.txt}")
private String petTypesInitObjectKey;
@Value("${app.init.pet-types.kms-encrypted:false}")
private Boolean petTypesInitObjectKmsEncrypted;
InitPetTypes(PetTypesRepository petTypesRepository, Optional<AwsCredentialsProvider> awsCredentialsProvider,
Optional<S3Client> s3Client) {
this.petTypesRepository = petTypesRepository;
this.s3Client = s3Client.orElse(null);
AwsCredentialsProvider credentialsProvider = awsCredentialsProvider.orElse(null);
this.kmsClient = credentialsProvider != null
? KmsClient.builder().credentialsProvider(credentialsProvider).build() : null;
}
@Override
public void afterPropertiesSet() throws Exception {
if (s3Client == null) {
logger.info("No S3Client configured, skipping loading pettypes.");
return;
}
logger.info("Loading Pet types from \"spring-petclinic-init\" bucket at " + petTypesInitObjectKey);
String fileContents = s3Client
.getObjectAsBytes(b -> b.bucket("spring-petclinic-init").key(petTypesInitObjectKey))
.asUtf8String();
// if kms encrypted, attempt to decrypt it
if (petTypesInitObjectKmsEncrypted) {
logger.info("Decrypting pet types using KMS key...");
SdkBytes encryptedData = SdkBytes.fromUtf8String(fileContents);
DecryptResponse decryptResponse = kmsClient
.decrypt(b -> b.ciphertextBlob(encryptedData).keyId("spring-petclinic-init"));
fileContents = decryptResponse.plaintext().asUtf8String();
}
List<PetType> foundTypes = fileContents.lines().map(s -> {
PetType type = new PetType();
type.setName(s);
return type;
}).toList();
logger.info("Found " + foundTypes.size() + " pet types");
// load the found types into the database
if (!foundTypes.isEmpty()) {
petTypesRepository.saveAllAndFlush(foundTypes);
// clean up the file if we've successfully loaded from it
logger.info("Deleting Pet types file from S3");
s3Client.deleteObject(builder -> builder.bucket("spring-petclinic-init").key(petTypesInitObjectKey));
}
}
}

View file

@ -0,0 +1,8 @@
package org.springframework.samples.petclinic.pettypes;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.samples.petclinic.owner.PetType;
public interface PetTypesRepository extends JpaRepository<PetType, Integer> {
}

View file

@ -23,3 +23,6 @@ logging.level.org.springframework=INFO
# Maximum time static resources should be cached # Maximum time static resources should be cached
spring.web.resources.cache.cachecontrol.max-age=12h spring.web.resources.cache.cachecontrol.max-age=12h
spring.cloud.aws.credentials.profile.name=${SPRING_AWS_PROFILE:dev}
spring.cloud.aws.s3.enabled=false