feat(caddy): make cluster storage for Caddy default, remember 'uc caddy deploy' options

This commit is contained in:
Pasha Sviderski
2026-11-05 16:03:34 +10:00
parent 76b04dd56c
commit 22b1c68798
13 changed files with 1173 additions and 98 deletions
+84 -10
View File
@@ -319,6 +319,8 @@ func TestDeployment(t *testing.T) {
deployment, err := cli.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{})
require.NoError(t, err)
deployment.Spec.UpdateConfig.MonitorPeriod = &caddyMonitorPeriod
image := deployment.Spec.Container.Image
assert.Regexp(t, `^ghcr\.io/unlabs-dev/caddy-uncloud:0\.\d+\.\d+$`, image)
_, err = deployment.Run(ctx)
require.NoError(t, err)
@@ -327,15 +329,72 @@ func TestDeployment(t *testing.T) {
require.NoError(t, err)
assert.Len(t, svc.Containers, 3)
assertServiceMatchesSpec(t, svc, deployment.Spec)
assert.Equal(t, map[string]string{api.LabelCaddyManaged: ""}, svc.Spec().Labels)
ctr := svc.Containers[0].Container
assert.Regexp(t, `^caddy:2\.\d+\.\d+$`, ctr.Config.Image)
config, err := cli.Caddy.Config(ctx, client.CaddyConfigOptions{})
require.NoError(t, err)
var config api.CaddyConfig
require.Eventually(t, func() bool {
config, err = cli.Caddy.Config(ctx, client.CaddyConfigOptions{})
return err == nil && strings.Contains(config.Caddyfile, "storage uncloud")
}, 10*time.Second, 200*time.Millisecond, "expected the Caddyfile to enable cluster storage")
assert.Empty(t, config.LastReconciliationError)
assert.Contains(t, config.Caddyfile, "# Caddyfile autogenerated by Uncloud")
assert.Contains(t, config.Caddyfile, "handle /.uncloud-verify")
// Setting the running image as custom only changes the reserved service labels so it must not recreate
// the containers.
containerIDs := serviceContainerIDs(svc)
deployment, err = cli.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{Image: image})
require.NoError(t, err)
_, err = deployment.Run(ctx)
require.NoError(t, err)
svc, err = cli.InspectService(ctx, client.CaddyServiceName)
require.NoError(t, err)
assert.True(t, containerIDs.Equal(serviceContainerIDs(svc)), "expected the containers to be updated in place")
assert.Equal(t, map[string]string{api.LabelCaddyManaged: "", api.LabelCaddyImageCustom: ""}, svc.Spec().Labels)
})
t.Run("caddy current spec deployment only adds a container on a new machine", func(t *testing.T) {
t.Cleanup(func() {
err := cli.RemoveService(ctx, client.CaddyServiceName)
if !errors.Is(err, api.ErrNotFound) {
require.NoError(t, err)
}
})
deployment, err := cli.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{})
require.NoError(t, err)
deployment.Spec.UpdateConfig.MonitorPeriod = &caddyMonitorPeriod
_, err = deployment.Run(ctx)
require.NoError(t, err)
svc, err := cli.InspectService(ctx, client.CaddyServiceName)
require.NoError(t, err)
require.Len(t, svc.Containers, 3)
// Remove a container to make its machine look like a new one that doesn't run Caddy yet, as 'uc machine add' sees it.
removed := svc.Containers[0]
err = cli.RemoveContainer(ctx, svc.ID, removed.Container.ID, container.RemoveOptions{Force: true})
require.NoError(t, err)
svc, err = cli.InspectService(ctx, client.CaddyServiceName)
require.NoError(t, err)
require.Len(t, svc.Containers, 2)
containerIDs := serviceContainerIDs(svc)
_, err = cli.NewDeployment(svc.Spec(), nil).Run(ctx)
require.NoError(t, err)
svc, err = cli.InspectService(ctx, client.CaddyServiceName)
require.NoError(t, err)
assert.Len(t, svc.Containers, 3)
assert.Len(t, serviceMachines(svc).ToSlice(), 3, "expected 1 container on each machine")
assert.True(t, containerIDs.IsSubset(serviceContainerIDs(svc)),
"expected other containers to be left unchanged")
assertServiceMatchesSpec(t, svc, deployment.Spec)
assert.Equal(t, map[string]string{api.LabelCaddyManaged: ""}, svc.Spec().Labels)
})
t.Run("caddy with machine placement", func(t *testing.T) {
@@ -365,6 +424,11 @@ func TestDeployment(t *testing.T) {
assert.Equal(t, c.Machines[0].ID, svc.Containers[0].MachineID)
// initialContainerID := svc.Containers[0].Container.ID
// Deploying the current spec keeps the placement so it must not add containers on other machines.
plan, err := cli.NewDeployment(svc.Spec(), nil).Plan(ctx)
require.NoError(t, err)
assert.Empty(t, plan.Operations)
// Deploy to all machines without a placement constraint.
deployment, err = cli.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{Image: image})
require.NoError(t, err)
@@ -447,7 +511,7 @@ func TestDeployment(t *testing.T) {
myapp.example.com {
reverse_proxy 1.2.3.4:8000
}`
caddyDeployment, err := cli.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{Config: caddyCaddyfile})
caddyDeployment, err := cli.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{Config: &caddyCaddyfile})
require.NoError(t, err)
caddyDeployment.Spec.UpdateConfig.MonitorPeriod = &caddyMonitorPeriod
@@ -1832,11 +1896,21 @@ func TestServiceLifecycle(t *testing.T) {
require.NoError(t, err)
assertContainerMatchesSpec(t, mc.Container, spec)
// Reserved service labels are rejected.
// Service labels with an invalid key format are rejected.
err = cli.UpdateContainer(ctx, serviceID, ctr.ID, api.UpdateContainerOptions{
ServiceLabels: &map[string]string{api.LabelManaged: ""},
ServiceLabels: &map[string]string{"my label": ""},
})
require.ErrorContains(t, err, "reserved prefix")
require.ErrorContains(t, err, "must not contain whitespace")
// The daemon accepts reserved service labels because Uncloud sets its own labels on services.
err = cli.UpdateContainer(ctx, serviceID, ctr.ID, api.UpdateContainerOptions{
ServiceLabels: &map[string]string{"uncloud.future": ""},
})
require.NoError(t, err)
mc, err = cli.InspectContainer(ctx, serviceID, ctr.ID)
require.NoError(t, err)
assert.Equal(t, map[string]string{"uncloud.future": ""}, mc.Container.ServiceSpec.Labels)
require.NoError(t, cli.RemoveContainer(ctx, serviceID, ctr.ID, container.RemoveOptions{Force: true}))
err = cli.UpdateContainer(ctx, serviceID, ctr.ID, api.UpdateContainerOptions{