refactor: simplify and speed up plan output formatting by removing name resolver dependency

This commit is contained in:
Pasha Sviderski
2026-03-19 13:14:09 +10:00
parent 95f80a7fda
commit e4cb71b878
13 changed files with 96 additions and 150 deletions
+1 -7
View File
@@ -125,15 +125,9 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
} }
} }
// Initialise a machine and container name resolver to properly format the plan output.
resolver, err := clusterClient.ServiceOperationNameResolver(ctx, svc)
if err != nil {
return fmt.Errorf("create machine and container name resolver for service operations: %w", err)
}
fmt.Println() fmt.Println()
fmt.Println("Deployment plan:") fmt.Println("Deployment plan:")
fmt.Println(plan.Format(resolver)) fmt.Println(plan.Format())
fmt.Println() fmt.Println()
confirmed, err := tui.Confirm("") confirmed, err := tui.Confirm("")
+1 -23
View File
@@ -11,11 +11,9 @@ import (
"github.com/docker/compose/v2/pkg/progress" "github.com/docker/compose/v2/pkg/progress"
"github.com/psviderski/uncloud/internal/cli" "github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/cli/tui" "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/psviderski/uncloud/pkg/client/compose" "github.com/psviderski/uncloud/pkg/client/compose"
"github.com/psviderski/uncloud/pkg/client/deploy" "github.com/psviderski/uncloud/pkg/client/deploy"
"github.com/psviderski/uncloud/pkg/client/deploy/operation"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -193,10 +191,7 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
fmt.Println() fmt.Println()
} }
if err = printPlan(ctx, clusterClient, plan); err != nil { fmt.Println(plan.Format())
return fmt.Errorf("print deployment plan: %w", err)
}
fmt.Println()
// Ask for plan confirmation before proceeding with the deployment unless auto-confirmed with --yes. // Ask for plan confirmation before proceeding with the deployment unless auto-confirmed with --yes.
if !opts.yes { if !opts.yes {
@@ -235,20 +230,3 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
return nil return nil
}, uncli.ProgressOut(), title) }, uncli.ProgressOut(), title)
} }
func printPlan(ctx context.Context, cli *client.Client, plan compose.Plan) error {
resolvers := make(map[string]operation.NameResolver)
for _, svcPlan := range plan.Services {
svc, err := cli.InspectService(ctx, svcPlan.ServiceID)
if err != nil && !errors.Is(err, api.ErrNotFound) {
return fmt.Errorf("inspect service: %w", err)
}
resolver, err := cli.ServiceOperationNameResolver(ctx, svc)
if err != nil {
return fmt.Errorf("create resolver for service '%s': %w", svcPlan.ServiceName, err)
}
resolvers[svcPlan.ServiceID] = resolver
}
fmt.Print(plan.Format(resolvers))
return nil
}
+1 -8
View File
@@ -214,15 +214,8 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
if len(plan.Operations) == 0 { if len(plan.Operations) == 0 {
fmt.Printf("%s service is up to date.\n", client.CaddyServiceName) fmt.Printf("%s service is up to date.\n", client.CaddyServiceName)
} else { } else {
// Initialise a machine and container name resolver to properly format the plan output.
resolver, err := clusterClient.ServiceOperationNameResolver(ctx, caddySvc)
if err != nil {
return fmt.Errorf("create machine and container name resolver for service operations: %w", err)
}
fmt.Println("caddy deployment plan:") fmt.Println("caddy deployment plan:")
fmt.Println(plan.Format(resolver)) fmt.Println(plan.Format())
fmt.Println()
if !opts.yes { if !opts.yes {
confirmed, err := tui.Confirm("") confirmed, err := tui.Confirm("")
+1 -7
View File
@@ -91,14 +91,8 @@ func scale(ctx context.Context, uncli *cli.CLI, opts scaleOptions) error {
} }
if opts.replicas < currentReplicas { if opts.replicas < currentReplicas {
// Initialise a machine and container name resolver to properly format the plan output.
resolver, err := clusterClient.ServiceOperationNameResolver(ctx, svc)
if err != nil {
return fmt.Errorf("create machine and container name resolver for service operations: %w", err)
}
fmt.Printf("Scaling plan for service %s (%d → %d replicas):\n", svc.Name, currentReplicas, opts.replicas) fmt.Printf("Scaling plan for service %s (%d → %d replicas):\n", svc.Name, currentReplicas, opts.replicas)
fmt.Println(plan.Format(resolver)) fmt.Println(plan.Format())
fmt.Println() fmt.Println()
// Ask for confirmation before scaling down as it may cause data loss. // Ask for confirmation before scaling down as it may cause data loss.
+3 -4
View File
@@ -25,12 +25,12 @@ func (p *Plan) IsEmpty() bool {
} }
// Format renders the entire deployment plan as a styled tree with a summary footer. // Format renders the entire deployment plan as a styled tree with a summary footer.
func (p *Plan) Format(resolvers map[string]operation.NameResolver) string { func (p *Plan) Format() string {
var out strings.Builder var out strings.Builder
// Format volume operations. // Format volume operations.
for _, op := range p.Volumes { for _, op := range p.Volumes {
out.WriteString(op.Format(nil)) out.WriteString(op.Format())
out.WriteString("\n") out.WriteString("\n")
} }
if len(p.Volumes) > 0 { if len(p.Volumes) > 0 {
@@ -39,8 +39,7 @@ func (p *Plan) Format(resolvers map[string]operation.NameResolver) string {
// Format service plans. // Format service plans.
for _, svcPlan := range p.Services { for _, svcPlan := range p.Services {
resolver := resolvers[svcPlan.ServiceID] out.WriteString(svcPlan.Format())
out.WriteString(svcPlan.Format(resolver))
out.WriteString("\n") out.WriteString("\n")
} }
+2 -2
View File
@@ -45,7 +45,7 @@ type ServicePlan struct {
} }
// Format renders the service plan as a styled block with a spec diff and nested container operations. // Format renders the service plan as a styled block with a spec diff and nested container operations.
func (sp *ServicePlan) Format(resolver operation.NameResolver) string { func (sp *ServicePlan) Format() string {
// Determine service-level operation type and extract the old spec from container operations. // Determine service-level operation type and extract the old spec from container operations.
// Assume replace operations precede remove operations (rolling strategy) so the first replace operation // Assume replace operations precede remove operations (rolling strategy) so the first replace operation
// (if exists) determines the old spec for the diff. Otherwise, fallback to the first remove operation. // (if exists) determines the old spec for the diff. Otherwise, fallback to the first remove operation.
@@ -156,7 +156,7 @@ func (sp *ServicePlan) Format(resolver operation.NameResolver) string {
if i == opsCount-1 { if i == opsCount-1 {
connector = tui.Faint.Render(" ╰──") connector = tui.Faint.Render(" ╰──")
} }
out.WriteString(connector + " " + op.Format(resolver)) out.WriteString(connector + " " + op.Format())
out.WriteString("\n") out.WriteString("\n")
} }
+24 -20
View File
@@ -17,6 +17,8 @@ type RunContainerOperation struct {
ServiceID string ServiceID string
Spec api.ServiceSpec Spec api.ServiceSpec
MachineID string MachineID string
// MachineName is used for formatting the operation as part of the deployment plan.
MachineName string
// SkipHealthMonitor skips the monitoring period and health checks after starting a container. // SkipHealthMonitor skips the monitoring period and health checks after starting a container.
SkipHealthMonitor bool SkipHealthMonitor bool
} }
@@ -43,13 +45,12 @@ func (o *RunContainerOperation) Execute(ctx context.Context, cli Client) error {
return nil return nil
} }
func (o *RunContainerOperation) Format(resolver NameResolver) string { func (o *RunContainerOperation) Format() string {
machineName := resolver.MachineName(o.MachineID)
return tui.BoldGreen.Render("+") + " " + return tui.BoldGreen.Render("+") + " " +
tui.Faint.Render("run container") + " " + tui.Faint.Render("run container") + " " +
o.Spec.Name + " " + o.Spec.Name + " " +
tui.Faint.Render("on") + " " + tui.Faint.Render("on") + " " +
machineName o.MachineName
} }
func (o *RunContainerOperation) String() string { func (o *RunContainerOperation) String() string {
@@ -59,9 +60,11 @@ func (o *RunContainerOperation) String() string {
// StopContainerOperation stops a container on a specific machine. // StopContainerOperation stops a container on a specific machine.
type StopContainerOperation struct { type StopContainerOperation struct {
ServiceID string ServiceID string
ContainerID string ContainerID string
MachineID string MachineID string
// MachineName is used for formatting the operation as part of the deployment plan.
MachineName string
StopGracePeriod *time.Duration StopGracePeriod *time.Duration
} }
@@ -72,8 +75,7 @@ func (o *StopContainerOperation) Execute(ctx context.Context, cli Client) error
return nil return nil
} }
func (o *StopContainerOperation) Format(resolver NameResolver) string { func (o *StopContainerOperation) Format() string {
machineName := resolver.MachineName(o.MachineID)
// TODO: pass service name to format the display name consistently with other operations. // TODO: pass service name to format the display name consistently with other operations.
displayName := stringid.TruncateID(o.ContainerID) displayName := stringid.TruncateID(o.ContainerID)
@@ -81,7 +83,7 @@ func (o *StopContainerOperation) Format(resolver NameResolver) string {
tui.Faint.Render("stop container") + " " + tui.Faint.Render("stop container") + " " +
displayName + " " + displayName + " " +
tui.Faint.Render("on") + " " + tui.Faint.Render("on") + " " +
machineName o.MachineName
} }
func (o *StopContainerOperation) String() string { func (o *StopContainerOperation) String() string {
@@ -91,7 +93,9 @@ func (o *StopContainerOperation) String() string {
// RemoveContainerOperation stops and removes a container from a specific machine. // RemoveContainerOperation stops and removes a container from a specific machine.
type RemoveContainerOperation struct { type RemoveContainerOperation struct {
MachineID string MachineID string
// MachineName is used for formatting the operation as part of the deployment plan.
MachineName string
Container api.ServiceContainer Container api.ServiceContainer
StopGracePeriod *time.Duration StopGracePeriod *time.Duration
} }
@@ -112,15 +116,14 @@ func (o *RemoveContainerOperation) Execute(ctx context.Context, cli Client) erro
return nil return nil
} }
func (o *RemoveContainerOperation) Format(resolver NameResolver) string { func (o *RemoveContainerOperation) Format() string {
machineName := resolver.MachineName(o.MachineID)
displayName := o.Container.ServiceSpec.Name + tui.Faint.Render("/") + o.Container.ShortID() displayName := o.Container.ServiceSpec.Name + tui.Faint.Render("/") + o.Container.ShortID()
return tui.BoldRed.Render("-") + " " + return tui.BoldRed.Render("-") + " " +
tui.Faint.Render("remove container") + " " + tui.Faint.Render("remove container") + " " +
displayName + " " + displayName + " " +
tui.Faint.Render("on") + " " + tui.Faint.Render("on") + " " +
machineName o.MachineName
} }
func (o *RemoveContainerOperation) String() string { func (o *RemoveContainerOperation) String() string {
@@ -132,9 +135,11 @@ func (o *RemoveContainerOperation) String() string {
// For start-first: starts new container, then removes old container. // For start-first: starts new container, then removes old container.
// For stop-first: stops old container, starts new container, then removes old container. // For stop-first: stops old container, starts new container, then removes old container.
type ReplaceContainerOperation struct { type ReplaceContainerOperation struct {
ServiceID string ServiceID string
Spec api.ServiceSpec Spec api.ServiceSpec
MachineID string MachineID string
// MachineName is used for formatting the operation as part of the deployment plan.
MachineName string
OldContainer api.ServiceContainer OldContainer api.ServiceContainer
// Order specifies the update order: "start-first" or "stop-first". // Order specifies the update order: "start-first" or "stop-first".
Order string Order string
@@ -223,8 +228,7 @@ func (o *ReplaceContainerOperation) Execute(ctx context.Context, cli Client) err
return nil return nil
} }
func (o *ReplaceContainerOperation) Format(resolver NameResolver) string { func (o *ReplaceContainerOperation) Format() string {
machineName := resolver.MachineName(o.MachineID)
displayName := o.Spec.Name + tui.Faint.Render("/") + o.OldContainer.ShortID() displayName := o.Spec.Name + tui.Faint.Render("/") + o.OldContainer.ShortID()
if o.Order == api.UpdateOrderStopFirst { if o.Order == api.UpdateOrderStopFirst {
@@ -232,14 +236,14 @@ func (o *ReplaceContainerOperation) Format(resolver NameResolver) string {
tui.Faint.Render("replace container") + " " + tui.Faint.Render("replace container") + " " +
displayName + " " + displayName + " " +
tui.Faint.Render("on") + " " + tui.Faint.Render("on") + " " +
machineName + " " + o.MachineName + " " +
tui.Yellow.Render("(stop-first)") tui.Yellow.Render("(stop-first)")
} }
return tui.BoldGreen.Render("+") + tui.Green.Render("/") + tui.BoldGreen.Render("-") + " " + return tui.BoldGreen.Render("+") + tui.Green.Render("/") + tui.BoldGreen.Render("-") + " " +
tui.Faint.Render("replace container") + " " + tui.Faint.Render("replace container") + " " +
displayName + " " + displayName + " " +
tui.Faint.Render("on") + " " + tui.Faint.Render("on") + " " +
machineName o.MachineName
} }
func (o *ReplaceContainerOperation) String() string { func (o *ReplaceContainerOperation) String() string {
+1 -8
View File
@@ -12,8 +12,7 @@ type Operation interface {
// Execute performs the operation using the provided client. // Execute performs the operation using the provided client.
Execute(ctx context.Context, cli Client) error Execute(ctx context.Context, cli Client) error
// Format returns a human-readable representation of the operation. // Format returns a human-readable representation of the operation.
// TODO: get rid of the resolver and assign the required names for formatting in the operation itself. Format() string
Format(resolver NameResolver) string
String() string String() string
} }
@@ -22,9 +21,3 @@ type Client interface {
api.ContainerClient api.ContainerClient
api.VolumeClient api.VolumeClient
} }
// NameResolver resolves machine and container IDs to their names.
type NameResolver interface {
MachineName(machineID string) string
ContainerName(containerID string) string
}
+2 -2
View File
@@ -20,10 +20,10 @@ func (o *SequenceOperation) Execute(ctx context.Context, cli Client) error {
return nil return nil
} }
func (o *SequenceOperation) Format(resolver NameResolver) string { func (o *SequenceOperation) Format() string {
lines := make([]string, len(o.Operations)) lines := make([]string, len(o.Operations))
for i, op := range o.Operations { for i, op := range o.Operations {
lines[i] = op.Format(resolver) lines[i] = op.Format()
} }
return strings.Join(lines, "\n") return strings.Join(lines, "\n")
+2 -2
View File
@@ -13,7 +13,7 @@ import (
type CreateVolumeOperation struct { type CreateVolumeOperation struct {
VolumeSpec api.VolumeSpec VolumeSpec api.VolumeSpec
MachineID string MachineID string
// MachineName is used for formatting the operation output only. // MachineName is used for formatting the operation as part of the deployment plan.
MachineName string MachineName string
} }
@@ -40,7 +40,7 @@ func (o *CreateVolumeOperation) Execute(ctx context.Context, cli Client) error {
return nil return nil
} }
func (o *CreateVolumeOperation) Format(_ NameResolver) string { func (o *CreateVolumeOperation) Format() string {
return fmt.Sprintf("%s create volume %s %s %s", return fmt.Sprintf("%s create volume %s %s %s",
tui.BoldGreen.Render("+"), tui.BoldGreen.Render("+"),
tui.NameStyle.Render(o.VolumeSpec.DockerVolumeName()), tui.NameStyle.Render(o.VolumeSpec.DockerVolumeName()),
+38 -6
View File
@@ -67,6 +67,12 @@ func (s *RollingStrategy) planReplicated(svc *api.Service, spec api.ServiceSpec)
return plan, err return plan, err
} }
// Build a machine ID to name map from the cluster state to resolve IDs for operations.
machineNames := make(map[string]string, len(s.state.Machines))
for _, m := range s.state.Machines {
machineNames[m.Info.Id] = m.Info.Name
}
sched := scheduler.NewServiceScheduler(s.state, spec) sched := scheduler.NewServiceScheduler(s.state, spec)
// TODO: return a detailed report on required constraints and which ones are satisfied? // TODO: return a detailed report on required constraints and which ones are satisfied?
availableMachines, err := sched.EligibleMachines() availableMachines, err := sched.EligibleMachines()
@@ -151,6 +157,7 @@ func (s *RollingStrategy) planReplicated(svc *api.Service, spec api.ServiceSpec)
ServiceID: plan.ServiceID, ServiceID: plan.ServiceID,
Spec: spec, Spec: spec,
MachineID: m.Id, MachineID: m.Id,
MachineName: m.Name,
SkipHealthMonitor: s.SkipHealthMonitor, SkipHealthMonitor: s.SkipHealthMonitor,
}) })
continue continue
@@ -172,6 +179,7 @@ func (s *RollingStrategy) planReplicated(svc *api.Service, spec api.ServiceSpec)
ServiceID: plan.ServiceID, ServiceID: plan.ServiceID,
Spec: spec, Spec: spec,
MachineID: m.Id, MachineID: m.Id,
MachineName: m.Name,
OldContainer: ctr, OldContainer: ctr,
Order: order, Order: order,
SkipHealthMonitor: s.SkipHealthMonitor, SkipHealthMonitor: s.SkipHealthMonitor,
@@ -184,6 +192,7 @@ func (s *RollingStrategy) planReplicated(svc *api.Service, spec api.ServiceSpec)
for _, c := range containers { for _, c := range containers {
plan.Operations = append(plan.Operations, &operation.RemoveContainerOperation{ plan.Operations = append(plan.Operations, &operation.RemoveContainerOperation{
MachineID: mid, MachineID: mid,
MachineName: machineNames[mid],
Container: c, Container: c,
StopGracePeriod: spec.StopGracePeriod, StopGracePeriod: spec.StopGracePeriod,
}) })
@@ -204,6 +213,12 @@ func (s *RollingStrategy) planGlobal(svc *api.Service, spec api.ServiceSpec) (Se
return plan, err return plan, err
} }
// Build a machine ID to name map from the cluster state to resolve IDs for operations.
machineNames := make(map[string]string, len(s.state.Machines))
for _, m := range s.state.Machines {
machineNames[m.Info.Id] = m.Info.Name
}
// Map machineID to service containers on that machine. For the global mode, there should be at most one // Map machineID to service containers on that machine. For the global mode, there should be at most one
// container per machine but we use a slice to handle multiple containers that may exist due to a bug // container per machine but we use a slice to handle multiple containers that may exist due to a bug
// or interruption in the previous deployment. // or interruption in the previous deployment.
@@ -223,7 +238,13 @@ func (s *RollingStrategy) planGlobal(svc *api.Service, spec api.ServiceSpec) (Se
for _, m := range availableMachines { for _, m := range availableMachines {
containers := containersOnMachine[m.Info.Id] containers := containersOnMachine[m.Info.Id]
ops, err := reconcileGlobalContainer( ops, err := reconcileGlobalContainer(
containers, spec, plan.ServiceID, m.Info.Id, s.ForceRecreate, s.SkipHealthMonitor) containers,
spec,
plan.ServiceID,
m.Info,
s.ForceRecreate,
s.SkipHealthMonitor,
)
if err != nil { if err != nil {
return plan, err return plan, err
} }
@@ -237,6 +258,7 @@ func (s *RollingStrategy) planGlobal(svc *api.Service, spec api.ServiceSpec) (Se
for _, c := range containers { for _, c := range containers {
plan.Operations = append(plan.Operations, &operation.RemoveContainerOperation{ plan.Operations = append(plan.Operations, &operation.RemoveContainerOperation{
MachineID: c.MachineID, MachineID: c.MachineID,
MachineName: machineNames[c.MachineID],
Container: c.Container, Container: c.Container,
StopGracePeriod: spec.StopGracePeriod, StopGracePeriod: spec.StopGracePeriod,
}) })
@@ -250,7 +272,10 @@ func (s *RollingStrategy) planGlobal(svc *api.Service, spec api.ServiceSpec) (Se
// It ensures exactly one container with the desired spec is running on the machine by creating a new container and // It ensures exactly one container with the desired spec is running on the machine by creating a new container and
// removing old ones. If there is a host port conflict, it stops the old container before starting a new one. // removing old ones. If there is a host port conflict, it stops the old container before starting a new one.
func reconcileGlobalContainer( func reconcileGlobalContainer(
containers []api.MachineServiceContainer, spec api.ServiceSpec, serviceID, machineID string, containers []api.MachineServiceContainer,
spec api.ServiceSpec,
serviceID string,
machine *pb.MachineInfo,
forceRecreate, skipHealthCheck bool, forceRecreate, skipHealthCheck bool,
) ([]operation.Operation, error) { ) ([]operation.Operation, error) {
var ops []operation.Operation var ops []operation.Operation
@@ -260,7 +285,8 @@ func reconcileGlobalContainer(
ops = append(ops, &operation.RunContainerOperation{ ops = append(ops, &operation.RunContainerOperation{
ServiceID: serviceID, ServiceID: serviceID,
Spec: spec, Spec: spec,
MachineID: machineID, MachineID: machine.Id,
MachineName: machine.Name,
SkipHealthMonitor: skipHealthCheck, SkipHealthMonitor: skipHealthCheck,
}) })
return ops, nil return ops, nil
@@ -290,6 +316,7 @@ func reconcileGlobalContainer(
} }
ops = append(ops, &operation.RemoveContainerOperation{ ops = append(ops, &operation.RemoveContainerOperation{
MachineID: old.MachineID, MachineID: old.MachineID,
MachineName: machine.Name,
Container: old.Container, Container: old.Container,
StopGracePeriod: spec.StopGracePeriod, StopGracePeriod: spec.StopGracePeriod,
}) })
@@ -325,7 +352,8 @@ func reconcileGlobalContainer(
ops = append(ops, &operation.StopContainerOperation{ ops = append(ops, &operation.StopContainerOperation{
ServiceID: serviceID, ServiceID: serviceID,
ContainerID: c.Container.ID, ContainerID: c.Container.ID,
MachineID: machineID, MachineID: machine.Id,
MachineName: machine.Name,
StopGracePeriod: spec.StopGracePeriod, StopGracePeriod: spec.StopGracePeriod,
}) })
} }
@@ -336,7 +364,8 @@ func reconcileGlobalContainer(
ops = append(ops, &operation.ReplaceContainerOperation{ ops = append(ops, &operation.ReplaceContainerOperation{
ServiceID: serviceID, ServiceID: serviceID,
Spec: spec, Spec: spec,
MachineID: machineID, MachineID: machine.Id,
MachineName: machine.Name,
OldContainer: containerToReplace.Container, OldContainer: containerToReplace.Container,
Order: order, Order: order,
SkipHealthMonitor: skipHealthCheck, SkipHealthMonitor: skipHealthCheck,
@@ -350,6 +379,7 @@ func reconcileGlobalContainer(
} }
ops = append(ops, &operation.RemoveContainerOperation{ ops = append(ops, &operation.RemoveContainerOperation{
MachineID: c.MachineID, MachineID: c.MachineID,
MachineName: machine.Name,
Container: c.Container, Container: c.Container,
StopGracePeriod: spec.StopGracePeriod, StopGracePeriod: spec.StopGracePeriod,
}) })
@@ -359,12 +389,14 @@ func reconcileGlobalContainer(
ops = append(ops, &operation.RunContainerOperation{ ops = append(ops, &operation.RunContainerOperation{
ServiceID: serviceID, ServiceID: serviceID,
Spec: spec, Spec: spec,
MachineID: machineID, MachineID: machine.Id,
MachineName: machine.Name,
SkipHealthMonitor: skipHealthCheck, SkipHealthMonitor: skipHealthCheck,
}) })
for _, c := range containers { for _, c := range containers {
ops = append(ops, &operation.RemoveContainerOperation{ ops = append(ops, &operation.RemoveContainerOperation{
MachineID: c.MachineID, MachineID: c.MachineID,
MachineName: machine.Name,
Container: c.Container, Container: c.Container,
StopGracePeriod: spec.StopGracePeriod, StopGracePeriod: spec.StopGracePeriod,
}) })
+20 -7
View File
@@ -6,6 +6,7 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-cmp/cmp/cmpopts"
"github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/pkg/api" "github.com/psviderski/uncloud/pkg/api"
"github.com/psviderski/uncloud/pkg/client/deploy/operation" "github.com/psviderski/uncloud/pkg/client/deploy/operation"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -315,8 +316,9 @@ func TestReconcileGlobalContainer(t *testing.T) {
}, },
expectedOps: []operation.Operation{ expectedOps: []operation.Operation{
&operation.RunContainerOperation{ &operation.RunContainerOperation{
ServiceID: "service-1", ServiceID: "service-1",
MachineID: "machine-1", MachineID: "machine-1",
MachineName: "machine-1",
}, },
}, },
}, },
@@ -335,6 +337,7 @@ func TestReconcileGlobalContainer(t *testing.T) {
&operation.ReplaceContainerOperation{ &operation.ReplaceContainerOperation{
ServiceID: "service-1", ServiceID: "service-1",
MachineID: "machine-1", MachineID: "machine-1",
MachineName: "machine-1",
OldContainer: container1, OldContainer: container1,
Order: api.UpdateOrderStopFirst, Order: api.UpdateOrderStopFirst,
}, },
@@ -358,16 +361,19 @@ func TestReconcileGlobalContainer(t *testing.T) {
ServiceID: "service-1", ServiceID: "service-1",
ContainerID: "container-2", ContainerID: "container-2",
MachineID: "machine-1", MachineID: "machine-1",
MachineName: "machine-1",
}, },
&operation.ReplaceContainerOperation{ &operation.ReplaceContainerOperation{
ServiceID: "service-1", ServiceID: "service-1",
MachineID: "machine-1", MachineID: "machine-1",
MachineName: "machine-1",
OldContainer: container1, OldContainer: container1,
Order: api.UpdateOrderStopFirst, Order: api.UpdateOrderStopFirst,
}, },
&operation.RemoveContainerOperation{ &operation.RemoveContainerOperation{
MachineID: "machine-1", MachineID: "machine-1",
Container: container2WithPort9090, MachineName: "machine-1",
Container: container2WithPort9090,
}, },
}, },
}, },
@@ -388,12 +394,14 @@ func TestReconcileGlobalContainer(t *testing.T) {
&operation.ReplaceContainerOperation{ &operation.ReplaceContainerOperation{
ServiceID: "service-1", ServiceID: "service-1",
MachineID: "machine-1", MachineID: "machine-1",
MachineName: "machine-1",
OldContainer: container1, OldContainer: container1,
Order: api.UpdateOrderStopFirst, Order: api.UpdateOrderStopFirst,
}, },
&operation.RemoveContainerOperation{ &operation.RemoveContainerOperation{
MachineID: "machine-1", MachineID: "machine-1",
Container: container2WithPort3000, MachineName: "machine-1",
Container: container2WithPort3000,
}, },
}, },
}, },
@@ -402,7 +410,12 @@ func TestReconcileGlobalContainer(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
ops, err := reconcileGlobalContainer( ops, err := reconcileGlobalContainer(
tt.containers, tt.spec, "service-1", "machine-1", tt.forceRecreate, false, tt.containers,
tt.spec,
"service-1",
&pb.MachineInfo{Id: "machine-1", Name: "machine-1"},
tt.forceRecreate,
false,
) )
assert.NoError(t, err) assert.NoError(t, err)
assertOperationsEqual(t, tt.expectedOps, ops) assertOperationsEqual(t, tt.expectedOps, ops)
-54
View File
@@ -1,54 +0,0 @@
package client
import (
"context"
"fmt"
"github.com/psviderski/uncloud/pkg/api"
)
// MapNameResolver resolves machine and container IDs to their names using a static map.
type MapNameResolver struct {
machines map[string]string
containers map[string]string
}
func NewNameResolver(machines, containers map[string]string) *MapNameResolver {
return &MapNameResolver{
machines: machines,
containers: containers,
}
}
func (r *MapNameResolver) MachineName(machineID string) string {
if name, ok := r.machines[machineID]; ok {
return name
}
return machineID
}
func (r *MapNameResolver) ContainerName(containerID string) string {
if name, ok := r.containers[containerID]; ok {
return name
}
return containerID
}
// ServiceOperationNameResolver returns a machine and container name resolver for a service that can be used to format
// deployment operations.
func (cli *Client) ServiceOperationNameResolver(ctx context.Context, svc api.Service) (*MapNameResolver, error) {
machines, err := cli.ListMachines(ctx, nil)
if err != nil {
return nil, fmt.Errorf("list machines: %w", err)
}
machineNames := make(map[string]string, len(machines))
for _, m := range machines {
machineNames[m.Machine.Id] = m.Machine.Name
}
containerNames := make(map[string]string, len(svc.Containers))
for _, c := range svc.Containers {
containerNames[c.Container.ID] = c.Container.Name
}
return NewNameResolver(machineNames, containerNames), nil
}