diff --git a/cmd/image-builder/bib_legacy.go b/cmd/image-builder/bib_legacy.go index f55287f2eb..af09cc485f 100644 --- a/cmd/image-builder/bib_legacy.go +++ b/cmd/image-builder/bib_legacy.go @@ -14,7 +14,7 @@ import ( "github.com/osbuild/image-builder/pkg/customizations/kickstart" "github.com/osbuild/image-builder/pkg/depsolvednf" "github.com/osbuild/image-builder/pkg/disk" - "github.com/osbuild/image-builder/pkg/distro/bootc" + bootcdistro "github.com/osbuild/image-builder/pkg/distro/bootc" "github.com/osbuild/image-builder/pkg/distro/defs" "github.com/osbuild/image-builder/pkg/distro/generic" "github.com/osbuild/image-builder/pkg/image" @@ -24,7 +24,7 @@ import ( "github.com/osbuild/image-builder/pkg/platform" "github.com/osbuild/image-builder/pkg/rpmmd" - podman_container "github.com/osbuild/image-builder/pkg/bootc" + "github.com/osbuild/image-builder/pkg/bootc" ) // all possible locations for the bib's distro definitions @@ -61,70 +61,86 @@ type ManifestConfig struct { UseLibrepo bool } -func manifestFromCobraForLegacyISO(imgref, buildImgref, imgTypeStr, rootFs, rpmCacheRoot string, config *blueprint.Blueprint, useLibrepo bool, cntArch arch.Arch) ([]byte, *mTLSConfig, error) { - container, err := podman_container.NewContainer(imgref) - if err != nil { - return nil, nil, err - } - defer func() { - if err := container.Stop(); err != nil { - olog.Printf("ERROR: problem stopping container: %v", err) +func manifestForLegacyISO(imgref, buildImgref, rootFs, rpmCacheRoot string, config *blueprint.Blueprint, useLibrepo bool, cntArch arch.Arch) ([]byte, *mTLSConfig, error) { + var baseCnt, buildCnt *bootc.Container + var sourceinfo, buildSourceinfo *osinfo.Info + if buildImgref == "" { + // no build container: start the base container and also treat it as a + // build container + container, err := bootc.NewContainerWithRepos(imgref) + if err != nil { + return nil, nil, err } - }() + defer func() { + if err := container.Stop(); err != nil { + olog.Printf("error stopping container: %v", err) + } + }() - var rootfsType string - if rootFs != "" { - rootfsType = rootFs - } else { - bic, err := container.InstallConfiguration() + baseCnt = container + buildCnt = container + + sourceinfo, err = osinfo.Load(container.Root()) if err != nil { - return nil, nil, fmt.Errorf("cannot get rootfs type for container: %w", err) - } - rootfsType = bic.Filesystem.Root.Type - if rootfsType == "" { - return nil, nil, fmt.Errorf(`no default root filesystem type specified in container, please use "--rootfs" to set manually`) + return nil, nil, err } - } + buildSourceinfo = sourceinfo - // Gather some data from the containers distro - sourceinfo, err := osinfo.Load(container.Root()) - if err != nil { - return nil, nil, err - } - - buildContainer := container - buildSourceinfo := sourceinfo - startedBuildContainer := false - defer func() { - if startedBuildContainer { - if err := buildContainer.Stop(); err != nil { - olog.Printf("ERROR: problem stopping container: %v", err) - } + buildImgref = imgref + } else { + var err error + // separate build container: start both, but only start the build + // container with repos support + baseCnt, err = bootc.NewContainer(imgref) + if err != nil { + return nil, nil, err } - }() + defer func() { + if err := baseCnt.Stop(); err != nil { + olog.Printf("error stopping base container: %v", err) + } + }() - if buildImgref != "" { - buildContainer, err = podman_container.NewContainer(buildImgref) + buildCnt, err = bootc.NewContainerWithRepos(buildImgref) if err != nil { return nil, nil, err } - startedBuildContainer = true + defer func() { + if err := buildCnt.Stop(); err != nil { + olog.Printf("error stopping build container: %v", err) + } + }() - // Gather some data from the containers distro - buildSourceinfo, err = osinfo.Load(buildContainer.Root()) + sourceinfo, err = osinfo.Load(baseCnt.Root()) if err != nil { return nil, nil, err } + buildSourceinfo, err = osinfo.Load(buildCnt.Root()) + if err != nil { + return nil, nil, err + } + } + + var rootfsType string + if rootFs != "" { + rootfsType = rootFs } else { - buildImgref = imgref + bic, err := baseCnt.InstallConfiguration() + if err != nil { + return nil, nil, fmt.Errorf("cannot get rootfs type for container: %w", err) + } + rootfsType = bic.Filesystem.Root.Type + if rootfsType == "" { + return nil, nil, fmt.Errorf(`no default root filesystem type specified in container, please use "--rootfs" to set manually`) + } } // This is needed just for RHEL and RHSM in most cases, but let's run it every time in case // the image has some non-standard dnf plugins. - if err := buildContainer.InitDNF(); err != nil { + if err := buildCnt.InitDNF(); err != nil { return nil, nil, err } - solver, err := buildContainer.NewContainerSolver(rpmCacheRoot, cntArch, sourceinfo) + solver, err := buildCnt.NewContainerSolver(rpmCacheRoot, cntArch, sourceinfo) if err != nil { return nil, nil, err } @@ -302,7 +318,7 @@ func manifestForISO(c *ManifestConfig, rng *rand.Rand) (*manifest.Manifest, erro if isoCust != nil && isoCust.VolumeID != "" { img.ISOCustomizations.Label = isoCust.VolumeID } else { - img.ISOCustomizations.Label = bootc.LabelForISO(&c.SourceInfo.OSRelease, c.Architecture.String()) + img.ISOCustomizations.Label = bootcdistro.LabelForISO(&c.SourceInfo.OSRelease, c.Architecture.String()) } img.InstallerCustomizations.FIPS = customizations.GetFIPS() img.Kickstart, err = kickstart.New(customizations) @@ -337,8 +353,8 @@ func manifestForISO(c *ManifestConfig, rng *rand.Rand) (*manifest.Manifest, erro img.Kickstart.OSTree = &kickstart.OSTree{ OSName: "default", } - img.InstallerCustomizations.LoraxTemplates = bootc.LoraxTemplates(c.SourceInfo.OSRelease) - img.InstallerCustomizations.LoraxTemplatePackage = bootc.LoraxTemplatePackage(c.SourceInfo.OSRelease) + img.InstallerCustomizations.LoraxTemplates = bootcdistro.LoraxTemplates(c.SourceInfo.OSRelease) + img.InstallerCustomizations.LoraxTemplatePackage = bootcdistro.LoraxTemplatePackage(c.SourceInfo.OSRelease) // see https://github.com/osbuild/bootc-image-builder/issues/733 img.ISOCustomizations.RootfsType = manifest.SquashfsRootfs @@ -351,7 +367,7 @@ func manifestForISO(c *ManifestConfig, rng *rand.Rand) (*manifest.Manifest, erro mf := manifest.New() - foundDistro, foundRunner, err := bootc.GetDistroAndRunner(c.SourceInfo.OSRelease) + foundDistro, foundRunner, err := bootcdistro.GetDistroAndRunner(c.SourceInfo.OSRelease) if err != nil { return nil, fmt.Errorf("failed to infer distro and runner: %w", err) } diff --git a/cmd/image-builder/bib_main.go b/cmd/image-builder/bib_main.go index 618b8637fe..82002510bd 100644 --- a/cmd/image-builder/bib_main.go +++ b/cmd/image-builder/bib_main.go @@ -147,7 +147,7 @@ func bibManifestFromCobra(cmd *cobra.Command, args []string, pbar progress.Progr // The anaconda-iso code is different enough for a separate function if imgTypeStr == "anaconda-iso" || imgTypeStr == "iso" { - return manifestFromCobraForLegacyISO(imgref, buildImgref, imgTypeStr, rootFs, rpmCacheRoot, config, useLibrepo, cntArch) + return manifestForLegacyISO(imgref, buildImgref, rootFs, rpmCacheRoot, config, useLibrepo, cntArch) } bootcInfo, err := bootc.ResolveBootcInfo(imgref) diff --git a/pkg/bib/container/container_test.go b/pkg/bib/container/container_test.go deleted file mode 100644 index f447afa5a9..0000000000 --- a/pkg/bib/container/container_test.go +++ /dev/null @@ -1 +0,0 @@ -package container_test diff --git a/pkg/bib/container/solver.go b/pkg/bib/container/solver.go deleted file mode 100644 index 92b957153d..0000000000 --- a/pkg/bib/container/solver.go +++ /dev/null @@ -1 +0,0 @@ -package container diff --git a/pkg/bootc/resolver.go b/pkg/bootc/resolver.go index d44dbd1819..963aba9034 100644 --- a/pkg/bootc/resolver.go +++ b/pkg/bootc/resolver.go @@ -56,16 +56,42 @@ type Container struct { id string root string arch string - extraOpts []string + storeOpts []string } -// New creates a new running container from the given image reference. -// -// NB: -// - --net host is used to make networking work in a nested container -// - /run/secrets is mounted from the host to make sure RHSM credentials are available +// Initialise a new container from the given image reference. func NewContainer(ref string) (*Container, error) { - extraOpts := []string{} + cnt := &Container{ + ref: ref, + } + if err := cnt.start("none", false); err != nil { + return nil, err + } + return cnt, nil +} + +// Initialise a new container from the given image reference and configure it +// to support initialising DNF repositories. The container will be started with +// host networking and mount secrets from the host if available. +func NewContainerWithRepos(ref string) (*Container, error) { + cnt := &Container{ + ref: ref, + } + if err := cnt.start("host", true); err != nil { + return nil, err + } + return cnt, nil +} + +func (cnt *Container) start(network string, mountSecrets bool) error { + args := []string{ + "run", + "--rm", + "--init", // If sleep infinity is run as PID 1, it doesn't get signals, thus we cannot easily stop the container + "--detach", + "--entrypoint", "sleep", // The entrypoint might be arbitrary, so let's just override it with sleep, we don't want to run anything + } + if isRootless, _ := isPodmanRootless(); isRootless { // When running bc-i-b In a rootless container, its typically the case that /var/lib/containers/storage // is a bind-mount of ~/.local/share/containers/storage, and we can't use this directly with podman @@ -74,75 +100,68 @@ func NewContainer(ref string) (*Container, error) { // static dir "/var/lib/containers/storage/libpod": database configuration mismatch // To avoid this we use an empty graphroot, and point --imagestore at /var/lib/containers/storage. // This means the database is in the right place, and we only look at the image layers in the real store. - extraOpts = append(extraOpts, + cnt.storeOpts = []string{ "--root=/run/osbuild/containers/store", - "--imagestore=/var/lib/containers/storage") + "--imagestore=/var/lib/containers/storage", + } } - const secretDir = "/run/secrets" - secretVolume := fmt.Sprintf("%s:%s", secretDir, secretDir) + args = append(args, cnt.storeOpts...) - args := []string{ - "run", - "--rm", - "--init", // If sleep infinity is run as PID 1, it doesn't get signals, thus we cannot easily stop the container - "--detach", - "--net", "host", // Networking in a nested container doesn't work without re-using this container's network - "--entrypoint", "sleep", // The entrypoint might be arbitrary, so let's just override it with sleep, we don't want to run anything + if network != "" { + args = append(args, "--network", network) } - // Re-mount the secret directory if it exists - if _, err := os.Stat(secretDir); err == nil { - args = append(args, "--volume", secretVolume) - } + if mountSecrets { + const secretDir = "/run/secrets" + secretVolume := fmt.Sprintf("%s:%s", secretDir, secretDir) - args = append(args, extraOpts...) + // Mount the secrets directory only if it exists + if _, err := os.Stat(secretDir); err == nil { + args = append(args, "--volume", secretVolume) + } + } - args = append(args, ref, "infinity") + args = append(args, cnt.ref, "infinity") output, err := exec.Command("podman", args...).Output() if err != nil { if e, ok := err.(*exec.ExitError); ok { - return nil, fmt.Errorf("running %s container failed: %w\nstderr:\n%s", ref, e, e.Stderr) + return fmt.Errorf("running %s container failed: %w\nstderr:\n%s", cnt.ref, e, e.Stderr) } - return nil, fmt.Errorf("running %s container failed with generic error: %w", ref, err) + return fmt.Errorf("running %s container failed with generic error: %w", cnt.ref, err) } - c := &Container{ - ref: ref, - extraOpts: extraOpts, - } - c.id = strings.TrimSpace(string(output)) + cnt.id = strings.TrimSpace(string(output)) // Ensure that the container is stopped when this function errors defer func() { if err != nil { - if stopErr := c.Stop(); stopErr != nil { + if stopErr := cnt.Stop(); stopErr != nil { err = fmt.Errorf("%w\nstopping the container failed too: %s", err, stopErr) } - c = nil } }() // not all containers set {{.Architecture}} so fallback - c.arch, err = findContainerArchInspect(c.id, ref, extraOpts) + cnt.arch, err = findContainerArchInspect(cnt.id, cnt.ref, cnt.storeOpts) if err != nil { - return nil, err + return err } args = []string{"mount"} - args = append(args, extraOpts...) - args = append(args, c.id) + args = append(args, cnt.storeOpts...) + args = append(args, cnt.id) /* #nosec G204 */ output, err = exec.Command("podman", args...).Output() if err != nil { if err, ok := err.(*exec.ExitError); ok { - return nil, fmt.Errorf("mounting %s container failed: %w\nstderr:\n%s", ref, err, err.Stderr) + return fmt.Errorf("mounting %s container failed: %w\nstderr:\n%s", cnt.ref, err, err.Stderr) } - return nil, fmt.Errorf("mounting %s container failed with generic error: %w", ref, err) + return fmt.Errorf("mounting %s container failed with generic error: %w", cnt.ref, err) } - c.root = strings.TrimSpace(string(output)) + cnt.root = strings.TrimSpace(string(output)) - return c, err + return err } // Stop stops the container. Since New() creates a container with --rm, this @@ -150,7 +169,7 @@ func NewContainer(ref string) (*Container, error) { func (c *Container) Stop() error { args := []string{"stop"} - args = append(args, c.extraOpts...) + args = append(args, c.storeOpts...) args = append(args, c.id) /* #nosec G204 */ @@ -159,7 +178,7 @@ func (c *Container) Stop() error { } args = []string{"rm"} - args = append(args, c.extraOpts...) + args = append(args, c.storeOpts...) args = append(args, "--ignore", c.id) // when the container is stopped by podman it may not honor the "--rm" @@ -206,7 +225,7 @@ func (c *Container) ResolveInfo() (*Info, error) { } bootcInfo.UnifiedKernel = unifiedKernel - size, err := getContainerSize(c.ref, c.extraOpts) + size, err := getContainerSize(c.ref, c.storeOpts) if err != nil { return nil, err } @@ -239,7 +258,7 @@ func (c *Container) Arch() string { // Reads a file from the container func (c *Container) ReadFile(path string) ([]byte, error) { args := []string{"exec"} - args = append(args, c.extraOpts...) + args = append(args, c.storeOpts...) args = append(args, c.id, "cat", path) /* #nosec G204 */ @@ -257,7 +276,7 @@ func (c *Container) ReadFile(path string) ([]byte, error) { // CopyInto copies a file into the container. func (c *Container) CopyInto(src, dest string) error { args := []string{"cp"} - args = append(args, c.extraOpts...) + args = append(args, c.storeOpts...) args = append(args, src, c.id+":"+dest) /* #nosec G204 */ @@ -270,7 +289,7 @@ func (c *Container) CopyInto(src, dest string) error { func (c *Container) ExecArgv() []string { args := []string{"podman", "exec"} - args = append(args, c.extraOpts...) + args = append(args, c.storeOpts...) args = append(args, "-i", c.id) return args } @@ -289,7 +308,7 @@ type BootcInstallConfiguration struct { // as given by `bootc install print-configuration` func (c *Container) InstallConfiguration() (BootcInstallConfiguration, error) { args := []string{"exec"} - args = append(args, c.extraOpts...) + args = append(args, c.storeOpts...) args = append(args, c.id, "bootc", "install", "print-configuration") /* #nosec G204 */ @@ -330,7 +349,7 @@ func (c *Container) InstallConfiguration() (BootcInstallConfiguration, error) { // InitrdModules gets the list of modules from the container's initrd func (c *Container) InitrdModules(kver string) ([]string, error) { args := []string{"exec"} - args = append(args, c.extraOpts...) + args = append(args, c.storeOpts...) args = append(args, c.id, "lsinitrd", "--mod", "--kver", kver) /* #nosec G204 */ @@ -348,7 +367,7 @@ func (c *Container) InitrdModules(kver string) ([]string, error) { // UnifiedKernel finds out if the kernel inside the bootc container is unified func (c *Container) UnifiedKernel() (bool, error) { args := []string{"exec"} - args = append(args, c.extraOpts...) + args = append(args, c.storeOpts...) args = append(args, c.id, "bootc", "container", "inspect", "--json") /* #nosec G204 */