Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental UI mode for skaffold dev #1533

Merged
merged 2 commits into from
Feb 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

110 changes: 106 additions & 4 deletions cmd/skaffold/app/cmd/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ package cmd
import (
"context"
"io"
"strings"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/color"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner"
"github.com/pkg/errors"
"github.com/rivo/tview"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
Expand All @@ -34,7 +38,7 @@ func NewCmdDev(out io.Writer) *cobra.Command {
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
opts.Command = "dev"
return dev(out)
return dev(out, opts.ExperimentalGUI)
},
}
AddRunDevFlags(cmd)
Expand All @@ -45,13 +49,17 @@ func NewCmdDev(out io.Writer) *cobra.Command {
cmd.Flags().IntVarP(&opts.WatchPollInterval, "watch-poll-interval", "i", 1000, "Interval (in ms) between two checks for file changes")
cmd.Flags().BoolVar(&opts.PortForward, "port-forward", true, "Port-forward exposed container ports within pods")
cmd.Flags().StringArrayVarP(&opts.CustomLabels, "label", "l", nil, "Add custom labels to deployed objects. Set multiple times for multiple labels")
cmd.Flags().BoolVar(&opts.ExperimentalGUI, "experimental-gui", false, "Experimental Graphical User Interface")

return cmd
}

func dev(out io.Writer) error {
func dev(out io.Writer, ui bool) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
catchCtrlC(cancel)
if !ui {
catchCtrlC(cancel)
}

cleanup := func() {}
if opts.Cleanup {
Expand All @@ -60,6 +68,25 @@ func dev(out io.Writer) error {
}()
}

var (
app *tview.Application
output *config.Output
)
if ui {
app, output = createApp()
defer app.Stop()

go func() {
app.Run()
cancel()
}()
} else {
output = &config.Output{
Main: out,
Logs: out,
}
}

for {
select {
case <-ctx.Done():
Expand All @@ -70,7 +97,7 @@ func dev(out io.Writer) error {
return errors.Wrap(err, "creating runner")
}

err = r.Dev(ctx, out, config.Build.Artifacts)
err = r.Dev(ctx, output, config.Build.Artifacts)
if r.HasDeployed() {
cleanup = func() {
if err := r.Cleanup(context.Background(), out); err != nil {
Expand All @@ -86,3 +113,78 @@ func dev(out io.Writer) error {
}
}
}

func createApp() (*tview.Application, *config.Output) {
app := tview.NewApplication()

mainView := tview.NewTextView()
mainView.
SetChangedFunc(func() {
app.Draw()
}).
SetDynamicColors(true).
SetBorder(true).
SetTitle("Build")

logsView := tview.NewTextView()
logsView.
SetChangedFunc(func() {
app.Draw()
}).
SetDynamicColors(true).
SetBorder(true).
SetTitle("Logs")

grid := tview.NewGrid()
grid.
SetRows(0, 0).
SetColumns(0).
SetBorders(false).
AddItem(mainView, 0, 0, 1, 1, 0, 0, false).
AddItem(logsView, 1, 0, 1, 1, 0, 0, false)

app.
SetRoot(grid, true).
SetFocus(grid)

output := &config.Output{
Main: color.ColoredWriter{Writer: ansiWriter(mainView)},
Logs: color.ColoredWriter{Writer: ansiWriter(logsView)},
}

return app, output
}

func ansiWriter(writer io.Writer) io.Writer {
return &ansi{
Writer: writer,
replacer: strings.NewReplacer(
"\033[31m", "[maroon]",
"\033[32m", "[green]",
"\033[33m", "[olive]",
"\033[34m", "[navy]",
"\033[35m", "[purple]",
"\033[36m", "[teal]",
"\033[37m", "[silver]",

"\033[91m", "[red]",
"\033[92m", "[lime]",
"\033[93m", "[yellow]",
"\033[94m", "[blue]",
"\033[95m", "[fuchsia]",
"\033[96m", "[aqua]",
"\033[97m", "[white]",

"\033[0m", "",
),
}
}

type ansi struct {
io.Writer
replacer *strings.Replacer
}

func (a *ansi) Write(text []byte) (int, error) {
return a.replacer.WriteString(a.Writer, string(text))
}
2 changes: 2 additions & 0 deletions docs/content/en/docs/references/cli/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ Usage:
Flags:
--cleanup Delete deployments after dev mode is interrupted (default true)
-d, --default-repo string Default repository value (overrides global config)
--experimental-gui Experimental Graphical User Interface
-f, --filename string Filename or URL to the pipeline file (default "skaffold.yaml")
-l, --label stringArray Add custom labels to deployed objects. Set multiple times for multiple labels
-n, --namespace string Run deployments in the specified namespace
Expand All @@ -228,6 +229,7 @@ Env vars:

* `SKAFFOLD_CLEANUP` (same as --cleanup)
* `SKAFFOLD_DEFAULT_REPO` (same as --default-repo)
* `SKAFFOLD_EXPERIMENTAL_GUI` (same as --experimental-gui)
* `SKAFFOLD_FILENAME` (same as --filename)
* `SKAFFOLD_LABEL` (same as --label)
* `SKAFFOLD_NAMESPACE` (same as --namespace)
Expand Down
10 changes: 10 additions & 0 deletions pkg/skaffold/color/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ var (
Purple = Color(35)
// Cyan can format text to be displayed to the terminal in cyan, using ANSI escape codes.
Cyan = Color(36)
// White can format text to be displayed to the terminal in white, using ANSI escape codes.
White = Color(37)
// None uses ANSI escape codes to reset all formatting.
None = Color(0)

Expand Down Expand Up @@ -99,12 +101,20 @@ type ColoredWriteCloser struct {
io.WriteCloser
}

// ColoredWriter forces printing with colors to an io.Writer.
type ColoredWriter struct {
io.Writer
}

// This implementation comes from logrus (https://github.com/sirupsen/logrus/blob/master/terminal_check_notappengine.go),
// unfortunately logrus doesn't expose a public interface we can use to call it.
func isTerminal(w io.Writer) bool {
if _, ok := w.(ColoredWriteCloser); ok {
return true
}
if _, ok := w.(ColoredWriter); ok {
return true
}

switch v := w.(type) {
case *os.File:
Expand Down
8 changes: 8 additions & 0 deletions pkg/skaffold/config/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,16 @@ limitations under the License.
package config

import (
"io"
"strings"
)

// Output defines which zones on the screen to print to
type Output struct {
Main io.Writer
Logs io.Writer
}

// SkaffoldOptions are options that are set by command line arguments not included
// in the config file itself
type SkaffoldOptions struct {
Expand All @@ -30,6 +37,7 @@ type SkaffoldOptions struct {
TailDev bool
PortForward bool
SkipTests bool
ExperimentalGUI bool
Profiles []string
CustomTag string
Namespace string
Expand Down
2 changes: 1 addition & 1 deletion pkg/skaffold/kubernetes/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ func (a *LogAggregator) streamRequest(ctx context.Context, headerColor color.Col
if _, err := headerColor.Fprintf(a.output, "%s ", header); err != nil {
return errors.Wrap(err, "writing pod prefix header to out")
}
if _, err := fmt.Fprint(a.output, string(line)); err != nil {
if _, err := color.White.Fprint(a.output, string(line)); err != nil {
return errors.Wrap(err, "writing pod log to out")
}
}
Expand Down
Loading