Skip to content

Commit

Permalink
Add DistroConfig interface and lift common handler functionality for …
Browse files Browse the repository at this point in the history
…containers into consumers of the interface
  • Loading branch information
adamperlin committed Jan 8, 2025
1 parent 47e5adf commit d834e04
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 257 deletions.
38 changes: 1 addition & 37 deletions frontend/linux/deb/distro/container.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package distro

import (
"context"
"fmt"

"github.com/Azure/dalec"
"github.com/Azure/dalec/frontend"
"github.com/moby/buildkit/client/llb"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
)

func (c *Config) BuildContainer(worker llb.State, sOpt dalec.SourceOpts, client gwclient.Client, spec *dalec.Spec, targetKey string, debSt llb.State, opts ...llb.ConstraintsOpt) (llb.State, error) {
func (c *Config) BuildContainer(client gwclient.Client, worker llb.State, sOpt dalec.SourceOpts, spec *dalec.Spec, targetKey string, debSt llb.State, opts ...llb.ConstraintsOpt) (llb.State, error) {
base := dalec.GetBaseOutputImage(spec, targetKey)
if base == "" {
base = c.DefaultOutputImage
Expand Down Expand Up @@ -74,36 +71,3 @@ func (c *Config) createSymlinks(worker llb.State, spec *dalec.Spec, targetKey st
AddMount(workPath, in)
}
}

func (c *Config) HandleContainer(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)
if err != nil {
return nil, nil, err
}

pg := dalec.ProgressGroup(spec.Name)
worker, err := c.Worker(sOpt, pg)
if err != nil {
return nil, nil, err
}

deb, err := c.BuildDeb(ctx, worker, sOpt, client, spec, targetKey, pg)
if err != nil {
return nil, nil, err
}

img, err := c.BuildImageConfig(ctx, client, spec, platform, targetKey)
if err != nil {
return nil, nil, err
}

ctr, err := c.BuildContainer(worker, sOpt, client, spec, targetKey, deb)
if err != nil {
return nil, nil, err
}

ref, err := c.runTests(ctx, client, spec, sOpt, targetKey, ctr, pg)
return ref, img, err
})
}
5 changes: 3 additions & 2 deletions frontend/linux/deb/distro/distro.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/Azure/dalec"
"github.com/Azure/dalec/frontend"
"github.com/Azure/dalec/frontend/linux"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/client/llb/sourceresolver"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
Expand Down Expand Up @@ -101,13 +102,13 @@ func (cfg *Config) RepoMounts(repos []dalec.PackageRepositoryConfig, sOpt dalec.
func (cfg *Config) Handle(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) {
var mux frontend.BuildMux

mux.Add("deb", cfg.HandleDeb, &targets.Target{
mux.Add("deb", linux.HandlePackage(cfg), &targets.Target{
Name: "deb",
Description: "Builds a deb package.",
Default: true,
})

mux.Add("testing/container", cfg.HandleContainer, &targets.Target{
mux.Add("testing/container", linux.HandleContainer(cfg), &targets.Target{
Name: "testing/container",
Description: "Builds a container image for testing purposes only.",
})
Expand Down
67 changes: 6 additions & 61 deletions frontend/linux/deb/distro/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ import (
"github.com/Azure/dalec/frontend"
"github.com/Azure/dalec/frontend/linux/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"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)

func (d *Config) BuildDeb(ctx context.Context, worker llb.State, sOpt dalec.SourceOpts, client gwclient.Client, spec *dalec.Spec, targetKey string, opts ...llb.ConstraintsOpt) (llb.State, error) {
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
Expand Down Expand Up @@ -132,65 +135,7 @@ func addPaths(paths []string, opts ...llb.ConstraintsOpt) llb.StateOption {
}
}

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)
if err != nil {
return nil, nil, err
}

pg := dalec.ProgressGroup(spec.Name)

worker, err := cfg.Worker(sOpt, pg)
if err != nil {
return nil, nil, err
}

st, err := cfg.BuildDeb(ctx, worker, sOpt, client, spec, targetKey, pg)
if err != nil {
return nil, nil, err
}

def, err := st.Marshal(ctx)
if err != nil {
return nil, nil, err
}

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
}

if err := ref.Evaluate(ctx); err != nil {
return ref, nil, err
}

ctr, err := cfg.BuildContainer(worker, sOpt, client, spec, targetKey, st, pg)
if err != nil {
return ref, nil, err
}

if ref, err := cfg.runTests(ctx, client, spec, sOpt, targetKey, ctr, pg); err != nil {
cfg, _ := cfg.BuildImageConfig(ctx, client, spec, platform, targetKey)
return ref, cfg, err
}

if platform == nil {
p := platforms.DefaultSpec()
platform = &p
}
return ref, &dalec.DockerImageSpec{Image: ocispecs.Image{Platform: *platform}}, nil
})
}

func (cfg *Config) runTests(ctx context.Context, client gwclient.Client, spec *dalec.Spec, sOpt dalec.SourceOpts, targetKey string, ctr llb.State, opts ...llb.ConstraintsOpt) (gwclient.Reference, error) {
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
Expand Down
174 changes: 174 additions & 0 deletions frontend/linux/distro_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package linux

import (
"context"
"encoding/json"
"fmt"

"github.com/Azure/dalec"
"github.com/Azure/dalec/frontend"
"github.com/containerd/platforms"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/client/llb/sourceresolver"
"github.com/pkg/errors"

gwclient "github.com/moby/buildkit/frontend/gateway/client"

ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
)

type DistroConfig interface {
// Validate does any distro or packaging-specific validation of a Dalec spec.
Validate(*dalec.Spec) error

// Worker returns the worker image for the particular distro
Worker(sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.State, error)

// BuildPkg returns an llb.State containing the built package
// which the passed in spec describes. This should be composable with
// BuildContainer(), which can consume the returned state.
BuildPkg(ctx context.Context,
client gwclient.Client,
worker llb.State,
sOpt dalec.SourceOpts,
spec *dalec.Spec, targetKey string, opts ...llb.ConstraintsOpt) (llb.State, error)

// BuildContainer consumes an llb.State containing the built package from the
// given *dalec.Spec, and installs it in a target container.
BuildContainer(client gwclient.Client, worker llb.State, sOpt dalec.SourceOpts,
spec *dalec.Spec, targetKey string,
pkgState llb.State, opts ...llb.ConstraintsOpt) (llb.State, error)

// RunTests runts the tests specified in a dalec spec against a built container, which may be the target container.
// Some distros may need to pass in a separate worker before mounting the target container.
RunTests(ctx context.Context, client gwclient.Client, worker llb.State, spec *dalec.Spec, sOpt dalec.SourceOpts, ctr llb.State,
targetKey string, opts ...llb.ConstraintsOpt) (gwclient.Reference, error)
}

func BuildImageConfig(ctx context.Context, resolver llb.ImageMetaResolver, spec *dalec.Spec, platform *ocispecs.Platform, targetKey string) (*dalec.DockerImageSpec, error) {
img, err := resolveConfig(ctx, resolver, spec, platform, targetKey)
if err != nil {
return nil, err
}

if err := dalec.BuildImageConfig(spec, targetKey, img); err != nil {
return nil, err
}

return img, nil
}

func resolveConfig(ctx context.Context, resolver llb.ImageMetaResolver, spec *dalec.Spec, platform *ocispecs.Platform, targetKey string) (*dalec.DockerImageSpec, error) {
ref := dalec.GetBaseOutputImage(spec, targetKey)
if ref == "" {
return dalec.BaseImageConfig(platform), nil
}

_, _, dt, err := resolver.ResolveImageConfig(ctx, ref, sourceresolver.Opt{
Platform: platform,
})
if err != nil {
return nil, err
}

var img dalec.DockerImageSpec
if err := json.Unmarshal(dt, &img); err != nil {
return nil, errors.Wrap(err, "error unmarshalling base image config")
}
return &img, nil
}

func HandleContainer(c DistroConfig) gwclient.BuildFunc {
return func(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)
if err != nil {
return nil, nil, err
}

pg := dalec.ProgressGroup(spec.Name)
worker, err := c.Worker(sOpt, pg)
if err != nil {
return nil, nil, err
}

deb, err := c.BuildPkg(ctx, client, worker, sOpt, spec, targetKey, pg)
if err != nil {
return nil, nil, err
}

img, err := BuildImageConfig(ctx, sOpt.Resolver, spec, platform, targetKey)

ctr, err := c.BuildContainer(client, worker, sOpt, spec, targetKey, deb)
if err != nil {
return nil, nil, err
}

ref, err := c.RunTests(ctx, client, worker, spec, sOpt, ctr, targetKey, pg)
return ref, img, err
})
}
}

func HandlePackage(cfg DistroConfig) gwclient.BuildFunc {
return func(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) {
if err := cfg.Validate(spec); err != nil {
return nil, nil, fmt.Errorf("rpm: invalid spec: %w", err)
}

pg := dalec.ProgressGroup("Building " + targetKey + " package: " + spec.Name)
sOpt, err := frontend.SourceOptFromClient(ctx, client)
if err != nil {
return nil, nil, err
}

worker, err := cfg.Worker(sOpt, pg)
if err != nil {
return nil, nil, errors.Wrap(err, "error building worker container")
}

pkgSt, err := cfg.BuildPkg(ctx, client, worker, sOpt, spec, targetKey, pg)
if err != nil {
return nil, nil, err
}

def, err := pkgSt.Marshal(ctx, pg)
if err != nil {
return nil, nil, fmt.Errorf("error marshalling llb: %w", err)
}

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
}
if err := ref.Evaluate(ctx); err != nil {
return ref, nil, err
}

ctr, err := cfg.BuildContainer(client, worker, sOpt, spec, targetKey, pkgSt)
if err != nil {
return ref, nil, err
}

if ref, err := cfg.RunTests(ctx, client, worker, spec, sOpt, ctr, targetKey, pg); err != nil {
cfg, _ := BuildImageConfig(ctx, client, spec, platform, targetKey)
return ref, cfg, err
}

if platform == nil {
p := platforms.DefaultSpec()
platform = &p
}
return ref, &dalec.DockerImageSpec{Image: ocispecs.Image{Platform: *platform}}, nil
})
}
}
Loading

0 comments on commit d834e04

Please sign in to comment.