Skip to content

Commit 31a8b13

Browse files
authored
feature: full-spectrum runtime Docker configuration (via #4965)
* reorganize docker things * Configurator WIP * implement Docker runtime config generator * add tests * update documentation * fix Markdown tables * Move Docker section * add note to README * move up `nodejs` install for more aggressive caching * drop exclusive test * fix missing `DISPLAY_OPERATION_ID`
1 parent b930021 commit 31a8b13

File tree

12 files changed

+596
-84
lines changed

12 files changed

+596
-84
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
/src
66
/swagger-ui-dist-package
77
/test
8+
/node_modules

Dockerfile

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
# Looking for information on environment variables?
2+
# We don't declare them here — take a look at our docs.
3+
# https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md
4+
15
FROM nginx:1.15-alpine
26

7+
RUN apk add nodejs
8+
39
LABEL maintainer="fehguy"
410

5-
ENV VERSION "v2.2.10"
6-
ENV FOLDER "swagger-ui-2.2.10"
7-
ENV API_URL "https://petstore.swagger.io/v2/swagger.json"
8-
ENV API_URLS ""
911
ENV API_KEY "**None**"
1012
ENV OAUTH_CLIENT_ID "**None**"
1113
ENV OAUTH_CLIENT_SECRET "**None**"
@@ -15,16 +17,16 @@ ENV OAUTH_ADDITIONAL_PARAMS "**None**"
1517
ENV SWAGGER_JSON "/app/swagger.json"
1618
ENV PORT 8080
1719
ENV BASE_URL ""
18-
ENV CONFIG_URL ""
1920

20-
COPY nginx.conf /etc/nginx/
21+
COPY ./docker/nginx.conf /etc/nginx/
2122

2223
# copy swagger files to the `/js` folder
2324
COPY ./dist/* /usr/share/nginx/html/
24-
COPY ./docker-run.sh /usr/share/nginx/
25+
COPY ./docker/run.sh /usr/share/nginx/
26+
COPY ./docker/configurator /usr/share/nginx/configurator
2527

26-
RUN chmod +x /usr/share/nginx/docker-run.sh
28+
RUN chmod +x /usr/share/nginx/run.sh
2729

2830
EXPOSE 8080
2931

30-
CMD ["sh", "/usr/share/nginx/docker-run.sh"]
32+
CMD ["sh", "/usr/share/nginx/run.sh"]

dist/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@
3737
<script src="./swagger-ui-standalone-preset.js"> </script>
3838
<script>
3939
window.onload = function() {
40-
41-
// Build a system
40+
// Begin Swagger UI call region
4241
const ui = SwaggerUIBundle({
4342
url: "https://petstore.swagger.io/v2/swagger.json",
4443
dom_id: '#swagger-ui',
@@ -52,6 +51,7 @@
5251
],
5352
layout: "StandaloneLayout"
5453
})
54+
// End Swagger UI call region
5555

5656
window.ui = ui
5757
}

docker/configurator/index.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const fs = require("fs")
2+
const path = require("path")
3+
4+
const translator = require("./translator")
5+
const configSchema = require("./variables")
6+
7+
const START_MARKER = "// Begin Swagger UI call region"
8+
const END_MARKER = "// End Swagger UI call region"
9+
10+
const targetPath = path.normalize(process.cwd() + "/" + process.argv[2])
11+
12+
const originalHtmlContent = fs.readFileSync(targetPath, "utf8")
13+
14+
const startMarkerIndex = originalHtmlContent.indexOf(START_MARKER)
15+
const endMarkerIndex = originalHtmlContent.indexOf(END_MARKER)
16+
17+
const beforeStartMarkerContent = originalHtmlContent.slice(0, startMarkerIndex)
18+
const afterEndMarkerContent = originalHtmlContent.slice(endMarkerIndex + END_MARKER.length)
19+
20+
fs.writeFileSync(targetPath, `${beforeStartMarkerContent}
21+
${START_MARKER}
22+
const ui = SwaggerUIBundle({
23+
${indent(translator(process.env, { injectBaseConfig: true }), 8, 2)}
24+
})
25+
${END_MARKER}
26+
${afterEndMarkerContent}`)
27+
28+
function indent(str, len, fromLine) {
29+
30+
return str
31+
.split("\n")
32+
.map((line, i) => {
33+
if(i + 1 >= fromLine) {
34+
return `${Array(len + 1).join(" ")}${line}`
35+
} else {
36+
return line
37+
}
38+
})
39+
.join("\n")
40+
}

docker/configurator/translator.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Converts an object of environment variables into a Swagger UI config object
2+
const configSchema = require("./variables")
3+
4+
const baseConfig = {
5+
url: {
6+
value: "https://petstore.swagger.io/v2/swagger.json",
7+
schema: {
8+
type: "string"
9+
}
10+
},
11+
dom_id: {
12+
value: "#swagger-ui",
13+
schema: {
14+
type: "string"
15+
}
16+
},
17+
deepLinking: {
18+
value: "true",
19+
schema: {
20+
type: "boolean"
21+
}
22+
},
23+
presets: {
24+
value: `[\n SwaggerUIBundle.presets.apis,\n SwaggerUIStandalonePreset\n]`,
25+
schema: {
26+
type: "array"
27+
}
28+
},
29+
plugins: {
30+
value: `[\n SwaggerUIBundle.plugins.DownloadUrl\n]`,
31+
schema: {
32+
type: "array"
33+
}
34+
},
35+
layout: {
36+
value: "StandaloneLayout",
37+
schema: {
38+
type: "string"
39+
}
40+
}
41+
}
42+
43+
function objectToKeyValueString(env, { injectBaseConfig = false, schema = configSchema } = {}) {
44+
let valueStorage = injectBaseConfig ? Object.assign({}, baseConfig) : {}
45+
const keys = Object.keys(env)
46+
47+
// Compute an intermediate representation that holds candidate values and schemas.
48+
//
49+
// This is useful for deduping between multiple env keys that set the same
50+
// config variable.
51+
52+
keys.forEach(key => {
53+
const varSchema = schema[key]
54+
const value = env[key]
55+
56+
if(!varSchema) return
57+
58+
const storageContents = valueStorage[varSchema.name]
59+
60+
if(storageContents) {
61+
if(varSchema.legacy === true) {
62+
// If we're looking at a legacy var, it should lose out to any already-set value
63+
return
64+
}
65+
delete valueStorage[varSchema.name]
66+
}
67+
68+
valueStorage[varSchema.name] = {
69+
value,
70+
schema: varSchema
71+
}
72+
})
73+
74+
// Compute a key:value string based on valueStorage's contents.
75+
76+
let result = ""
77+
78+
Object.keys(valueStorage).forEach(key => {
79+
const value = valueStorage[key]
80+
81+
const escapedName = /[^a-zA-Z0-9]/.test(key) ? `"${key}"` : key
82+
83+
if (value.schema.type === "string") {
84+
result += `${escapedName}: "${value.value}",\n`
85+
} else {
86+
result += `${escapedName}: ${value.value === "" ? `undefined` : value.value},\n`
87+
}
88+
})
89+
90+
return result.trim()
91+
}
92+
93+
module.exports = objectToKeyValueString

docker/configurator/variables.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
const standardVariables = {
2+
CONFIG_URL: {
3+
type: "string",
4+
name: "configUrl"
5+
},
6+
DOM_ID: {
7+
type: "string",
8+
name: "dom_id"
9+
},
10+
SPEC: {
11+
type: "object",
12+
name: "spec"
13+
},
14+
URL: {
15+
type: "string",
16+
name: "url"
17+
},
18+
URLS: {
19+
type: "array",
20+
name: "urls"
21+
},
22+
URLS_PRIMARY_NAME: {
23+
type: "string",
24+
name: "urls.primaryName"
25+
},
26+
LAYOUT: {
27+
type: "string",
28+
name: "layout"
29+
},
30+
DEEP_LINKING: {
31+
type: "boolean",
32+
name: "deepLinking"
33+
},
34+
DISPLAY_OPERATION_ID: {
35+
type: "boolean",
36+
name: "displayOperationId"
37+
},
38+
DEFAULT_MODELS_EXPAND_DEPTH: {
39+
type: "number",
40+
name: "defaultModelsExpandDepth"
41+
},
42+
DEFAULT_MODEL_EXPAND_DEPTH: {
43+
type: "number",
44+
name: "defaultModelExpandDepth"
45+
},
46+
DEFAULT_MODEL_RENDERING: {
47+
type: "string",
48+
name: "defaultModelRendering"
49+
},
50+
DISPLAY_REQUEST_DURATION: {
51+
type: "boolean",
52+
name: "displayRequestDuration"
53+
},
54+
DOC_EXPANSION: {
55+
type: "string",
56+
name: "docExpansion"
57+
},
58+
FILTER: {
59+
type: "string",
60+
name: "filter"
61+
},
62+
MAX_DISPLAYED_TAGS: {
63+
type: "number",
64+
name: "maxDisplayedTags"
65+
},
66+
SHOW_EXTENSIONS: {
67+
type: "boolean",
68+
name: "showExtensions"
69+
},
70+
SHOW_COMMON_EXTENSIONS: {
71+
type: "boolean",
72+
name: "showCommonExtensions"
73+
},
74+
OAUTH2_REDIRECT_URL: {
75+
type: "string",
76+
name: "oauth2RedirectUrl"
77+
},
78+
SHOW_MUTATED_REQUEST: {
79+
type: "boolean",
80+
name: "showMutatedRequest"
81+
},
82+
SUPPORTED_SUBMIT_METHODS: {
83+
type: "array",
84+
name: "supportedSubmitMethods"
85+
},
86+
VALIDATOR_URL: {
87+
type: "string",
88+
name: "validatorUrl"
89+
}
90+
}
91+
92+
const legacyVariables = {
93+
API_URL: {
94+
type: "string",
95+
name: "url",
96+
legacy: true
97+
},
98+
API_URLS: {
99+
type: "array",
100+
name: "urls",
101+
legacy: true
102+
}
103+
}
104+
105+
module.exports = Object.assign({}, standardVariables, legacyVariables)
File renamed without changes.

docker-run.sh renamed to docker/run.sh

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#! /bin/sh
22

33
set -e
4-
54
BASE_URL=${BASE_URL:-/}
65
NGINX_ROOT=/usr/share/nginx/html
76
INDEX_FILE=$NGINX_ROOT/index.html
87

8+
node /usr/share/nginx/configurator $INDEX_FILE
9+
910
replace_in_index () {
1011
if [ "$1" != "**None**" ]; then
1112
sed -i "s|/\*||g" $INDEX_FILE
@@ -40,27 +41,6 @@ if [[ -f $SWAGGER_JSON ]]; then
4041
REL_PATH="./$(basename $SWAGGER_JSON)"
4142
sed -i "s|https://petstore.swagger.io/v2/swagger.json|$REL_PATH|g" $INDEX_FILE
4243
sed -i "s|http://example.com/api|$REL_PATH|g" $INDEX_FILE
43-
else
44-
sed -i "s|https://petstore.swagger.io/v2/swagger.json|$API_URL|g" $INDEX_FILE
45-
sed -i "s|http://example.com/api|$API_URL|g" $INDEX_FILE
46-
fi
47-
48-
if [[ -n "$VALIDATOR_URL" ]]; then
49-
sed -i "s|.*validatorUrl:.*$||g" $INDEX_FILE
50-
TMP_VU="$VALIDATOR_URL"
51-
[[ "$VALIDATOR_URL" != "null" && "$VALIDATOR_URL" != "undefined" ]] && TMP_VU="\"${VALIDATOR_URL}\""
52-
sed -i "s|\(url: .*,\)|\1\n validatorUrl: ${TMP_VU},|g" $INDEX_FILE
53-
unset TMP_VU
54-
fi
55-
56-
# replace `url` with `urls` option if API_URLS is set
57-
if [[ -n "$API_URLS" ]]; then
58-
sed -i "s|^\(\s*\)url: .*,|\1urls: $API_URLS,|g" $INDEX_FILE
59-
fi
60-
61-
if [[ -n "$CONFIG_URL" ]]; then
62-
sed -i "s|^\(\s*\)url: .*,|\1configUrl: '$CONFIG_URL',|g" $INDEX_FILE
63-
sed -i "s|^\(\s*\)urls: .*,|\1configUrl: '$CONFIG_URL',|g" $INDEX_FILE
6444
fi
6545

6646
# replace the PORT that nginx listens on if PORT is supplied

0 commit comments

Comments
 (0)