Skip to content

Commit 8bfb5a1

Browse files
authored
Merge branch 'develop' into 6852-produce-c-header-file-for-rust-integration-path
2 parents ddffc2d + ab65625 commit 8bfb5a1

File tree

20 files changed

+675
-65
lines changed

20 files changed

+675
-65
lines changed

java/deployment/cdk/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727

2828
<dependencies>
2929
<!-- AWS Cloud Development Kit -->
30+
<dependency>
31+
<groupId>software.amazon.awssdk</groupId>
32+
<artifactId>ecr</artifactId>
33+
</dependency>
3034
<dependency>
3135
<groupId>software.amazon.awscdk</groupId>
3236
<artifactId>aws-cdk-lib</artifactId>

java/deployment/cdk/src/main/java/sleeper/cdk/SleeperCdkApp.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import software.amazon.awscdk.StackProps;
2222
import software.amazon.awscdk.Tags;
2323
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
24+
import software.amazon.awssdk.services.ecr.EcrClient;
2425
import software.amazon.awssdk.services.s3.S3Client;
2526

2627
import sleeper.core.properties.instance.InstanceProperties;
@@ -41,8 +42,9 @@ public static void main(String[] args) {
4142
.build());
4243

4344
try (S3Client s3Client = S3Client.create();
45+
EcrClient ecrClient = EcrClient.create();
4446
DynamoDbClient dynamoClient = DynamoDbClient.create()) {
45-
SleeperInstanceProps props = SleeperInstanceProps.fromContext(app, s3Client, dynamoClient);
47+
SleeperInstanceProps props = SleeperInstanceProps.fromContext(app, s3Client, ecrClient, dynamoClient);
4648
InstanceProperties instanceProperties = props.getInstanceProperties();
4749
String id = instanceProperties.get(ID);
4850

java/deployment/cdk/src/main/java/sleeper/cdk/SleeperInstanceProps.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import software.amazon.awscdk.Stack;
1919
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
20+
import software.amazon.awssdk.services.ecr.EcrClient;
2021
import software.amazon.awssdk.services.s3.S3Client;
2122
import software.amazon.awssdk.services.s3.internal.BucketUtils;
2223
import software.constructs.Construct;
@@ -79,11 +80,12 @@ public static Builder builder() {
7980
*
8081
* @param configuration the configuration of the instance to deploy
8182
* @param s3Client the S3 client, to scan for jars to deploy and validate the current state
83+
* @param ecrClient the ECR client, to scan for container images to deploy and validate the current state
8284
* @param dynamoClient the DynamoDB client, to validate the current state
8385
* @return the builder
8486
*/
85-
public static Builder builder(SleeperInstanceConfiguration configuration, S3Client s3Client, DynamoDbClient dynamoClient) {
86-
return builder(configuration.getInstanceProperties(), s3Client, dynamoClient)
87+
public static Builder builder(SleeperInstanceConfiguration configuration, S3Client s3Client, EcrClient ecrClient, DynamoDbClient dynamoClient) {
88+
return builder(configuration.getInstanceProperties(), s3Client, ecrClient, dynamoClient)
8789
.tableProperties(configuration.getTableProperties());
8890
}
8991

@@ -92,13 +94,14 @@ public static Builder builder(SleeperInstanceConfiguration configuration, S3Clie
9294
*
9395
* @param instanceProperties the configuration of the instance to deploy
9496
* @param s3Client the S3 client, to scan for jars to deploy and validate the current state
97+
* @param ecrClient the ECR client, to scan for container images to deploy and validate the current state
9598
* @param dynamoClient the DynamoDB client, to validate the current state
9699
* @return the builder
97100
*/
98-
public static Builder builder(InstanceProperties instanceProperties, S3Client s3Client, DynamoDbClient dynamoClient) {
101+
public static Builder builder(InstanceProperties instanceProperties, S3Client s3Client, EcrClient ecrClient, DynamoDbClient dynamoClient) {
99102
return builder()
100103
.instanceProperties(instanceProperties)
101-
.artefacts(SleeperArtefacts.fromProperties(s3Client))
104+
.artefacts(SleeperArtefacts.fromProperties(s3Client, ecrClient))
102105
.newInstanceValidator(new NewInstanceValidator(s3Client, dynamoClient));
103106
}
104107

@@ -108,11 +111,12 @@ public static Builder builder(InstanceProperties instanceProperties, S3Client s3
108111
*
109112
* @param scope the scope to read context variables from
110113
* @param s3Client the S3 client, to scan for jars to deploy and validate the current state
114+
* @param ecrClient the ECR client, to scan for container images to deploy and validate the current state
111115
* @param dynamoClient the DynamoDB client, to validate the current state
112116
* @return the configuration
113117
*/
114-
public static SleeperInstanceProps fromContext(Construct scope, S3Client s3Client, DynamoDbClient dynamoClient) {
115-
return fromContext(CdkContext.from(scope), s3Client, dynamoClient);
118+
public static SleeperInstanceProps fromContext(Construct scope, S3Client s3Client, EcrClient ecrClient, DynamoDbClient dynamoClient) {
119+
return fromContext(CdkContext.from(scope), s3Client, ecrClient, dynamoClient);
116120
}
117121

118122
/**
@@ -121,10 +125,11 @@ public static SleeperInstanceProps fromContext(Construct scope, S3Client s3Clien
121125
*
122126
* @param context the context to read variables from
123127
* @param s3Client the S3 client, to scan for jars to deploy and validate the current state
128+
* @param ecrClient the ECR client, to scan for container images to deploy and validate the current state
124129
* @param dynamoClient the DynamoDB client, to validate the current state
125130
* @return the configuration
126131
*/
127-
public static SleeperInstanceProps fromContext(CdkContext context, S3Client s3Client, DynamoDbClient dynamoClient) {
132+
public static SleeperInstanceProps fromContext(CdkContext context, S3Client s3Client, EcrClient ecrClient, DynamoDbClient dynamoClient) {
128133
Path propertiesFile = Path.of(context.tryGetContext("propertiesfile"));
129134
SleeperInstanceConfiguration configuration = SleeperInstanceConfiguration.fromLocalConfiguration(propertiesFile);
130135
String instanceId = context.tryGetContext("id");
@@ -135,7 +140,7 @@ public static SleeperInstanceProps fromContext(CdkContext context, S3Client s3Cl
135140
if (artefactsId != null) {
136141
configuration.getInstanceProperties().set(ARTEFACTS_DEPLOYMENT_ID, artefactsId);
137142
}
138-
return builder(configuration.getInstanceProperties(), s3Client, dynamoClient)
143+
return builder(configuration.getInstanceProperties(), s3Client, ecrClient, dynamoClient)
139144
.tableProperties(configuration.getTableProperties())
140145
.networkingProvider(scope -> SleeperNetworking.createByContext(scope, context, configuration.getInstanceProperties()))
141146
.validateProperties(context.getBooleanOrDefault("validate", true))

java/deployment/cdk/src/main/java/sleeper/cdk/artefacts/SleeperArtefacts.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
*/
1616
package sleeper.cdk.artefacts;
1717

18+
import software.amazon.awssdk.services.ecr.EcrClient;
1819
import software.amazon.awssdk.services.s3.S3Client;
1920

21+
import sleeper.cdk.artefacts.containers.SleeperContainerImageDigestProvider;
2022
import sleeper.cdk.artefacts.containers.SleeperContainerImages;
2123
import sleeper.cdk.artefacts.containers.SleeperContainerImagesFromProperties;
2224
import sleeper.cdk.artefacts.jars.SleeperJarVersionIdProvider;
@@ -40,29 +42,34 @@ public interface SleeperArtefacts {
4042

4143
/**
4244
* Retrieves existing artefacts from AWS based on settings in the instance properties. Takes container images from
43-
* AWS ECR and jars from S3. Requires an S3 client to look up the version ID of each jar in the S3 bucket.
45+
* AWS ECR and jars from S3. Requires an S3 client to look up the version ID of each jar in the S3 bucket, and an
46+
* ECR client to look up the digest of each container image in ECR.
4447
*
45-
* @param s3Client the S3 client
46-
* @return the artefacts for deployment of Sleeper
48+
* @param s3Client the S3 client
49+
* @param ecrClient the ECR client
50+
* @return the artefacts for deployment of Sleeper
4751
*/
48-
static SleeperArtefacts fromProperties(S3Client s3Client) {
52+
static SleeperArtefacts fromProperties(S3Client s3Client, EcrClient ecrClient) {
4953
return instanceProperties -> new SleeperInstanceArtefacts(instanceProperties,
5054
new SleeperJarsFromProperties(instanceProperties,
5155
SleeperJarVersionIdProvider.from(s3Client, instanceProperties)),
52-
new SleeperContainerImagesFromProperties(instanceProperties));
56+
new SleeperContainerImagesFromProperties(instanceProperties,
57+
SleeperContainerImageDigestProvider.from(ecrClient, instanceProperties)));
5358
}
5459

5560
/**
5661
* Retrieves existing artefacts from AWS based on settings in the instance properties. Takes container images from
57-
* AWS ECR and jars from S3. Requires a provider to look up the version ID of each jar in the S3 bucket.
62+
* AWS ECR and jars from S3. Requires a provider to look up the version ID of each jar in the S3 bucket, and a
63+
* provider to look up the digest of each container image in ECR.
5864
*
5965
* @param versionIdProvider the provider to look up the jar version IDs
66+
* @param digestIdProvider the provider to look up the container image digests
6067
* @return the artefacts for deployment of Sleeper
6168
*/
62-
static SleeperArtefacts fromProperties(SleeperJarVersionIdProvider versionIdProvider) {
69+
static SleeperArtefacts fromProperties(SleeperJarVersionIdProvider versionIdProvider, SleeperContainerImageDigestProvider digestIdProvider) {
6370
return instanceProperties -> new SleeperInstanceArtefacts(instanceProperties,
6471
new SleeperJarsFromProperties(instanceProperties, versionIdProvider),
65-
new SleeperContainerImagesFromProperties(instanceProperties));
72+
new SleeperContainerImagesFromProperties(instanceProperties, digestIdProvider));
6673
}
6774

6875
/**
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright 2022-2025 Crown Copyright
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package sleeper.cdk.artefacts.containers;
17+
18+
import org.slf4j.Logger;
19+
import org.slf4j.LoggerFactory;
20+
import software.amazon.awssdk.services.ecr.EcrClient;
21+
import software.amazon.awssdk.services.ecr.model.DescribeImagesRequest;
22+
import software.amazon.awssdk.services.ecr.model.DescribeImagesResponse;
23+
import software.amazon.awssdk.services.ecr.model.ImageDetail;
24+
import software.amazon.awssdk.services.ecr.model.ImageIdentifier;
25+
26+
import sleeper.core.deploy.DockerDeployment;
27+
import sleeper.core.properties.instance.InstanceProperties;
28+
29+
import java.util.HashMap;
30+
import java.util.Map;
31+
32+
import static sleeper.core.properties.instance.CdkDefinedInstanceProperty.VERSION;
33+
34+
/**
35+
* Finds container images to deploy. Looks up the latest digest for each image in an ECR repository. The
36+
* deployment will be done against a specific digest of each image. It will only check the repository once for each
37+
* image, and you can reuse the same object for multiple Sleeper instances.
38+
*/
39+
public class SleeperContainerImageDigestProvider {
40+
41+
public static final Logger LOGGER = LoggerFactory.getLogger(SleeperContainerImageDigestProvider.class);
42+
43+
private final GetDigest getDigest;
44+
private final Map<DockerDeployment, String> latestDigestByContainer = new HashMap<>();
45+
46+
public SleeperContainerImageDigestProvider(GetDigest getDigest) {
47+
this.getDigest = getDigest;
48+
}
49+
50+
/**
51+
* Creates a provider that looks up the latest digest for each image in an ECR repository. The deployment will be
52+
* done against a specific digest of each image. It will only check the repository once for each image, and you can
53+
* reuse the same object for multiple Sleeper instances.
54+
*
55+
* @param ecrClient the ECR client
56+
* @param instanceProperties the instance properties
57+
* @return an image digest provider
58+
*/
59+
public static SleeperContainerImageDigestProvider from(EcrClient ecrClient, InstanceProperties instanceProperties) {
60+
return new SleeperContainerImageDigestProvider(GetDigest.fromEcrRepository(ecrClient, instanceProperties));
61+
}
62+
63+
/**
64+
* Get the digest of the image in the Docker deployment.
65+
*
66+
* @param deployment the Docker deployment
67+
* @return the digest for the given deployment
68+
*/
69+
public String getDigestToDeploy(DockerDeployment deployment) {
70+
return latestDigestByContainer.computeIfAbsent(deployment, getDigest::getDigest);
71+
}
72+
73+
/**
74+
* Checks the digest for the latest version of a given image in an ECR repository. When we provide the CDK with a
75+
* specific image digest, it can tell when the image has changed even if it still has the same name.
76+
*/
77+
public interface GetDigest {
78+
/**
79+
* Get the digest of the image in the Docker deployment.
80+
*
81+
* @param deployment the Docker deployment
82+
* @return the digest for the given deployment
83+
*/
84+
String getDigest(DockerDeployment deployment);
85+
86+
/**
87+
* Implementation of GetDigest that checks the digest for the latest version of a given image in an ECR
88+
* repository.
89+
*
90+
* @param ecrClient the ECR client
91+
* @param instanceProperties the instance properties
92+
* @return the get digest implementation
93+
*/
94+
static GetDigest fromEcrRepository(EcrClient ecrClient, InstanceProperties instanceProperties) {
95+
return deployment -> {
96+
String image = deployment.getDeploymentName();
97+
LOGGER.info("Checking latest digest for image: {}", image);
98+
99+
DescribeImagesResponse response = ecrClient.describeImages(DescribeImagesRequest.builder()
100+
.repositoryName(deployment.getEcrRepositoryName(instanceProperties))
101+
.imageIds(ImageIdentifier.builder().imageTag(instanceProperties.get(VERSION)).build())
102+
.build());
103+
104+
String digest = response.imageDetails().stream()
105+
.findFirst()
106+
.map(ImageDetail::imageDigest)
107+
.orElseThrow(() -> new RuntimeException("No image digest found!"));
108+
LOGGER.info("Found latest digest for image {}: {}", image, digest);
109+
return digest;
110+
};
111+
}
112+
}
113+
114+
}

java/deployment/cdk/src/main/java/sleeper/cdk/artefacts/containers/SleeperContainerImagesFromProperties.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package sleeper.cdk.artefacts.containers;
1717

18-
import software.amazon.awscdk.services.ecs.ContainerImage;
18+
import software.amazon.awscdk.services.ecs.EcrImage;
1919
import software.amazon.awscdk.services.lambda.DockerImageCode;
2020
import software.amazon.awscdk.services.lambda.EcrImageCodeProps;
2121
import software.constructs.Construct;
@@ -29,17 +29,19 @@
2929
public class SleeperContainerImagesFromProperties implements SleeperContainerImages {
3030

3131
private final InstanceProperties instanceProperties;
32+
private final SleeperContainerImageDigestProvider digest;
3233

33-
public SleeperContainerImagesFromProperties(InstanceProperties instanceProperties) {
34+
public SleeperContainerImagesFromProperties(InstanceProperties instanceProperties, SleeperContainerImageDigestProvider digest) {
3435
this.instanceProperties = instanceProperties;
36+
this.digest = digest;
3537
}
3638

3739
@Override
3840
public SleeperEcsImages ecsImagesAtScope(Construct scope) {
3941
SleeperEcrRepositoriesAtScope repositories = new SleeperEcrRepositoriesAtScope(scope, instanceProperties);
40-
return deployment -> ContainerImage.fromEcrRepository(
42+
return deployment -> EcrImage.fromEcrRepository(
4143
repositories.getRepository(deployment),
42-
instanceProperties.get(VERSION));
44+
digest.getDigestToDeploy(deployment));
4345
}
4446

4547
@Override

java/deployment/cdk/src/test/java/sleeper/cdk/SleeperInstanceIT.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import software.amazon.awscdk.StackProps;
2626

2727
import sleeper.cdk.artefacts.SleeperArtefacts;
28+
import sleeper.cdk.artefacts.containers.SleeperContainerImageDigestProvider;
2829
import sleeper.cdk.artefacts.jars.SleeperJarVersionIdProvider;
2930
import sleeper.cdk.testutil.SleeperInstancePrinter;
3031
import sleeper.core.properties.instance.InstanceProperties;
@@ -69,7 +70,7 @@ private Stack createSleeperInstanceAsRootStack() {
6970
SleeperInstanceProps sleeperProps = SleeperInstanceProps.builder()
7071
.instanceProperties(instanceProperties)
7172
.version("1.2.3")
72-
.artefacts(SleeperArtefacts.fromProperties(jarVersionIds()))
73+
.artefacts(SleeperArtefacts.fromProperties(jarVersionIds(), imageDigest()))
7374
.skipCheckingVersionMatchesProperties(true)
7475
.build();
7576
return SleeperInstance.createAsRootStack(app, "TestInstance", stackProps, sleeperProps);
@@ -78,4 +79,9 @@ private Stack createSleeperInstanceAsRootStack() {
7879
private SleeperJarVersionIdProvider jarVersionIds() {
7980
return new SleeperJarVersionIdProvider(jar -> jar.getArtifactId() + "-test-version");
8081
}
82+
83+
private SleeperContainerImageDigestProvider imageDigest() {
84+
return new SleeperContainerImageDigestProvider(image -> image.getDeploymentName() + "-test-digest");
85+
}
86+
8187
}

0 commit comments

Comments
 (0)