From 161183f27eb2bb6863bbbb2df0ed3eb3196dbd4a Mon Sep 17 00:00:00 2001 From: Taylor Becker Date: Thu, 29 Feb 2024 22:43:05 -0500 Subject: [PATCH] Add optional seeding of pet types on startup from S3 (#3) --- build.gradle | 8 ++ pom.xml | 45 +++++++++- .../petclinic/pettypes/InitPetTypes.java | 87 +++++++++++++++++++ .../pettypes/PetTypesRepository.java | 8 ++ src/main/resources/application.properties | 3 + 5 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/springframework/samples/petclinic/pettypes/InitPetTypes.java create mode 100644 src/main/java/org/springframework/samples/petclinic/pettypes/PetTypesRepository.java diff --git a/build.gradle b/build.gradle index e2f5769e8..e6b8210a6 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,14 @@ dependencies { testImplementation 'org.testcontainers:mysql' checkstyle 'io.spring.javaformat:spring-javaformat-checkstyle:0.0.41' 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') { diff --git a/pom.xml b/pom.xml index cf47f8aa6..1bdb6b53a 100644 --- a/pom.xml +++ b/pom.xml @@ -35,8 +35,28 @@ 0.0.11 0.0.40 + 2.24.13 + + + + io.awspring.cloud + spring-cloud-aws-dependencies + 3.1.0 + pom + import + + + + software.amazon.awssdk + bom + ${aws.java.sdk.version} + pom + import + + + @@ -139,6 +159,25 @@ jakarta.xml.bind-api + + io.awspring.cloud + spring-cloud-aws-starter-s3 + + + + software.amazon.awssdk + kms + + + + software.amazon.awssdk + sso + + + + software.amazon.awssdk + ssooidc + @@ -220,7 +259,7 @@ spring-boot-maven-plugin - build-info @@ -378,7 +417,7 @@ - org.eclipse.m2e @@ -436,4 +475,4 @@ - \ No newline at end of file + diff --git a/src/main/java/org/springframework/samples/petclinic/pettypes/InitPetTypes.java b/src/main/java/org/springframework/samples/petclinic/pettypes/InitPetTypes.java new file mode 100644 index 000000000..265e60095 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/pettypes/InitPetTypes.java @@ -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, + Optional 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 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)); + } + } + +} diff --git a/src/main/java/org/springframework/samples/petclinic/pettypes/PetTypesRepository.java b/src/main/java/org/springframework/samples/petclinic/pettypes/PetTypesRepository.java new file mode 100644 index 000000000..d0c17a843 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/pettypes/PetTypesRepository.java @@ -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 { + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5d3eeed32..99f4a977d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -23,3 +23,6 @@ logging.level.org.springframework=INFO # Maximum time static resources should be cached spring.web.resources.cache.cachecontrol.max-age=12h + +spring.cloud.aws.credentials.profile.name=${SPRING_AWS_PROFILE:dev} +spring.cloud.aws.s3.enabled=false