diff --git a/frontend/azlinux/azlinux3.go b/frontend/azlinux/azlinux3.go index c09b074a8..54665b7d2 100644 --- a/frontend/azlinux/azlinux3.go +++ b/frontend/azlinux/azlinux3.go @@ -3,7 +3,6 @@ package azlinux import ( "context" "encoding/json" - "path/filepath" "github.com/Azure/dalec" "github.com/moby/buildkit/client/llb" @@ -13,8 +12,7 @@ import ( ) const ( - AzLinux3TargetKey = "azlinux3" - tdnfCacheNameAzlinux3 = "azlinux3-tdnf-cache" + AzLinux3TargetKey = "azlinux3" // Azlinux3Ref is the image ref used for the base worker image Azlinux3Ref = "mcr.microsoft.com/azurelinux/base/core:3.0" @@ -49,6 +47,8 @@ func (w azlinux3) Base(sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.S img := llb.Image(Azlinux3Ref, llb.WithMetaResolver(sOpt.Resolver), dalec.WithConstraints(opts...)) return img.Run( + w.Install([]string{"dnf"}, installWithConstraints(opts), tdnfOnly), + ).Run( w.Install([]string{"rpm-build", "mariner-rpm-macros", "build-essential", "ca-certificates"}, installWithConstraints(opts)), dalec.WithConstraints(opts...), ).Root(), nil @@ -57,7 +57,7 @@ func (w azlinux3) Base(sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.S func (w azlinux3) Install(pkgs []string, opts ...installOpt) llb.RunOption { var cfg installConfig setInstallOptions(&cfg, opts) - return dalec.WithRunOptions(tdnfInstall(&cfg, "3.0", pkgs), w.tdnfCacheMount(cfg.root)) + return dalec.WithRunOptions(dnfInstall(&cfg, "3.0", pkgs, AzLinux3TargetKey)) } func (w azlinux3) BasePackages() []string { @@ -91,7 +91,3 @@ func (azlinux3) WorkerImageConfig(ctx context.Context, resolver llb.ImageMetaRes return &cfg, nil } - -func (azlinux3) tdnfCacheMount(root string) llb.RunOption { - return llb.AddMount(filepath.Join(root, tdnfCacheDir), llb.Scratch(), llb.AsPersistentCacheDir(tdnfCacheNameAzlinux3, llb.CacheMountLocked)) -} diff --git a/frontend/azlinux/handle_rpm.go b/frontend/azlinux/handle_rpm.go index 2ed253e7d..d475f674d 100644 --- a/frontend/azlinux/handle_rpm.go +++ b/frontend/azlinux/handle_rpm.go @@ -55,13 +55,31 @@ func handleRPM(w worker) gwclient.BuildFunc { } } -type installFunc func(dalec.SourceOpts) (llb.RunOption, error) +func platformFuzzyMatches(p *ocispecs.Platform) bool { + if p == nil { + return true + } + + // Note, this is intentionally not doing a strict match here + // (e.g. [platforms.OnlyStrict]) + // This is used to see if we can get some optimizations when building for a + // non-native platformm and in most cases the [platforms.Only] vector handles + // things like building armv7 on an arm64 machine, which should be able to run + // natively. + return platforms.Only(platforms.DefaultSpec()).Match(*p) +} + +func installBuildDeps(w worker, sOpt dalec.SourceOpts, spec *dalec.Spec, targetKey string, platform *ocispecs.Platform, opts ...llb.ConstraintsOpt) (llb.StateOption, error) { + deps := spec.GetBuildDeps(targetKey) + if len(deps) == 0 { + return func(in llb.State) llb.State { return in }, nil + } + + opts = append(opts, dalec.ProgressGroup("Install build deps")) -// Creates and installs an rpm meta-package that requires the passed in deps as runtime-dependencies -func installBuildDepsPackage(target string, packageName string, w worker, sOpt dalec.SourceOpts, deps map[string]dalec.PackageConstraints, platform *ocispecs.Platform, installOpts ...installOpt) installFunc { // depsOnly is a simple dalec spec that only includes build dependencies and their constraints depsOnly := dalec.Spec{ - Name: fmt.Sprintf("%s-build-dependencies", packageName), + Name: spec.Name + "-build-dependencies", Description: "Provides build dependencies for mariner2 and azlinux3", Version: "1.0", License: "Apache 2.0", @@ -71,46 +89,42 @@ func installBuildDepsPackage(target string, packageName string, w worker, sOpt d }, } - return func(Opt dalec.SourceOpts) (llb.RunOption, error) { - pg := dalec.ProgressGroup("Building container for build dependencies") + // create an RPM with just the build dependencies, using our same base worker + rpmDir, err := createRPM(w, sOpt, &depsOnly, targetKey, platform, opts...) + if err != nil { + return nil, err + } + + rpmMountDir := "/tmp/rpms" + pkg := []string{"/tmp/rpms/*/*.rpm"} - // create an RPM with just the build dependencies, using our same base worker - rpmDir, err := createRPM(w, sOpt, &depsOnly, target, platform, pg) + if !platformFuzzyMatches(platform) { + base, err := w.Base(sOpt, opts...) if err != nil { return nil, err } - var opts []llb.ConstraintsOpt - opts = append(opts, dalec.ProgressGroup("Install build deps")) - - rpmMountDir := "/tmp/rpms" - - installOpts = append([]installOpt{ - noGPGCheck, - withMounts(llb.AddMount(rpmMountDir, rpmDir, llb.SourcePath("/RPMS"))), - installWithConstraints(opts), - }, installOpts...) - - // install the built RPMs into the worker itself - return w.Install([]string{"/tmp/rpms/*/*.rpm"}, installOpts...), nil - } -} - -func installBuildDeps(w worker, sOpt dalec.SourceOpts, spec *dalec.Spec, targetKey string, platform *ocispecs.Platform, opts ...llb.ConstraintsOpt) (llb.StateOption, error) { - deps := spec.GetBuildDeps(targetKey) - if len(deps) == 0 { - return func(in llb.State) llb.State { return in }, nil - } - - opts = append(opts, dalec.ProgressGroup("Install build deps")) - - installOpt, err := installBuildDepsPackage(targetKey, spec.Name, w, sOpt, deps, platform, installWithConstraints(opts))(sOpt) - if err != nil { - return nil, err + return func(in llb.State) llb.State { + return base.Run( + w.Install( + pkg, + withMounts(llb.AddMount(rpmMountDir, rpmDir, llb.SourcePath("/RPMS"))), + atRoot("/tmp/rootfs"), + withPlatform(platform), + ), + ).AddMount("/tmp/rootfs", in) + }, nil } return func(in llb.State) llb.State { - return in.Run(installOpt, dalec.WithConstraints(opts...)).Root() + return in.Run( + w.Install( + []string{"/tmp/rpms/*/*.rpm"}, + withMounts(llb.AddMount(rpmMountDir, rpmDir, llb.SourcePath("/RPMS"))), + installWithConstraints(opts), + ), + dalec.WithConstraints(opts...), + ).Root() }, nil } @@ -205,15 +219,13 @@ func createRPM(w worker, sOpt dalec.SourceOpts, spec *dalec.Spec, targetKey stri } var runOpts []llb.RunOption - if platform != nil { - if platforms.Only(platforms.DefaultSpec()).Match(*platform) && hasGolangBuildDep(spec, targetKey) { - native, err := rpmWorker(w, sOpt, spec, targetKey, nil, opts...) - if err != nil { - return llb.Scratch(), err - } - - runOpts = append(runOpts, nativeGoMount(native, platform)) + if !platformFuzzyMatches(platform) && hasGolangBuildDep(spec, targetKey) { + native, err := rpmWorker(w, sOpt, spec, targetKey, nil, opts...) + if err != nil { + return llb.Scratch(), err } + + runOpts = append(runOpts, nativeGoMount(native, platform)) } specPath := filepath.Join("SPECS", spec.Name, spec.Name+".spec") diff --git a/frontend/azlinux/handler.go b/frontend/azlinux/handler.go index cea35c858..68fc0e703 100644 --- a/frontend/azlinux/handler.go +++ b/frontend/azlinux/handler.go @@ -14,10 +14,6 @@ import ( ocispecs "github.com/opencontainers/image-spec/specs-go/v1" ) -const ( - tdnfCacheDir = "/var/cache/tdnf" -) - type worker interface { Base(sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.State, error) Install(pkgs []string, opts ...installOpt) llb.RunOption diff --git a/frontend/azlinux/install.go b/frontend/azlinux/install.go index c933e8335..25a3046ab 100644 --- a/frontend/azlinux/install.go +++ b/frontend/azlinux/install.go @@ -7,6 +7,7 @@ import ( "github.com/Azure/dalec" "github.com/moby/buildkit/client/llb" + ocispecs "github.com/opencontainers/image-spec/specs-go/v1" ) type installConfig struct { @@ -23,7 +24,14 @@ type installConfig struct { // Additional mounts to add to the tdnf install command (useful if installing RPMS which are mounted to a local directory) mounts []llb.RunOption + // Instructs the installer to install packages for the specified platform + platform *ocispecs.Platform + constraints []llb.ConstraintsOpt + + // This forces the use of tdnf + // Note this will almost certainly not work when platform is set. + tdnfOnly bool } type installOpt func(*installConfig) @@ -32,6 +40,10 @@ func noGPGCheck(cfg *installConfig) { cfg.noGPGCheck = true } +func tdnfOnly(cfg *installConfig) { + cfg.tdnfOnly = true +} + func withMounts(opts ...llb.RunOption) installOpt { return func(cfg *installConfig) { cfg.mounts = append(cfg.mounts, opts...) @@ -48,13 +60,19 @@ func atRoot(root string) installOpt { } } +func withPlatform(p *ocispecs.Platform) installOpt { + return func(cfg *installConfig) { + cfg.platform = p + } +} + func installWithConstraints(opts []llb.ConstraintsOpt) installOpt { return func(cfg *installConfig) { cfg.constraints = opts } } -func tdnfInstallFlags(cfg *installConfig) string { +func dnfInstallFlags(cfg *installConfig) string { var cmdOpts string if cfg.noGPGCheck { @@ -63,12 +81,30 @@ func tdnfInstallFlags(cfg *installConfig) string { if cfg.root != "" { cmdOpts += " --installroot=" + cfg.root - cmdOpts += " --setopt=reposdir=/etc/yum.repos.d" + cmdOpts += " --setopt reposdir=/etc/yum.repos.d" + } + + if cfg.platform != nil { + // cmdOpts += " --ignorearch=true" + cmdOpts += " --forcearch=" + ociArchToOS(cfg.platform) } return cmdOpts } +func ociArchToOS(p *ocispecs.Platform) string { + switch p.Architecture { + case "amd64": + return "x86_64" + case "arm64": + return "aarch64" + // azlinux only supports amd64 and arm64 + // We shouldn't need any other arches. + default: + return p.Architecture + } +} + func setInstallOptions(cfg *installConfig, opts []installOpt) { for _, o := range opts { o(cfg) @@ -111,9 +147,14 @@ rm -rf `+rpmdbDir+` const manifestSh = "manifest.sh" -func tdnfInstall(cfg *installConfig, relVer string, pkgs []string) llb.RunOption { - cmdFlags := tdnfInstallFlags(cfg) - cmdArgs := fmt.Sprintf("set -ex; tdnf install -y --refresh --releasever=%s %s %s", relVer, cmdFlags, strings.Join(pkgs, " ")) +func dnfInstall(cfg *installConfig, relVer string, pkgs []string, cachePrefix string) llb.RunOption { + cmdFlags := dnfInstallFlags(cfg) + + cmd := "dnf" + if cfg.tdnfOnly { + cmd = "tdnf" + } + cmdArgs := fmt.Sprintf("set -ex; %s install -y --refresh --releasever=%s %s %s", cmd, relVer, cmdFlags, strings.Join(pkgs, " ")) var runOpts []llb.RunOption @@ -128,6 +169,7 @@ func tdnfInstall(cfg *installConfig, relVer string, pkgs []string) llb.RunOption runOpts = append(runOpts, dalec.ShArgs(cmdArgs)) runOpts = append(runOpts, cfg.mounts...) + runOpts = append(runOpts, llb.AddMount("/var/cache/"+cmd, llb.Scratch(), llb.AsPersistentCacheDir(cachePrefix+"-"+cmd+"-"+"cache", llb.CacheMountLocked))) return dalec.WithRunOptions(runOpts...) } diff --git a/frontend/azlinux/mariner2.go b/frontend/azlinux/mariner2.go index 078d10336..0c2676959 100644 --- a/frontend/azlinux/mariner2.go +++ b/frontend/azlinux/mariner2.go @@ -3,7 +3,6 @@ package azlinux import ( "context" "encoding/json" - "path/filepath" "github.com/Azure/dalec" "github.com/moby/buildkit/client/llb" @@ -13,8 +12,7 @@ import ( ) const ( - Mariner2TargetKey = "mariner2" - tdnfCacheNameMariner2 = "mariner2-tdnf-cache" + Mariner2TargetKey = "mariner2" Mariner2Ref = "mcr.microsoft.com/cbl-mariner/base/core:2.0" Mariner2WorkerContextName = "dalec-mariner2-worker" @@ -46,6 +44,8 @@ func (w mariner2) Base(sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.S } return base.Run( + w.Install([]string{"dnf"}, installWithConstraints(opts), tdnfOnly), + ).Run( w.Install([]string{"rpm-build", "mariner-rpm-macros", "build-essential", "ca-certificates"}, installWithConstraints(opts)), dalec.WithConstraints(opts...), ).Root(), nil @@ -54,7 +54,7 @@ func (w mariner2) Base(sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.S func (w mariner2) Install(pkgs []string, opts ...installOpt) llb.RunOption { var cfg installConfig setInstallOptions(&cfg, opts) - return dalec.WithRunOptions(tdnfInstall(&cfg, "2.0", pkgs), w.tdnfCacheMount(cfg.root)) + return dalec.WithRunOptions(dnfInstall(&cfg, "2.0", pkgs, Mariner2TargetKey)) } func (w mariner2) BasePackages() []string { @@ -90,7 +90,3 @@ func (mariner2) WorkerImageConfig(ctx context.Context, resolver llb.ImageMetaRes return &cfg, nil } - -func (mariner2) tdnfCacheMount(root string) llb.RunOption { - return llb.AddMount(filepath.Join(root, tdnfCacheDir), llb.Scratch(), llb.AsPersistentCacheDir(tdnfCacheNameMariner2, llb.CacheMountLocked)) -}