build: add experimental support for print flag

Print flag can be used to make additional information
requests about the build and print their results.

Currently Dockerfile supports: outline, targets, subrequests.describe

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2022-05-04 18:48:50 -07:00
parent 17dc0e1108
commit eefa8188e1
98 changed files with 8536 additions and 3339 deletions

View File

@ -0,0 +1,81 @@
package subrequests
import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
"text/tabwriter"
"github.com/moby/buildkit/frontend/gateway/client"
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/solver/errdefs"
"github.com/pkg/errors"
)
const RequestSubrequestsDescribe = "frontend.subrequests.describe"
var SubrequestsDescribeDefinition = Request{
Name: RequestSubrequestsDescribe,
Version: "1.0.0",
Type: TypeRPC,
Description: "List available subrequest types",
Metadata: []Named{
{Name: "result.json"},
{Name: "result.txt"},
},
}
func Describe(ctx context.Context, c client.Client) ([]Request, error) {
gwcaps := c.BuildOpts().Caps
if err := (&gwcaps).Supports(gwpb.CapFrontendCaps); err != nil {
return nil, errdefs.NewUnsupportedSubrequestError(RequestSubrequestsDescribe)
}
res, err := c.Solve(ctx, client.SolveRequest{
FrontendOpt: map[string]string{
"requestid": RequestSubrequestsDescribe,
"frontend.caps": "moby.buildkit.frontend.subrequests",
},
Frontend: "dockerfile.v0",
})
if err != nil {
var reqErr *errdefs.UnsupportedSubrequestError
if errors.As(err, &reqErr) {
return nil, err
}
var capErr *errdefs.UnsupportedFrontendCapError
if errors.As(err, &capErr) {
return nil, errdefs.NewUnsupportedSubrequestError(RequestSubrequestsDescribe)
}
return nil, err
}
dt, ok := res.Metadata["result.json"]
if !ok {
return nil, errors.Errorf("no result.json metadata in response")
}
var reqs []Request
if err := json.Unmarshal(dt, &reqs); err != nil {
return nil, errors.Wrap(err, "failed to parse describe result")
}
return reqs, nil
}
func PrintDescribe(dt []byte, w io.Writer) error {
var d []Request
if err := json.Unmarshal(dt, &d); err != nil {
return err
}
tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0)
fmt.Fprintf(tw, "NAME\tVERSION\tDESCRIPTION\n")
for _, r := range d {
fmt.Fprintf(tw, "%s\t%s\t%s\n", strings.TrimPrefix(r.Name, "frontend."), r.Version, r.Description)
}
return tw.Flush()
}

View File

@ -0,0 +1,146 @@
package outline
import (
"bytes"
"encoding/json"
"fmt"
"io"
"text/tabwriter"
"github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/frontend/subrequests"
"github.com/moby/buildkit/solver/pb"
)
const RequestSubrequestsOutline = "frontend.outline"
var SubrequestsOutlineDefinition = subrequests.Request{
Name: RequestSubrequestsOutline,
Version: "1.0.0",
Type: subrequests.TypeRPC,
Description: "List all parameters current build target supports",
Opts: []subrequests.Named{
{
Name: "target",
Description: "Target build stage",
},
},
Metadata: []subrequests.Named{
{Name: "result.json"},
{Name: "result.txt"},
},
}
type Outline struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Args []Arg `json:"args,omitempty"`
Secrets []Secret `json:"secrets,omitempty"`
SSH []SSH `json:"ssh,omitempty"`
Cache []CacheMount `json:"cache,omitempty"`
Sources [][]byte `json:"sources,omitempty"`
}
func (o Outline) ToResult() (*client.Result, error) {
res := client.NewResult()
dt, err := json.MarshalIndent(o, "", " ")
if err != nil {
return nil, err
}
res.AddMeta("result.json", dt)
b := bytes.NewBuffer(nil)
if err := PrintOutline(dt, b); err != nil {
return nil, err
}
res.AddMeta("result.txt", b.Bytes())
res.AddMeta("version", []byte(SubrequestsOutlineDefinition.Version))
return res, nil
}
type Arg struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Value string `json:"value,omitempty"`
Location *pb.Location `json:"location,omitempty"`
}
type Secret struct {
Name string `json:"name"`
Required bool `json:"required,omitempty"`
Location *pb.Location `json:"location,omitempty"`
}
type SSH struct {
Name string `json:"name"`
Required bool `json:"required,omitempty"`
Location *pb.Location `json:"location,omitempty"`
}
type CacheMount struct {
ID string `json:"ID"`
Location *pb.Location `json:"location,omitempty"`
}
func PrintOutline(dt []byte, w io.Writer) error {
var o Outline
if err := json.Unmarshal(dt, &o); err != nil {
return err
}
if o.Name != "" || o.Description != "" {
tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0)
name := o.Name
if o.Name == "" {
name = "(default)"
}
fmt.Fprintf(tw, "TARGET:\t%s\n", name)
if o.Description != "" {
fmt.Fprintf(tw, "DESCRIPTION:\t%s\n", o.Description)
}
tw.Flush()
fmt.Println()
}
if len(o.Args) > 0 {
tw := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
fmt.Fprintf(tw, "BUILD ARG\tVALUE\tDESCRIPTION\n")
for _, a := range o.Args {
fmt.Fprintf(tw, "%s\t%s\t%s\n", a.Name, a.Value, a.Description)
}
tw.Flush()
fmt.Println()
}
if len(o.Secrets) > 0 {
tw := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
fmt.Fprintf(tw, "SECRET\tREQUIRED\n")
for _, s := range o.Secrets {
b := ""
if s.Required {
b = "true"
}
fmt.Fprintf(tw, "%s\t%s\n", s.Name, b)
}
tw.Flush()
fmt.Println()
}
if len(o.SSH) > 0 {
tw := tabwriter.NewWriter(w, 0, 0, 3, ' ', 0)
fmt.Fprintf(tw, "SSH\tREQUIRED\n")
for _, s := range o.SSH {
b := ""
if s.Required {
b = "true"
}
fmt.Fprintf(tw, "%s\t%s\n", s.Name, b)
}
tw.Flush()
fmt.Println()
}
return nil
}

View File

@ -0,0 +1,84 @@
package targets
import (
"bytes"
"encoding/json"
"fmt"
"io"
"text/tabwriter"
"github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/frontend/subrequests"
"github.com/moby/buildkit/solver/pb"
)
const RequestTargets = "frontend.targets"
var SubrequestsTargetsDefinition = subrequests.Request{
Name: RequestTargets,
Version: "1.0.0",
Type: subrequests.TypeRPC,
Description: "List all targets current build supports",
Opts: []subrequests.Named{},
Metadata: []subrequests.Named{
{Name: "result.json"},
{Name: "result.txt"},
},
}
type List struct {
Targets []Target `json:"targets"`
Sources [][]byte `json:"sources"`
}
func (l List) ToResult() (*client.Result, error) {
res := client.NewResult()
dt, err := json.MarshalIndent(l, "", " ")
if err != nil {
return nil, err
}
res.AddMeta("result.json", dt)
b := bytes.NewBuffer(nil)
if err := PrintTargets(dt, b); err != nil {
return nil, err
}
res.AddMeta("result.txt", b.Bytes())
res.AddMeta("version", []byte(SubrequestsTargetsDefinition.Version))
return res, nil
}
type Target struct {
Name string `json:"name,omitempty"`
Default bool `json:"default,omitempty"`
Description string `json:"description,omitempty"`
Base string `json:"base,omitempty"`
Platform string `json:"platform,omitempty"`
Location *pb.Location `json:"location,omitempty"`
}
func PrintTargets(dt []byte, w io.Writer) error {
var l List
if err := json.Unmarshal(dt, &l); err != nil {
return err
}
tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0)
fmt.Fprintf(tw, "TARGET\tDESCRIPTION\n")
for _, t := range l.Targets {
name := t.Name
if name == "" && t.Default {
name = "(default)"
} else {
if t.Default {
name = fmt.Sprintf("%s (default)", name)
}
}
fmt.Fprintf(tw, "%s\t%s\n", name, t.Description)
}
return tw.Flush()
}

View File

@ -0,0 +1,21 @@
package subrequests
type Request struct {
Name string `json:"name"`
Version string `json:"version"`
Type RequestType `json:"type"`
Description string `json:"description"`
Opts []Named `json:"opts"`
Inputs []Named `json:"inputs"`
Metadata []Named `json:"metadata"`
Refs []Named `json:"refs"`
}
type Named struct {
Name string `json:"name"`
Description string `json:"description"`
}
type RequestType string
const TypeRPC RequestType = "rpc"