Skip to content

Commit fa1115c

Browse files
committed
tmp
Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>
1 parent 5c7b533 commit fa1115c

2 files changed

Lines changed: 129 additions & 198 deletions

File tree

targets/linux/deb/distro/container.go

Lines changed: 128 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,19 @@ import (
1010
"github.com/project-dalec/dalec/targets"
1111
)
1212

13-
type BuildDistrolessContainerInput struct {
14-
Config *Config
15-
Client gwclient.Client // Replace with interface.
16-
Worker llb.State
17-
SOpt dalec.SourceOpts
18-
Spec *dalec.Spec
19-
Target string
20-
DebSt llb.State // Why is this DebSt?
21-
Opts []llb.ConstraintsOpt
13+
type BuildContainerInput struct {
14+
Config *Config
15+
DefaultOutputImage string
16+
Client gwclient.Client // Replace with interface.
17+
Worker llb.State
18+
SOpt dalec.SourceOpts
19+
Spec *dalec.Spec
20+
Target string
21+
DebSt llb.State // Why is this DebSt?
22+
Opts []llb.ConstraintsOpt
2223
}
2324

24-
func BuildDistrolessContainer(ctx context.Context, input BuildDistrolessContainerInput) llb.State {
25+
func BuildDistrolessContainer(ctx context.Context, input BuildContainerInput) llb.State {
2526
opts := append(input.Opts, frontend.IgnoreCache(input.Client), dalec.ProgressGroup("Build Container Image"))
2627

2728
// Those base repos come from distro configuration.
@@ -32,23 +33,8 @@ func BuildDistrolessContainer(ctx context.Context, input BuildDistrolessContaine
3233

3334
withRepos := input.Config.RepoMounts(repos, input.SOpt, opts...)
3435

35-
// Step 1: Bootstrap base image structure from scratch
36-
baseImg := llb.Scratch().
37-
File(llb.Mkdir("/etc", 0o755), opts...).
38-
File(llb.Mkdir("/etc/apt", 0o755), opts...).
39-
File(llb.Mkdir("/etc/apt/apt.conf.d", 0o755), opts...).
40-
File(llb.Mkdir("/etc/apt/preferences.d", 0o755), opts...).
41-
File(llb.Mkdir("/etc/apt/sources.list.d", 0o755), opts...).
42-
File(llb.Mkdir("/var", 0o755), opts...).
43-
File(llb.Mkdir("/var/cache", 0o755), opts...).
44-
File(llb.Mkdir("/var/cache/apt", 0o755), opts...).
45-
File(llb.Mkdir("/var/cache/apt/archives", 0o755), opts...).
46-
File(llb.Mkdir("/var/lib", 0o755), opts...).
47-
File(llb.Mkdir("/var/lib/dpkg", 0o755), opts...).
48-
File(llb.Mkfile("/var/lib/dpkg/status", 0o644, []byte{}), opts...)
49-
50-
// Step 2: Build base packages if configured
51-
var basePkg llb.State
36+
basePkg := llb.Scratch()
37+
5238
if len(input.Config.BasePackages) > 0 {
5339
runtimePkgs := make(dalec.PackageDependencyList, len(input.Config.BasePackages))
5440
for _, pkgName := range input.Config.BasePackages {
@@ -64,225 +50,170 @@ func BuildDistrolessContainer(ctx context.Context, input BuildDistrolessContaine
6450
Runtime: runtimePkgs,
6551
},
6652
}
53+
6754
basePkg = input.Config.BuildPkg(ctx, input.Client, input.SOpt, basePkgSpec, input.Target, opts...)
55+
}
56+
57+
baseImg := llb.Scratch()
58+
59+
if input.DefaultOutputImage != "" {
60+
baseImg = llb.Image(input.DefaultOutputImage, llb.WithMetaResolver(input.SOpt.Resolver), dalec.WithConstraints(opts...))
61+
}
62+
63+
// Allow spec to override the base image entirely
64+
bi, err := input.Spec.GetSingleBase(input.Target)
65+
if err != nil {
66+
return dalec.ErrorState(llb.Scratch(), err)
67+
}
68+
if bi != nil {
69+
baseImg = bi.ToState(input.SOpt, opts...)
6870
} else {
69-
basePkg = llb.Scratch()
71+
baseImg = baseImg.File(llb.Mkdir("/etc", 0o755), opts...).
72+
File(llb.Mkdir("/etc/apt", 0o755), opts...).
73+
File(llb.Mkdir("/etc/apt/apt.conf.d", 0o755), opts...).
74+
File(llb.Mkdir("/etc/apt/preferences.d", 0o755), opts...).
75+
File(llb.Mkdir("/etc/apt/sources.list.d", 0o755), opts...).
76+
File(llb.Mkdir("/var", 0o755), opts...).
77+
File(llb.Mkdir("/var/cache", 0o755), opts...).
78+
File(llb.Mkdir("/var/cache/apt", 0o755), opts...).
79+
File(llb.Mkdir("/var/cache/apt/archives", 0o755), opts...).
80+
File(llb.Mkdir("/var/lib", 0o755), opts...).
81+
File(llb.Mkdir("/var/lib/dpkg", 0o755), opts...).
82+
File(llb.Mkfile("/var/lib/dpkg/status", 0o644, []byte{}), opts...)
7083
}
7184

72-
// Step 3: Use worker to download all packages + deps and install into baseImg
73-
// Worker has apt-get, dpkg, etc. while baseImg is just empty directories
74-
const installScript = `#!/bin/sh
75-
set -ex
76-
# cache-bust-v2
85+
installScript := `#!/bin/sh
86+
set -exu
87+
88+
rootfs=/tmp/rootfs
89+
apt_archives=/var/cache/apt/archives
7790
7891
# Ensure apt cache directory exists
79-
mkdir -p /var/cache/apt/archives
80-
81-
# Copy local packages (base + spec) to apt cache
82-
cp /base-packages/*.deb /var/cache/apt/archives/
83-
cp /spec-packages/*.deb /var/cache/apt/archives/
84-
85-
apt-get update
86-
87-
# Download essential packages and all dependencies for our local packages
88-
# Point apt at the empty target dpkg status so it thinks nothing is installed,
89-
# which makes it download the full dependency tree and resolve conflicts properly.
90-
essential=$(dpkg-query -Wf '${Package} ${Essential}\n' | awk '$2 == "yes" {print $1}')
91-
# Get names of local packages so we can exclude them from apt-get install
92-
local_pkgs=$(for f in /var/cache/apt/archives/*.deb; do dpkg-deb -f "$f" Package 2>/dev/null; done | sort -u)
93-
local_deps=$(for f in /var/cache/apt/archives/*.deb; do dpkg-deb -f "$f" Depends 2>/dev/null; done | tr ',' '\n' | sed 's/([^)]*)//g; s/|.*//; s/ //g' | grep -v '^$' | sort -u)
94-
# Filter out deps that are satisfied by local packages (they aren't in apt repos)
95-
echo "$local_pkgs" > /tmp/local_pkg_names
96-
filtered_deps=$(echo "$local_deps" | grep -vxFf /tmp/local_pkg_names || true)
97-
apt-get -o Dir::State::status=/tmp/rootfs/var/lib/dpkg/status \
98-
--yes --download-only install $essential $filtered_deps
99-
100-
# Extract all packages into the target rootfs
101-
for f in /var/cache/apt/archives/*.deb; do
102-
dpkg-deb --extract "$f" /tmp/rootfs
92+
mkdir -p "${apt_archives}"
93+
94+
apt update
95+
96+
# Upgrade worker so we pull latest package versions.
97+
apt dist-upgrade -y
98+
99+
essential_packages=$(dpkg-query -Wf '${Package} ${Essential}\n' | awk '$2 == "yes" {print $1}')
100+
local_package_files=$(ls /base-packages/*.deb /spec-packages/*.deb)
101+
102+
# Extract dependencies of local packages for downloading.
103+
local_packages_dependencies=$(for f in ${local_package_files}; do dpkg-deb -f "${f}" Depends 2>/dev/null; done | tr ',' '\n' | sed 's/([^)]*)//g; s/|.*//; s/ //g' | grep -v '^$' | sort -u)
104+
105+
# Get names of local packages so we can exclude them from apt-get install.
106+
local_package_names=$(for f in ${local_package_files}; do dpkg-deb -f "${f}" Package 2>/dev/null; done | sort -u)
107+
108+
# Spec packages may depend on base packages, so we need to filter to only download remaining packages, since downloading local packages
109+
# would fail.
110+
dependencies_to_download=$(echo "${local_packages_dependencies}" | grep -vxF "${local_package_names}")
111+
112+
# Use state file from target image so we only download necessary packages. If target image is a base image, less packages should
113+
# be downloaded.
114+
apt-get -o Dir::State::status="${rootfs}/var/lib/dpkg/status" \
115+
--yes --download-only install ${essential_packages} ${dependencies_to_download}
116+
117+
# Extract all packages into the target rootfs.
118+
#
119+
# Extract base-files first to establish merged-usr symlinks (/bin -> usr/bin, etc.)
120+
# before other packages create those paths as real directories, which would
121+
# cause tar to fail when base-files tries to create the symlinks later.
122+
base_files_package=$(ls "${apt_archives}"/base-files_*.deb)
123+
for f in ${base_files_package} ${local_package_files} $(ls "${apt_archives}"/*.deb | grep -vxF "${base_files_package}"); do
124+
dpkg-deb --extract "${f}" "${rootfs}"
125+
done
126+
127+
# Fix merged-usr: on Noble+, /bin, /sbin, /lib should be symlinks to usr/bin, usr/sbin, usr/lib
128+
# but dpkg-deb --extract may recreate them as real directories.
129+
for dir in bin sbin lib; do
130+
if [ -d "${rootfs}/usr/${dir}" ] && [ -d "${rootfs}/${dir}" ] && [ ! -L "${rootfs}/${dir}" ]; then
131+
cp -a "${rootfs}/${dir}"/* "${rootfs}/usr/${dir}/" 2>/dev/null || true
132+
rm -rf "${rootfs}/${dir}"
133+
ln -s "usr/${dir}" "${rootfs}/${dir}"
134+
fi
103135
done
104136
105137
# dpkg-deb --extract doesn't run postinst scripts, so the /bin/sh symlink
106138
# normally created by update-alternatives is missing. Create it manually.
107-
# Also fix merged-usr: on Noble+, /bin should be a symlink to usr/bin but
108-
# dpkg-deb --extract may create it as a real directory if extraction order
109-
# causes a directory to be created before base-files' symlink.
110-
if [ -d /tmp/rootfs/usr/bin ] && [ -d /tmp/rootfs/bin ] && [ ! -L /tmp/rootfs/bin ]; then
111-
# /bin is a real dir but should be a symlink on merged-usr systems
112-
# Move any contents and replace with symlink
113-
cp -a /tmp/rootfs/bin/* /tmp/rootfs/usr/bin/ 2>/dev/null || true
114-
rm -rf /tmp/rootfs/bin
115-
ln -s usr/bin /tmp/rootfs/bin
116-
fi
117-
if [ -d /tmp/rootfs/usr/sbin ] && [ -d /tmp/rootfs/sbin ] && [ ! -L /tmp/rootfs/sbin ]; then
118-
cp -a /tmp/rootfs/sbin/* /tmp/rootfs/usr/sbin/ 2>/dev/null || true
119-
rm -rf /tmp/rootfs/sbin
120-
ln -s usr/sbin /tmp/rootfs/sbin
121-
fi
122-
if [ -d /tmp/rootfs/usr/lib ] && [ -d /tmp/rootfs/lib ] && [ ! -L /tmp/rootfs/lib ]; then
123-
cp -a /tmp/rootfs/lib/* /tmp/rootfs/usr/lib/ 2>/dev/null || true
124-
rm -rf /tmp/rootfs/lib
125-
ln -s usr/lib /tmp/rootfs/lib
126-
fi
127-
if [ ! -e /tmp/rootfs/usr/bin/sh ]; then
128-
if [ -x /tmp/rootfs/usr/bin/dash ]; then
129-
ln -s dash /tmp/rootfs/usr/bin/sh
130-
elif [ -x /tmp/rootfs/bin/dash ]; then
131-
ln -s dash /tmp/rootfs/bin/sh
132-
fi
139+
if [ ! -e "${rootfs}/usr/bin/sh" ] && [ ! -e "${rootfs}/bin/sh" ]; then
140+
ln -s dash "${rootfs}/usr/bin/sh"
133141
fi
134142
135143
# Remove usrmerge package - our merged-usr fixup above already handles this,
136144
# and usrmerge's postinst fails on overlayfs (which BuildKit uses).
137145
# Create a fake dpkg status entry so dpkg thinks it's installed.
138-
for f in /var/cache/apt/archives/usrmerge_*.deb /var/cache/apt/archives/usr-is-merged_*.deb; do
139-
if [ -f "$f" ]; then
140-
pkg=$(dpkg-deb -f "$f" Package)
141-
ver=$(dpkg-deb -f "$f" Version)
142-
arch=$(dpkg-deb -f "$f" Architecture)
143-
printf 'Package: %s\nStatus: install ok installed\nVersion: %s\nArchitecture: %s\nDescription: faked by dalec\n\n' "$pkg" "$ver" "$arch" >> /tmp/rootfs/var/lib/dpkg/status
144-
rm "$f"
145-
fi
146+
#
147+
# This only runs when usrmerge package is not installed in the base image, since only then the deb file will be downloaded.
148+
for f in $(ls "${apt_archives}"/usrmerge_*.deb "${apt_archives}"/usr-is-merged_*.deb 2>/dev/null); do
149+
pkg=$(dpkg-deb -f "${f}" Package)
150+
ver=$(dpkg-deb -f "${f}" Version)
151+
arch=$(dpkg-deb -f "${f}" Architecture)
152+
printf 'Package: %s\nStatus: install ok installed\nVersion: %s\nArchitecture: %s\nDescription: faked by dalec\n\n' "${pkg}" "${ver}" "${arch}" >> "${rootfs}/var/lib/dpkg/status"
153+
rm "${f}"
146154
done
147155
148-
cp /var/cache/apt/archives/*.deb /tmp/rootfs/var/cache/apt/archives/
156+
cp ${local_package_files} "${apt_archives}"/*.deb "${rootfs}${apt_archives}"/
149157
150-
# Copy apt sources from worker into rootfs so the final container can install packages
158+
# Copy apt sources from worker into rootfs so the final container can install packages. Do we want that?
159+
# There is no guarantee that the final image will have access to the same sources worker had (e.g. with mounted repos).
151160
#
152-
# TODO: This is a workaround. For running tests, installing test steps should follow the same logic as here.
153-
cp -a /etc/apt/sources.list /tmp/rootfs/etc/apt/sources.list 2>/dev/null || true
154-
cp -a /etc/apt/sources.list.d/* /tmp/rootfs/etc/apt/sources.list.d/ 2>/dev/null || true
161+
# A the moment this is necessary so we can for example install test dependencies without using worker image.
162+
cp -ar /etc/apt/sources.list* "${rootfs}/etc/apt/"
155163
`
156164

157165
script := llb.Scratch().File(llb.Mkfile("install.sh", 0o755, []byte(installScript)), opts...)
158166

167+
// Use worker to download all packages + deps and install into baseImg.
159168
baseImg = input.Worker.Run(
160169
dalec.WithConstraints(opts...),
161170
llb.AddMount("/tmp/install.sh", script, llb.SourcePath("install.sh")),
162171
llb.AddMount("/base-packages", basePkg, llb.Readonly),
163172
llb.AddMount("/spec-packages", input.DebSt, llb.Readonly),
164173
withRepos,
174+
dalec.WithMountedAptCache(input.Config.AptCachePrefix, opts...),
165175
llb.AddEnv("DEBIAN_FRONTEND", "noninteractive"),
166176
dalec.ShArgs("/tmp/install.sh"),
167177
frontend.IgnoreCache(input.Client, targets.IgnoreCacheKeyContainer),
168178
).AddMount("/tmp/rootfs", baseImg)
169179

170-
// Allow spec to override the base image entirely
171-
bi, err := input.Spec.GetSingleBase(input.Target)
172-
if err != nil {
173-
return dalec.ErrorState(llb.Scratch(), err)
174-
}
175-
if bi != nil {
176-
baseImg = bi.ToState(input.SOpt, opts...)
177-
}
178-
179180
debug := llb.Scratch().File(llb.Mkfile("debug", 0o644, []byte(`debug=2`)), opts...)
180-
debugOpt := llb.AddMount("/etc/dpkg/dpkg.cfg.d/99-dalec-debug", debug, llb.SourcePath("debug"), llb.Readonly)
181181

182-
// Run dpkg --install to properly configure the packages
183-
// Try /usr/bin/sh first (merged-usr), fall back to /bin/sh (non-merged)
182+
// Run dpkg --install to properly configure the packages.
184183
baseImg = baseImg.Run(
185184
dalec.WithConstraints(opts...),
186-
debugOpt,
187-
llb.AddEnv("DEBIAN_FRONTEND", "noninteractive"),
188-
llb.Args([]string{"/bin/sh", "-c", "dpkg --install --force-depends /var/cache/apt/archives/*.deb && rm -rf /var/cache/apt/archives/*.deb"}),
189-
frontend.IgnoreCache(input.Client, targets.IgnoreCacheKeyContainer),
190-
).Root()
191-
192-
return baseImg.With(dalec.InstallPostSymlinks(input.Spec.GetImagePost(input.Target), input.Worker, opts...))
193-
}
194-
195-
func (c *Config) BuildContainer(ctx context.Context, client gwclient.Client, sOpt dalec.SourceOpts, spec *dalec.Spec, targetKey string, debSt llb.State, opts ...llb.ConstraintsOpt) llb.State {
196-
if true {
197-
return BuildDistrolessContainer(ctx, BuildDistrolessContainerInput{
198-
Config: c,
199-
Client: client,
200-
Worker: c.Worker(sOpt, dalec.Platform(sOpt.TargetPlatform), dalec.WithConstraints(opts...)),
201-
SOpt: sOpt,
202-
Spec: spec,
203-
Target: targetKey,
204-
DebSt: debSt,
205-
Opts: opts,
206-
})
207-
}
208-
209-
opts = append(opts, frontend.IgnoreCache(client), dalec.ProgressGroup("Build Container Image"))
210-
211-
baseImg := llb.Image(c.DefaultOutputImage, llb.WithMetaResolver(sOpt.Resolver), dalec.WithConstraints(opts...))
212-
213-
bi, err := spec.GetSingleBase(targetKey)
214-
if err != nil {
215-
return dalec.ErrorState(llb.Scratch(), err)
216-
}
217-
218-
if bi != nil {
219-
baseImg = bi.ToState(sOpt, opts...)
220-
}
221-
222-
// Those base repos come from distro configuration.
223-
repos := dalec.GetExtraRepos(c.ExtraRepos, "install")
224-
225-
// These are user specified via spec.
226-
repos = append(repos, spec.GetInstallRepos(targetKey)...)
227-
228-
withRepos := c.RepoMounts(repos, sOpt, opts...)
229-
230-
debug := llb.Scratch().File(llb.Mkfile("debug", 0o644, []byte(`debug=2`)), opts...)
231-
opts = append(opts, dalec.ProgressGroup("Install spec package"))
232-
233-
// If we have base packages to install, create a meta-package to install them.
234-
if len(c.BasePackages) > 0 {
235-
runtimePkgs := make(dalec.PackageDependencyList, len(c.BasePackages))
236-
for _, pkgName := range c.BasePackages {
237-
runtimePkgs[pkgName] = dalec.PackageConstraints{}
238-
}
239-
basePkgSpec := &dalec.Spec{
240-
Name: "dalec-deb-base-packages",
241-
Packager: "dalec",
242-
Description: "Base Packages for Debian-based Distros",
243-
Version: "0.1",
244-
Revision: "1",
245-
Dependencies: &dalec.PackageDependencies{
246-
Runtime: runtimePkgs,
247-
},
248-
}
249-
250-
basePkg := c.BuildPkg(ctx, client, sOpt, basePkgSpec, targetKey, opts...)
251-
252-
// Update the base image to include the base packages.
253-
// This may include things that are necessary to even install the debSt package.
254-
// So this must be done separately from the debSt package.
255-
opts := append(opts, dalec.ProgressGroup("Install base image packages"))
256-
baseImg = baseImg.Run(
257-
dalec.WithConstraints(opts...),
258-
InstallLocalPkg(basePkg, true, opts...),
259-
dalec.WithMountedAptCache(c.AptCachePrefix, opts...),
260-
).Root()
261-
}
262-
263-
worker := c.Worker(sOpt, dalec.Platform(sOpt.TargetPlatform), dalec.WithConstraints(opts...))
264-
265-
return baseImg.Run(
266-
dalec.WithConstraints(opts...),
267-
withRepos,
268-
dalec.WithMountedAptCache(c.AptCachePrefix, opts...),
269-
// This file makes dpkg give more verbose output which can be useful when things go awry.
270185
llb.AddMount("/etc/dpkg/dpkg.cfg.d/99-dalec-debug", debug, llb.SourcePath("debug"), llb.Readonly),
271186
dalec.RunOptFunc(func(cfg *llb.ExecInfo) {
272187
// Warning: HACK here
273188
// The base ubuntu image has this `excludes` config file which prevents
274189
// installation of a lot of things, including doc files.
275190
// This is mounting over that file with an empty file so that our test suite
276191
// passes (as it is looking at these files).
277-
if !spec.GetArtifacts(targetKey).HasDocs() {
192+
if !input.Spec.GetArtifacts(input.Target).HasDocs() {
278193
return
279194
}
280195

281196
tmp := llb.Scratch().File(llb.Mkfile("tmp", 0o644, nil), opts...)
282197
llb.AddMount("/etc/dpkg/dpkg.cfg.d/excludes", tmp, llb.SourcePath("tmp")).SetRunOption(cfg)
283198
}),
284-
InstallLocalPkg(debSt, true, opts...),
285-
frontend.IgnoreCache(client, targets.IgnoreCacheKeyContainer),
286-
).Root().
287-
With(dalec.InstallPostSymlinks(spec.GetImagePost(targetKey), worker, opts...))
199+
llb.AddEnv("DEBIAN_FRONTEND", "noninteractive"),
200+
llb.Args([]string{"/usr/bin/sh", "-c", "dpkg --install --force-depends /var/cache/apt/archives/*.deb && rm -rf /var/cache/apt/archives/*.deb"}),
201+
frontend.IgnoreCache(input.Client, targets.IgnoreCacheKeyContainer),
202+
).Root()
203+
204+
return baseImg.With(dalec.InstallPostSymlinks(input.Spec.GetImagePost(input.Target), input.Worker, opts...))
205+
}
206+
207+
func (c *Config) BuildContainer(ctx context.Context, client gwclient.Client, sOpt dalec.SourceOpts, spec *dalec.Spec, targetKey string, debSt llb.State, opts ...llb.ConstraintsOpt) llb.State {
208+
return BuildDistrolessContainer(ctx, BuildContainerInput{
209+
Config: c,
210+
DefaultOutputImage: c.DefaultOutputImage,
211+
Client: client,
212+
Worker: c.Worker(sOpt, dalec.Platform(sOpt.TargetPlatform), dalec.WithConstraints(opts...)),
213+
SOpt: sOpt,
214+
Spec: spec,
215+
Target: targetKey,
216+
DebSt: debSt,
217+
Opts: opts,
218+
})
288219
}

0 commit comments

Comments
 (0)