Skip to content

Commit bff46ac

Browse files
committed
feat: Add support for default settings at /config/default.yaml
1 parent d9dac91 commit bff46ac

File tree

10 files changed

+120
-12
lines changed

10 files changed

+120
-12
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,16 @@ Copy your SPA resources to `/app/`. All resources in this directory will be serv
4444

4545
### YAML Configuration
4646

47-
The container is configured using YAML files.
47+
The application container is configured via YAML files at startup time.
4848

49-
You can mount your custom configuration file at `/config/config.yaml`. Every specified option in this file will override the default configuration.
49+
If you need to configure some default settings for your application image, you can add a default configuration file to `/config/default.yaml`.
50+
51+
To configure settings at runtime, you can mount your runtime configuration file at `/config/config.yaml`. Every specified setting in this file will override the default setting.
5052

5153
It is also possible to merge multiple configuration files by specifying the `CONFIG_FILES` environment variable like `CONFIG_FILES="file:///config/config1.yaml|file:///config/config2.yaml"`. The configuration options, specified in the first configuration file in that variable, will have priority over the options in later declared files.
5254

53-
The following configuration shows the default values for every available option:
55+
The following configuration shows the default values of this base image for every available setting:
5456
```yaml
55-
# Do not edit or replace this file!
56-
# Instead create a second config file, which overrides these defaults.
5757
default:
5858
# Specifies to which host names this configuration should apply.
5959
server_names:
@@ -114,7 +114,7 @@ default:
114114
style-src: "'self'"
115115
```
116116
117-
Aside to the `default` configuration block, you can also define other blocks, which might define configuration options for special hosts. The non-default blocks will inherit the configured options of the default-block if they are not explicitly redeclared.
117+
Aside to the `default` configuration block, you can also define other blocks, which might define configuration settings for special hosts. The non-default blocks will inherit the configured settings of the default-block if they are not explicitly redeclared.
118118

119119
Example:
120120
```yaml

config/bootstrap.sh

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@ test -n "${APP_ROOT}" || ( echo 'APP_ROOT is not defined!' && false )
44
test -n "${CONFIG_DIR}" || ( echo 'CONFIG_DIR is not defined!' && false )
55

66
if [ -z "${CONFIG_FILES}" ] && [ -f "${CONFIG_DIR}/config.yaml" ]; then
7-
export CONFIG_FILES="file://${CONFIG_DIR}/config.yaml"
7+
CONFIG_FILES="file://${CONFIG_DIR}/config.yaml"
8+
fi
9+
10+
if [ -f "${CONFIG_DIR}/default.yaml" ]; then
11+
if [ -z "${CONFIG_FILES}" ]; then
12+
CONFIG_FILES="file://${CONFIG_DIR}/default.yaml"
13+
else
14+
CONFIG_FILES="${CONFIG_FILES}|file://${CONFIG_DIR}/default.yaml"
15+
fi
816
fi
917

1018
if [ -z "${CONFIG_FILES}" ]; then
11-
export CONFIG_FILES="file://${CONFIG_DIR}/.internal_default.yaml"
19+
CONFIG_FILES="file://${CONFIG_DIR}/.internal_default.yaml"
1220
else
13-
export CONFIG_FILES="merge:${CONFIG_FILES}|file://${CONFIG_DIR}/.internal_default.yaml"
21+
CONFIG_FILES="merge:${CONFIG_FILES}|file://${CONFIG_DIR}/.internal_default.yaml"
1422
fi
1523

1624
rm -rf "${CONFIG_DIR}/.out"

examples/angular-example/Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ RUN npm ci && npm run build:prod
88

99
FROM codecentric/single-page-application-server:1-nginx-stable-alpine
1010

11+
COPY ./default-config.yaml /config/default.yaml
12+
1113
# Usually we would mount this file at runtime, but we will add it to the image for demo purposes.
1214
COPY ./runtime-config.yaml /config/config.yaml
1315

examples/angular-example/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ This is a small Angular example app, which includes a `spa_config.js` to configu
88
2. Add `"src/spa_config.js"` to `.projects["your-project"].architect.build.options.assets` in `angular.json`
99
3. Declare `spaConfig` in the `window` object via the TypeScript definition file `/src/@types/spa_config.d.ts`
1010
4. Add `<script src="./spa_config.js"></script>` to `index.html`
11+
5. Add a `/config/default.yaml` configuration file to the image, which allows 'unsafe-inline' styles. (See `default-config.yaml`, [#angular/26152](https://github.com/angular/angular/issues/26152))
1112

1213
## Build Dockerfile
1314

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
default:
2+
hardening:
3+
content_security_policy:
4+
style-src: "'self' 'unsafe-inline'"
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
11
default:
22
spa_config:
33
appTitle: "Prod Title"
4-
hardening:
5-
content_security_policy:
6-
style-src: "'self' 'unsafe-inline'"
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package de.codecentric.spa.server.tests;
2+
3+
import de.codecentric.spa.server.tests.containers.SpaServerContainer;
4+
import de.codecentric.spa.server.tests.helpers.Http;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.junit.jupiter.api.Test;
7+
import org.testcontainers.shaded.com.google.common.collect.ImmutableMap;
8+
9+
import java.io.IOException;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
13+
@Slf4j
14+
public class DefaultConfigTests {
15+
16+
@Test
17+
public void shouldBeAppliedWithLeastPriority() throws IOException, InterruptedException {
18+
19+
try (var container = new SpaServerContainer(SpaServerContainer.Options.builder()
20+
.indexResourcePath("spa_config/index_with_spa_config_without_hash.html")
21+
.defaultConfigResourcePath("default_config/default.yaml")
22+
.configResourcePath("default_config/config.yaml")
23+
.build())) {
24+
container.start();
25+
26+
var response = Http
27+
.get("http://" + container.getHost() + ":" + container.getMappedPort(SpaServerContainer.DEFAULT_HTTP_PORT));
28+
assertThat(response.statusCode()).isEqualTo(200);
29+
30+
assertThat((String) response.body()).contains(
31+
"<script type=\"text/javascript\" src=\"./spa_config.01c639aa476ec270940bf7d3ebdab1de56a79fdc.js\"></script>"
32+
);
33+
34+
response = Http.get(String.format(
35+
"http://%s:%d/spa_config.01c639aa476ec270940bf7d3ebdab1de56a79fdc.js",
36+
container.getHost(),
37+
container.getMappedPort(SpaServerContainer.DEFAULT_HTTP_PORT)
38+
));
39+
40+
assertThat((String) response.body()).isEqualTo(
41+
"var spaConfig = {\"defaultProperty\":\"This is not overridden\",\"endpoints\":{},\"testProperty\":\"Overridden Value\"}"
42+
);
43+
}
44+
45+
}
46+
47+
@Test
48+
public void shouldBeAppliedWhenConfigFilesEnvironmentVariableIsSet() throws IOException, InterruptedException {
49+
50+
try (var container = new SpaServerContainer(SpaServerContainer.Options.builder()
51+
.indexResourcePath("spa_config/index_with_spa_config_without_hash.html")
52+
.defaultConfigResourcePath("default_config/default.yaml")
53+
.additionalConfigResources(ImmutableMap.of("/config/custom.yaml", "default_config/config.yaml"))
54+
.build())) {
55+
container.addEnv("CONFIG_FILES", "file:///config/custom.yaml");
56+
container.start();
57+
58+
var response = Http
59+
.get("http://" + container.getHost() + ":" + container.getMappedPort(SpaServerContainer.DEFAULT_HTTP_PORT));
60+
assertThat(response.statusCode()).isEqualTo(200);
61+
62+
assertThat((String) response.body()).contains(
63+
"<script type=\"text/javascript\" src=\"./spa_config.01c639aa476ec270940bf7d3ebdab1de56a79fdc.js\"></script>"
64+
);
65+
66+
response = Http.get(String.format(
67+
"http://%s:%d/spa_config.01c639aa476ec270940bf7d3ebdab1de56a79fdc.js",
68+
container.getHost(),
69+
container.getMappedPort(SpaServerContainer.DEFAULT_HTTP_PORT)
70+
));
71+
72+
assertThat((String) response.body()).isEqualTo(
73+
"var spaConfig = {\"defaultProperty\":\"This is not overridden\",\"endpoints\":{},\"testProperty\":\"Overridden Value\"}"
74+
);
75+
}
76+
77+
}
78+
79+
}

tests/src/test/java/de/codecentric/spa/server/tests/containers/SpaServerContainer.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,16 @@ public SpaServerContainer(Options options) {
7777
withExposedPorts(options.exposedPorts);
7878
waitingFor(Wait.forListeningPort());
7979

80+
if (options.defaultConfigResourcePath != null) {
81+
withClasspathResourceMapping(options.defaultConfigResourcePath, "/config/default.yaml", BindMode.READ_ONLY);
82+
}
83+
8084
if (options.configResourcePath != null) {
8185
withClasspathResourceMapping(options.configResourcePath, "/config/config.yaml", BindMode.READ_ONLY);
8286
}
87+
88+
options.additionalConfigResources
89+
.forEach((key, value) -> withClasspathResourceMapping(value, key, BindMode.READ_ONLY));
8390
}
8491

8592
private SpaServerContainer(@NonNull Future image) {
@@ -93,6 +100,9 @@ private SpaServerContainer(@NonNull Future image) {
93100
@Builder
94101
public static class Options {
95102
public String configResourcePath;
103+
public String defaultConfigResourcePath;
104+
@Builder.Default
105+
public Map<String, String> additionalConfigResources = ImmutableMap.of();
96106
@Builder.Default
97107
public String indexResourcePath = "simple_spa/index.html";
98108
public String spaConfigFileName;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
default:
2+
spa_config:
3+
testProperty: "Overridden Value"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
default:
2+
spa_config:
3+
defaultProperty: "This is not overridden"
4+
testProperty: "Default Value"

0 commit comments

Comments
 (0)