Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
Signed-off-by: Anthony Emengo <aemengo@vmware.com>
  • Loading branch information
Anthony Emengo committed Sep 13, 2021
1 parent a7487c9 commit 229092a
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 29 deletions.
2 changes: 1 addition & 1 deletion build.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
GID: opts.GroupID,
PreviousImage: opts.PreviousImage,
Interactive: opts.Interactive,
Termui: termui.NewTermui(),
Termui: termui.NewTermui(imageRef.Name(), bldr, runImageName),
}

lifecycleVersion := ephemeralBuilder.LifecycleDescriptor().Info.Version
Expand Down
5 changes: 5 additions & 0 deletions internal/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ func (b *Builder) Order() dist.Order {
return b.order
}

// BaseImageName returns the name of the builder base image
func (b *Builder) BaseImageName() string {
return b.baseImageName
}

// Name returns the name of the builder
func (b *Builder) Name() string {
return b.image.Name()
Expand Down
139 changes: 139 additions & 0 deletions internal/termui/dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package termui

import (
"fmt"

"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"

"github.com/buildpacks/pack/internal/builder"
"github.com/buildpacks/pack/internal/dist"
)

type Dashboard struct {
app app
imagesView *tview.Flex
planList *tview.List
logsView *tview.TextView
logs string
}

func NewDashboard(app app, appName string, bldr *builder.Builder, runImageName string, buildpackInfo []dist.BuildpackInfo) *Dashboard {
imagesView, planList, logsView := initDashboard(appName, bldr, runImageName, buildpackInfo)

d := &Dashboard{
app: app,
imagesView: imagesView,
planList: planList,
logsView: logsView,
}

leftPane := tview.NewFlex().
SetDirection(tview.FlexRow).
AddItem(imagesView, 11, 0, false).
AddItem(planList, 0, 1, true)

screen := tview.NewFlex().
SetDirection(tview.FlexColumn).
AddItem(leftPane, 0, 1, true).
AddItem(logsView, 0, 1, false)

d.app.SetRoot(screen, true)
return d
}

func (d *Dashboard) Handle(txt string) {
d.app.QueueUpdateDraw(func() {
d.logs = d.logs + txt + "\n"
d.logsView.SetText(tview.TranslateANSI(d.logs))
})
}

func (d *Dashboard) Stop() {
panic("implement me")
}

func initDashboard(appName string, bldr *builder.Builder, runImageName string, buildpackInfos []dist.BuildpackInfo) (*tview.Flex, *tview.List, *tview.TextView) {
appTree, builderTree := initTrees(appName, bldr, runImageName)
imagesView := tview.NewFlex().
SetDirection(tview.FlexRow).
AddItem(appTree, 0, 1, false).
AddItem(builderTree, 0, 1, true)
imagesView.SetBorder(true).
SetTitleAlign(tview.AlignLeft).
SetTitle("| [::b]images[::-] |").
SetBackgroundColor(backgroundColor)

planList := tview.NewList()
planList.SetMainTextColor(tcell.ColorMediumTurquoise).
SetSelectedTextColor(tcell.ColorMediumTurquoise).
SetSelectedBackgroundColor(tcell.ColorDarkSlateGray).
SetSecondaryTextColor(tcell.ColorDimGray).
SetBorder(true).
SetBorderPadding(1, 1, 1, 1).
SetTitle("| [::b]plan[::-] |").
SetTitleAlign(tview.AlignLeft).
SetBackgroundColor(backgroundColor)

for _, buildpackInfo := range buildpackInfos {
planList.AddItem(
buildpackInfo.FullName(),
info(buildpackInfo),
'✔',
func() {},
)
}

logsView := tview.NewTextView()
logsView.SetDynamicColors(true).
SetTextAlign(tview.AlignLeft).
SetBorderPadding(1, 1, 3, 1).
SetTitleAlign(tview.AlignLeft).
SetBackgroundColor(backgroundColor)

return imagesView, planList, logsView
}

func initTrees(appName string, bldr *builder.Builder, runImageName string) (*tview.TreeView, *tview.TreeView) {
var (
appImage = tview.NewTreeNode(fmt.Sprintf("app: [white::b]%s", appName)).SetColor(tcell.ColorDimGray)
appRunImage = tview.NewTreeNode(fmt.Sprintf(" run: [white::b]%s", runImageName)).SetColor(tcell.ColorDimGray)
builderImage = tview.NewTreeNode(fmt.Sprintf("builder: [white::b]%s", bldr.BaseImageName())).SetColor(tcell.ColorDimGray)
lifecycle = tview.NewTreeNode(fmt.Sprintf(" lifecycle: [white::b]%s", bldr.LifecycleDescriptor().Info.Version.String())).SetColor(tcell.ColorDimGray)
runImage = tview.NewTreeNode(fmt.Sprintf(" run: [white::b]%s", bldr.Stack().RunImage.Image)).SetColor(tcell.ColorDimGray)
buildpacks = tview.NewTreeNode(" [mediumturquoise::b]buildpacks")
)

appImage.AddChild(appRunImage)
builderImage.AddChild(lifecycle)
builderImage.AddChild(runImage)
builderImage.AddChild(buildpacks)

appTree := tview.NewTreeView()
appTree.
SetRoot(appImage).
SetGraphics(true).
SetGraphicsColor(tcell.ColorMediumTurquoise).
SetTitleAlign(tview.AlignLeft).
SetBorderPadding(1, 0, 4, 0).
SetBackgroundColor(backgroundColor)

builderTree := tview.NewTreeView()
builderTree.
SetRoot(builderImage).
SetGraphics(true).
SetGraphicsColor(tcell.ColorMediumTurquoise).
SetTitleAlign(tview.AlignLeft).
SetBorderPadding(0, 0, 4, 0).
SetBackgroundColor(backgroundColor)

return appTree, builderTree
}

func info(buildpackInfo dist.BuildpackInfo) string {
if buildpackInfo.Description != "" {
return buildpackInfo.Description
}

return buildpackInfo.Homepage
}
53 changes: 42 additions & 11 deletions internal/termui/detect.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
package termui

import (
"regexp"
"time"

"github.com/rivo/tview"

"github.com/buildpacks/pack/internal/builder"
"github.com/buildpacks/pack/internal/dist"
)

type Detect struct {
app app
textView *tview.TextView
doneChan chan bool
app app
textView *tview.TextView
buildpackRegex *regexp.Regexp
buildpackChan chan dist.BuildpackInfo
doneChan chan bool
bldr *builder.Builder
}

func NewDetect(app app) *Detect {
func NewDetect(app app, buildpackChan chan dist.BuildpackInfo, bldr *builder.Builder) *Detect {
d := &Detect{
app: app,
textView: detectStatusTV(app),
doneChan: make(chan bool, 1),
app: app,
textView: detectStatusTV(),
buildpackRegex: regexp.MustCompile(`(\S+\/\S+)\s+([\d\.]+)`),
buildpackChan: buildpackChan,
doneChan: make(chan bool, 1),
bldr: bldr,
}

go d.start()
Expand All @@ -25,6 +35,13 @@ func NewDetect(app app) *Detect {
return d
}

func (d *Detect) Handle(txt string) {
m := d.buildpackRegex.FindStringSubmatch(txt)
if len(m) == 3 {
d.buildpackChan <- d.find(m[1], m[2])
}
}

func (d *Detect) Stop() {
d.doneChan <- true
}
Expand All @@ -45,24 +62,38 @@ func (d *Detect) start() {
for {
select {
case <-ticker.C:
d.textView.SetText(texts[i])
d.app.QueueUpdateDraw(func() {
d.textView.SetText(texts[i])
})

i++
if i == len(texts) {
i = 0
}
case <-d.doneChan:
ticker.Stop()
d.textView.SetText(doneText)

d.app.QueueUpdateDraw(func() {
d.textView.SetText(doneText)
})
return
}
}
}

func detectStatusTV(app app) *tview.TextView {
func (d *Detect) find(buildpackID, buildpackVersion string) dist.BuildpackInfo {
for _, buildpack := range d.bldr.Buildpacks() {
if buildpack.ID == buildpackID && buildpack.Version == buildpackVersion {
return buildpack
}
}

return dist.BuildpackInfo{}
}

func detectStatusTV() *tview.TextView {
tv := tview.NewTextView()
tv.SetBackgroundColor(backgroundColor)
tv.SetChangedFunc(func() { app.Draw() })
return tv
}

Expand Down
75 changes: 58 additions & 17 deletions internal/termui/termui.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import (
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"

"github.com/buildpacks/pack/internal/builder"
"github.com/buildpacks/pack/internal/container"
"github.com/buildpacks/pack/internal/dist"
)

var (
Expand All @@ -21,34 +23,49 @@ var (
type app interface {
SetRoot(root tview.Primitive, fullscreen bool) *tview.Application
Draw() *tview.Application
QueueUpdateDraw(f func()) *tview.Application
Run() error
}

type page interface {
Handle(txt string)
Stop()
}

type Termui struct {
app app
currentPage page
textChan chan string
appName string
runImageName string
bldr *builder.Builder
exitCode int64

app app
currentPage page
textChan chan string
buildpackChan chan dist.BuildpackInfo
}

func NewTermui() *Termui {
func NewTermui(appName string, bldr *builder.Builder, runImageName string) *Termui {
return &Termui{
app: tview.NewApplication(),
textChan: make(chan string, 10),
appName: appName,
bldr: bldr,
runImageName: runImageName,
app: tview.NewApplication(),
buildpackChan: make(chan dist.BuildpackInfo, 50),
textChan: make(chan string, 10),
}
}

// Run starts the terminal UI process in the foreground
// and the passed in function in the background
func (s *Termui) Run(funk func()) error {
go funk()
go func() {
funk()
s.showBuildStatus()
}()
go s.handle()
defer s.stop()

s.currentPage = NewDetect(s.app)
s.currentPage = NewDetect(s.app, s.buildpackChan, s.bldr)
return s.app.Run()
}

Expand All @@ -61,8 +78,11 @@ func (s *Termui) handle() {
switch {
case strings.Contains(txt, "===> ANALYZING"):
s.currentPage.Stop()

s.currentPage = NewDashboard(s.app, s.appName, s.bldr, s.runImageName, collect(s.buildpackChan))
s.currentPage.Handle(txt)
default:
// no-op
s.currentPage.Handle(txt)
}
}
}
Expand Down Expand Up @@ -93,18 +113,39 @@ func (s *Termui) Handler() container.Handler {
return err
case err := <-errChan:
return err
case body := <-bodyChan:
s.exitCode = body.StatusCode
return nil
default:
if !scanner.Scan() {
err := scanner.Err()
if err != nil {
return err
}

return nil
if scanner.Scan() {
s.textChan <- scanner.Text()
continue
}

s.textChan <- scanner.Text()
if err := scanner.Err(); err != nil {
return err
}
}
}
}
}

func (s *Termui) showBuildStatus() {
if s.exitCode == 0 {
s.textChan <- "[green::b]\n\nBUILD SUCCEEDED"
return
}

s.textChan <- "[red::b]\n\nBUILD FAILED"
}

func collect(buildpackChan chan dist.BuildpackInfo) []dist.BuildpackInfo {
close(buildpackChan)

var result []dist.BuildpackInfo
for txt := range buildpackChan {
result = append(result, txt)
}

return result
}

0 comments on commit 229092a

Please sign in to comment.