targets/linux/deb/distro/pkg.go (178 lines of code) (raw):
package distro
import (
"context"
"io/fs"
"path/filepath"
"strings"
"github.com/Azure/dalec"
"github.com/Azure/dalec/frontend"
"github.com/Azure/dalec/frontend/pkg/bkfs"
"github.com/Azure/dalec/packaging/linux/deb"
"github.com/moby/buildkit/client/llb"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
func (d *Config) Validate(spec *dalec.Spec) error {
return nil
}
func (d *Config) BuildPkg(ctx context.Context, client gwclient.Client, worker llb.State, sOpt dalec.SourceOpts, spec *dalec.Spec, targetKey string, opts ...llb.ConstraintsOpt) (llb.State, error) {
opts = append(opts, dalec.ProgressGroup("Build deb package"))
versionID := d.VersionID
if versionID == "" {
var err error
versionID, err = deb.ReadDistroVersionID(ctx, client, worker)
if err != nil {
return worker, err
}
}
worker = worker.With(d.InstallBuildDeps(sOpt, spec, targetKey))
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
}
builder := worker.With(dalec.SetBuildNetworkMode(spec))
st, err := deb.BuildDeb(builder, spec, srcPkg, versionID, opts...)
if err != nil {
return llb.Scratch(), err
}
// Filter out everything except the .deb files
filtered := llb.Scratch().File(
llb.Copy(st, "/", "/",
dalec.WithIncludes([]string{"**/*.deb"}),
),
)
signed, err := frontend.MaybeSign(ctx, client, filtered, spec, targetKey, sOpt)
if err != nil {
return llb.Scratch(), err
}
// Merge the signed state with the original state
// The signed files should overwrite the unsigned ones.
st = st.File(llb.Copy(signed, "/", "/"))
return st, nil
}
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) RunTests(ctx context.Context, client gwclient.Client, _ llb.State, spec *dalec.Spec, sOpt dalec.SourceOpts, ctr llb.State, targetKey string, opts ...llb.ConstraintsOpt) (gwclient.Reference, error) {
def, err := ctr.Marshal(ctx, opts...)
if err != nil {
return nil, err
}
res, err := client.Solve(ctx, gwclient.SolveRequest{
Definition: def.ToPB(),
})
if err != nil {
return nil, err
}
ref, err := res.SingleRef()
if err != nil {
return nil, err
}
withTestDeps := cfg.InstallTestDeps(sOpt, targetKey, spec, opts...)
err = frontend.RunTests(ctx, client, spec, ref, withTestDeps, targetKey, sOpt.TargetPlatform)
return ref, err
}
func (cfg *Config) HandleSourcePkg(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, platform)
if err != nil {
return nil, nil, err
}
pg := dalec.ProgressGroup(spec.Name)
pc := dalec.Platform(platform)
worker, err := cfg.Worker(sOpt, pg, pc)
if err != nil {
return nil, nil, err
}
versionID, err := deb.ReadDistroVersionID(ctx, client, worker)
if err != nil {
return nil, nil, err
}
worker = worker.With(cfg.InstallBuildDeps(sOpt, spec, targetKey, 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, pc)
if err != nil {
return nil, nil, errors.Wrap(err, "error building source package")
}
def, err := st.Marshal(ctx)
if err != nil {
return nil, nil, errors.Wrap(err, "error marshalling source package state")
}
res, err := client.Solve(ctx, gwclient.SolveRequest{
Definition: def.ToPB(),
})
if err != nil {
return nil, nil, err
}
ref, err := res.SingleRef()
if err != nil {
return nil, nil, err
}
return ref, nil, nil
})
}