Skip to content

Commit

Permalink
Improve UI for drive initialization and discovery commands (#708)
Browse files Browse the repository at this point in the history
  • Loading branch information
Praveenrajmani committed Feb 11, 2023
1 parent 260a5b6 commit 5bfcce7
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 39 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -6,3 +6,4 @@ directpv
kubectl-directpv
!kubectl-directpv/
vdb.xml
drives.yaml
48 changes: 44 additions & 4 deletions cmd/kubectl-directpv/discover.go
Expand Up @@ -22,8 +22,10 @@ import (
"fmt"
"os"
"strings"
"sync"
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/fatih/color"
"github.com/jedib0t/go-pretty/v6/table"
directpvtypes "github.com/minio/directpv/pkg/apis/directpv.min.io/types"
Expand Down Expand Up @@ -201,9 +203,11 @@ func writeInitConfig(config InitConfig) error {
return config.Write(f)
}

func discoverDevices(ctx context.Context, nodes []types.Node) (devices map[directpvtypes.NodeID][]types.Device, err error) {
func discoverDevices(ctx context.Context, nodes []types.Node, teaProgram *tea.Program) (devices map[directpvtypes.NodeID][]types.Device, err error) {
var nodeNames []string
nodeClient := client.NodeClient()
totalNodeCount := len(nodes)
discoveryProgressMap := make(map[string]progressLog, totalNodeCount)
for i := range nodes {
nodeNames = append(nodeNames, nodes[i].Name)
updateFunc := func() error {
Expand All @@ -215,6 +219,14 @@ func discoverDevices(ctx context.Context, nodes []types.Node) (devices map[direc
if _, err := nodeClient.Update(ctx, node, metav1.UpdateOptions{TypeMeta: types.NewNodeTypeMeta()}); err != nil {
return err
}
if teaProgram != nil {
discoveryProgressMap[node.Name] = progressLog{
log: fmt.Sprintf("Discovering node '%v'", node.Name),
}
teaProgram.Send(progressNotification{
progressLogs: toProgressLogs(discoveryProgressMap),
})
}
return nil
}
if err = retry.RetryOnConflict(retry.DefaultRetry, updateFunc); err != nil {
Expand Down Expand Up @@ -249,6 +261,15 @@ func discoverDevices(ctx context.Context, nodes []types.Node) (devices map[direc
node := event.Node
if !node.Spec.Refresh {
devices[directpvtypes.NodeID(node.Name)] = node.GetDevicesByNames(drivesArgs)
if teaProgram != nil {
discoveryProgressMap[node.Name] = progressLog{
log: fmt.Sprintf("Discovered node '%v'", node.Name),
done: true,
}
teaProgram.Send(progressNotification{
progressLogs: toProgressLogs(discoveryProgressMap),
})
}
}
if len(devices) >= len(nodes) {
return
Expand Down Expand Up @@ -284,13 +305,32 @@ func discoverMain(ctx context.Context) {
dryRunPrinter(nodeList)
return
}

resultMap, err := discoverDevices(ctx, nodes)
var teaProgram *tea.Program
var wg sync.WaitGroup
if !quietFlag {
m := newProgressModel(false)
teaProgram = tea.NewProgram(m)
wg.Add(1)
go func() {
defer wg.Done()
if _, err := teaProgram.Run(); err != nil {
fmt.Println("error running program:", err)
os.Exit(1)
}
}()
}
resultMap, err := discoverDevices(ctx, nodes, teaProgram)
if err != nil {
utils.Eprintf(quietFlag, true, "%v\n", err)
os.Exit(1)
}

if teaProgram != nil {
teaProgram.Send(progressNotification{
done: true,
err: err,
})
wg.Wait()
}
if err := showDevices(resultMap); err != nil {
if !errors.Is(err, errDiscoveryFailed) {
utils.Eprintf(quietFlag, true, "%v\n", err)
Expand Down
68 changes: 62 additions & 6 deletions cmd/kubectl-directpv/init.go
Expand Up @@ -21,8 +21,10 @@ import (
"fmt"
"os"
"strings"
"sync"
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/fatih/color"
"github.com/google/uuid"
"github.com/jedib0t/go-pretty/v6/table"
Expand Down Expand Up @@ -110,6 +112,9 @@ func toInitRequestObjects(config *InitConfig, requestID string) (initRequests []
}

func showResults(results []initResult) {
if len(results) == 0 {
return
}
writer := newTableWriter(
table.Row{
"REQUEST_ID",
Expand Down Expand Up @@ -164,14 +169,33 @@ func showResults(results []initResult) {
writer.Render()
}

func initDevices(ctx context.Context, initRequests []types.InitRequest, requestID string) (results []initResult, err error) {
var totalReqCount int
func toProgressLogs(progressMap map[string]progressLog) (logs []progressLog) {
for _, v := range progressMap {
logs = append(logs, v)
}
return
}

func initDevices(ctx context.Context, initRequests []types.InitRequest, requestID string, teaProgram *tea.Program) (results []initResult, err error) {
totalReqCount := len(initRequests)
totalTasks := totalReqCount * 2
var completedTasks int
initProgressMap := make(map[string]progressLog, totalReqCount)
for i := range initRequests {
_, err := client.InitRequestClient().Create(ctx, &initRequests[i], metav1.CreateOptions{TypeMeta: types.NewInitRequestTypeMeta()})
initReq, err := client.InitRequestClient().Create(ctx, &initRequests[i], metav1.CreateOptions{TypeMeta: types.NewInitRequestTypeMeta()})
if err != nil {
return nil, err
}
totalReqCount++
if teaProgram != nil {
completedTasks++
initProgressMap[initReq.Name] = progressLog{
log: fmt.Sprintf("Processing initialization request '%s' for node '%v'", initReq.Name, initReq.GetNodeID()),
}
teaProgram.Send(progressNotification{
progressLogs: toProgressLogs(initProgressMap),
percent: float64(completedTasks) / float64(totalTasks),
})
}
}
ctx, cancel := context.WithTimeout(ctx, initRequestListTimeout)
defer cancel()
Expand Down Expand Up @@ -205,6 +229,17 @@ func initDevices(ctx context.Context, initRequests []types.InitRequest, requestI
devices: initReq.Status.Results,
failed: initReq.Status.Status == directpvtypes.InitStatusError,
})
if teaProgram != nil {
completedTasks++
initProgressMap[initReq.Name] = progressLog{
log: fmt.Sprintf("Processed initialization request '%s' for node '%v'", initReq.Name, initReq.GetNodeID()),
done: true,
}
teaProgram.Send(progressNotification{
progressLogs: toProgressLogs(initProgressMap),
percent: float64(completedTasks) / float64(totalTasks),
})
}
}
if len(results) >= totalReqCount {
return
Expand Down Expand Up @@ -249,10 +284,31 @@ func initMain(ctx context.Context, inputFile string) {
LabelSelector: directpvtypes.ToLabelSelector(labelMap),
})
}()
results, err := initDevices(ctx, initRequests, requestID)
if err != nil {
var teaProgram *tea.Program
var wg sync.WaitGroup
if !quietFlag {
m := newProgressModel(true)
teaProgram = tea.NewProgram(m)
wg.Add(1)
go func() {
defer wg.Done()
if _, err := teaProgram.Run(); err != nil {
fmt.Println("error running program:", err)
os.Exit(1)
}
}()
}
results, err := initDevices(ctx, initRequests, requestID, teaProgram)
if err != nil && quietFlag {
utils.Eprintf(quietFlag, true, "%v\n", err)
os.Exit(1)
}
if teaProgram != nil {
teaProgram.Send(progressNotification{
done: true,
err: err,
})
wg.Wait()
}
showResults(results)
}
10 changes: 5 additions & 5 deletions cmd/kubectl-directpv/install.go
Expand Up @@ -24,7 +24,6 @@ import (
"sync"
"time"

"github.com/charmbracelet/bubbles/progress"
tea "github.com/charmbracelet/bubbletea"
"github.com/fatih/color"
"github.com/jedib0t/go-pretty/v6/table"
Expand Down Expand Up @@ -77,7 +76,10 @@ var installCmd = &cobra.Command{
$ kubectl {PLUGIN_NAME} install -o yaml > directpv-install.yaml
6. Install DirectPV with apparmor profile
$ kubectl {PLUGIN_NAME} install --apparmor-profile apparmor.json`,
$ kubectl {PLUGIN_NAME} install --apparmor-profile directpv
7. Install DirectPV with seccomp profile
$ kubectl {PLUGIN_NAME} install --seccomp-profile profiles/seccomp.json`,
`{PLUGIN_NAME}`,
consts.AppName,
),
Expand Down Expand Up @@ -289,9 +291,7 @@ func installMain(ctx context.Context) {
var installedComponents []installer.Component
var wg sync.WaitGroup
if dryRunPrinter == nil && !quietFlag {
m := progressModel{
model: progress.New(progress.WithGradient("#FFFFFF", "#FFFFFF")),
}
m := newProgressModel(true)
teaProgram := tea.NewProgram(m)
wg.Add(1)
go func() {
Expand Down
96 changes: 74 additions & 22 deletions cmd/kubectl-directpv/progress_model.go
Expand Up @@ -22,29 +22,52 @@ import (
"time"

"github.com/charmbracelet/bubbles/progress"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/fatih/color"
)

const (
padding = 1
maxWidth = 80
tick = "✔"
)

type progressLog struct {
log string
done bool
}

type progressNotification struct {
log string
message string
percent float64
done bool
err error
log string
progressLogs []progressLog
message string
percent float64
done bool
err error
}

type progressModel struct {
model progress.Model
message string
logs []string
done bool
err error
model *progress.Model
spinner spinner.Model
message string
progressLogs []progressLog
logs []string
done bool
err error
}

func newProgressModel(withProgressBar bool) *progressModel {
progressM := &progressModel{}
progressM.spinner = spinner.New()
progressM.spinner.Spinner = spinner.Points
progressM.spinner.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("#F7971E"))
if withProgressBar {
progress := progress.New(progress.WithDefaultGradient())
progressM.model = &progress
}
return progressM
}

func finalPause() tea.Cmd {
Expand All @@ -54,15 +77,17 @@ func finalPause() tea.Cmd {
}

func (m progressModel) Init() tea.Cmd {
return nil
return m.spinner.Tick
}

func (m progressModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.model.Width = msg.Width - padding*2 - 4
if m.model.Width > maxWidth {
m.model.Width = maxWidth
if m.model != nil {
m.model.Width = msg.Width - padding*2 - 4
if m.model.Width > maxWidth {
m.model.Width = maxWidth
}
}
return m, nil

Expand All @@ -74,6 +99,9 @@ func (m progressModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.logs = append(m.logs, msg.log)
}
}
if len(msg.progressLogs) > 0 {
m.progressLogs = msg.progressLogs
}
m.message = msg.message
if msg.err != nil {
m.err = msg.err
Expand All @@ -84,30 +112,54 @@ func (m progressModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.done = msg.done
cmds = append(cmds, tea.Sequence(finalPause(), tea.Quit))
}
if msg.percent > 0.0 {
if m.model != nil && msg.percent > 0.0 {
cmds = append(cmds, m.model.SetPercent(msg.percent))
}
return m, tea.Batch(cmds...)

// FrameMsg is sent when the progress bar wants to animate itself
case progress.FrameMsg:
progressModel, cmd := m.model.Update(msg)
m.model = progressModel.(progress.Model)
return m, cmd
if m.model != nil {
progressModel, cmd := m.model.Update(msg)
pModel := progressModel.(progress.Model)
m.model = &pModel
return m, cmd
}
return m, nil

default:
return m, nil
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd
}
}

func (m progressModel) View() (str string) {
pad := strings.Repeat(" ", padding)
str = "\n" + pad + m.model.View() + "\n\n"
str = "\n"
if m.model != nil {
str = str + pad + m.model.View() + "\n\n"
}
if !m.done {
str += pad + fmt.Sprintf("%s \n\n", m.message)
if m.message != "" {
str += pad + fmt.Sprintf("%s \n\n", m.message)
}
}
for i := range m.progressLogs {
if m.progressLogs[i].done {
str += pad + fmt.Sprintf("%s %s\n", color.HiYellowString(m.progressLogs[i].log), m.spinner.Style.Render(tick))
} else {
str += pad + fmt.Sprintf("%s %s\n", color.HiYellowString(m.progressLogs[i].log), m.spinner.View())
}
if i == len(m.progressLogs)-1 {
str += "\n"
}
}
for i := range m.logs {
str += pad + color.HiYellowString(fmt.Sprintf("%s \n\n", m.logs[i]))
str += pad + color.HiYellowString(fmt.Sprintf("%s \n", m.logs[i]))
if i == len(m.logs)-1 {
str += "\n"
}
}
if m.err != nil {
str += pad + color.HiRedString("Error; %s \n\n", m.err.Error())
Expand Down

0 comments on commit 5bfcce7

Please sign in to comment.