Skip to content
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
7 changes: 7 additions & 0 deletions docs/spec.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1300,6 +1300,13 @@
],
"description": "Labels is the list of labels to set in the image metadata."
},
"minimization_profile": {
"type": [
"string",
"null"
],
"description": "MinimizationProfile selects an optional post-install image minimization\npolicy. An empty value leaves the image unchanged; \"default\" enables the\ntarget-specific default policy."
},
"post": {
"$ref": "#/$defs/PostInstall",
"description": "Post is the post install configuration for the image.\nThis allows making additional modifications to the container rootfs after the package(s) are installed.\n\nUse this to perform actions that would otherwise require additional tooling inside the container that is not relevant to\nthe resulting container and makes a post-install script as part of the package unnecessary."
Expand Down
12 changes: 12 additions & 0 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,18 @@ func (s *Spec) GetImagePost(target string) *PostInstall {
return nil
}

func (s *Spec) GetImageMinimizationProfile(target string) string {
if img := s.Targets[target].Image; img != nil && img.MinimizationProfile != "" {
return img.MinimizationProfile
}

if s.Image != nil {
return s.Image.MinimizationProfile
}

return ""
}

func (s *Spec) GetArtifacts(targetKey string) Artifacts {
if t, ok := s.Targets[targetKey]; ok {
// If unset then we should use the global artifacts but if set or deliberately empty then we should use that.
Expand Down
10 changes: 10 additions & 0 deletions image.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,15 @@ import (
type DockerImageSpec = dockerspec.DockerOCIImage
type DockerImageConfig = dockerspec.DockerOCIImageConfig

const ImageMinimizationProfileDefault = "default"

// ImageConfig is the configuration for the output image.
// When the target output is a container image, this is used to configure the image.
type ImageConfig struct {
// MinimizationProfile selects an optional post-install image minimization
// policy. An empty value leaves the image unchanged; "default" enables the
// target-specific default policy.
MinimizationProfile string `yaml:"minimization_profile,omitempty" json:"minimization_profile,omitempty"`
// Entrypoint sets the image's "entrypoint" field.
// This is used to control the default command to run when the image is run.
Entrypoint string `yaml:"entrypoint,omitempty" json:"entrypoint,omitempty"`
Expand Down Expand Up @@ -156,6 +162,10 @@ func (i *ImageConfig) validate() error {
errs = append(errs, errors.New("cannot specify both image.base and image.bases"))
}

if i.MinimizationProfile != "" && i.MinimizationProfile != ImageMinimizationProfileDefault {
errs = append(errs, errors.Errorf("unsupported image minimization profile %q", i.MinimizationProfile))
}

for i, base := range i.Bases {
if err := base.validate(); err != nil && !errorIsOnly(err, errNoImageSourcePath) {
errs = append(errs, errors.Wrapf(err, "bases[%d]", i))
Expand Down
4 changes: 4 additions & 0 deletions imgconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ func MergeSpecImage(spec *Spec, targetKey string) *ImageConfig {
}

if i := spec.Targets[targetKey].Image; i != nil {
if i.MinimizationProfile != "" {
cfg.MinimizationProfile = i.MinimizationProfile
}

if i.Entrypoint != "" {
cfg.Entrypoint = i.Entrypoint
}
Expand Down
38 changes: 26 additions & 12 deletions imgconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,22 +83,24 @@ func TestMergeSpecImage(t *testing.T) {
t.Run("target overrides all string fields", func(t *testing.T) {
spec := &Spec{
Image: &ImageConfig{
Entrypoint: "/bin/old",
Cmd: "old",
WorkingDir: "/old",
StopSignal: "SIGINT",
Base: "old:latest",
User: "root",
Entrypoint: "/bin/old",
Cmd: "old",
WorkingDir: "/old",
StopSignal: "SIGINT",
Base: "old:latest",
User: "root",
MinimizationProfile: "default",
},
Targets: map[string]Target{
"t1": {
Image: &ImageConfig{
Entrypoint: "/bin/new",
Cmd: "new",
WorkingDir: "/new",
StopSignal: "SIGTERM",
Base: "new:latest",
User: "nobody",
Entrypoint: "/bin/new",
Cmd: "new",
WorkingDir: "/new",
StopSignal: "SIGTERM",
Base: "new:latest",
User: "nobody",
MinimizationProfile: "default",
},
},
},
Expand All @@ -110,6 +112,18 @@ func TestMergeSpecImage(t *testing.T) {
assert.Check(t, cmp.Equal(cfg.StopSignal, "SIGTERM"))
assert.Check(t, cmp.Equal(cfg.Base, "new:latest"))
assert.Check(t, cmp.Equal(cfg.User, "nobody"))
assert.Check(t, cmp.Equal(cfg.MinimizationProfile, "default"))
})

t.Run("target minimization profile overrides spec profile", func(t *testing.T) {
spec := &Spec{
Targets: map[string]Target{
"t1": {Image: &ImageConfig{MinimizationProfile: "default"}},
},
}

assert.Check(t, cmp.Equal(spec.GetImageMinimizationProfile("t1"), "default"))
assert.Check(t, cmp.Equal(spec.GetImageMinimizationProfile("other"), ""))
})

t.Run("target env appends to spec env", func(t *testing.T) {
Expand Down
5 changes: 5 additions & 0 deletions load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,11 @@ func TestImage_validate(t *testing.T) {
Name: "No base image",
Image: ImageConfig{},
},
{
Name: "unsupported image minimization profile",
Image: ImageConfig{MinimizationProfile: "v1"},
expectErr: "unsupported image minimization profile",
},
{
Name: "image.base set",
Image: ImageConfig{
Expand Down
5 changes: 5 additions & 0 deletions targets/linux/rpm/distro/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ func (cfg *Config) BuildContainer(ctx context.Context, client gwclient.Client, s
pkgs = append(pkgs, filepath.Join(baseMountPath, "**/*.rpm"))
}

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(
Expand Down
27 changes: 27 additions & 0 deletions targets/linux/rpm/distro/dnf_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ type dnfInstallConfig struct {
forceArch string

disableProxyConfig bool

postInstallPath string
postInstallScript llb.State
}

type DnfInstallOpt func(*dnfInstallConfig)
Expand Down Expand Up @@ -96,6 +99,16 @@ func DnfInstallWithConstraints(opts []llb.ConstraintsOpt) DnfInstallOpt {
}
}

// DnfWithPostInstallScript runs the mounted script after package installation
// in the same worker operation. The script can modify the install root before
// the operation's filesystem layer is committed.
func DnfWithPostInstallScript(path string, script llb.State) DnfInstallOpt {
return func(cfg *dnfInstallConfig) {
cfg.postInstallPath = path
cfg.postInstallScript = script
}
}

func dnfInstallFlags(cfg *dnfInstallConfig) string {
var cmdOpts string

Expand Down Expand Up @@ -244,6 +257,11 @@ func dnfCommand(cfg *dnfInstallConfig, releaseVer string, exe string, dnfSubCmd
installFlags := dnfInstallFlags(cfg)
installFlags += " -y --setopt varsdir=/etc/dnf/vars --releasever=" + releaseVer + " "
forceArch := cfg.forceArch
postInstallCommand := ""
if cfg.postInstallPath != "" {
postInstallCommand = fmt.Sprintf("\n%s", cfg.postInstallPath)
}

installScriptDt := `#!/usr/bin/env bash
set -eux -o pipefail

Expand Down Expand Up @@ -278,13 +296,22 @@ configure_dnf_proxy
trap cleanup_dnf_proxy EXIT

$cmd $dnf_sub_cmd $install_flags "${@}"
` + postInstallCommand + `
`
var runOpts []llb.RunOption

installScript := llb.Scratch().File(llb.Mkfile("install.sh", 0o700, []byte(installScriptDt)), cfg.constraints...)
const installScriptPath = "/tmp/dalec/internal/dnf/install.sh"

runOpts = append(runOpts, llb.AddMount(installScriptPath, installScript, llb.SourcePath("install.sh"), llb.Readonly))
if cfg.postInstallPath != "" {
runOpts = append(runOpts, llb.AddMount(
cfg.postInstallPath,
cfg.postInstallScript,
llb.SourcePath(filepath.Base(cfg.postInstallPath)),
llb.Readonly,
))
}

// TODO(adamperlin): see if this can be removed for dnf
// If we have keys to import in order to access a repo, we need to create a script to use `gpg` to import them
Expand Down
Loading