invoke: add messages

Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
This commit is contained in:
Kohei Tokunaga 2022-08-08 17:07:09 +09:00
parent 7b660c4e30
commit 2f9d46ce27
4 changed files with 34 additions and 12 deletions

View File

@ -709,10 +709,10 @@ func Invoke(ctx context.Context, cfg ContainerConfig) error {
} }
func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, docker DockerAPI, configDir string, w progress.Writer) (resp map[string]*client.SolveResponse, err error) { func Build(ctx context.Context, drivers []DriverInfo, opt map[string]Options, docker DockerAPI, configDir string, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
return BuildWithResultHandler(ctx, drivers, opt, docker, configDir, w, nil) return BuildWithResultHandler(ctx, drivers, opt, docker, configDir, w, nil, false)
} }
func BuildWithResultHandler(ctx context.Context, drivers []DriverInfo, opt map[string]Options, docker DockerAPI, configDir string, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultContext)) (resp map[string]*client.SolveResponse, err error) { func BuildWithResultHandler(ctx context.Context, drivers []DriverInfo, opt map[string]Options, docker DockerAPI, configDir string, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultContext), allowNoOutput bool) (resp map[string]*client.SolveResponse, err error) {
if len(drivers) == 0 { if len(drivers) == 0 {
return nil, errors.Errorf("driver required for build") return nil, errors.Errorf("driver required for build")
} }
@ -737,7 +737,7 @@ func BuildWithResultHandler(ctx context.Context, drivers []DriverInfo, opt map[s
noOutputTargets = append(noOutputTargets, name) noOutputTargets = append(noOutputTargets, name)
} }
} }
if len(noOutputTargets) > 0 { if len(noOutputTargets) > 0 && !allowNoOutput {
var warnNoOutputBuf bytes.Buffer var warnNoOutputBuf bytes.Buffer
warnNoOutputBuf.WriteString("No output specified ") warnNoOutputBuf.WriteString("No output specified ")
if len(noOutputTargets) == 1 && noOutputTargets[0] == "default" { if len(noOutputTargets) == 1 && noOutputTargets[0] == "default" {

View File

@ -233,7 +233,7 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
contextPathHash = in.contextPath contextPathHash = in.contextPath
} }
imageID, res, err := buildTargets(ctx, dockerCli, map[string]build.Options{defaultTargetName: opts}, in.progress, contextPathHash, in.builder, in.metadataFile) imageID, res, err := buildTargets(ctx, dockerCli, map[string]build.Options{defaultTargetName: opts}, in.progress, contextPathHash, in.builder, in.metadataFile, in.invoke != "")
err = wrapBuildError(err, false) err = wrapBuildError(err, false)
if err != nil { if err != nil {
return err return err
@ -250,7 +250,7 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
return errors.Errorf("failed to configure terminal: %v", err) return errors.Errorf("failed to configure terminal: %v", err)
} }
err = monitor.RunMonitor(ctx, cfg, func(ctx context.Context) (*build.ResultContext, error) { err = monitor.RunMonitor(ctx, cfg, func(ctx context.Context) (*build.ResultContext, error) {
_, rr, err := buildTargets(ctx, dockerCli, map[string]build.Options{defaultTargetName: opts}, in.progress, contextPathHash, in.builder, in.metadataFile) _, rr, err := buildTargets(ctx, dockerCli, map[string]build.Options{defaultTargetName: opts}, in.progress, contextPathHash, in.builder, in.metadataFile, true)
return rr, err return rr, err
}, io.NopCloser(os.Stdin), nopCloser{os.Stdout}, nopCloser{os.Stderr}) }, io.NopCloser(os.Stdin), nopCloser{os.Stdout}, nopCloser{os.Stderr})
if err != nil { if err != nil {
@ -271,7 +271,7 @@ type nopCloser struct {
func (c nopCloser) Close() error { return nil } func (c nopCloser) Close() error { return nil }
func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]build.Options, progressMode, contextPathHash, instance string, metadataFile string) (imageID string, res *build.ResultContext, err error) { func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]build.Options, progressMode, contextPathHash, instance string, metadataFile string, allowNoOutput bool) (imageID string, res *build.ResultContext, err error) {
dis, err := getInstanceOrDefault(ctx, dockerCli, instance, contextPathHash) dis, err := getInstanceOrDefault(ctx, dockerCli, instance, contextPathHash)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
@ -290,7 +290,7 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]bu
if res == nil || driverIndex < idx { if res == nil || driverIndex < idx {
idx, res = driverIndex, gotRes idx, res = driverIndex, gotRes
} }
}) }, allowNoOutput)
err1 := printer.Wait() err1 := printer.Wait()
if err == nil { if err == nil {
err = err1 err = err1

View File

@ -13,6 +13,14 @@ import (
"golang.org/x/term" "golang.org/x/term"
) )
const helpMessage = `
Available commads are:
reload reloads the context and build it.
rollback re-runs the interactive container with initial rootfs contents.
exit exits monitor.
help shows this message.
`
// RunMonitor provides an interactive session for running and managing containers via specified IO. // RunMonitor provides an interactive session for running and managing containers via specified IO.
func RunMonitor(ctx context.Context, containerConfig build.ContainerConfig, reloadFunc func(context.Context) (*build.ResultContext, error), stdin io.ReadCloser, stdout, stderr io.WriteCloser) error { func RunMonitor(ctx context.Context, containerConfig build.ContainerConfig, reloadFunc func(context.Context) (*build.ResultContext, error), stdin io.ReadCloser, stdout, stderr io.WriteCloser) error {
monitorIn, monitorOut := ioSetPipe() monitorIn, monitorOut := ioSetPipe()
@ -34,11 +42,18 @@ func RunMonitor(ctx context.Context, containerConfig build.ContainerConfig, relo
m := &monitor{ m := &monitor{
invokeIO: newIOForwarder(containerIn), invokeIO: newIOForwarder(containerIn),
muxIO: newMuxIO(ioSetIn{stdin, stdout, stderr}, []ioSetOutContext{monitorOutCtx, containerOutCtx}, 1, "Switched IO\n"), muxIO: newMuxIO(ioSetIn{stdin, stdout, stderr}, []ioSetOutContext{monitorOutCtx, containerOutCtx}, 1, func(prev int, res int) string {
if prev == 0 && res == 0 {
// No toggle happened because container I/O isn't enabled.
return "No running interactive containers. You can start one by issuing rollback command\n"
}
return "Switched IO\n"
}),
} }
// Start container automatically // Start container automatically
go func() { go func() {
fmt.Fprintf(stdout, "Launching interactive container. Press Ctrl-a-c to switch to monitor console\n")
m.rollback(ctx, containerConfig) m.rollback(ctx, containerConfig)
}() }()
@ -73,13 +88,18 @@ func RunMonitor(ctx context.Context, containerConfig build.ContainerConfig, relo
// rollback the running container with the new result // rollback the running container with the new result
containerConfig.ResultCtx = res containerConfig.ResultCtx = res
m.rollback(ctx, containerConfig) m.rollback(ctx, containerConfig)
fmt.Fprint(stdout, "Interactive container was restarted. Press Ctrl-a-c to switch to the new container\n")
} }
case "rollback": case "rollback":
m.rollback(ctx, containerConfig) m.rollback(ctx, containerConfig)
fmt.Fprint(stdout, "Interactive container was restarted. Press Ctrl-a-c to switch to the new container\n")
case "exit": case "exit":
return return
case "help":
fmt.Fprint(stdout, helpMessage)
default: default:
fmt.Printf("unknown command: %q\n", l) fmt.Printf("unknown command: %q\n", l)
fmt.Fprint(stdout, helpMessage)
} }
} }
}() }()
@ -227,7 +247,7 @@ type ioSetOutContext struct {
// newMuxIO forwards IO stream to/from "in" and "outs". // newMuxIO forwards IO stream to/from "in" and "outs".
// "outs" are closed automatically when "in" reaches EOF. // "outs" are closed automatically when "in" reaches EOF.
// "in" doesn't closed automatically so the caller needs to explicitly close it. // "in" doesn't closed automatically so the caller needs to explicitly close it.
func newMuxIO(in ioSetIn, out []ioSetOutContext, initIdx int, toggleMessage string) *muxIO { func newMuxIO(in ioSetIn, out []ioSetOutContext, initIdx int, toggleMessage func(prev int, res int) string) *muxIO {
m := &muxIO{ m := &muxIO{
enabled: make(map[int]struct{}), enabled: make(map[int]struct{}),
in: in, in: in,
@ -327,7 +347,7 @@ type muxIO struct {
in ioSetIn in ioSetIn
out []ioSetOutContext out []ioSetOutContext
closedCh chan struct{} closedCh chan struct{}
toggleMessage string toggleMessage func(prev int, res int) string
} }
func (m *muxIO) waitClosed() { func (m *muxIO) waitClosed() {
@ -357,6 +377,7 @@ func (m *muxIO) toggleIO() {
if m.out[m.cur].disableHook != nil { if m.out[m.cur].disableHook != nil {
m.out[m.cur].disableHook() m.out[m.cur].disableHook()
} }
prev := m.cur
for { for {
if m.cur+1 >= m.maxCur { if m.cur+1 >= m.maxCur {
m.cur = 0 m.cur = 0
@ -368,10 +389,11 @@ func (m *muxIO) toggleIO() {
} }
break break
} }
res := m.cur
if m.out[m.cur].enableHook != nil { if m.out[m.cur].enableHook != nil {
m.out[m.cur].enableHook() m.out[m.cur].enableHook()
} }
fmt.Fprintf(m.in.stdout, m.toggleMessage) fmt.Fprint(m.in.stdout, m.toggleMessage(prev, res))
} }
func traceReader(r io.ReadCloser, f func(rune) (bool, error)) io.ReadCloser { func traceReader(r io.ReadCloser, f func(rune) (bool, error)) io.ReadCloser {

View File

@ -131,7 +131,7 @@ func TestMuxIO(t *testing.T) {
outBufs = append(outBufs, outBuf) outBufs = append(outBufs, outBuf)
outs = append(outs, ioSetOutContext{out, nil, nil}) outs = append(outs, ioSetOutContext{out, nil, nil})
} }
mio := newMuxIO(in, outs, tt.initIdx, "") mio := newMuxIO(in, outs, tt.initIdx, func(prev int, res int) string { return "" })
for _, i := range tt.inputs { for _, i := range tt.inputs {
// Add input to muxIO // Add input to muxIO
istr, writeback := i(mio) istr, writeback := i(mio)