Skip to content

Commit fc01b01

Browse files
committed
Merge branch '3.4.x'
2 parents 6fcbf14 + e01a23b commit fc01b01

File tree

2 files changed

+73
-2
lines changed
  • spring-boot-project/spring-boot-autoconfigure/src

2 files changed

+73
-2
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/FileWatcher.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ void watch(Set<Path> paths, Runnable action) {
100100

101101
private static Path resolveSymlinkIfNecessary(Path path) throws IOException {
102102
if (Files.isSymbolicLink(path)) {
103-
Path target = Files.readSymbolicLink(path);
103+
Path target = path.resolveSibling(Files.readSymbolicLink(path));
104104
return resolveSymlinkIfNecessary(target);
105105
}
106106
return path;

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/FileWatcherTests.java

+72-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,8 @@
3131
import org.junit.jupiter.api.Test;
3232
import org.junit.jupiter.api.io.TempDir;
3333

34+
import org.springframework.util.FileSystemUtils;
35+
3436
import static org.assertj.core.api.Assertions.assertThat;
3537
import static org.assertj.core.api.Assertions.assertThatCode;
3638
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -40,6 +42,7 @@
4042
* Tests for {@link FileWatcher}.
4143
*
4244
* @author Moritz Halbritter
45+
* @author Brian Clozel
4346
*/
4447
class FileWatcherTests {
4548

@@ -191,6 +194,74 @@ void testRelativeDirectories() throws Exception {
191194
}
192195
}
193196

197+
/*
198+
* Replicating a letsencrypt folder structure like:
199+
* "/folder/live/certname/privkey.pem -> ../../archive/certname/privkey32.pem"
200+
*/
201+
@Test
202+
void shouldFollowRelativePathSymlinks(@TempDir Path tempDir) throws Exception {
203+
Path folder = tempDir.resolve("folder");
204+
Path live = folder.resolve("live").resolve("certname");
205+
Path archive = folder.resolve("archive").resolve("certname");
206+
Path link = live.resolve("privkey.pem");
207+
Path targetFile = archive.resolve("privkey32.pem");
208+
Files.createDirectories(live);
209+
Files.createDirectories(archive);
210+
Files.createFile(targetFile);
211+
Path relativePath = Path.of("../../archive/certname/privkey32.pem");
212+
Files.createSymbolicLink(link, relativePath);
213+
try {
214+
WaitingCallback callback = new WaitingCallback();
215+
this.fileWatcher.watch(Set.of(link), callback);
216+
Files.writeString(targetFile, "Some content");
217+
callback.expectChanges();
218+
}
219+
finally {
220+
FileSystemUtils.deleteRecursively(folder);
221+
}
222+
}
223+
224+
/*
225+
* Replicating a k8s configmap folder structure like:
226+
* "secret.txt -> ..data/secret.txt",
227+
* "..data/ -> ..a72e81ff-f0e1-41d8-a19b-068d3d1d4e2f/",
228+
* "..a72e81ff-f0e1-41d8-a19b-068d3d1d4e2f/secret.txt"
229+
*
230+
* After a secret update, this will look like: "secret.txt -> ..data/secret.txt",
231+
* "..data/ -> ..bba2a61f-ce04-4c35-93aa-e455110d4487/",
232+
* "..bba2a61f-ce04-4c35-93aa-e455110d4487/secret.txt"
233+
*/
234+
@Test
235+
void shouldTriggerOnConfigMapUpdates(@TempDir Path tempDir) throws Exception {
236+
Path configMap1 = createConfigMap(tempDir, "secret.txt");
237+
Path configMap2 = createConfigMap(tempDir, "secret.txt");
238+
Path data = tempDir.resolve("..data");
239+
Files.createSymbolicLink(data, configMap1);
240+
Path secretFile = tempDir.resolve("secret.txt");
241+
Files.createSymbolicLink(secretFile, data.resolve("secret.txt"));
242+
try {
243+
WaitingCallback callback = new WaitingCallback();
244+
this.fileWatcher.watch(Set.of(secretFile), callback);
245+
Files.delete(data);
246+
Files.createSymbolicLink(data, configMap2);
247+
FileSystemUtils.deleteRecursively(configMap1);
248+
callback.expectChanges();
249+
}
250+
finally {
251+
FileSystemUtils.deleteRecursively(configMap2);
252+
Files.delete(data);
253+
Files.delete(secretFile);
254+
}
255+
}
256+
257+
Path createConfigMap(Path parentDir, String secretFileName) throws IOException {
258+
Path configMapFolder = parentDir.resolve(".." + UUID.randomUUID());
259+
Files.createDirectory(configMapFolder);
260+
Path secret = configMapFolder.resolve(secretFileName);
261+
Files.createFile(secret);
262+
return configMapFolder;
263+
}
264+
194265
private static final class WaitingCallback implements Runnable {
195266

196267
private final CountDownLatch latch = new CountDownLatch(1);

0 commit comments

Comments
 (0)