Skip to content

Commit eacbeea

Browse files
committed
refactor apt package management and enhance template functionality
1 parent 1d1ff28 commit eacbeea

7 files changed

Lines changed: 315 additions & 58 deletions

File tree

ports/_common/apt.dockerfile

Lines changed: 67 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,74 @@
1+
{{- /*
2+
The base set of apt packages installed into every port. A port may tailor it
3+
when importing this partial:
4+
5+
{{ template "apt.dockerfile" dict "Exclude" (list "eza") }}
6+
drop packages the upstream image's apt does not provide (e.g. the node
7+
image has no eza), or that the base image already ships (the python image
8+
already has python3/python3-pip).
9+
10+
{{ template "apt.dockerfile" dict "Include" (list "neovim") }}
11+
add extra packages on top of the base set.
12+
13+
The final list is sorted so the rendered Dockerfile is deterministic.
14+
*/ -}}
15+
{{- $packages := list
16+
"apt-transport-https"
17+
"bash"
18+
"bash-completion"
19+
"build-essential"
20+
"ca-certificates"
21+
"curl"
22+
"dirmngr"
23+
"eza"
24+
"fd-find"
25+
"git"
26+
"git-lfs"
27+
"gnupg2"
28+
"htop"
29+
"init-system-helpers"
30+
"iproute2"
31+
"jq"
32+
"less"
33+
"libc6"
34+
"libgcc1"
35+
"libgssapi-krb5-2"
36+
"libicu[0-9][0-9]"
37+
"libkrb5-3"
38+
"libstdc++6"
39+
"locales"
40+
"lsb-release"
41+
"lsof"
42+
"make"
43+
"man-db"
44+
"manpages"
45+
"manpages-dev"
46+
"ncdu"
47+
"openssh-client"
48+
"procps"
49+
"psmisc"
50+
"python3"
51+
"python3-pip"
52+
"ripgrep"
53+
"rsync"
54+
"strace"
55+
"sudo"
56+
"tmux"
57+
"tzdata"
58+
"unzip"
59+
"vim"
60+
"wget"
61+
"zip"
62+
"zlib1g"
63+
"zsh"
64+
-}}
65+
{{- $packages = sortStrings (concat (without $packages (optList . "Exclude")) (optList . "Include")) -}}
166
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
267
--mount=type=cache,target=/var/lib/apt,sharing=locked \
368
rm -f /etc/apt/apt.conf.d/docker-clean \
469
&& export DEBIAN_FRONTEND=noninteractive \
570
&& apt-get update \
6-
&& apt-get install -y --no-install-recommends \
7-
apt-transport-https \
8-
bash \
9-
bash-completion \
10-
build-essential \
11-
ca-certificates \
12-
curl \
13-
dirmngr \
14-
eza \
15-
fd-find \
16-
git \
17-
git-lfs \
18-
gnupg2 \
19-
htop \
20-
init-system-helpers \
21-
iproute2 \
22-
jq \
23-
less \
24-
libc6 \
25-
libgcc1 \
26-
libgssapi-krb5-2 \
27-
libicu[0-9][0-9] \
28-
libkrb5-3 \
29-
libstdc++6 \
30-
locales \
31-
lsb-release \
32-
lsof \
33-
make \
34-
man-db \
35-
manpages \
36-
manpages-dev \
37-
ncdu \
38-
openssh-client \
39-
procps \
40-
psmisc \
41-
{{- if .SystemPython }}
42-
python3 \
43-
python3-pip \
44-
{{- end }}
45-
ripgrep \
46-
rsync \
47-
strace \
48-
sudo \
49-
tmux \
50-
tzdata \
51-
unzip \
52-
vim \
53-
wget \
54-
zip \
55-
zlib1g \
56-
zsh \
71+
&& apt-get install -y --no-install-recommends \{{ range $packages }}
72+
{{ . }} \{{ end }}
5773
&& echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen \
5874
&& locale-gen

ports/dev-golang/Dockerfile.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
ARG BASE=docker.io/library/golang
22
FROM ${BASE}
33

4-
{{ template "apt.dockerfile" dict "SystemPython" true }}
4+
{{ template "apt.dockerfile" . }}
55

66
{{ template "node.dockerfile" . }}
77

ports/dev-node/Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
1717
ca-certificates \
1818
curl \
1919
dirmngr \
20-
eza \
2120
fd-find \
2221
git \
2322
git-lfs \

ports/dev-node/Dockerfile.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
ARG BASE=docker.io/library/node
22
FROM ${BASE}
33

4-
{{ template "apt.dockerfile" dict "SystemPython" true }}
4+
{{ template "apt.dockerfile" dict "Exclude" (list "eza") }}
55

66
# The official node image ships a `node` user at UID/GID 1000, which collides
77
# with the dev user the shared "user" partial creates at 1000:1000. Remove it

ports/dev-python/Dockerfile.tmpl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
ARG BASE=docker.io/library/python
22
FROM ${BASE}
33

4-
{{ template "apt.dockerfile" dict "SystemPython" false }}
4+
{{ template "apt.dockerfile" dict "Exclude" (list
5+
"python3"
6+
"python3-pip"
7+
) }}
58

69
{{ template "node.dockerfile" . }}
710

scripts/gen-dockerfiles/main.go

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"os"
1717
"path/filepath"
1818
"regexp"
19+
"sort"
1920
"text/template"
2021
)
2122

@@ -33,9 +34,19 @@ const (
3334
// collapsing keeps the output to a single separating blank line.
3435
var blankRuns = regexp.MustCompile(`\n{3,}`)
3536

36-
// funcs are template helpers. dict builds a map from alternating key/value
37-
// arguments so a Dockerfile can pass options to a shared partial, e.g.
38-
// {{ template "apt.dockerfile" dict "SystemPython" false }}.
37+
// funcs are template helpers shared by every partial.
38+
//
39+
// dict builds a map from alternating key/value arguments so a port can pass
40+
// options to a partial; optList reads one such option back out. list/without/
41+
// concat/sortStrings manipulate string slices so a partial can expose a
42+
// tweakable list — e.g. apt.dockerfile builds its package set from a base list,
43+
// then lets a port drop or add entries:
44+
//
45+
// {{ template "apt.dockerfile" dict "Exclude" (list "eza") }}
46+
// {{ template "apt.dockerfile" dict "Include" (list "neovim") }}
47+
//
48+
// A port needing no options invokes the partial with "." like every other
49+
// partial — optList turns the resulting nil data into empty lists.
3950
var funcs = template.FuncMap{
4051
"dict": func(pairs ...any) (map[string]any, error) {
4152
if len(pairs)%2 != 0 {
@@ -51,6 +62,56 @@ var funcs = template.FuncMap{
5162
}
5263
return m, nil
5364
},
65+
// optList reads a string-slice option from a partial's data (the dict a
66+
// port passes), tolerating nil data and absent keys. This keeps the nil
67+
// check on the receiving side, so a port with no options can invoke the
68+
// partial with "." instead of an empty dict.
69+
"optList": func(data any, key string) []string {
70+
m, ok := data.(map[string]any)
71+
if !ok {
72+
return nil
73+
}
74+
v, _ := m[key].([]string)
75+
return v
76+
},
77+
// list collects its arguments into a slice, e.g. list "a" "b" "c".
78+
"list": func(items ...string) []string {
79+
return items
80+
},
81+
// concat appends every slice into one, skipping nil/absent operands so a
82+
// partial can fold in an optional .Include without a nil guard.
83+
"concat": func(lists ...[]string) []string {
84+
var out []string
85+
for _, l := range lists {
86+
out = append(out, l...)
87+
}
88+
return out
89+
},
90+
// without returns the elements of s that are not in drop. A nil/absent
91+
// drop (an unset .Exclude) leaves s unchanged.
92+
"without": func(s, drop []string) []string {
93+
if len(drop) == 0 {
94+
return s
95+
}
96+
skip := make(map[string]struct{}, len(drop))
97+
for _, d := range drop {
98+
skip[d] = struct{}{}
99+
}
100+
out := make([]string, 0, len(s))
101+
for _, v := range s {
102+
if _, ok := skip[v]; !ok {
103+
out = append(out, v)
104+
}
105+
}
106+
return out
107+
},
108+
// sortStrings returns a sorted copy, so a partial's rendered list stays
109+
// deterministic regardless of where .Include entries were appended.
110+
"sortStrings": func(s []string) []string {
111+
out := append([]string(nil), s...)
112+
sort.Strings(out)
113+
return out
114+
},
54115
}
55116

56117
func main() {

0 commit comments

Comments
 (0)