targets/linux/rpm/distro/debug.go (154 lines of code) (raw):

package distro import ( "context" "fmt" "slices" "github.com/Azure/dalec" "github.com/Azure/dalec/frontend" "github.com/Azure/dalec/packaging/linux/rpm" "github.com/containerd/platforms" "github.com/moby/buildkit/client/llb" gwclient "github.com/moby/buildkit/frontend/gateway/client" "github.com/moby/buildkit/frontend/subrequests/targets" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) // DebugWorker returns a worker image with the build dependencies specified in `spec` installed, // if needed. // It is most useful for `HandleSources` handler in which we aren't building a full worker image with // build dependencies because we aren't executing build steps, but we may still have source generators // which depend on `build` dependencies in the spec in order to run. func (c *Config) DebugWorker(ctx context.Context, client gwclient.Client, spec *dalec.Spec, targetKey string, sOpt dalec.SourceOpts, opts ...llb.ConstraintsOpt) (llb.State, error) { worker, err := c.Worker(sOpt, opts...) if err != nil { return llb.Scratch(), err } deps := dalec.SortMapKeys(spec.GetBuildDeps(targetKey)) if spec.HasGomods() { hasGolang := func(s string) bool { return s == "golang" || s == "msft-golang" } if !slices.ContainsFunc(deps, hasGolang) { return llb.Scratch(), errors.New("spec contains go modules but does not have golang in build deps") } worker = worker.With(c.InstallBuildDeps(ctx, client, spec, sOpt, targetKey)) } if spec.HasCargohomes() { hasRust := func(s string) bool { return s == "rust" } if !slices.ContainsFunc(deps, hasRust) { return llb.Scratch(), errors.New("spec contains go modules but does not have golang in build deps") } worker = worker.With(c.InstallBuildDeps(ctx, client, spec, sOpt, targetKey)) } return worker, nil } func (c *Config) HandleBuildroot(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 := rpm.ValidateSpec(spec); err != nil { return nil, nil, fmt.Errorf("rpm: invalid spec: %w", err) } pg := dalec.ProgressGroup("Setting up " + targetKey + " rpm buildroot: " + spec.Name) sOpt, err := frontend.SourceOptFromClient(ctx, client, platform) if err != nil { return nil, nil, err } pc := dalec.Platform(platform) worker, err := c.Worker(sOpt, pg, pc) if err != nil { return nil, nil, errors.Wrap(err, "error building worker container") } worker = worker.With(c.InstallBuildDeps(ctx, client, spec, sOpt, targetKey, pg, pc)) br, err := rpm.SpecToBuildrootLLB(worker, spec, sOpt, targetKey, pg) if err != nil { return nil, nil, err } def, err := br.Marshal(ctx, pc) 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 platform == nil { p := platforms.DefaultSpec() platform = &p } return ref, &dalec.DockerImageSpec{Image: ocispecs.Image{Platform: *platform}}, nil }) } func (c *Config) HandleSources(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 } pc := dalec.Platform(platform) worker, err := c.DebugWorker(ctx, client, spec, targetKey, sOpt, pc) if err != nil { return nil, nil, err } sources, err := rpm.ToSourcesLLB(worker, spec, sOpt, pc) if err != nil { return nil, nil, err } // Now we can merge sources into the desired path st := dalec.MergeAtPath(llb.Scratch(), sources, "/SOURCES") def, err := st.Marshal(ctx, pc) 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 } return ref, &dalec.DockerImageSpec{}, nil }) } func (c *Config) HandleSpec(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) { pc := dalec.Platform(platform) st, err := rpm.ToSpecLLB(spec, llb.Scratch(), targetKey, "", pc) if err != nil { return nil, nil, err } def, err := st.Marshal(ctx, pc) 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() return ref, &dalec.DockerImageSpec{}, err }) } // HandleDebug returns a build function that adds support for some debugging targets for RPM builds. func (c *Config) HandleDebug() gwclient.BuildFunc { return func(ctx context.Context, client gwclient.Client) (*gwclient.Result, error) { var r frontend.BuildMux r.Add("buildroot", c.HandleBuildroot, &targets.Target{ Name: "buildroot", Description: "Outputs an rpm buildroot suitable for passing to rpmbuild.", }) r.Add("sources", c.HandleSources, &targets.Target{ Name: "sources", Description: "Outputs all the sources specified in the spec file in the format given to rpmbuild.", }) r.Add("spec", c.HandleSpec, &targets.Target{ Name: "spec", Description: "Outputs the generated RPM spec file", }) return r.Handle(ctx, client) } }