mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-28 03:53:33 +00:00
fix: compose deployment plan preview
This commit is contained in:
+32
-30
@@ -6,10 +6,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/v2/types"
|
|
||||||
"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/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
|
"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/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -26,9 +26,8 @@ type deployOptions struct {
|
|||||||
func NewDeployCommand() *cobra.Command {
|
func NewDeployCommand() *cobra.Command {
|
||||||
opts := deployOptions{}
|
opts := deployOptions{}
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "deploy [FLAGS] [SERVICE...]",
|
Use: "deploy [FLAGS] [SERVICE...]",
|
||||||
// TODO: remove WIP when the command is fully implemented.
|
Short: "Deploy services from a Compose file.",
|
||||||
Short: "WIP: Deploy services from a Compose file.",
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
uncli := cmd.Context().Value("cli").(*cli.CLI)
|
||||||
|
|
||||||
@@ -58,8 +57,8 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(opts.services) > 0 {
|
if len(opts.services) > 0 {
|
||||||
// TODO: handle dependencies properly.
|
// Includes service dependencies by default. This is the default docker compose behavior.
|
||||||
project, err = project.WithSelectedServices(opts.services, types.IgnoreDependencies)
|
project, err = project.WithSelectedServices(opts.services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("select services: %w", err)
|
return fmt.Errorf("select services: %w", err)
|
||||||
}
|
}
|
||||||
@@ -87,30 +86,8 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Deployment plan:")
|
fmt.Println("Deployment plan:")
|
||||||
|
if err = printPlan(ctx, clusterClient, plan); err != nil {
|
||||||
for _, op := range plan.Operations {
|
return fmt.Errorf("print deployment plan: %w", err)
|
||||||
svcPlan, ok := op.(*deploy.Plan)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("expected service Plan, got: %T", op)
|
|
||||||
}
|
|
||||||
|
|
||||||
svc, err := clusterClient.InspectService(ctx, svcPlan.ServiceID)
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, api.ErrNotFound) {
|
|
||||||
return fmt.Errorf("inspect service: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Printf("- Run service [name=%s]\n", svcPlan.ServiceName)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("- Update service [name=%s]\n", svc.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialise a machine and container name resolver to properly format the service 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(indent(svcPlan.Format(resolver), " "))
|
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
@@ -131,6 +108,31 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
}, uncli.ProgressOut(), "Deploying services")
|
}, uncli.ProgressOut(), "Deploying services")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printPlan(ctx context.Context, cli *client.Client, plan deploy.SequenceOperation) error {
|
||||||
|
for _, op := range plan.Operations {
|
||||||
|
svcPlan, ok := op.(*deploy.Plan)
|
||||||
|
if !ok {
|
||||||
|
fmt.Println("- " + op.Format(nil))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
svc, err := cli.InspectService(ctx, svcPlan.ServiceID)
|
||||||
|
if err != nil && !errors.Is(err, api.ErrNotFound) {
|
||||||
|
return fmt.Errorf("inspect service: %w", err)
|
||||||
|
}
|
||||||
|
// Initialise a machine and container name resolver to properly format the service plan output.
|
||||||
|
resolver, err := cli.ServiceOperationNameResolver(ctx, svc)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create machine and container name resolver for service operations: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("- Deploy service [name=%s]\n", svcPlan.ServiceName)
|
||||||
|
fmt.Println(indent(svcPlan.Format(resolver), " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func indent(text, prefix string) string {
|
func indent(text, prefix string) string {
|
||||||
lines := strings.Split(text, "\n")
|
lines := strings.Split(text, "\n")
|
||||||
for i, line := range lines {
|
for i, line := range lines {
|
||||||
|
|||||||
@@ -142,9 +142,15 @@ func (d *Deployment) planVolumes(serviceSpecs []api.ServiceSpec) ([]*deploy.Crea
|
|||||||
var ops []*deploy.CreateVolumeOperation
|
var ops []*deploy.CreateVolumeOperation
|
||||||
for machineID, volumes := range scheduledVolumes {
|
for machineID, volumes := range scheduledVolumes {
|
||||||
for _, v := range volumes {
|
for _, v := range volumes {
|
||||||
|
machineName := machineID
|
||||||
|
if m, ok := d.state.Machine(machineID); ok {
|
||||||
|
machineName = m.Info.Name
|
||||||
|
}
|
||||||
|
|
||||||
ops = append(ops, &deploy.CreateVolumeOperation{
|
ops = append(ops, &deploy.CreateVolumeOperation{
|
||||||
MachineID: machineID,
|
MachineID: machineID,
|
||||||
VolumeSpec: v,
|
MachineName: machineName,
|
||||||
|
VolumeSpec: v,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type Operation interface {
|
|||||||
// can be provided. But in reality, the operation is tightly coupled with the client that was used to create it.
|
// can be provided. But in reality, the operation is tightly coupled with the client that was used to create it.
|
||||||
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(resolver NameResolver) string
|
Format(resolver NameResolver) string
|
||||||
String() string
|
String() string
|
||||||
}
|
}
|
||||||
@@ -115,6 +116,8 @@ func (o *RemoveContainerOperation) String() string {
|
|||||||
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 string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *CreateVolumeOperation) Execute(ctx context.Context, cli Client) error {
|
func (o *CreateVolumeOperation) Execute(ctx context.Context, cli Client) error {
|
||||||
@@ -140,9 +143,8 @@ func (o *CreateVolumeOperation) Execute(ctx context.Context, cli Client) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *CreateVolumeOperation) Format(resolver NameResolver) string {
|
func (o *CreateVolumeOperation) Format(_ NameResolver) string {
|
||||||
machineName := resolver.MachineName(o.MachineID)
|
return fmt.Sprintf("%s: Create volume [name=%s]", o.MachineName, o.VolumeSpec.DockerVolumeName())
|
||||||
return fmt.Sprintf("%s: Create volume [name=%s]", machineName, o.VolumeSpec.DockerVolumeName())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *CreateVolumeOperation) String() string {
|
func (o *CreateVolumeOperation) String() string {
|
||||||
|
|||||||
@@ -57,3 +57,13 @@ func InspectClusterState(ctx context.Context, cli Client) (*ClusterState, error)
|
|||||||
Machines: machines,
|
Machines: machines,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Machine returns the machine with the given name or ID from the cluster state.
|
||||||
|
func (s *ClusterState) Machine(nameOrID string) (*Machine, bool) {
|
||||||
|
for _, m := range s.Machines {
|
||||||
|
if m.Info.Id == nameOrID || m.Info.Name == nameOrID {
|
||||||
|
return m, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user