Skip to content
Closed
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
47 changes: 41 additions & 6 deletions targets/linux/rpm/distro/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ func (cfg *Config) BuildContainer(ctx context.Context, client gwclient.Client, s
pkgs := []string{
filepath.Join(rpmMountDir, "**/*.rpm"),
}
hasBasePackages := !skipBase && len(cfg.BasePackages) > 0

if !skipBase && len(cfg.BasePackages) > 0 {
if hasBasePackages {
opts := append(opts, dalec.ProgressGroup("Create base virtual package"))

var basePkgStates []llb.State
Expand All @@ -61,28 +62,62 @@ func (cfg *Config) BuildContainer(ctx context.Context, client gwclient.Client, s
}

basePkgs = dalec.MergeAtPath(basePkgs, basePkgStates, "/", opts...)
pkgs = append(pkgs, filepath.Join(baseMountPath, "**/*.rpm"))
if !cfg.InstallBasePackagesSeparately {
pkgs = append(pkgs, filepath.Join(baseMountPath, "**/*.rpm"))
}
}

worker := cfg.Worker(sOpt, dalec.Platform(sOpt.TargetPlatform), dalec.WithConstraints(opts...))

if hasBasePackages && cfg.InstallBasePackagesSeparately {
baseInstallOpts := []DnfInstallOpt{
DnfAtRoot(workPath),
DnfWithSourceOpts(sOpt),
DnfInstallWithConstraints(opts),
IncludeDocs(spec.GetArtifacts(targetKey).HasDocs()),
}
rootfs = worker.Run(
dalec.WithConstraints(opts...),
cfg.Install([]string{filepath.Join(baseMountPath, "**/*.rpm")}, baseInstallOpts...),
llb.AddMount(baseMountPath, basePkgs, llb.SourcePath("/RPMS")),
frontend.IgnoreCache(client, targets.IgnoreCacheKeyContainer),
).AddMount(workPath, rootfs)
}

minimize := !skipBase && spec.GetImageMinimizationProfile(targetKey) == dalec.ImageMinimizationProfileDefault
if minimize {
installOpts = append(installOpts, minimizeInstall(opts...))
}

worker := cfg.Worker(sOpt, dalec.Platform(sOpt.TargetPlatform), dalec.WithConstraints(opts...))

rootfs = worker.Run(
runOpts := []llb.RunOption{
dalec.WithConstraints(opts...), // Make sure constraints (and platform specifically) are applied before install is set
cfg.Install(pkgs, installOpts...),
llb.AddMount(rpmMountDir, rpmDir, llb.SourcePath("/RPMS")),
llb.AddMount(baseMountPath, basePkgs, llb.SourcePath("/RPMS")),
frontend.IgnoreCache(client, targets.IgnoreCacheKeyContainer),
}
if hasBasePackages {
// The minimizer uses both RPM mounts to determine the packages that must
// remain, even when the base package was installed in an earlier run.
runOpts = append(runOpts, llb.AddMount(baseMountPath, basePkgs, llb.SourcePath("/RPMS")))
}

rootfs = worker.Run(
runOpts...,
).AddMount(workPath, rootfs)

if post := spec.GetImagePost(targetKey); post != nil && len(post.Symlinks) > 0 {
rootfs = rootfs.With(dalec.InstallPostSymlinks(post, worker, frontend.NativeSymlinkSupport(client), opts...))
}

if minimize && hasBasePackages && cfg.InstallBasePackagesSeparately {
squashOpts := append(opts, dalec.ProgressGroup("Squash RPM container"))
rootfs = llb.Scratch().File(llb.Copy(rootfs, "/", "/", &llb.CopyInfo{
CopyDirContentsOnly: true,
CreateDestPath: true,
AllowWildcard: true,
}), squashOpts...)
}

return rootfs
}

Expand Down
198 changes: 198 additions & 0 deletions targets/linux/rpm/distro/container_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package distro

import (
"context"
"errors"
"testing"

"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/client/llb/sourceresolver"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/solver/pb"
"github.com/opencontainers/go-digest"
"github.com/project-dalec/dalec"
"github.com/project-dalec/dalec/internal/test"
"gotest.tools/v3/assert"
)

func TestBuildContainerBasePackageInstallOrder(t *testing.T) {
t.Parallel()

tests := []struct {
name string
separate bool
wantInstalls [][]string
}{
{
name: "combined",
separate: false,
wantInstalls: [][]string{{
"/tmp/rpms/**/*.rpm",
"/tmp/rpms-base/**/*.rpm",
}},
},
{
name: "separate",
separate: true,
wantInstalls: [][]string{
{"/tmp/rpms-base/**/*.rpm"},
{"/tmp/rpms/**/*.rpm"},
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

var installs [][]string
var installConfigs []*dnfInstallConfig
cfg := &Config{
ContextRef: "worker",
BasePackages: []dalec.Spec{{
Name: "dalec-base-test",
Version: "1.0.0",
Revision: "1",
License: "MIT",
Description: "Test base package",
}},
InstallBasePackagesSeparately: tc.separate,
InstallFunc: func(cfg *dnfInstallConfig, _ string, pkgs []string) llb.RunOption {
installs = append(installs, append([]string(nil), pkgs...))
installConfigs = append(installConfigs, cfg)
return llb.Args(append([]string{"install"}, pkgs...))
},
}
sOpt := dalec.SourceOpts{
GetContext: func(string, ...llb.LocalOption) (*llb.State, error) {
st := llb.Scratch()
return &st, nil
},
}
spec := &dalec.Spec{
Dependencies: &dalec.PackageDependencies{
ExtraRepos: []dalec.PackageRepositoryConfig{
{
Config: map[string]dalec.Source{
"test.repo": {
Inline: &dalec.SourceInline{
File: &dalec.SourceInlineFile{Contents: "[test]"},
},
},
},
Envs: []string{"install"},
},
},
},
}

cfg.BuildContainer(
context.Background(),
&containerTestClient{},
sOpt,
spec,
"test",
llb.Scratch(),
)

assert.DeepEqual(t, installs, tc.wantInstalls)
if tc.separate {
assert.Equal(t, len(installConfigs[0].mounts), 0)
assert.Equal(t, len(installConfigs[0].keys), 0)
assert.Equal(t, len(installConfigs[1].mounts), 1)
}
})
}
}

func TestBuildContainerSquashesSeparateMinimizedInstall(t *testing.T) {
t.Parallel()

tests := []struct {
name string
separate bool
wantSquash bool
}{
{name: "combined", separate: false, wantSquash: false},
{name: "separate", separate: true, wantSquash: true},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

cfg := &Config{
ContextRef: "worker",
BasePackages: []dalec.Spec{{
Name: "dalec-base-test",
Version: "1.0.0",
Revision: "1",
License: "MIT",
Description: "Test base package",
}},
InstallBasePackagesSeparately: tc.separate,
InstallFunc: func(_ *dnfInstallConfig, _ string, pkgs []string) llb.RunOption {
return llb.Args(append([]string{"install"}, pkgs...))
},
}
sOpt := dalec.SourceOpts{
GetContext: func(string, ...llb.LocalOption) (*llb.State, error) {
st := llb.Scratch()
return &st, nil
},
}
spec := &dalec.Spec{
Image: &dalec.ImageConfig{
MinimizationProfile: dalec.ImageMinimizationProfileDefault,
},
}

state := cfg.BuildContainer(
context.Background(),
&containerTestClient{},
sOpt,
spec,
"test",
llb.Scratch(),
)

var foundSquash bool
for _, op := range test.LLBOpsFromState(context.Background(), t, state) {
if pg := op.OpMetadata.ProgressGroup; pg != nil && pg.Name == "Squash RPM container" {
foundSquash = true
}
}
assert.Equal(t, foundSquash, tc.wantSquash)
})
}
}

type containerTestClient struct{}

func (c *containerTestClient) BuildOpts() gwclient.BuildOpts {
return gwclient.BuildOpts{}
}

func (c *containerTestClient) Solve(context.Context, gwclient.SolveRequest) (*gwclient.Result, error) {
return nil, errors.New("not implemented")
}

func (c *containerTestClient) Inputs(context.Context) (map[string]llb.State, error) {
return nil, errors.New("not implemented")
}

func (c *containerTestClient) NewContainer(context.Context, gwclient.NewContainerRequest) (gwclient.Container, error) {
return nil, errors.New("not implemented")
}

func (c *containerTestClient) ResolveImageConfig(context.Context, string, sourceresolver.Opt) (string, digest.Digest, []byte, error) {
return "", "", nil, errors.New("not implemented")
}

func (c *containerTestClient) ResolveSourceMetadata(context.Context, *pb.SourceOp, sourceresolver.Opt) (*sourceresolver.MetaResponse, error) {
return nil, errors.New("not implemented")
}

func (c *containerTestClient) Warn(context.Context, digest.Digest, string, gwclient.WarnOpts) error {
return errors.New("not implemented")
}
3 changes: 3 additions & 0 deletions targets/linux/rpm/distro/distro.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ type Config struct {
// Dependencies to install in base image
BasePackages []dalec.Spec
RepoPlatformConfig *dalec.RepoPlatformConfig
// InstallBasePackagesSeparately bootstraps the base userland before
// installing the application RPM and its runtime dependency graph.
InstallBasePackagesSeparately bool

DefaultOutputImage string

Expand Down
17 changes: 9 additions & 8 deletions targets/linux/rpm/suse/sles15.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ var ConfigSLES15 = &distro.Config{
CacheName: zypperCacheSLES15,
CacheDir: []string{"/var/cache/zypp"},

ReleaseVer: "15",
BuilderPackages: builderPackages,
BasePackages: basePackages(SLES15TargetKey),
RepoPlatformConfig: &defaultPlatformConfig,
InstallFunc: distro.ZypperInstall,
CrossArchInstallUnsupported: true,
FullName: sles15FullName,
RPMMacros: sles15RPMMacros,
ReleaseVer: "15",
BuilderPackages: builderPackages,
BasePackages: basePackages(SLES15TargetKey),
InstallBasePackagesSeparately: true,
RepoPlatformConfig: &defaultPlatformConfig,
InstallFunc: distro.ZypperInstall,
CrossArchInstallUnsupported: true,
FullName: sles15FullName,
RPMMacros: sles15RPMMacros,
}

// sles15RPMMacros are the rpm macro overrides needed to make SUSE's rpm behave
Expand Down
Loading