Skip to content

Commit 42f7033

Browse files
committed
Generate Dockerfiles from Selmer templates
Replace the hand-rolled string/vector concatenation in the Dockerfile generators with Selmer templates under resources/templates/: - Dockerfile.tmpl - the outer skeleton (FROM, JDK copy, install section(s), entrypoint, CMD) - lein.tmpl - the leiningen install section - tools-deps.tmpl - the tools-deps install section The templates read like real Dockerfiles: the install RUN commands live in them directly, with the distro-specific dep lists looped in and dynamic values (checksums, signing key, bundled clojure version) interpolated as Selmer placeholders. The build-tool namespaces are reduced to computing that context. Output is identical to before for every image except the combined "latest" image, which loses a stray blank line between ENTRYPOINT and CMD so it matches the single-build-tool images. Verified by regenerating under both babashka (bb run dockerfiles) and the JVM build-images path. bb test and cljfmt pass. Adds selmer/selmer to deps.edn for the JVM path; babashka bundles Selmer.
1 parent 2029b96 commit 42f7033

10 files changed

Lines changed: 160 additions & 144 deletions

File tree

deps.edn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{org.clojure/clojure {:mvn/version "1.12.4"}
33
org.clojure/math.combinatorics {:mvn/version "0.3.2"}
44
org.clojure/core.async {:mvn/version "1.9.865"}
5+
selmer/selmer {:mvn/version "1.12.61"}
56
com.gfredericks/test.chuck {:git/url "https://github.com/gfredericks/test.chuck"
67
:git/sha "ab5c11b013d3526e587dd53a860fa651b3e8a5a7"}}
78

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM {{from}}
2+
3+
{% if copy-java %}ENV JAVA_HOME=/opt/java/openjdk
4+
COPY --from=eclipse-temurin:{{jdk-version}} $JAVA_HOME $JAVA_HOME
5+
ENV PATH="${JAVA_HOME}/bin:${PATH}"
6+
7+
{% endif %}{{body}}
8+
9+
{% if entrypoint %}COPY entrypoint /usr/local/bin/entrypoint
10+
11+
ENTRYPOINT ["entrypoint"]
12+
{% endif %}{{cmd}}

resources/templates/lein.tmpl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
ENV LEIN_VERSION={{lein-version}}
2+
ENV LEIN_INSTALL=/usr/local/bin/
3+
4+
WORKDIR /tmp
5+
6+
# Download the whole repo as an archive
7+
RUN set -eux; \
8+
{% for dep in install-deps %}{{dep}} && \
9+
{% endfor %}mkdir -p $LEIN_INSTALL && \
10+
wget -q https://codeberg.org/leiningen/leiningen/raw/tag/$LEIN_VERSION/bin/lein-pkg && \
11+
echo "Comparing lein-pkg checksum ..." && \
12+
sha256sum lein-pkg && \
13+
echo "{{lein-pkg-hash}} *lein-pkg" | sha256sum -c - && \
14+
mv lein-pkg $LEIN_INSTALL/lein && \
15+
chmod 0755 $LEIN_INSTALL/lein && \
16+
export GNUPGHOME="$(mktemp -d)" && \
17+
export FILENAME_EXT=jar && \
18+
gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys {{gpg-key}} && \
19+
wget -q https://codeberg.org/leiningen/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT && \
20+
wget -q https://codeberg.org/leiningen/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT.asc && \
21+
echo "Verifying file PGP signature..." && \
22+
gpg --batch --verify leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT.asc leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT && \
23+
gpgconf --kill all && \
24+
rm -rf "$GNUPGHOME" leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT.asc && \
25+
mkdir -p /usr/share/java && \
26+
mkdir -p /root/.lein && \
27+
mv leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT /usr/share/java/leiningen-$LEIN_VERSION-standalone.jar{% for dep in uninstall-deps %} && \
28+
{{dep}}{% endfor %}
29+
30+
ENV PATH=$PATH:$LEIN_INSTALL
31+
ENV LEIN_ROOT 1
32+
33+
# Install clojure {{clojure-version}} so users don't have to download it every time
34+
RUN echo '(defproject dummy "" :dependencies [[org.clojure/clojure "{{clojure-version}}"]])' > project.clj \
35+
&& lein deps && rm project.clj
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
ENV CLOJURE_VERSION={{clojure-version}}
2+
3+
WORKDIR /tmp
4+
5+
RUN \
6+
{% for dep in install-deps %}{{dep}} && \
7+
{% endfor %}curl -fsSLO https://download.clojure.org/install/linux-install-$CLOJURE_VERSION.sh && \
8+
sha256sum linux-install-$CLOJURE_VERSION.sh && \
9+
echo "{{install-hash}} *linux-install-$CLOJURE_VERSION.sh" | sha256sum -c - && \
10+
chmod +x linux-install-$CLOJURE_VERSION.sh && \
11+
./linux-install-$CLOJURE_VERSION.sh && \
12+
rm linux-install-$CLOJURE_VERSION.sh && \
13+
clojure -e "(clojure-version)"{% for dep in uninstall-deps %} && \
14+
{{dep}}{% endfor %}
15+
16+
# Docker bug makes rlwrap crash w/o short sleep first
17+
# Bug: https://github.com/moby/moby/issues/28009
18+
# As of 2021-09-10 this bug still exists, despite that issue being closed
19+
COPY rlwrap.retry /usr/local/bin/rlwrap

src/docker_clojure/dockerfile.clj

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[clojure.string :as str]
55
[docker-clojure.dockerfile.lein :as lein]
66
[docker-clojure.dockerfile.tools-deps :as tools-deps]
7-
[docker-clojure.dockerfile.shared :refer [copy-resource-file! entrypoint]]
7+
[docker-clojure.dockerfile.shared :refer [copy-resource-file! render-template]]
88
[docker-clojure.log :refer [log]]))
99

1010
(defn build-dir [{:keys [base-image-tag jdk-version build-tool]}]
@@ -19,40 +19,55 @@
1919
(defn all-prereqs [dir variant]
2020
(tools-deps/prereqs dir variant))
2121

22-
(defn all-contents [installer-hashes variant]
23-
(concat
24-
["" "### INSTALL LEIN ###"]
25-
(lein/install
26-
installer-hashes
27-
(assoc variant :build-tool-version
28-
(get-in variant [:build-tool-versions "lein"])))
29-
["" "### INSTALL TOOLS-DEPS ###"]
30-
(tools-deps/install
31-
installer-hashes
32-
(assoc variant :build-tool-version
33-
(get-in variant [:build-tool-versions "tools-deps"])))
34-
[""]
35-
(entrypoint variant)
36-
["" "CMD [\"-M\", \"--repl\"]"]))
22+
(defn copy-java?
23+
"Debian variants copy the JDK in from an eclipse-temurin image; the temurin
24+
base images already have it."
25+
[{:keys [distro]}]
26+
(contains? #{:debian :debian-slim} (-> distro namespace keyword)))
3727

38-
(defn copy-java-from-temurin-contents
39-
[{:keys [jdk-version] :as _variant}]
40-
["ENV JAVA_HOME=/opt/java/openjdk"
41-
(str "COPY --from=eclipse-temurin:" jdk-version " $JAVA_HOME $JAVA_HOME")
42-
"ENV PATH=\"${JAVA_HOME}/bin:${PATH}\""
43-
""])
28+
(defn entrypoint?
29+
"JDK 16+ ships a `repl` so we install our wrapper entrypoint for it."
30+
[{:keys [jdk-version]}]
31+
(>= jdk-version 16))
4432

45-
(defn contents [installer-hashes {:keys [build-tool distro] :as variant}]
46-
(str/join "\n"
47-
(concat [(format "FROM %s" (:base-image-tag variant))
48-
""]
49-
(case (-> distro namespace keyword)
50-
(:debian :debian-slim) (copy-java-from-temurin-contents variant)
51-
[])
52-
(case build-tool
53-
:docker-clojure.core/all (all-contents installer-hashes variant)
54-
"lein" (lein/contents installer-hashes variant)
55-
"tools-deps" (tools-deps/contents installer-hashes variant)))))
33+
(defn for-build-tool
34+
"Return `variant` with `:build-tool-version` set to the given tool's version.
35+
Single-build-tool variants already carry it; the combined `latest` image
36+
pulls each tool's version from `:build-tool-versions`."
37+
[variant build-tool]
38+
(cond-> variant
39+
(nil? (:build-tool-version variant))
40+
(assoc :build-tool-version (get-in variant [:build-tool-versions build-tool]))))
41+
42+
(defn body
43+
"The build-tool install section(s) that go between the base image setup and
44+
the entrypoint. The combined `latest` image stacks both behind headers."
45+
[installer-hashes {:keys [build-tool] :as variant}]
46+
(case build-tool
47+
"lein" (lein/install installer-hashes variant)
48+
"tools-deps" (tools-deps/install installer-hashes variant)
49+
:docker-clojure.core/all
50+
(str "\n### INSTALL LEIN ###\n"
51+
(lein/install installer-hashes (for-build-tool variant "lein"))
52+
"\n\n### INSTALL TOOLS-DEPS ###\n"
53+
(tools-deps/install installer-hashes (for-build-tool variant "tools-deps")))))
54+
55+
(defn command
56+
[{:keys [build-tool] :as variant}]
57+
(case build-tool
58+
"lein" (lein/command variant)
59+
"tools-deps" (tools-deps/command variant)
60+
:docker-clojure.core/all "CMD [\"-M\", \"--repl\"]"))
61+
62+
(defn contents [installer-hashes variant]
63+
(render-template
64+
"templates/Dockerfile.tmpl"
65+
{:from (:base-image-tag variant)
66+
:copy-java (copy-java? variant)
67+
:jdk-version (:jdk-version variant)
68+
:body (body installer-hashes variant)
69+
:entrypoint (entrypoint? variant)
70+
:cmd (command variant)}))
5671

5772
(defn shared-prereqs [dir {:keys [build-tool]}]
5873
(let [entrypoint (case build-tool
Lines changed: 15 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
(ns docker-clojure.dockerfile.lein
22
(:require [clojure.string :as str]
33
[docker-clojure.dockerfile.shared
4-
:refer [concat-commands entrypoint install-distro-deps
4+
:refer [install-distro-deps render-template
55
uninstall-distro-build-deps]]))
66

77
(defn prereqs [_ _] nil)
@@ -20,6 +20,10 @@
2020

2121
(def uninstall-build-deps (partial uninstall-distro-build-deps distro-deps))
2222

23+
;; Clojure version pre-installed into lein images so users don't download it on
24+
;; first use.
25+
(def ^:const bundled-clojure-version "1.12.1")
26+
2327
(def ^:const old-key "6A2D483DB59437EBB97D09B1040193357D0606ED")
2428
(def ^:const new-key "9D13D9426A0814B3373CF5E3D8A8243577A7859F")
2529

@@ -32,54 +36,16 @@
3236
:else old-key)))
3337

3438
(defn install [installer-hashes {:keys [build-tool-version] :as variant}]
35-
(let [install-dep-cmds (install-deps variant)
36-
uninstall-dep-cmds (uninstall-build-deps variant)]
37-
(-> [(format "ENV LEIN_VERSION=%s" build-tool-version)
38-
"ENV LEIN_INSTALL=/usr/local/bin/"
39-
""
40-
"WORKDIR /tmp"
41-
""
42-
"# Download the whole repo as an archive"
43-
"RUN set -eux; \\"]
44-
(concat-commands install-dep-cmds)
45-
(concat-commands
46-
["mkdir -p $LEIN_INSTALL"
47-
"wget -q https://codeberg.org/leiningen/leiningen/raw/tag/$LEIN_VERSION/bin/lein-pkg"
48-
"echo \"Comparing lein-pkg checksum ...\""
49-
"sha256sum lein-pkg"
50-
(str "echo \"" (get-in installer-hashes ["lein" build-tool-version]) " *lein-pkg\" | sha256sum -c -")
51-
"mv lein-pkg $LEIN_INSTALL/lein"
52-
"chmod 0755 $LEIN_INSTALL/lein"
53-
"export GNUPGHOME=\"$(mktemp -d)\""
54-
"export FILENAME_EXT=jar" ; used to be zip but hopefully it's always jar now?
55-
(str "gpg --batch --keyserver hkps://keyserver.ubuntu.com --recv-keys "
56-
(gpg-key build-tool-version))
57-
"wget -q https://codeberg.org/leiningen/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT"
58-
"wget -q https://codeberg.org/leiningen/leiningen/releases/download/$LEIN_VERSION/leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT.asc"
59-
"echo \"Verifying file PGP signature...\""
60-
"gpg --batch --verify leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT.asc leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT"
61-
"gpgconf --kill all"
62-
"rm -rf \"$GNUPGHOME\" leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT.asc"
63-
"mkdir -p /usr/share/java"
64-
"mkdir -p /root/.lein"
65-
"mv leiningen-$LEIN_VERSION-standalone.$FILENAME_EXT /usr/share/java/leiningen-$LEIN_VERSION-standalone.jar"]
66-
(empty? uninstall-dep-cmds))
67-
(concat-commands uninstall-dep-cmds :end)
68-
(concat
69-
[""
70-
"ENV PATH=$PATH:$LEIN_INSTALL"
71-
"ENV LEIN_ROOT 1"
72-
""
73-
"# Install clojure 1.12.1 so users don't have to download it every time"
74-
"RUN echo '(defproject dummy \"\" :dependencies [[org.clojure/clojure \"1.12.1\"]])' > project.clj \\"
75-
" && lein deps && rm project.clj"])
76-
77-
(->> (remove nil?)))))
39+
(render-template
40+
"templates/lein.tmpl"
41+
{:lein-version build-tool-version
42+
:clojure-version bundled-clojure-version
43+
:lein-pkg-hash (get-in installer-hashes ["lein" build-tool-version])
44+
:gpg-key (gpg-key build-tool-version)
45+
:install-deps (install-deps variant)
46+
:uninstall-deps (uninstall-build-deps variant)}))
7847

7948
(defn command [{:keys [jdk-version]}]
8049
(if (>= jdk-version 16)
81-
["CMD [\"repl\"]"]
82-
["CMD [\"lein\", \"repl\"]"]))
83-
84-
(defn contents [installer-hashes variant]
85-
(concat (install installer-hashes variant) [""] (entrypoint variant) (command variant)))
50+
"CMD [\"repl\"]"
51+
"CMD [\"lein\", \"repl\"]"))

src/docker_clojure/dockerfile/shared.clj

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
(ns docker-clojure.dockerfile.shared
2-
(:require [clojure.string :as str]
3-
[clojure.java.io :as io]))
2+
(:require [clojure.java.io :as io]
3+
[clojure.string :as str]
4+
[selmer.parser :as selmer]
5+
[selmer.util :as selmer-util]))
46

5-
(defn concat-commands [base cmds & [end?]]
6-
(let [commands (if end?
7-
(butlast cmds)
8-
cmds)]
9-
(concat base
10-
(map #(str % " && \\")
11-
commands)
12-
(when end? [(last cmds)]))))
7+
;; Dockerfiles aren't HTML, so don't let Selmer escape any of the values we
8+
;; interpolate (quotes, brackets, etc. must pass through verbatim).
9+
(selmer-util/turn-off-escaping!)
10+
11+
(defn render-template
12+
"Render the Selmer template at resource path `tmpl` with `context`, trimming
13+
any trailing newline so callers control the surrounding whitespace."
14+
[tmpl context]
15+
(-> tmpl io/resource slurp (selmer/render context) str/trim-newline))
1316

1417
(defn get-deps [type distro-deps distro]
1518
(some->> distro namespace keyword (get distro-deps) type))
@@ -61,14 +64,3 @@
6164
dest (io/file build-dir filename)]
6265
(->> src slurp contents-processor (spit dest))
6366
(file-processor dest))))
64-
65-
(defn entrypoint
66-
"This is the same for every build-tool so far, so it's in here. If that
67-
changes move it into the build-tool-specific namespaces (or future protocol)."
68-
[{:keys [jdk-version]}]
69-
(if (>= jdk-version 16)
70-
(concat
71-
["COPY entrypoint /usr/local/bin/entrypoint"]
72-
[""]
73-
["ENTRYPOINT [\"entrypoint\"]"])
74-
nil))
Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
(ns docker-clojure.dockerfile.tools-deps
22
(:require [docker-clojure.dockerfile.shared
3-
:refer [concat-commands copy-resource-file! entrypoint
4-
install-distro-deps uninstall-distro-build-deps]]))
3+
:refer [copy-resource-file! install-distro-deps render-template
4+
uninstall-distro-build-deps]]))
55

66
(defn prereqs [dir _variant]
77
(copy-resource-file! dir "rlwrap.retry" identity
@@ -23,40 +23,15 @@
2323

2424
(def uninstall-build-deps (partial uninstall-distro-build-deps distro-deps))
2525

26-
(def docker-bug-notice
27-
["# Docker bug makes rlwrap crash w/o short sleep first"
28-
"# Bug: https://github.com/moby/moby/issues/28009"
29-
"# As of 2021-09-10 this bug still exists, despite that issue being closed"])
30-
3126
(defn install [installer-hashes {:keys [build-tool-version] :as variant}]
32-
(let [install-dep-cmds (install-deps variant)
33-
uninstall-dep-cmds (uninstall-build-deps variant)]
34-
(-> [(format "ENV CLOJURE_VERSION=%s" build-tool-version)
35-
""
36-
"WORKDIR /tmp"
37-
""
38-
"RUN \\"]
39-
(concat-commands install-dep-cmds)
40-
(concat-commands
41-
["curl -fsSLO https://download.clojure.org/install/linux-install-$CLOJURE_VERSION.sh"
42-
"sha256sum linux-install-$CLOJURE_VERSION.sh"
43-
(str "echo \"" (get-in installer-hashes ["tools-deps" build-tool-version]) " *linux-install-$CLOJURE_VERSION.sh\" | sha256sum -c -")
44-
"chmod +x linux-install-$CLOJURE_VERSION.sh"
45-
"./linux-install-$CLOJURE_VERSION.sh"
46-
"rm linux-install-$CLOJURE_VERSION.sh"
47-
"clojure -e \"(clojure-version)\""] (empty? uninstall-dep-cmds))
48-
(concat-commands uninstall-dep-cmds :end)
49-
(concat [""] docker-bug-notice
50-
["COPY rlwrap.retry /usr/local/bin/rlwrap"])
51-
(->> (remove nil?)))))
27+
(render-template
28+
"templates/tools-deps.tmpl"
29+
{:clojure-version build-tool-version
30+
:install-hash (get-in installer-hashes ["tools-deps" build-tool-version])
31+
:install-deps (install-deps variant)
32+
:uninstall-deps (uninstall-build-deps variant)}))
5233

5334
(defn command [{:keys [jdk-version]}]
5435
(if (>= jdk-version 16)
55-
["CMD [\"-M\", \"--repl\"]"]
56-
[(str "CMD [\"clj\"]")]))
57-
58-
(defn contents [installer-hashes variant]
59-
(concat (install installer-hashes variant)
60-
[""]
61-
(entrypoint variant)
62-
(command variant)))
36+
"CMD [\"-M\", \"--repl\"]"
37+
"CMD [\"clj\"]"))

target/debian-bookworm-25/latest/Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,4 @@ COPY rlwrap.retry /usr/local/bin/rlwrap
7070
COPY entrypoint /usr/local/bin/entrypoint
7171

7272
ENTRYPOINT ["entrypoint"]
73-
7473
CMD ["-M", "--repl"]

test/docker_clojure/dockerfile_test.clj

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,21 @@
2929
:jdk-version 11})
3030
"LABEL "))))
3131
(testing "lein variant includes lein-specific contents"
32-
(with-redefs [lein/contents (constantly ["leiningen vs. the ants"])]
32+
(with-redefs [lein/install (constantly "leiningen vs. the ants")]
3333
(is (str/includes? (contents cfg/installer-hashes
3434
{:base-image-tag "base:foo"
3535
:distro :distro/distro
3636
:build-tool "lein"
37-
:maintainer "Me Myself"})
37+
:maintainer "Me Myself"
38+
:jdk-version 11})
3839
"leiningen vs. the ants"))))
3940
(testing "tools-deps variant includes tools-deps-specific contents"
40-
(with-redefs [tools-deps/contents (constantly
41-
["Tools Deps is not a build tool"])]
41+
(with-redefs [tools-deps/install (constantly
42+
"Tools Deps is not a build tool")]
4243
(is (str/includes? (contents cfg/installer-hashes
4344
{:base-image-tag "base:foo"
4445
:distro :distro/distro
4546
:build-tool "tools-deps"
46-
:maintainer "Me Myself"})
47+
:maintainer "Me Myself"
48+
:jdk-version 11})
4749
"Tools Deps is not a build tool")))))

0 commit comments

Comments
 (0)