mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
feat(pre-deploy): list hook containers in 'uc ps' and 'uc inspect'
This commit is contained in:
+47
-17
@@ -10,11 +10,11 @@ import (
|
|||||||
"charm.land/lipgloss/v2"
|
"charm.land/lipgloss/v2"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/psviderski/uncloud/internal/cli/tui"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/cli"
|
"github.com/psviderski/uncloud/internal/cli"
|
||||||
|
"github.com/psviderski/uncloud/internal/cli/tui"
|
||||||
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
"github.com/psviderski/uncloud/pkg/client"
|
"github.com/psviderski/uncloud/pkg/client"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -66,12 +66,13 @@ type containerInfo struct {
|
|||||||
serviceName string
|
serviceName string
|
||||||
machineName string
|
machineName string
|
||||||
id string
|
id string
|
||||||
name string
|
|
||||||
image string
|
image string
|
||||||
status string
|
status string
|
||||||
highlight containerHighlight
|
highlight containerHighlight
|
||||||
created time.Time
|
created time.Time
|
||||||
ip string
|
ip string
|
||||||
|
// Hook type (e.g., "pre-deploy"), empty for regular containers.
|
||||||
|
hook string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPs(ctx context.Context, uncli *cli.CLI, opts psOptions) error {
|
func runPs(ctx context.Context, uncli *cli.CLI, opts psOptions) error {
|
||||||
@@ -132,7 +133,21 @@ func runPs(ctx context.Context, uncli *cli.CLI, opts psOptions) error {
|
|||||||
|
|
||||||
func printContainers(containers []containerInfo) error {
|
func printContainers(containers []containerInfo) error {
|
||||||
t := tui.NewTable()
|
t := tui.NewTable()
|
||||||
t.Headers("SERVICE", "CONTAINER ID", "CONTAINER NAME", "IMAGE", "CREATED", "STATUS", "IP ADDRESS", "MACHINE")
|
|
||||||
|
// Show HOOK column only when hook containers are present.
|
||||||
|
hasHooks := false
|
||||||
|
for _, ctr := range containers {
|
||||||
|
if ctr.hook != "" {
|
||||||
|
hasHooks = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasHooks {
|
||||||
|
t.Headers("SERVICE", "CONTAINER ID", "IMAGE", "CREATED", "STATUS", "HOOK", "IP ADDRESS", "MACHINE")
|
||||||
|
} else {
|
||||||
|
t.Headers("SERVICE", "CONTAINER ID", "IMAGE", "CREATED", "STATUS", "IP ADDRESS", "MACHINE")
|
||||||
|
}
|
||||||
|
|
||||||
for _, ctr := range containers {
|
for _, ctr := range containers {
|
||||||
id := ctr.id
|
id := ctr.id
|
||||||
@@ -154,16 +169,28 @@ func printContainers(containers []containerInfo) error {
|
|||||||
statusStyle = lipgloss.NewStyle() // Default
|
statusStyle = lipgloss.NewStyle() // Default
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Row(
|
if hasHooks {
|
||||||
ctr.serviceName,
|
t.Row(
|
||||||
id,
|
ctr.serviceName,
|
||||||
ctr.name,
|
id,
|
||||||
tui.FormatImage(ctr.image, tui.NoStyle),
|
tui.FormatImage(ctr.image, tui.NoStyle),
|
||||||
created,
|
created,
|
||||||
statusStyle.Render(ctr.status),
|
statusStyle.Render(ctr.status),
|
||||||
ctr.ip,
|
ctr.hook,
|
||||||
ctr.machineName,
|
ctr.ip,
|
||||||
)
|
ctr.machineName,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
t.Row(
|
||||||
|
ctr.serviceName,
|
||||||
|
id,
|
||||||
|
tui.FormatImage(ctr.image, tui.NoStyle),
|
||||||
|
created,
|
||||||
|
statusStyle.Render(ctr.status),
|
||||||
|
ctr.ip,
|
||||||
|
ctr.machineName,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(t)
|
fmt.Println(t)
|
||||||
@@ -220,7 +247,7 @@ func collectContainers(ctx context.Context, cli *client.Client) ([]containerInfo
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ctr := range msc.Containers {
|
for _, ctr := range append(msc.Containers, msc.HookContainers...) {
|
||||||
if ctr.Container.State == nil || ctr.Container.Config == nil {
|
if ctr.Container.State == nil || ctr.Container.Config == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -242,6 +269,9 @@ func collectContainers(ctx context.Context, cli *client.Client) ([]containerInfo
|
|||||||
highlight = highlightSuccess
|
highlight = highlightSuccess
|
||||||
} else if ctr.Container.State.Status == "running" {
|
} else if ctr.Container.State.Status == "running" {
|
||||||
highlight = highlightNormal
|
highlight = highlightNormal
|
||||||
|
} else if ctr.IsHook() && ctr.Container.State.Status == "exited" && ctr.Container.State.ExitCode == 0 {
|
||||||
|
// Hook containers (e.g., pre-deploy) are expected to exit successfully.
|
||||||
|
highlight = highlightNormal
|
||||||
} else { // Other non-critical but noteworthy states
|
} else { // Other non-critical but noteworthy states
|
||||||
highlight = highlightWarning
|
highlight = highlightWarning
|
||||||
}
|
}
|
||||||
@@ -259,12 +289,12 @@ func collectContainers(ctx context.Context, cli *client.Client) ([]containerInfo
|
|||||||
serviceName: ctr.ServiceName(),
|
serviceName: ctr.ServiceName(),
|
||||||
machineName: machineName,
|
machineName: machineName,
|
||||||
id: ctr.Container.ID,
|
id: ctr.Container.ID,
|
||||||
name: ctr.Container.Name,
|
|
||||||
image: ctr.Container.Config.Image,
|
image: ctr.Container.Config.Image,
|
||||||
status: status,
|
status: status,
|
||||||
highlight: highlight,
|
highlight: highlight,
|
||||||
created: created,
|
created: created,
|
||||||
ip: ipStr,
|
ip: ipStr,
|
||||||
|
hook: ctr.Config.Labels[api.LabelHook],
|
||||||
}
|
}
|
||||||
containers = append(containers, info)
|
containers = append(containers, info)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ func TestCollectContainers_NilMetadata(t *testing.T) {
|
|||||||
if len(containers) > 0 {
|
if len(containers) > 0 {
|
||||||
c := containers[0]
|
c := containers[0]
|
||||||
assert.Equal(t, "container1", c.id)
|
assert.Equal(t, "container1", c.id)
|
||||||
assert.Equal(t, "test-container", c.name)
|
|
||||||
assert.Equal(t, "machine-1", c.machineName, "Should fall back to the single machine name when metadata is nil")
|
assert.Equal(t, "machine-1", c.machineName, "Should fall back to the single machine name when metadata is nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,23 +60,33 @@ func inspect(ctx context.Context, uncli *cli.CLI, opts inspectOptions) error {
|
|||||||
fmt.Printf("Mode: %s\n", svc.Mode)
|
fmt.Printf("Mode: %s\n", svc.Mode)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
|
// Combine regular and hook containers.
|
||||||
|
allContainers := append(svc.Containers, svc.HookContainers...)
|
||||||
|
|
||||||
// Parse created times for sorting and display.
|
// Parse created times for sorting and display.
|
||||||
createdTimes := make(map[string]time.Time, len(svc.Containers))
|
createdTimes := make(map[string]time.Time, len(allContainers))
|
||||||
for _, ctr := range svc.Containers {
|
for _, ctr := range allContainers {
|
||||||
createdTimes[ctr.Container.ID], _ = time.Parse(time.RFC3339Nano, ctr.Container.Created)
|
createdTimes[ctr.Container.ID], _ = time.Parse(time.RFC3339Nano, ctr.Container.Created)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort containers by created time (newest first).
|
// Sort containers by created time (newest first).
|
||||||
slices.SortFunc(svc.Containers, func(a, b api.MachineServiceContainer) int {
|
slices.SortFunc(allContainers, func(a, b api.MachineServiceContainer) int {
|
||||||
return createdTimes[b.Container.ID].Compare(createdTimes[a.Container.ID])
|
return createdTimes[b.Container.ID].Compare(createdTimes[a.Container.ID])
|
||||||
})
|
})
|
||||||
|
|
||||||
// Print the list of containers in a table format.
|
// Print the list of containers in a table format.
|
||||||
|
// Show HOOK column only when hook containers are present.
|
||||||
|
hasHooks := len(svc.HookContainers) > 0
|
||||||
|
|
||||||
t := tui.NewTable()
|
t := tui.NewTable()
|
||||||
t.Headers("CONTAINER ID", "IMAGE", "CREATED", "STATUS", "IP ADDRESS", "MACHINE")
|
if hasHooks {
|
||||||
|
t.Headers("CONTAINER ID", "IMAGE", "CREATED", "STATUS", "HOOK", "IP ADDRESS", "MACHINE")
|
||||||
|
} else {
|
||||||
|
t.Headers("CONTAINER ID", "IMAGE", "CREATED", "STATUS", "IP ADDRESS", "MACHINE")
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
for _, ctr := range svc.Containers {
|
for _, ctr := range allContainers {
|
||||||
created := units.HumanDuration(now.Sub(createdTimes[ctr.Container.ID])) + " ago"
|
created := units.HumanDuration(now.Sub(createdTimes[ctr.Container.ID])) + " ago"
|
||||||
|
|
||||||
machine := machinesNamesByID[ctr.MachineID]
|
machine := machinesNamesByID[ctr.MachineID]
|
||||||
@@ -95,14 +105,26 @@ func inspect(ctx context.Context, uncli *cli.CLI, opts inspectOptions) error {
|
|||||||
ipStr = ip.String()
|
ipStr = ip.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Row(
|
if hasHooks {
|
||||||
stringid.TruncateID(ctr.Container.ID),
|
t.Row(
|
||||||
tui.FormatImage(ctr.Container.Config.Image, tui.NoStyle),
|
stringid.TruncateID(ctr.Container.ID),
|
||||||
created,
|
tui.FormatImage(ctr.Container.Config.Image, tui.NoStyle),
|
||||||
state,
|
created,
|
||||||
ipStr,
|
state,
|
||||||
machine,
|
ctr.Container.Config.Labels[api.LabelHook],
|
||||||
)
|
ipStr,
|
||||||
|
machine,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
t.Row(
|
||||||
|
stringid.TruncateID(ctr.Container.ID),
|
||||||
|
tui.FormatImage(ctr.Container.Config.Image, tui.NoStyle),
|
||||||
|
created,
|
||||||
|
state,
|
||||||
|
ipStr,
|
||||||
|
machine,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(t)
|
fmt.Println(t)
|
||||||
|
|||||||
Reference in New Issue
Block a user