mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-28 03:53:33 +00:00
feat: implement 'caddy deploy' command to deploy global service using caddy:latest image
This commit is contained in:
@@ -3,6 +3,7 @@ package caddy
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"uncloud/internal/cli"
|
"uncloud/internal/cli"
|
||||||
)
|
)
|
||||||
@@ -43,9 +44,25 @@ func deploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
|
|||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
if err = client.DeployCaddy(ctx, opts.image); err != nil {
|
d, err := client.NewCaddyDeployment(opts.image)
|
||||||
return fmt.Errorf("deploy caddy: %w", err)
|
if err != nil {
|
||||||
|
return fmt.Errorf("create caddy deployment: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
plan, err := d.Plan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("plan caddy deployment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(plan.SequenceOperation.Operations) == 0 {
|
||||||
|
fmt.Println("caddy service is up to date.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||||
|
if _, err = d.Run(ctx); err != nil {
|
||||||
|
return fmt.Errorf("deploy caddy: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, uncli.ProgressOut(), "Deploying service "+d.Spec.Name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/docker/cli/cli/streams"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os"
|
||||||
"uncloud/internal/cli/client"
|
"uncloud/internal/cli/client"
|
||||||
"uncloud/internal/cli/client/connector"
|
"uncloud/internal/cli/client/connector"
|
||||||
"uncloud/internal/cli/config"
|
"uncloud/internal/cli/config"
|
||||||
@@ -328,3 +330,8 @@ func (cli *CLI) promptResetMachine() error {
|
|||||||
// TODO: implement resetting the remote machine.
|
// TODO: implement resetting the remote machine.
|
||||||
return fmt.Errorf("resetting the remote machine is not implemented yet")
|
return fmt.Errorf("resetting the remote machine is not implemented yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProgressOut returns an output stream for progress writer.
|
||||||
|
func (cli *CLI) ProgressOut() *streams.Out {
|
||||||
|
return streams.NewOut(os.Stdout)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,43 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"uncloud/internal/api"
|
||||||
"errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (cli *Client) DeployCaddy(ctx context.Context, image string) error {
|
const CaddyServiceName = "caddy"
|
||||||
return errors.New("not implemented")
|
|
||||||
|
// NewCaddyDeployment creates a new deployment for a Caddy reverse proxy service.
|
||||||
|
// The service is deployed in global mode to all machines in the cluster. If the image is not provided, the latest
|
||||||
|
// version of the official Caddy Docker image is used.
|
||||||
|
func (cli *Client) NewCaddyDeployment(image string) (*Deployment, error) {
|
||||||
|
if image == "" {
|
||||||
|
// TODO: fetch the latest version tag from the Docker Hub registry.
|
||||||
|
image = "caddy:latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
spec := api.ServiceSpec{
|
||||||
|
Container: api.ContainerSpec{
|
||||||
|
Command: []string{"caddy", "run", "-c", "/config/caddy.json", "--watch"},
|
||||||
|
Image: image,
|
||||||
|
Volumes: []string{"/var/lib/uncloud/caddy:/config"},
|
||||||
|
},
|
||||||
|
Mode: api.ServiceModeGlobal,
|
||||||
|
Name: CaddyServiceName,
|
||||||
|
Ports: []api.PortSpec{
|
||||||
|
{
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 80,
|
||||||
|
Protocol: api.ProtocolTCP,
|
||||||
|
Mode: api.PortModeHost,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PublishedPort: 443,
|
||||||
|
ContainerPort: 443,
|
||||||
|
Protocol: api.ProtocolTCP,
|
||||||
|
Mode: api.PortModeHost,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cli.NewDeployment(spec, &RollingStrategy{})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type Deployment struct {
|
|||||||
|
|
||||||
type Plan struct {
|
type Plan struct {
|
||||||
ServiceID string
|
ServiceID string
|
||||||
Operation
|
SequenceOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDeployment creates a new deployment for the given service specification.
|
// NewDeployment creates a new deployment for the given service specification.
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ func (s *RollingStrategy) planGlobal(
|
|||||||
return plan, fmt.Errorf("list machines: %w", err)
|
return plan, fmt.Errorf("list machines: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
seqOp := &SequenceOperation{}
|
|
||||||
// TODO: figure out how to return a warning if there are machines down. Embed the machinesDown in the plan?
|
// TODO: figure out how to return a warning if there are machines down. Embed the machinesDown in the plan?
|
||||||
// WARNING: failed to run a service container on machine '%s' which is Down.
|
// WARNING: failed to run a service container on machine '%s' which is Down.
|
||||||
var machinesDown []*pb.MachineInfo
|
var machinesDown []*pb.MachineInfo
|
||||||
@@ -89,9 +88,8 @@ func (s *RollingStrategy) planGlobal(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return plan, err
|
return plan, err
|
||||||
}
|
}
|
||||||
seqOp.Operations = append(seqOp.Operations, ops...)
|
plan.SequenceOperation.Operations = append(plan.SequenceOperation.Operations, ops...)
|
||||||
}
|
}
|
||||||
plan.Operation = seqOp
|
|
||||||
|
|
||||||
return plan, nil
|
return plan, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ func TestDeployment(t *testing.T) {
|
|||||||
|
|
||||||
plan, err := deploy.Plan(ctx)
|
plan, err := deploy.Plan(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.IsType(t, &client.SequenceOperation{}, plan.Operation)
|
assert.Len(t, plan.SequenceOperation.Operations, 3) // 3 run
|
||||||
assert.Len(t, plan.Operation.(*client.SequenceOperation).Operations, 3) // 3 run
|
|
||||||
|
|
||||||
svcID, err := deploy.Run(ctx)
|
svcID, err := deploy.Run(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -89,8 +88,7 @@ func TestDeployment(t *testing.T) {
|
|||||||
|
|
||||||
plan, err = deploy.Plan(ctx)
|
plan, err = deploy.Plan(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.IsType(t, &client.SequenceOperation{}, plan.Operation)
|
assert.Len(t, plan.SequenceOperation.Operations, 6) // 3 run + 3 remove
|
||||||
assert.Len(t, plan.Operation.(*client.SequenceOperation).Operations, 6) // 3 run + 3 remove
|
|
||||||
|
|
||||||
svcID, err = deploy.Run(ctx)
|
svcID, err = deploy.Run(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -129,8 +127,7 @@ func TestDeployment(t *testing.T) {
|
|||||||
|
|
||||||
plan, err = deploy.Plan(ctx)
|
plan, err = deploy.Plan(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.IsType(t, &client.SequenceOperation{}, plan.Operation)
|
assert.Len(t, plan.SequenceOperation.Operations, 9) // 3 stop + 3 run + 3 remove
|
||||||
assert.Len(t, plan.Operation.(*client.SequenceOperation).Operations, 9) // 3 stop + 3 run + 3 remove
|
|
||||||
|
|
||||||
svcID, err = deploy.Run(ctx)
|
svcID, err = deploy.Run(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -152,8 +149,7 @@ func TestDeployment(t *testing.T) {
|
|||||||
|
|
||||||
plan, err = deploy.Plan(ctx)
|
plan, err = deploy.Plan(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.IsType(t, &client.SequenceOperation{}, plan.Operation)
|
assert.Len(t, plan.SequenceOperation.Operations, 0) // no-op
|
||||||
assert.Len(t, plan.Operation.(*client.SequenceOperation).Operations, 0) // no-op
|
|
||||||
|
|
||||||
svcID, err = deploy.Run(ctx)
|
svcID, err = deploy.Run(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -165,6 +161,51 @@ func TestDeployment(t *testing.T) {
|
|||||||
assert.Equal(t, api.ServiceModeGlobal, svc.Mode)
|
assert.Equal(t, api.ServiceModeGlobal, svc.Mode)
|
||||||
assert.Len(t, svc.Containers, 3)
|
assert.Len(t, svc.Containers, 3)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("caddy", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
name := "caddy"
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err := cli.RemoveService(ctx, name)
|
||||||
|
if errors.Is(err, client.ErrNotFound) {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
deploy, err := cli.NewCaddyDeployment("")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = deploy.Run(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
svc, err := cli.InspectService(ctx, name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, name, svc.Name)
|
||||||
|
assert.Equal(t, api.ServiceModeGlobal, svc.Mode)
|
||||||
|
assert.Len(t, svc.Containers, 3)
|
||||||
|
|
||||||
|
ctr := svc.Containers[0].Container
|
||||||
|
assert.Equal(t, "caddy:latest", ctr.Config.Image)
|
||||||
|
|
||||||
|
ports, err := ctr.ServicePorts()
|
||||||
|
require.NoError(t, err)
|
||||||
|
expectedPorts := []api.PortSpec{
|
||||||
|
{
|
||||||
|
PublishedPort: 80,
|
||||||
|
ContainerPort: 80,
|
||||||
|
Protocol: api.ProtocolTCP,
|
||||||
|
Mode: api.PortModeHost,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PublishedPort: 443,
|
||||||
|
ContainerPort: 443,
|
||||||
|
Protocol: api.ProtocolTCP,
|
||||||
|
Mode: api.PortModeHost,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, expectedPorts, ports)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunService(t *testing.T) {
|
func TestRunService(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user