-
Notifications
You must be signed in to change notification settings - Fork 536
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* [wip] add containerd UI handlers Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * Add containerd support - Add UI handlers (done by @wagoodman) - Add containerd types and wrappers (done by @wagoodman) - Add flag for specifying containerd address Closes #201 Signed-off-by: Shane Dell <shanedell100@gmail.com> * Fix lint Signed-off-by: Shane Dell <shanedell100@gmail.com> * add containerd ui handler Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add containerd scheme to readme Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add test for scheme detection Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <alex.goodman@anchore.com> Signed-off-by: Shane Dell <shanedell100@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <alex.goodman@anchore.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
- Loading branch information
1 parent
594ba5f
commit 23e3de7
Showing
7 changed files
with
317 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
package ui | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
tea "github.com/charmbracelet/bubbletea" | ||
"github.com/charmbracelet/lipgloss" | ||
"github.com/dustin/go-humanize" | ||
"github.com/wagoodman/go-partybus" | ||
"github.com/wagoodman/go-progress" | ||
|
||
"github.com/anchore/bubbly/bubbles/taskprogress" | ||
stereoscopeParsers "github.com/anchore/stereoscope/pkg/event/parsers" | ||
"github.com/anchore/stereoscope/pkg/image/containerd" | ||
"github.com/anchore/syft/internal/log" | ||
) | ||
|
||
var _ interface { | ||
progress.Stager | ||
progress.Progressable | ||
} = (*containerdPullProgressAdapter)(nil) | ||
|
||
type containerdPullStatus interface { | ||
Complete() bool | ||
Layers() []containerd.LayerID | ||
Current(containerd.LayerID) progress.Progressable | ||
} | ||
|
||
type containerdPullProgressAdapter struct { | ||
status containerdPullStatus | ||
formatter containerdPullStatusFormatter | ||
} | ||
|
||
type containerdPullStatusFormatter struct { | ||
auxInfoStyle lipgloss.Style | ||
pullCompletedStyle lipgloss.Style | ||
pullDownloadStyle lipgloss.Style | ||
pullStageChars []string | ||
layerCaps []string | ||
} | ||
|
||
func (m *Handler) handlePullContainerdImage(e partybus.Event) []tea.Model { | ||
_, pullStatus, err := stereoscopeParsers.ParsePullContainerdImage(e) | ||
if err != nil { | ||
log.WithFields("error", err).Warn("unable to parse event") | ||
return nil | ||
} | ||
|
||
if pullStatus == nil { | ||
return nil | ||
} | ||
|
||
tsk := m.newTaskProgress( | ||
taskprogress.Title{ | ||
Default: "Pull image", | ||
Running: "Pulling image", | ||
Success: "Pulled image", | ||
}, | ||
taskprogress.WithStagedProgressable( | ||
newContainerdPullProgressAdapter(pullStatus), | ||
), | ||
) | ||
|
||
tsk.HintStyle = lipgloss.NewStyle() | ||
tsk.HintEndCaps = nil | ||
|
||
return []tea.Model{tsk} | ||
} | ||
|
||
func newContainerdPullProgressAdapter(status *containerd.PullStatus) *containerdPullProgressAdapter { | ||
return &containerdPullProgressAdapter{ | ||
status: status, | ||
formatter: newContainerdPullStatusFormatter(), | ||
} | ||
} | ||
|
||
func newContainerdPullStatusFormatter() containerdPullStatusFormatter { | ||
return containerdPullStatusFormatter{ | ||
auxInfoStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("#777777")), | ||
pullCompletedStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("#fcba03")), | ||
pullDownloadStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("#777777")), | ||
pullStageChars: strings.Split("▁▃▄▅▆▇█", ""), | ||
layerCaps: strings.Split("▕▏", ""), | ||
} | ||
} | ||
|
||
func (d containerdPullProgressAdapter) Size() int64 { | ||
return -1 | ||
} | ||
|
||
func (d containerdPullProgressAdapter) Current() int64 { | ||
return 1 | ||
} | ||
|
||
func (d containerdPullProgressAdapter) Error() error { | ||
if d.status.Complete() { | ||
return progress.ErrCompleted | ||
} | ||
// TODO: return intermediate error indications | ||
return nil | ||
} | ||
|
||
func (d containerdPullProgressAdapter) Stage() string { | ||
return d.formatter.Render(d.status) | ||
} | ||
|
||
// Render crafts the given docker image pull status summarized into a single line. | ||
func (f containerdPullStatusFormatter) Render(pullStatus containerdPullStatus) string { | ||
var size, current uint64 | ||
|
||
layers := pullStatus.Layers() | ||
status := make(map[containerd.LayerID]progress.Progressable) | ||
completed := make([]string, len(layers)) | ||
|
||
// fetch the current state | ||
for idx, layer := range layers { | ||
completed[idx] = " " | ||
status[layer] = pullStatus.Current(layer) | ||
} | ||
|
||
numCompleted := 0 | ||
for idx, layer := range layers { | ||
prog := status[layer] | ||
curN := prog.Current() | ||
curSize := prog.Size() | ||
|
||
if progress.IsCompleted(prog) { | ||
input := f.pullStageChars[len(f.pullStageChars)-1] | ||
completed[idx] = f.formatPullPhase(prog.Error() != nil, input) | ||
} else if curN != 0 { | ||
var ratio float64 | ||
switch { | ||
case curN == 0 || curSize < 0: | ||
ratio = 0 | ||
case curN >= curSize: | ||
ratio = 1 | ||
default: | ||
ratio = float64(curN) / float64(curSize) | ||
} | ||
|
||
i := int(ratio * float64(len(f.pullStageChars)-1)) | ||
input := f.pullStageChars[i] | ||
completed[idx] = f.formatPullPhase(status[layer].Error() != nil, input) | ||
} | ||
|
||
if progress.IsErrCompleted(status[layer].Error()) { | ||
numCompleted++ | ||
} | ||
} | ||
|
||
for _, layer := range layers { | ||
prog := status[layer] | ||
size += uint64(prog.Size()) | ||
current += uint64(prog.Current()) | ||
} | ||
|
||
var progStr, auxInfo string | ||
if len(layers) > 0 { | ||
render := strings.Join(completed, "") | ||
prefix := f.pullCompletedStyle.Render(fmt.Sprintf("%d Layers", len(layers))) | ||
auxInfo = f.auxInfoStyle.Render(fmt.Sprintf("[%s / %s]", humanize.Bytes(current), humanize.Bytes(size))) | ||
if len(layers) == numCompleted { | ||
auxInfo = f.auxInfoStyle.Render(fmt.Sprintf("[%s] Extracting...", humanize.Bytes(size))) | ||
} | ||
|
||
progStr = fmt.Sprintf("%s%s%s%s", prefix, f.layerCap(false), render, f.layerCap(true)) | ||
} | ||
|
||
return progStr + auxInfo | ||
} | ||
|
||
// formatPullPhase returns a single character that represents the status of a layer pull. | ||
func (f containerdPullStatusFormatter) formatPullPhase(completed bool, inputStr string) string { | ||
if completed { | ||
return f.pullCompletedStyle.Render(f.pullStageChars[len(f.pullStageChars)-1]) | ||
} | ||
return f.pullDownloadStyle.Render(inputStr) | ||
} | ||
|
||
func (f containerdPullStatusFormatter) layerCap(end bool) string { | ||
l := len(f.layerCaps) | ||
if l == 0 { | ||
return "" | ||
} | ||
if end { | ||
return f.layerCaps[l-1] | ||
} | ||
return f.layerCaps[0] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.