mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-03 17:43:42 +08:00 
			
		
		
		
	Merge pull request #2666 from tonistiigi/bake-entitlements
bake: enable support for entitlements
This commit is contained in:
		
							
								
								
									
										19
									
								
								bake/bake.go
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								bake/bake.go
									
									
									
									
									
								
							@@ -25,6 +25,7 @@ import (
 | 
			
		||||
	"github.com/moby/buildkit/client"
 | 
			
		||||
	"github.com/moby/buildkit/client/llb"
 | 
			
		||||
	"github.com/moby/buildkit/session/auth/authprovider"
 | 
			
		||||
	"github.com/moby/buildkit/util/entitlements"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/tonistiigi/go-csvvalue"
 | 
			
		||||
	"github.com/zclconf/go-cty/cty"
 | 
			
		||||
@@ -542,7 +543,7 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
 | 
			
		||||
			o := t[kk[1]]
 | 
			
		||||
 | 
			
		||||
			switch keys[1] {
 | 
			
		||||
			case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh", "attest":
 | 
			
		||||
			case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh", "attest", "entitlements":
 | 
			
		||||
				if len(parts) == 2 {
 | 
			
		||||
					o.ArrValue = append(o.ArrValue, parts[1])
 | 
			
		||||
				}
 | 
			
		||||
@@ -708,6 +709,7 @@ type Target struct {
 | 
			
		||||
	ShmSize          *string            `json:"shm-size,omitempty" hcl:"shm-size,optional"`
 | 
			
		||||
	Ulimits          []string           `json:"ulimits,omitempty" hcl:"ulimits,optional"`
 | 
			
		||||
	Call             *string            `json:"call,omitempty" hcl:"call,optional" cty:"call"`
 | 
			
		||||
	Entitlements     []string           `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
 | 
			
		||||
	// IMPORTANT: if you add more fields here, do not forget to update newOverrides/AddOverrides and docs/bake-reference.md.
 | 
			
		||||
 | 
			
		||||
	// linked is a private field to mark a target used as a linked one
 | 
			
		||||
@@ -732,6 +734,12 @@ func (t *Target) normalize() {
 | 
			
		||||
	t.NoCacheFilter = removeDupes(t.NoCacheFilter)
 | 
			
		||||
	t.Ulimits = removeDupes(t.Ulimits)
 | 
			
		||||
 | 
			
		||||
	if t.NetworkMode != nil && *t.NetworkMode == "host" {
 | 
			
		||||
		t.Entitlements = append(t.Entitlements, "network.host")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Entitlements = removeDupes(t.Entitlements)
 | 
			
		||||
 | 
			
		||||
	for k, v := range t.Contexts {
 | 
			
		||||
		if v == "" {
 | 
			
		||||
			delete(t.Contexts, k)
 | 
			
		||||
@@ -831,6 +839,9 @@ func (t *Target) Merge(t2 *Target) {
 | 
			
		||||
	if t2.Description != "" {
 | 
			
		||||
		t.Description = t2.Description
 | 
			
		||||
	}
 | 
			
		||||
	if t2.Entitlements != nil { // merge
 | 
			
		||||
		t.Entitlements = append(t.Entitlements, t2.Entitlements...)
 | 
			
		||||
	}
 | 
			
		||||
	t.Inherits = append(t.Inherits, t2.Inherits...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -885,6 +896,8 @@ func (t *Target) AddOverrides(overrides map[string]Override) error {
 | 
			
		||||
			t.Platforms = o.ArrValue
 | 
			
		||||
		case "output":
 | 
			
		||||
			t.Outputs = o.ArrValue
 | 
			
		||||
		case "entitlements":
 | 
			
		||||
			t.Entitlements = append(t.Entitlements, o.ArrValue...)
 | 
			
		||||
		case "annotations":
 | 
			
		||||
			t.Annotations = append(t.Annotations, o.ArrValue...)
 | 
			
		||||
		case "attest":
 | 
			
		||||
@@ -1368,6 +1381,10 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
 | 
			
		||||
	}
 | 
			
		||||
	bo.Ulimits = ulimits
 | 
			
		||||
 | 
			
		||||
	for _, ent := range t.Entitlements {
 | 
			
		||||
		bo.Allow = append(bo.Allow, entitlements.Entitlement(ent))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return bo, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/moby/buildkit/util/entitlements"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
@@ -1726,3 +1727,70 @@ func TestAnnotations(t *testing.T) {
 | 
			
		||||
	require.Len(t, bo["app"].Exports, 1)
 | 
			
		||||
	require.Equal(t, "bar", bo["app"].Exports[0].Attrs["annotation-manifest[linux/amd64].foo"])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestHCLEntitlements(t *testing.T) {
 | 
			
		||||
	fp := File{
 | 
			
		||||
		Name: "docker-bake.hcl",
 | 
			
		||||
		Data: []byte(
 | 
			
		||||
			`target "app" {
 | 
			
		||||
				entitlements = ["security.insecure", "network.host"]
 | 
			
		||||
			}`),
 | 
			
		||||
	}
 | 
			
		||||
	ctx := context.TODO()
 | 
			
		||||
	m, g, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	bo, err := TargetsToBuildOpt(m, &Input{})
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	require.Equal(t, 1, len(g))
 | 
			
		||||
	require.Equal(t, []string{"app"}, g["default"].Targets)
 | 
			
		||||
 | 
			
		||||
	require.Equal(t, 1, len(m))
 | 
			
		||||
	require.Contains(t, m, "app")
 | 
			
		||||
	require.Len(t, m["app"].Entitlements, 2)
 | 
			
		||||
	require.Equal(t, "security.insecure", m["app"].Entitlements[0])
 | 
			
		||||
	require.Equal(t, "network.host", m["app"].Entitlements[1])
 | 
			
		||||
 | 
			
		||||
	require.Len(t, bo["app"].Allow, 2)
 | 
			
		||||
	require.Equal(t, entitlements.EntitlementSecurityInsecure, bo["app"].Allow[0])
 | 
			
		||||
	require.Equal(t, entitlements.EntitlementNetworkHost, bo["app"].Allow[1])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEntitlementsForNetHost(t *testing.T) {
 | 
			
		||||
	fp := File{
 | 
			
		||||
		Name: "docker-bake.hcl",
 | 
			
		||||
		Data: []byte(
 | 
			
		||||
			`target "app" {
 | 
			
		||||
				dockerfile = "app.Dockerfile"
 | 
			
		||||
			}`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fp2 := File{
 | 
			
		||||
		Name: "docker-compose.yml",
 | 
			
		||||
		Data: []byte(
 | 
			
		||||
			`services:
 | 
			
		||||
  app:
 | 
			
		||||
    build:
 | 
			
		||||
      network: "host"
 | 
			
		||||
`),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.TODO()
 | 
			
		||||
	m, g, err := ReadTargets(ctx, []File{fp, fp2}, []string{"app"}, nil, nil)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	bo, err := TargetsToBuildOpt(m, &Input{})
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	require.Equal(t, 1, len(g))
 | 
			
		||||
	require.Equal(t, []string{"app"}, g["default"].Targets)
 | 
			
		||||
 | 
			
		||||
	require.Equal(t, 1, len(m))
 | 
			
		||||
	require.Contains(t, m, "app")
 | 
			
		||||
	require.Len(t, m["app"].Entitlements, 1)
 | 
			
		||||
	require.Equal(t, "network.host", m["app"].Entitlements[0])
 | 
			
		||||
 | 
			
		||||
	require.Len(t, bo["app"].Allow, 1)
 | 
			
		||||
	require.Equal(t, entitlements.EntitlementNetworkHost, bo["app"].Allow[0])
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										175
									
								
								bake/entitlements.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								bake/entitlements.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,175 @@
 | 
			
		||||
package bake
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/containerd/console"
 | 
			
		||||
	"github.com/docker/buildx/build"
 | 
			
		||||
	"github.com/moby/buildkit/util/entitlements"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type EntitlementKey string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	EntitlementKeyNetworkHost      EntitlementKey = "network.host"
 | 
			
		||||
	EntitlementKeySecurityInsecure EntitlementKey = "security.insecure"
 | 
			
		||||
	EntitlementKeyFSRead           EntitlementKey = "fs.read"
 | 
			
		||||
	EntitlementKeyFSWrite          EntitlementKey = "fs.write"
 | 
			
		||||
	EntitlementKeyFS               EntitlementKey = "fs"
 | 
			
		||||
	EntitlementKeyImagePush        EntitlementKey = "image.push"
 | 
			
		||||
	EntitlementKeyImageLoad        EntitlementKey = "image.load"
 | 
			
		||||
	EntitlementKeyImage            EntitlementKey = "image"
 | 
			
		||||
	EntitlementKeySSH              EntitlementKey = "ssh"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type EntitlementConf struct {
 | 
			
		||||
	NetworkHost      bool
 | 
			
		||||
	SecurityInsecure bool
 | 
			
		||||
	FSRead           []string
 | 
			
		||||
	FSWrite          []string
 | 
			
		||||
	ImagePush        []string
 | 
			
		||||
	ImageLoad        []string
 | 
			
		||||
	SSH              bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseEntitlements(in []string) (EntitlementConf, error) {
 | 
			
		||||
	var conf EntitlementConf
 | 
			
		||||
	for _, e := range in {
 | 
			
		||||
		switch e {
 | 
			
		||||
		case string(EntitlementKeyNetworkHost):
 | 
			
		||||
			conf.NetworkHost = true
 | 
			
		||||
		case string(EntitlementKeySecurityInsecure):
 | 
			
		||||
			conf.SecurityInsecure = true
 | 
			
		||||
		case string(EntitlementKeySSH):
 | 
			
		||||
			conf.SSH = true
 | 
			
		||||
		default:
 | 
			
		||||
			k, v, _ := strings.Cut(e, "=")
 | 
			
		||||
			switch k {
 | 
			
		||||
			case string(EntitlementKeyFSRead):
 | 
			
		||||
				conf.FSRead = append(conf.FSRead, v)
 | 
			
		||||
			case string(EntitlementKeyFSWrite):
 | 
			
		||||
				conf.FSWrite = append(conf.FSWrite, v)
 | 
			
		||||
			case string(EntitlementKeyFS):
 | 
			
		||||
				conf.FSRead = append(conf.FSRead, v)
 | 
			
		||||
				conf.FSWrite = append(conf.FSWrite, v)
 | 
			
		||||
			case string(EntitlementKeyImagePush):
 | 
			
		||||
				conf.ImagePush = append(conf.ImagePush, v)
 | 
			
		||||
			case string(EntitlementKeyImageLoad):
 | 
			
		||||
				conf.ImageLoad = append(conf.ImageLoad, v)
 | 
			
		||||
			case string(EntitlementKeyImage):
 | 
			
		||||
				conf.ImagePush = append(conf.ImagePush, v)
 | 
			
		||||
				conf.ImageLoad = append(conf.ImageLoad, v)
 | 
			
		||||
			default:
 | 
			
		||||
				return conf, errors.Errorf("uknown entitlement key %q", k)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// TODO: dedupe slices and parent paths
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return conf, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c EntitlementConf) Validate(m map[string]build.Options) (EntitlementConf, error) {
 | 
			
		||||
	var expected EntitlementConf
 | 
			
		||||
 | 
			
		||||
	for _, v := range m {
 | 
			
		||||
		if err := c.check(v, &expected); err != nil {
 | 
			
		||||
			return EntitlementConf{}, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return expected, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c EntitlementConf) check(bo build.Options, expected *EntitlementConf) error {
 | 
			
		||||
	for _, e := range bo.Allow {
 | 
			
		||||
		switch e {
 | 
			
		||||
		case entitlements.EntitlementNetworkHost:
 | 
			
		||||
			if !c.NetworkHost {
 | 
			
		||||
				expected.NetworkHost = true
 | 
			
		||||
			}
 | 
			
		||||
		case entitlements.EntitlementSecurityInsecure:
 | 
			
		||||
			if !c.SecurityInsecure {
 | 
			
		||||
				expected.SecurityInsecure = true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c EntitlementConf) Prompt(ctx context.Context, out io.Writer) error {
 | 
			
		||||
	var term bool
 | 
			
		||||
	if _, err := console.ConsoleFromFile(os.Stdin); err == nil {
 | 
			
		||||
		term = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var msgs []string
 | 
			
		||||
	var flags []string
 | 
			
		||||
 | 
			
		||||
	if c.NetworkHost {
 | 
			
		||||
		msgs = append(msgs, " - Running build containers that can access host network")
 | 
			
		||||
		flags = append(flags, "network.host")
 | 
			
		||||
	}
 | 
			
		||||
	if c.SecurityInsecure {
 | 
			
		||||
		msgs = append(msgs, " - Running privileged containers that can make system changes")
 | 
			
		||||
		flags = append(flags, "security.insecure")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(msgs) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(out, "Your build is requesting privileges for following possibly insecure capabilities:\n\n")
 | 
			
		||||
	for _, m := range msgs {
 | 
			
		||||
		fmt.Fprintf(out, "%s\n", m)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, f := range flags {
 | 
			
		||||
		flags[i] = "--allow=" + f
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if term {
 | 
			
		||||
		fmt.Fprintf(out, "\nIn order to not see this message in the future pass %q to grant requested privileges.\n", strings.Join(flags, " "))
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Fprintf(out, "\nPass %q to grant requested privileges.\n", strings.Join(flags, " "))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	args := append([]string(nil), os.Args...)
 | 
			
		||||
	if v, ok := os.LookupEnv("DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND"); ok && v != "" {
 | 
			
		||||
		args[0] = v
 | 
			
		||||
	}
 | 
			
		||||
	idx := slices.Index(args, "bake")
 | 
			
		||||
 | 
			
		||||
	if idx != -1 {
 | 
			
		||||
		fmt.Fprintf(out, "\nYour full command with requested privileges:\n\n")
 | 
			
		||||
		fmt.Fprintf(out, "%s %s %s\n\n", strings.Join(args[:idx+1], " "), strings.Join(flags, " "), strings.Join(args[idx+1:], " "))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if term {
 | 
			
		||||
		fmt.Fprintf(out, "Do you want to grant requested privileges and continue? [y/N] ")
 | 
			
		||||
		reader := bufio.NewReader(os.Stdin)
 | 
			
		||||
		answerCh := make(chan string, 1)
 | 
			
		||||
		go func() {
 | 
			
		||||
			answer, _, _ := reader.ReadLine()
 | 
			
		||||
			answerCh <- string(answer)
 | 
			
		||||
			close(answerCh)
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
		case answer := <-answerCh:
 | 
			
		||||
			if strings.ToLower(string(answer)) == "y" {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return errors.Errorf("additional privileges requested")
 | 
			
		||||
}
 | 
			
		||||
@@ -49,6 +49,7 @@ type bakeOptions struct {
 | 
			
		||||
	listVars    bool
 | 
			
		||||
	sbom        string
 | 
			
		||||
	provenance  string
 | 
			
		||||
	allow       []string
 | 
			
		||||
 | 
			
		||||
	builder      string
 | 
			
		||||
	metadataFile string
 | 
			
		||||
@@ -102,6 +103,11 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
 | 
			
		||||
	}
 | 
			
		||||
	contextPathHash, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	ent, err := bake.ParseEntitlements(in.allow)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx2, cancel := context.WithCancel(context.TODO())
 | 
			
		||||
	defer cancel()
 | 
			
		||||
 | 
			
		||||
@@ -138,6 +144,9 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
 | 
			
		||||
 | 
			
		||||
	progressMode := progressui.DisplayMode(cFlags.progress)
 | 
			
		||||
	var printer *progress.Printer
 | 
			
		||||
 | 
			
		||||
	makePrinter := func() error {
 | 
			
		||||
		var err error
 | 
			
		||||
		printer, err = progress.NewPrinter(ctx2, os.Stderr, progressMode,
 | 
			
		||||
			progress.WithDesc(progressTextDesc, progressConsoleDesc),
 | 
			
		||||
			progress.WithMetrics(mp, attributes),
 | 
			
		||||
@@ -145,7 +154,10 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
 | 
			
		||||
				printWarnings(os.Stderr, printer.Warnings(), progressMode)
 | 
			
		||||
			}),
 | 
			
		||||
		)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := makePrinter(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -234,6 +246,20 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	exp, err := ent.Validate(bo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := exp.Prompt(ctx, &syncWriter{w: dockerCli.Err(), wait: printer.Wait}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if printer.IsDone() {
 | 
			
		||||
		// init new printer as old one was stopped to show the prompt
 | 
			
		||||
		if err := makePrinter(); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := saveLocalStateGroup(dockerCli, in, targets, bo, overrides, def); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -412,6 +438,7 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
 | 
			
		||||
	flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--set=*.attest=type=provenance"`)
 | 
			
		||||
	flags.StringArrayVar(&options.overrides, "set", nil, `Override target value (e.g., "targetpattern.key=value")`)
 | 
			
		||||
	flags.StringVar(&options.callFunc, "call", "build", `Set method for evaluating build ("check", "outline", "targets")`)
 | 
			
		||||
	flags.StringArrayVar(&options.allow, "allow", nil, "Allow build to access specified resources")
 | 
			
		||||
 | 
			
		||||
	flags.VarPF(callAlias(&options.callFunc, "check"), "check", "", `Shorthand for "--call=check"`)
 | 
			
		||||
	flags.Lookup("check").NoOptDefVal = "true"
 | 
			
		||||
@@ -652,3 +679,21 @@ func immutableSort(s []string) []string {
 | 
			
		||||
	}
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type syncWriter struct {
 | 
			
		||||
	w    io.Writer
 | 
			
		||||
	once sync.Once
 | 
			
		||||
	wait func() error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *syncWriter) Write(p []byte) (n int, err error) {
 | 
			
		||||
	w.once.Do(func() {
 | 
			
		||||
		if w.wait != nil {
 | 
			
		||||
			err = w.wait()
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	return w.w.Write(p)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ Build from a file
 | 
			
		||||
 | 
			
		||||
| Name                                | Type          | Default | Description                                                                                         |
 | 
			
		||||
|:------------------------------------|:--------------|:--------|:----------------------------------------------------------------------------------------------------|
 | 
			
		||||
| `--allow`                           | `stringArray` |         | Allow build to access specified resources                                                           |
 | 
			
		||||
| [`--builder`](#builder)             | `string`      |         | Override the configured builder instance                                                            |
 | 
			
		||||
| [`--call`](#call)                   | `string`      | `build` | Set method for evaluating build (`check`, `outline`, `targets`)                                     |
 | 
			
		||||
| [`--check`](#check)                 | `bool`        |         | Shorthand for `--call=check`                                                                        |
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,15 @@ func (p *Printer) Wait() error {
 | 
			
		||||
	return p.err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Printer) IsDone() bool {
 | 
			
		||||
	select {
 | 
			
		||||
	case <-p.done:
 | 
			
		||||
		return true
 | 
			
		||||
	default:
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Printer) Pause() error {
 | 
			
		||||
	p.paused = make(chan struct{})
 | 
			
		||||
	return p.Wait()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user