mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
131 lines
3.4 KiB
Go
131 lines
3.4 KiB
Go
package compose
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"charm.land/lipgloss/v2"
|
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
|
"github.com/psviderski/uncloud/pkg/api"
|
|
"github.com/psviderski/uncloud/pkg/client/deploy"
|
|
"github.com/psviderski/uncloud/pkg/client/deploy/operation"
|
|
)
|
|
|
|
// Plan holds the compose-level deployment plan with volume and service operations.
|
|
type Plan struct {
|
|
Volumes []*operation.CreateVolumeOperation
|
|
Services []*deploy.ServicePlan
|
|
}
|
|
|
|
// IsEmpty returns true if the plan has no volume or service operations.
|
|
func (p *Plan) IsEmpty() bool {
|
|
return len(p.Volumes) == 0 && len(p.Services) == 0
|
|
}
|
|
|
|
// Format renders the entire deployment plan as a styled tree with a summary footer.
|
|
func (p *Plan) Format() string {
|
|
var out strings.Builder
|
|
|
|
// Format volume operations.
|
|
for _, op := range p.Volumes {
|
|
out.WriteString(op.Format())
|
|
out.WriteString("\n")
|
|
}
|
|
if len(p.Volumes) > 0 {
|
|
out.WriteString("\n")
|
|
}
|
|
|
|
// Format service plans.
|
|
for _, svcPlan := range p.Services {
|
|
out.WriteString(svcPlan.Format())
|
|
out.WriteString("\n")
|
|
}
|
|
|
|
// Format summary footer.
|
|
summary := p.formatSummary()
|
|
out.WriteString(tui.Faint.Render(strings.Repeat("─", lipgloss.Width(summary))))
|
|
out.WriteString("\n")
|
|
out.WriteString(summary)
|
|
out.WriteString("\n")
|
|
|
|
return out.String()
|
|
}
|
|
|
|
// formatSummary counts all operations across the plan and renders the summary footer.
|
|
func (p *Plan) formatSummary() string {
|
|
var createCount, startFirstCount, stopFirstCount, removeCount int
|
|
machines := make(map[string]struct{})
|
|
|
|
for _, op := range p.Volumes {
|
|
machines[op.MachineID] = struct{}{}
|
|
createCount++
|
|
}
|
|
|
|
for _, svcPlan := range p.Services {
|
|
for _, op := range svcPlan.Operations {
|
|
switch o := op.(type) {
|
|
case *operation.RunContainerOperation:
|
|
machines[o.MachineID] = struct{}{}
|
|
createCount++
|
|
case *operation.ReplaceContainerOperation:
|
|
machines[o.MachineID] = struct{}{}
|
|
if o.Order == api.UpdateOrderStopFirst {
|
|
stopFirstCount++
|
|
} else {
|
|
startFirstCount++
|
|
}
|
|
case *operation.RemoveContainerOperation:
|
|
machines[o.MachineID] = struct{}{}
|
|
removeCount++
|
|
case *operation.StopContainerOperation:
|
|
machines[o.MachineID] = struct{}{}
|
|
removeCount++
|
|
}
|
|
}
|
|
}
|
|
|
|
var parts []string
|
|
if createCount > 0 {
|
|
parts = append(parts,
|
|
tui.BoldGreen.Render(strconv.Itoa(createCount))+" "+tui.Green.Render("create"))
|
|
}
|
|
if startFirstCount > 0 {
|
|
parts = append(parts,
|
|
tui.BoldGreen.Render(strconv.Itoa(startFirstCount))+" "+tui.Green.Render("replace (start-first)"))
|
|
}
|
|
if stopFirstCount > 0 {
|
|
parts = append(parts,
|
|
tui.BoldYellow.Render(strconv.Itoa(stopFirstCount))+" "+tui.Yellow.Render("replace (stop-first)"))
|
|
}
|
|
if removeCount > 0 {
|
|
parts = append(parts,
|
|
tui.BoldRed.Render(strconv.Itoa(removeCount))+" "+tui.Red.Render("remove"))
|
|
}
|
|
|
|
machinesWord := "machines"
|
|
if len(machines) == 1 {
|
|
machinesWord = "machine"
|
|
}
|
|
parts = append(parts, fmt.Sprintf("across %s %s", tui.Bold.Render(strconv.Itoa(len(machines))), machinesWord))
|
|
|
|
sep := " " + tui.Faint.Render("·") + " "
|
|
return strings.Join(parts, sep)
|
|
}
|
|
|
|
// Execute runs all volume operations followed by all service operations.
|
|
func (p *Plan) Execute(ctx context.Context, cli operation.Client) error {
|
|
for _, op := range p.Volumes {
|
|
if err := op.Execute(ctx, cli); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, sp := range p.Services {
|
|
if err := sp.Execute(ctx, cli); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|