Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bib: run "dnf" inside the container again #670

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions bib/cmd/bootc-image-builder/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@ type ManifestConfig struct {
// Extracted information about the source container image
SourceInfo *source.Info

// Path to the tree that contains /etc used for osbuild-depsolve-dnf
DepsolverRootDir string

// RootFSType specifies the filesystem type for the root partition
RootFSType string
}
Expand Down
43 changes: 20 additions & 23 deletions bib/cmd/bootc-image-builder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/osbuild/images/pkg/rpmmd"

"github.com/osbuild/bootc-image-builder/bib/internal/buildconfig"
"github.com/osbuild/bootc-image-builder/bib/internal/cntdnf"
podman_container "github.com/osbuild/bootc-image-builder/bib/internal/container"
"github.com/osbuild/bootc-image-builder/bib/internal/imagetypes"
"github.com/osbuild/bootc-image-builder/bib/internal/setup"
Expand Down Expand Up @@ -102,20 +103,13 @@ func getContainerSize(imgref string) (uint64, error) {
return size, nil
}

func makeManifest(c *ManifestConfig, cacheRoot string) (manifest.OSBuildManifest, map[string][]rpmmd.RepoConfig, error) {
func makeManifest(c *ManifestConfig, solver *dnfjson.Solver, cacheRoot string) (manifest.OSBuildManifest, map[string][]rpmmd.RepoConfig, error) {
manifest, err := Manifest(c)
if err != nil {
return nil, nil, fmt.Errorf("cannot get manifest: %w", err)
}

// depsolve packages
solver := dnfjson.NewSolver(
c.SourceInfo.OSRelease.PlatformID,
c.SourceInfo.OSRelease.VersionID,
c.Architecture.String(),
fmt.Sprintf("%s-%s", c.SourceInfo.OSRelease.ID, c.SourceInfo.OSRelease.VersionID),
cacheRoot)
solver.SetRootDir(c.DepsolverRootDir)
depsolvedSets := make(map[string][]rpmmd.PackageSpec)
depsolvedRepos := make(map[string][]rpmmd.RepoConfig)
for name, pkgSet := range manifest.GetPackageSetChains() {
Expand Down Expand Up @@ -285,32 +279,35 @@ func manifestFromCobra(cmd *cobra.Command, args []string) ([]byte, *mTLSConfig,
rootfsType = "ext4"
}
}
// Gather some data from the containers distro
sourceinfo, err := source.LoadInfo(container.Root())
if err != nil {
return nil, nil, err
}

// 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 := container.InitDNF(); err != nil {
return nil, nil, err
}

sourceinfo, err := source.LoadInfo(container.Root())
solver, err := cntdnf.NewContainerSolver(rpmCacheRoot, container, cntArch, sourceinfo)
if err != nil {
return nil, nil, err
}

manifestConfig := &ManifestConfig{
Architecture: cntArch,
Config: config,
ImageTypes: imageTypes,
Imgref: imgref,
TLSVerify: tlsVerify,
RootfsMinsize: cntSize * containerSizeToDiskSizeMultiplier,
DistroDefPaths: distroDefPaths,
SourceInfo: sourceinfo,
RootFSType: rootfsType,
DepsolverRootDir: container.Root(),
}

manifest, repos, err := makeManifest(manifestConfig, rpmCacheRoot)
Architecture: cntArch,
Config: config,
ImageTypes: imageTypes,
Imgref: imgref,
TLSVerify: tlsVerify,
RootfsMinsize: cntSize * containerSizeToDiskSizeMultiplier,
DistroDefPaths: distroDefPaths,
SourceInfo: sourceinfo,
RootFSType: rootfsType,
}

manifest, repos, err := makeManifest(manifestConfig, solver, rpmCacheRoot)
if err != nil {
return nil, nil, err
}
Expand Down
48 changes: 48 additions & 0 deletions bib/internal/cntdnf/cntdnf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package cntdnf

import (
"fmt"
"path/filepath"

"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/dnfjson"

"github.com/osbuild/bootc-image-builder/bib/internal/container"
"github.com/osbuild/bootc-image-builder/bib/internal/source"
)

func injectDNFJson(cnt *container.Container) ([]string, error) {
if err := cnt.CopyInto("/usr/libexec/osbuild-depsolve-dnf", "/osbuild-depsolve-dnf"); err != nil {
return nil, fmt.Errorf("cannot prepare depsolve in the container: %w", err)
}
// copy the python module too
globPath := "/usr/lib/*/site-packages/osbuild"
matches, err := filepath.Glob(globPath)
if err != nil || len(matches) == 0 {
return nil, fmt.Errorf("cannot find osbuild python module in %q: %w", globPath, err)
}
if len(matches) != 1 {
return nil, fmt.Errorf("unexpected number of osbuild python module matches: %v", matches)
}
if err := cnt.CopyInto(matches[0], "/"); err != nil {
return nil, fmt.Errorf("cannot prepare depsolve python-modules in the container: %w", err)
}
return append(cnt.ExecArgv(), "/osbuild-depsolve-dnf"), nil
}

func NewContainerSolver(cacheRoot string, cnt *container.Container, architecture arch.Arch, sourceInfo *source.Info) (*dnfjson.Solver, error) {
depsolverCmd, err := injectDNFJson(cnt)
if err != nil {
return nil, fmt.Errorf("cannot inject depsolve into the container: %w", err)
}

solver := dnfjson.NewSolver(
sourceInfo.OSRelease.PlatformID,
sourceInfo.OSRelease.VersionID,
architecture.String(),
fmt.Sprintf("%s-%s", sourceInfo.OSRelease.ID, sourceInfo.OSRelease.VersionID),
cacheRoot)
solver.SetDNFJSONPath(depsolverCmd[0], depsolverCmd[1:]...)
solver.SetRootDir("/")
return solver, nil
}
134 changes: 134 additions & 0 deletions bib/internal/cntdnf/cntdnf_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package cntdnf_test

import (
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/rpmmd"

"github.com/osbuild/bootc-image-builder/bib/internal/cntdnf"
"github.com/osbuild/bootc-image-builder/bib/internal/container"
"github.com/osbuild/bootc-image-builder/bib/internal/source"
)

const (
dnfTestingImageRHEL = "registry.access.redhat.com/ubi9:latest"
dnfTestingImageCentos = "quay.io/centos/centos:stream9"
)

func TestDNFJsonWorks(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip("skipping test; not running as root")
}
if _, err := os.Stat("/usr/libexec/osbuild-depsolve-dnf"); err != nil {
t.Skip("cannot find /usr/libexec/osbuild-depsolve-dnf")
}
cacheRoot := t.TempDir()

cnt, err := container.New(dnfTestingImageCentos)
require.NoError(t, err)
err = cnt.InitDNF()
require.NoError(t, err)

sourceInfo, err := source.LoadInfo(cnt.Root())
require.NoError(t, err)
solver, err := cntdnf.NewContainerSolver(cacheRoot, cnt, arch.Current(), sourceInfo)
require.NoError(t, err)
res, err := solver.Depsolve([]rpmmd.PackageSet{
{
Include: []string{"coreutils"},
},
}, 0)
require.NoError(t, err)
assert.True(t, len(res.Packages) > 0)
}

func subscribeMachine(t *testing.T) (restore func()) {
if _, err := exec.LookPath("subscription-manager"); err != nil {
t.Skip("no subscription-manager found")
return func() {}
}

matches, err := filepath.Glob("/etc/pki/entitlement/*.pem")
if err == nil && len(matches) > 0 {
return func() {}
}

rhsmOrg := os.Getenv("RHSM_ORG")
rhsmActivationKey := os.Getenv("RHSM_ACTIVATION_KEY")
if rhsmOrg == "" || rhsmActivationKey == "" {
t.Skip("no RHSM_{ORG,ACTIVATION_KEY} env vars found")
return func() {}
}

err = exec.Command("subscription-manager", "register",
"--org", rhsmOrg,
"--activationkey", rhsmActivationKey).Run()
require.NoError(t, err)

return func() {
err := exec.Command("subscription-manager", "unregister").Run()
require.NoError(t, err)
}
}

func TestDNFInitGivesAccessToSubscribedContent(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip("skipping test; not running as root")
}
if runtime.GOARCH != "amd64" {
t.Skip("skipping test; only runs on x86_64")
}

restore := subscribeMachine(t)
defer restore()

cnt, err := container.New(dnfTestingImageRHEL)
require.NoError(t, err)
err = cnt.InitDNF()
require.NoError(t, err)

content, err := cnt.ReadFile("/etc/yum.repos.d/redhat.repo")
require.NoError(t, err)
assert.Contains(t, string(content), "rhel-9-for-x86_64-baseos-rpms")
}

func TestDNFJsonWorkWithSubscribedContent(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip("skipping test; not running as root")
}
if runtime.GOARCH != "amd64" {
t.Skip("skipping test; only runs on x86_64")
}
if _, err := os.Stat("/usr/libexec/osbuild-depsolve-dnf"); err != nil {
t.Skip("cannot find /usr/libexec/osbuild-depsolve-dnf")
}
cacheRoot := t.TempDir()

restore := subscribeMachine(t)
defer restore()

cnt, err := container.New(dnfTestingImageRHEL)
require.NoError(t, err)
err = cnt.InitDNF()
require.NoError(t, err)

sourceInfo, err := source.LoadInfo(cnt.Root())
require.NoError(t, err)
solver, err := cntdnf.NewContainerSolver(cacheRoot, cnt, arch.ARCH_X86_64, sourceInfo)
require.NoError(t, err)
res, err := solver.Depsolve([]rpmmd.PackageSet{
{
Include: []string{"coreutils"},
},
}, 0)
require.NoError(t, err)
assert.True(t, len(res.Packages) > 0)
}
3 changes: 2 additions & 1 deletion bib/internal/source/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import (
"os"
"path"

"github.com/osbuild/images/pkg/distro"
"github.com/sirupsen/logrus"

"github.com/osbuild/images/pkg/distro"
)

type OSRelease struct {
Expand Down
2 changes: 2 additions & 0 deletions plans/all.fmf
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ prepare:
how: install
package:
- edk2-aarch64
- osbuild-depsolve-dnf
- podman
- pytest
- python3-boto3
- python3-flake8
- python3-paramiko
- python3-pip
- skopeo
- subscription-manager
- qemu-kvm
- qemu-system-aarch64
- qemu-user-static
Expand Down
Loading