-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathcore.clj
More file actions
238 lines (214 loc) · 8.94 KB
/
core.clj
File metadata and controls
238 lines (214 loc) · 8.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
(ns docker-clojure.core
(:require
[clojure.java.io :as io]
[clojure.java.shell :refer [sh with-sh-dir]]
[clojure.math.combinatorics :as combo]
[clojure.spec.alpha :as s]
[clojure.string :as str]
[clojure.core.async :refer [<!! chan to-chan! pipeline-blocking] :as async]
[docker-clojure.config :as cfg]
[docker-clojure.dockerfile :as df]
[docker-clojure.manifest :as manifest]
[docker-clojure.util :refer [get-or-default default-docker-tag
full-docker-tag]]
[docker-clojure.log :refer [log] :as logger]
[clojure.edn :as edn]))
(defn exclude-variant?
"Returns true if the map `variant` contains every key-value pair in the map
`exclusion`. `variant` may contain additional keys that are not in
`exclusion`. Some values of `exclusion` can also be a predicate of one
argument which is then tested against the respective value from `variant`.
Returns false if any of the keys in `exclusions` are missing from `variant` or
have different values, or the predicate value returned false."
[variant exclusion]
(every? (fn [[k v]]
(if (fn? v)
(v (get variant k))
(= v (get variant k))))
exclusion))
(defn base-image-tag
[base-image jdk-version distro]
(str base-image ":"
(case base-image
"eclipse-temurin" (str jdk-version "-jdk-")
"debian" ""
"-")
(name distro)))
(defn exclude?
"Returns true if `variant` matches one of `exclusions` elements (meaning
`(contains-every-key-value? variant exclusion)` returns true)."
[exclusions variant]
(some (partial exclude-variant? variant) exclusions))
(s/def ::variant
(s/keys :req-un [::cfg/jdk-version ::cfg/base-image ::cfg/base-image-tag
::cfg/distro ::cfg/build-tool ::cfg/build-tool-version
::cfg/maintainer ::cfg/docker-tag]
:opt-un [::cfg/build-tool-versions ::cfg/architectures]))
(defn assoc-if
[m pred k v]
(if (pred)
(assoc m k v)
m))
(defn variant-map [[base-image jdk-version distro
[build-tool build-tool-info]]]
(let [variant-arch (get cfg/distro-architectures
(-> distro namespace keyword))
base {:jdk-version jdk-version
:base-image base-image
:base-image-tag (base-image-tag base-image
jdk-version distro)
:distro distro
:build-tool build-tool
:build-tool-version (:version build-tool-info)
:maintainer (str/join " & " cfg/maintainers)}]
(cond-> base
true (assoc :docker-tag (default-docker-tag base))
variant-arch (assoc :architectures variant-arch))))
(defn pull-image [image]
(sh "docker" "pull" image))
(defn generate-dockerfile! [variant]
(let [build-dir (df/build-dir variant)
filename "Dockerfile"]
(log "Generating" (str build-dir "/" filename))
(df/write-file build-dir filename variant)
(assoc variant
:build-dir build-dir
:dockerfile filename)))
(defn build-image [{:keys [docker-tag base-image architectures] :as variant}]
(let [image-tag (str "clojure:" docker-tag)
_ (log "Pulling base image" base-image)
_ (pull-image base-image)
{:keys [dockerfile build-dir]}
(generate-dockerfile! variant)
host-arch (let [jvm-arch (System/getProperty "os.arch")]
(if (= "aarch64" jvm-arch)
"arm64v8"
jvm-arch))
platform-flag (if (contains? (or architectures
cfg/default-architectures)
host-arch)
nil
(str "--platform=linux/" (first architectures)))
build-cmd (remove nil? ["docker" "buildx" "build" "--no-cache"
"-t" image-tag platform-flag "--load"
"-f" dockerfile "."])]
(apply log "Running" build-cmd)
(let [{:keys [out err exit]}
(with-sh-dir build-dir (apply sh build-cmd))]
(if (zero? exit)
(log "Succeeded building" (str "clojure:" docker-tag))
(log "ERROR building" (str "clojure:" docker-tag ":") err out))))
(log)
[::done variant])
(def latest-variant
"The latest variant is special because we include all 3 build tools via the
[::all] value on the end."
(list (-> cfg/base-images :default first)
cfg/default-jdk-version
(get-or-default cfg/default-distros cfg/default-jdk-version)
[::all]))
(defn image-variant-combinations
[base-images jdk-versions distros build-tools]
(mapcat
(fn [jdk-version]
(let [jdk-base-images (get-or-default base-images jdk-version)]
(mapcat #(combo/cartesian-product #{%}
#{jdk-version}
(get-or-default distros %)
build-tools)
jdk-base-images)))
jdk-versions))
(defn image-variants
[base-images jdk-versions distros build-tools]
(into []
(comp
(map variant-map)
(remove #(= ::s/invalid (s/conform ::variant %))))
(image-variant-combinations base-images jdk-versions distros
build-tools)))
(defn rand-delay
"Runs argument f w/ any supplied args after a random delay of 100-1000 ms"
[f & args]
(let [rand-time (+ 100 (rand-int 900))]
(Thread/sleep rand-time)
(apply f args)))
(defn build-images
[parallelization variants]
(log "Building images" parallelization "at a time")
(let [variants-ch (to-chan! variants)
builds-ch (chan parallelization)]
;; Kick off builds with a random delay so we don't have Docker race
;; conditions (e.g. build container name collisions)
(async/thread (pipeline-blocking parallelization builds-ch
(map (partial rand-delay build-image))
variants-ch))
(while (<!! builds-ch))))
(defn generate-dockerfiles! [variants]
(log "Generated" (count variants) "variants")
(doseq [variant variants]
(generate-dockerfile! variant)))
(defn valid-variants []
(remove (partial exclude? cfg/exclusions)
(image-variants cfg/base-images cfg/jdk-versions cfg/distros
cfg/*build-tools*)))
(defn generate-manifest! [variants args]
(let [git-head (->> ["git" "rev-parse" "HEAD"] (apply sh) :out)
target-file (or (first args) :stdout)
manifest (manifest/generate {:maintainers cfg/maintainers
:architectures cfg/default-architectures
:git-repo cfg/git-repo}
git-head variants)]
(log "Writing manifest of" (count variants) "variants to" target-file "...")
(let [output-writer (if (= :stdout target-file)
*out*
(io/writer target-file))]
(.write output-writer manifest)
(when (not= :stdout target-file)
(.close output-writer)))))
(defn sort-variants
[variants]
(sort
(fn [v1 v2]
(cond
(= "latest" (:docker-tag v1)) -1
(= "latest" (:docker-tag v2)) 1
:else (let [c (compare (:jdk-version v1) (:jdk-version v2))]
(if (not= c 0)
c
(let [c (compare (full-docker-tag v1) (full-docker-tag v2))]
(if (not= c 0)
c
(throw
(ex-info "No two variants should have the same full Docker tag"
{:v1 v1, :v2 v2}))))))))
variants))
(defn generate-variants
[args]
(let [key-vals (->> args
(map #(if (str/starts-with? % ":")
(edn/read-string %)
%)) ; TODO: Maybe replace this with bb/cli
(partition 2))
variant-filter #(or
(empty? key-vals)
(every? (fn [[k v]]
(= (get % k) v))
key-vals))]
(filter variant-filter (valid-variants))))
(defn run
"Entrypoint for exec-fn."
[{:keys [cmd args parallelization]}]
(logger/start)
(let [variants (generate-variants args)]
(case cmd
:clean (df/clean-all)
:dockerfiles (generate-dockerfiles! variants)
:manifest (-> variants sort-variants (generate-manifest! args))
:build-images (build-images parallelization variants)))
(logger/stop))
(defn -main
[& cmd-args]
(let [[cmd & args] cmd-args]
(run {:cmd (if cmd (keyword cmd) :build-images)
:args args
:parallelization 4})))