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

Add more Ubuntu versions and Debian support #439

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
6 changes: 4 additions & 2 deletions cmd/frontend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import (

"github.com/Azure/dalec/frontend"
"github.com/Azure/dalec/frontend/azlinux"
"github.com/Azure/dalec/frontend/debian"
"github.com/Azure/dalec/frontend/debug"
"github.com/Azure/dalec/frontend/jammy"
"github.com/Azure/dalec/frontend/ubuntu"
"github.com/Azure/dalec/frontend/windows"
"github.com/moby/buildkit/frontend/gateway/grpcclient"
"github.com/moby/buildkit/util/appcontext"
Expand Down Expand Up @@ -35,7 +36,8 @@ func main() {
frontend.WithBuiltinHandler(azlinux.Mariner2TargetKey, azlinux.NewMariner2Handler()),
frontend.WithBuiltinHandler(azlinux.AzLinux3TargetKey, azlinux.NewAzlinux3Handler()),
frontend.WithBuiltinHandler(windows.DefaultTargetKey, windows.Handle),
frontend.WithBuiltinHandler(jammy.DefaultTargetKey, jammy.Handle),
ubuntu.Handlers,
debian.Handlers,
frontend.WithTargetForwardingHandler,
)); err != nil {
bklog.L.WithError(err).Fatal("error running frontend")
Expand Down
75 changes: 58 additions & 17 deletions frontend/deb/debroot.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,25 @@ func sourcePatchesDir(sOpt dalec.SourceOpts, base llb.State, dir, name string, s
return append(states, series), nil
}

type SourcePkgConfig struct {
// PrependPath is a list of paths to be prepended to the $PATH var in build
// scripts.
PrependPath []string
// APpendPath is a list of paths to be appended to the $PATH var in build
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// APpendPath is a list of paths to be appended to the $PATH var in build
// AppendPath is a list of paths to be appended to the $PATH var in build

// scripts.
AppendPath []string
}

// Addpath creates a SourcePkgConfig where the first argument is sets
// [SourcePkgConfig.PrependPath] and the 2nd argument sets
// [SourcePkgConfig.AppendPath]
func AddPath(pre, post []string) SourcePkgConfig {
return SourcePkgConfig{
PrependPath: pre,
AppendPath: post,
}
}

// Debroot creates a debian root directory suitable for use with debbuild.
// This does not include sources in case you want to mount sources (instead of copying them) later.
//
Expand All @@ -75,7 +94,7 @@ func sourcePatchesDir(sOpt dalec.SourceOpts, base llb.State, dir, name string, s
// an upgrade even if it is technically the same underlying source.
// It may be left blank but is highly recommended to set this.
// Use [ReadDistroVersionID] to get a suitable value.
func Debroot(sOpt dalec.SourceOpts, spec *dalec.Spec, worker, in llb.State, target, dir, distroVersionID string, opts ...llb.ConstraintsOpt) (llb.State, error) {
func Debroot(ctx context.Context, sOpt dalec.SourceOpts, spec *dalec.Spec, worker, in llb.State, target, dir, distroVersionID string, cfg SourcePkgConfig, opts ...llb.ConstraintsOpt) (llb.State, error) {
control, err := controlFile(spec, in, target, dir)
if err != nil {
return llb.Scratch(), errors.Wrap(err, "error generating control file")
Expand Down Expand Up @@ -122,10 +141,10 @@ func Debroot(sOpt dalec.SourceOpts, spec *dalec.Spec, worker, in llb.State, targ
dalecDir := base.
File(llb.Mkdir(filepath.Join(dir, "dalec"), 0o755), opts...)

states = append(states, dalecDir.File(llb.Mkfile(filepath.Join(dir, "dalec/build.sh"), 0o700, createBuildScript(spec)), opts...))
states = append(states, dalecDir.File(llb.Mkfile(filepath.Join(dir, "dalec/patch.sh"), 0o700, createPatchScript(spec)), opts...))
states = append(states, dalecDir.File(llb.Mkfile(filepath.Join(dir, "dalec/fix_sources.sh"), 0o700, fixupSources(spec)), opts...))
states = append(states, dalecDir.File(llb.Mkfile(filepath.Join(dir, "dalec/fix_perms.sh"), 0o700, fixupArtifactPerms(spec)), opts...))
states = append(states, dalecDir.File(llb.Mkfile(filepath.Join(dir, "dalec/build.sh"), 0o700, createBuildScript(spec, &cfg)), opts...))
states = append(states, dalecDir.File(llb.Mkfile(filepath.Join(dir, "dalec/patch.sh"), 0o700, createPatchScript(spec, &cfg)), opts...))
states = append(states, dalecDir.File(llb.Mkfile(filepath.Join(dir, "dalec/fix_sources.sh"), 0o700, fixupSources(spec, &cfg)), opts...))
states = append(states, dalecDir.File(llb.Mkfile(filepath.Join(dir, "dalec/fix_perms.sh"), 0o700, fixupArtifactPerms(spec, &cfg)), opts...))

customEnable, err := customDHInstallSystemdPostinst(spec)
if err != nil {
Expand Down Expand Up @@ -161,12 +180,9 @@ func Debroot(sOpt dalec.SourceOpts, spec *dalec.Spec, worker, in llb.State, targ
return dalec.MergeAtPath(in, states, "/"), nil
}

func fixupArtifactPerms(spec *dalec.Spec) []byte {
func fixupArtifactPerms(spec *dalec.Spec, cfg *SourcePkgConfig) []byte {
buf := bytes.NewBuffer(nil)

fmt.Fprintln(buf, "#!/usr/bin/env sh")
fmt.Fprintln(buf, "set -ex")
fmt.Fprintln(buf)
writeScriptHeader(buf, cfg)

basePath := filepath.Join("debian", spec.Name)

Expand Down Expand Up @@ -201,9 +217,9 @@ func fixupArtifactPerms(spec *dalec.Spec) []byte {
// to bring those back.
//
// This is called from `debian/rules` after the source tarball has been extracted.
func fixupSources(spec *dalec.Spec) []byte {
func fixupSources(spec *dalec.Spec, cfg *SourcePkgConfig) []byte {
buf := bytes.NewBuffer(nil)
writeScriptHeader(buf)
writeScriptHeader(buf, cfg)

// now, we need to find all the sources that are file-backed and fix them up
for name, src := range spec.Sources {
Expand All @@ -223,20 +239,45 @@ func fixupSources(spec *dalec.Spec) []byte {
fmt.Fprintln(buf)
}

if spec.HasGomods() {
// Older go versions did not have support for the `GOMODCACHE` var
// This is a hack to try and make the build work by linking the go modules
// we've already fetched into to module dir under $GOPATH
fmt.Fprintf(buf, `env | grep PATH; test -n "$(go env GOMODCACHE)" || (GOPATH="$(go env GOPATH)"; mkdir -p "${GOPATH}/pkg" && ln -s "$(pwd)/%s" "${GOPATH}/pkg/mod")`, gomodsName)
// Above command does not have a newline due to quoting issues, so add that here.
fmt.Fprint(buf, "\n")
}

return buf.Bytes()
}

func writeScriptHeader(buf io.Writer) {
func setupPathVar(pre, post []string) string {
if len(pre) == 0 && len(post) == 0 {
return ""
}

full := append(pre, "$PATH")
full = append(full, post...)
return strings.Join(full, ":")
}

func writeScriptHeader(buf io.Writer, cfg *SourcePkgConfig) {
fmt.Fprintln(buf, "#!/usr/bin/env sh")
fmt.Fprintln(buf)

fmt.Fprintln(buf, "set -ex")

if cfg != nil {
if pathVar := setupPathVar(cfg.PrependPath, cfg.AppendPath); pathVar != "" {
fmt.Fprintln(buf, "export PATH="+pathVar)
}
}
}

func createPatchScript(spec *dalec.Spec) []byte {
func createPatchScript(spec *dalec.Spec, cfg *SourcePkgConfig) []byte {
buf := bytes.NewBuffer(nil)

writeScriptHeader(buf)
writeScriptHeader(buf, cfg)

for name, patches := range spec.Patches {
for _, patch := range patches {
Expand All @@ -248,9 +289,9 @@ func createPatchScript(spec *dalec.Spec) []byte {
return buf.Bytes()
}

func createBuildScript(spec *dalec.Spec) []byte {
func createBuildScript(spec *dalec.Spec, cfg *SourcePkgConfig) []byte {
buf := bytes.NewBuffer(nil)
writeScriptHeader(buf)
writeScriptHeader(buf, cfg)

sorted := dalec.SortMapKeys(spec.Build.Env)
for _, k := range sorted {
Expand Down
5 changes: 4 additions & 1 deletion frontend/deb/distro/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ func (c *Config) BuildContainer(worker llb.State, sOpt dalec.SourceOpts, client

opts = append(opts, dalec.ProgressGroup("Build Container Image"))

withRepos, err := c.RepoMounts(spec.GetInstallRepos(targetKey), sOpt, opts...)
repos := dalec.GetExtraRepos(c.ExtraRepos, "install")
repos = append(repos, spec.GetInstallRepos(targetKey)...)

withRepos, err := c.RepoMounts(repos, sOpt, opts...)
if err != nil {
return llb.Scratch(), err
}
Expand Down
5 changes: 5 additions & 0 deletions frontend/deb/distro/distro.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ type Config struct {
RepoPlatformConfig *dalec.RepoPlatformConfig

DefaultOutputImage string

// ExtraRepos is used by distributions that want to enable extra repositories
// that are not inthe base worker config.
// A prime example of this is adding Debian backports on debian distrubutions.
ExtraRepos []dalec.PackageRepositoryConfig
}

func (cfg *Config) BuildImageConfig(ctx context.Context, resolver llb.ImageMetaResolver, spec *dalec.Spec, platform *ocispecs.Platform, targetKey string) (*dalec.DockerImageSpec, error) {
Expand Down
13 changes: 10 additions & 3 deletions frontend/deb/distro/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func (d *Config) InstallBuildDeps(sOpt dalec.SourceOpts, spec *dalec.Spec, targe
opts := append(opts, dalec.ProgressGroup("Install build dependencies"))
opts = append([]llb.ConstraintsOpt{dalec.WithConstraint(c)}, opts...)

srcPkg, err := deb.SourcePackage(sOpt, in, depsSpec, targetKey, "", opts...)
srcPkg, err := deb.SourcePackage(ctx, sOpt, in, depsSpec, targetKey, "", deb.SourcePkgConfig{}, opts...)
if err != nil {
return in, err
}
Expand All @@ -144,7 +144,10 @@ func (d *Config) InstallBuildDeps(sOpt dalec.SourceOpts, spec *dalec.Spec, targe
return in, errors.Wrap(err, "error creating intermediate package for installing build dependencies")
}

customRepos, err := d.RepoMounts(spec.GetBuildRepos(targetKey), sOpt, opts...)
repos := dalec.GetExtraRepos(d.ExtraRepos, "build")
repos = append(repos, spec.GetBuildRepos(targetKey)...)

customRepos, err := d.RepoMounts(repos, sOpt, opts...)
if err != nil {
return in, err
}
Expand All @@ -153,6 +156,7 @@ func (d *Config) InstallBuildDeps(sOpt dalec.SourceOpts, spec *dalec.Spec, targe
dalec.WithConstraints(opts...),
customRepos,
InstallLocalPkg(pkg, opts...),
dalec.WithMountedAptCache(d.AptCachePrefix),
).Root(), nil
})
}
Expand All @@ -166,7 +170,10 @@ func (d *Config) InstallTestDeps(sOpt dalec.SourceOpts, targetKey string, spec *

return func(in llb.State) llb.State {
return in.Async(func(ctx context.Context, in llb.State, c *llb.Constraints) (llb.State, error) {
withRepos, err := d.RepoMounts(spec.GetTestRepos(targetKey), sOpt, opts...)
repos := dalec.GetExtraRepos(d.ExtraRepos, "test")
repos = append(repos, spec.GetTestRepos(targetKey)...)

withRepos, err := d.RepoMounts(repos, sOpt, opts...)
if err != nil {
return in, err
}
Expand Down
103 changes: 101 additions & 2 deletions frontend/deb/distro/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package distro

import (
"context"
"io/fs"
"path/filepath"
"strings"

"github.com/Azure/dalec"
"github.com/Azure/dalec/frontend"
"github.com/Azure/dalec/frontend/deb"
"github.com/Azure/dalec/frontend/pkg/bkfs"
"github.com/containerd/platforms"
"github.com/moby/buildkit/client/llb"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
Expand All @@ -26,7 +30,14 @@ func (d *Config) BuildDeb(ctx context.Context, worker llb.State, sOpt dalec.Sour
}

worker = worker.With(d.InstallBuildDeps(sOpt, spec, targetKey))
srcPkg, err := deb.SourcePackage(sOpt, worker, spec, targetKey, versionID, opts...)

var cfg deb.SourcePkgConfig
extraPaths, err := prepareGo(ctx, client, &cfg, worker, spec, targetKey, opts...)
if err != nil {
return worker, err
}

srcPkg, err := deb.SourcePackage(ctx, sOpt, worker.With(extraPaths), spec, targetKey, versionID, cfg, opts...)
if err != nil {
return worker, err
}
Expand All @@ -40,6 +51,87 @@ func (d *Config) BuildDeb(ctx context.Context, worker llb.State, sOpt dalec.Sour
return frontend.MaybeSign(ctx, client, st, spec, targetKey, sOpt)
}

func noOpStateOpt(in llb.State) llb.State {
return in
}

func prepareGo(ctx context.Context, client gwclient.Client, cfg *deb.SourcePkgConfig, worker llb.State, spec *dalec.Spec, targetKey string, opts ...llb.ConstraintsOpt) (llb.StateOption, error) {
goBin, err := searchForAltGolang(ctx, client, spec, targetKey, worker, opts...)
if err != nil {
return noOpStateOpt, errors.Wrap(err, "error while looking for alternate go bin path")
}

if goBin == "" {
return noOpStateOpt, nil
}
cfg.PrependPath = append(cfg.PrependPath, goBin)
return addPaths([]string{goBin}, opts...), nil
}

func searchForAltGolang(ctx context.Context, client gwclient.Client, spec *dalec.Spec, targetKey string, in llb.State, opts ...llb.ConstraintsOpt) (string, error) {
if !spec.HasGomods() {
return "", nil
}
var candidates []string

deps := spec.GetBuildDeps(targetKey)
if _, hasNormalGo := deps["golang"]; hasNormalGo {
return "", nil
}

for dep := range deps {
if strings.HasPrefix(dep, "golang-") {
// Get the base version component
_, ver, _ := strings.Cut(dep, "-")
// Trim off any potential extra stuff like `golang-1.20-go` (ie the `-go` bit)
// This is just for having definitive search paths to check it should
// not be an issue if this is not like the above example and its
// something else like `-doc` since we are still going to check the
// binary exists anyway (plus this would be highly unlikely in any case).
ver, _, _ = strings.Cut(ver, "-")
candidates = append(candidates, "usr/lib/go-"+ver+"/bin")
}
}

if len(candidates) == 0 {
return "", nil
}

stfs, err := bkfs.FromState(ctx, &in, client, opts...)
if err != nil {
return "", err
}

for _, p := range candidates {
_, err := fs.Stat(stfs, filepath.Join(p, "go"))
if err == nil {
// bkfs does not allow a leading `/` in the stat path per spec for [fs.FS]
// Add that in here
p := "/" + p
return p, nil
}
}

return "", nil
}

// prepends the provided values to $PATH
func addPaths(paths []string, opts ...llb.ConstraintsOpt) llb.StateOption {
return func(in llb.State) llb.State {
if len(paths) == 0 {
return in
}
return in.Async(func(ctx context.Context, in llb.State, c *llb.Constraints) (llb.State, error) {
opts := []llb.ConstraintsOpt{dalec.WithConstraint(c), dalec.WithConstraints(opts...)}
pathEnv, _, err := in.GetEnv(ctx, "PATH", opts...)
if err != nil {
return in, err
}
return in.AddEnv("PATH", strings.Join(append(paths, pathEnv), ":")), nil
})
}
}

func (cfg *Config) HandleDeb(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) {
return frontend.BuildWithPlatform(ctx, client, func(ctx context.Context, client gwclient.Client, platform *ocispecs.Platform, spec *dalec.Spec, targetKey string) (gwclient.Reference, *dalec.DockerImageSpec, error) {
sOpt, err := frontend.SourceOptFromClient(ctx, client)
Expand Down Expand Up @@ -141,7 +233,14 @@ func (cfg *Config) HandleSourcePkg(ctx context.Context, client gwclient.Client)
}

worker = worker.With(cfg.InstallBuildDeps(sOpt, spec, targetKey, pg))
st, err := deb.SourcePackage(sOpt, worker, spec, targetKey, versionID, pg)

var cfg deb.SourcePkgConfig
extraPaths, err := prepareGo(ctx, client, &cfg, worker, spec, targetKey, pg)
if err != nil {
return nil, nil, err
}

st, err := deb.SourcePackage(ctx, sOpt, worker.With(extraPaths), spec, targetKey, versionID, cfg, pg)
if err != nil {
return nil, nil, errors.Wrap(err, "error building source package")
}
Expand Down
1 change: 1 addition & 0 deletions frontend/deb/distro/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func (cfg *Config) Worker(sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (ll
Run(
dalec.WithConstraints(opts...),
AptInstall(cfg.BuilderPackages, opts...),
dalec.WithMountedAptCache(cfg.AptCachePrefix),
).
// This file prevents installation of things like docs in ubuntu
// containers We don't want to exclude this because tests want to
Expand Down
7 changes: 4 additions & 3 deletions frontend/deb/pkg.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package deb

import (
"context"
"fmt"
"path/filepath"
"strings"
Expand All @@ -14,7 +15,7 @@ const (
// Unique name that would not normally be in the spec
// This will get used to create the source tar for go module deps
gomodsName = "xxxdalecGomodsInternal"
DebHelperCompat = "13"
DebHelperCompat = "11"
)

func mountSources(sources map[string]llb.State, dir string, mod func(string) string) llb.RunOption {
Expand Down Expand Up @@ -70,11 +71,11 @@ func createPatches(spec *dalec.Spec, sources map[string]llb.State, worker llb.St
return patches
}

func SourcePackage(sOpt dalec.SourceOpts, worker llb.State, spec *dalec.Spec, targetKey, distroVersionID string, opts ...llb.ConstraintsOpt) (llb.State, error) {
func SourcePackage(ctx context.Context, sOpt dalec.SourceOpts, worker llb.State, spec *dalec.Spec, targetKey, distroVersionID string, cfg SourcePkgConfig, opts ...llb.ConstraintsOpt) (llb.State, error) {
if err := validateSpec(spec); err != nil {
return llb.Scratch(), err
}
dr, err := Debroot(sOpt, spec, worker, llb.Scratch(), targetKey, "", distroVersionID)
dr, err := Debroot(ctx, sOpt, spec, worker, llb.Scratch(), targetKey, "", distroVersionID, cfg, opts...)
if err != nil {
return llb.Scratch(), err
}
Expand Down
Loading