feat(caddy): add ListCertificates method, refactor Caddy client layout (#31)

This commit is contained in:
Pasha Sviderski committed 2026-09-30 16:43:46 +10:00
1 parent e50c5fbe57
commit 930e7ea637
23 files changed
+1112 -149

No files matched your search

+15 -15
View File
@@ -228,19 +228,19 @@ func TestClusterLifecycle(t *testing.T) {
t.Cleanup(func() {
cleanupCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
_, _ = clients[0].CaddyStorage.Delete(cleanupCtx, &pb.DeleteCaddyStorageRequest{Key: prefix})
_, _ = clients[1].CaddyStorage.Delete(cleanupCtx, &pb.DeleteCaddyStorageRequest{Key: prefix})
_, _ = clients[0].Caddy.Storage.Delete(cleanupCtx, &pb.DeleteCaddyStorageRequest{Key: prefix})
_, _ = clients[1].Caddy.Storage.Delete(cleanupCtx, &pb.DeleteCaddyStorageRequest{Key: prefix})
})
// Create and overwrite a key on the first machine before waiting for replication.
var updatedAt time.Time
for _, value := range [][]byte{[]byte("test-value"), []byte("replacement-value")} {
_, err := clients[0].CaddyStorage.Store(ctx, &pb.StoreCaddyStorageRequest{Key: key, Value: value})
_, err := clients[0].Caddy.Storage.Store(ctx, &pb.StoreCaddyStorageRequest{Key: key, Value: value})
require.NoError(t, err)
// A Load through another machine must find the value on the machine that accepted the local write,
// regardless of whether Corrosion has replicated it to the other machines yet.
loadResp, err := clients[1].CaddyStorage.Load(client.ProxySingleMachineContext(ctx, c.Machines[0].ID),
loadResp, err := clients[1].Caddy.Storage.Load(client.ProxySingleMachineContext(ctx, c.Machines[0].ID),
&pb.LoadCaddyStorageRequest{Key: key})
require.NoError(t, err)
require.Equal(t, value, loadResp.Value)
@@ -256,7 +256,7 @@ func TestClusterLifecycle(t *testing.T) {
// Write a distinct key on the second machine, then capture the combined store version from both machines.
otherValue := []byte("second-value")
_, err := clients[1].CaddyStorage.Store(ctx, &pb.StoreCaddyStorageRequest{Key: otherKey, Value: otherValue})
_, err := clients[1].Caddy.Storage.Store(ctx, &pb.StoreCaddyStorageRequest{Key: otherKey, Value: otherValue})
require.NoError(t, err)
version := storeVersion(clients[0], clients[1])
values := map[string][]byte{key: []byte("replacement-value"), otherKey: otherValue}
@@ -268,7 +268,7 @@ func TestClusterLifecycle(t *testing.T) {
// Both final values must be readable locally as soon as the wait returns.
for k, v := range values {
resp, err := cli.CaddyStorage.Load(ctx, &pb.LoadCaddyStorageRequest{Key: k})
resp, err := cli.Caddy.Storage.Load(ctx, &pb.LoadCaddyStorageRequest{Key: k})
require.NoError(t, err)
assert.Equal(t, v, resp.Value)
require.NoError(t, resp.UpdatedAt.CheckValid())
@@ -276,7 +276,7 @@ func TestClusterLifecycle(t *testing.T) {
assert.True(t, resp.UpdatedAt.AsTime().Equal(updatedAt))
}
statResp, err := cli.CaddyStorage.Stat(ctx, &pb.StatCaddyStorageRequest{Key: k})
statResp, err := cli.Caddy.Storage.Stat(ctx, &pb.StatCaddyStorageRequest{Key: k})
require.NoError(t, err)
assert.Equal(t, k, statResp.Key)
assert.True(t, statResp.UpdatedAt.AsTime().Equal(resp.UpdatedAt.AsTime()))
@@ -285,25 +285,25 @@ func TestClusterLifecycle(t *testing.T) {
}
// A path with descendants should exist as a directory even though no value is stored at that key.
statResp, err := cli.CaddyStorage.Stat(ctx, &pb.StatCaddyStorageRequest{Key: prefix + "/key"})
statResp, err := cli.Caddy.Storage.Stat(ctx, &pb.StatCaddyStorageRequest{Key: prefix + "/key"})
require.NoError(t, err)
assert.Equal(t, prefix+"/key", statResp.Key)
assert.Nil(t, statResp.UpdatedAt)
assert.EqualValues(t, 0, statResp.Size)
assert.False(t, statResp.IsTerminal)
listResp, err := cli.CaddyStorage.List(ctx, &pb.ListCaddyStorageRequest{Prefix: prefix, Recursive: true})
listResp, err := cli.Caddy.Storage.List(ctx, &pb.ListCaddyStorageRequest{Prefix: prefix, Recursive: true})
require.NoError(t, err)
assert.Equal(t, []string{prefix + "/key", key, otherKey}, listResp.Keys)
// A non-recursive list should only return the immediate child keys.
listResp, err = cli.CaddyStorage.List(ctx, &pb.ListCaddyStorageRequest{Prefix: prefix, Recursive: false})
listResp, err = cli.Caddy.Storage.List(ctx, &pb.ListCaddyStorageRequest{Prefix: prefix, Recursive: false})
require.NoError(t, err)
assert.Equal(t, []string{prefix + "/key", otherKey}, listResp.Keys)
}
// Delete through one machine. The deletion must reach the other replicas through Corrosion.
_, err = clients[2].CaddyStorage.Delete(ctx, &pb.DeleteCaddyStorageRequest{Key: prefix})
_, err = clients[2].Caddy.Storage.Delete(ctx, &pb.DeleteCaddyStorageRequest{Key: prefix})
require.NoError(t, err)
version = storeVersion(clients[2])
@@ -311,16 +311,16 @@ func TestClusterLifecycle(t *testing.T) {
waitForStoreVersion(cli, version)
for k := range values {
_, err = cli.CaddyStorage.Load(ctx, &pb.LoadCaddyStorageRequest{Key: k})
_, err = cli.Caddy.Storage.Load(ctx, &pb.LoadCaddyStorageRequest{Key: k})
assert.Equal(t, codes.NotFound, status.Code(err))
_, err = cli.CaddyStorage.Stat(ctx, &pb.StatCaddyStorageRequest{Key: k})
_, err = cli.Caddy.Storage.Stat(ctx, &pb.StatCaddyStorageRequest{Key: k})
assert.Equal(t, codes.NotFound, status.Code(err))
}
_, err = cli.CaddyStorage.List(ctx, &pb.ListCaddyStorageRequest{Prefix: prefix, Recursive: true})
_, err = cli.Caddy.Storage.List(ctx, &pb.ListCaddyStorageRequest{Prefix: prefix, Recursive: true})
assert.Equal(t, codes.NotFound, status.Code(err))
// Delete is idempotent on each machine.
_, err = cli.CaddyStorage.Delete(ctx, &pb.DeleteCaddyStorageRequest{Key: prefix})
_, err = cli.Caddy.Storage.Delete(ctx, &pb.DeleteCaddyStorageRequest{Key: prefix})
require.NoError(t, err)
}
})
+4 -4
View File
@@ -459,8 +459,8 @@ func TestMachineOperations(t *testing.T) {
t.Run("remove machine clears container records from cluster store", func(t *testing.T) {
// The Caddy controller needs a local Caddy container to supply the global config before it can generate
// routes. Place it on the connected machine by ID because earlier tests rename that machine.
caddyDeployment, err := cli.NewCaddyDeployment("", "", api.Placement{
Machines: []string{c.Machines[0].ID},
caddyDeployment, err := cli.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{
Placement: api.Placement{Machines: []string{c.Machines[0].ID}},
})
require.NoError(t, err)
monitorPeriod := 5 * time.Second
@@ -513,7 +513,7 @@ func TestMachineOperations(t *testing.T) {
// The Caddyfile contains both upstream IPs before the machine removal.
require.Eventually(t, func() bool {
cfg, err := cli.Caddy.GetConfig(ctx, nil)
cfg, err := cli.Caddy.Config(ctx, client.CaddyConfigOptions{})
if err != nil {
return false
}
@@ -528,7 +528,7 @@ func TestMachineOperations(t *testing.T) {
// so the Caddy controller regenerates a Caddyfile without that upstream while keeping the
// upstreams for the still-running containers.
require.Eventually(t, func() bool {
cfg, err := cli.Caddy.GetConfig(ctx, nil)
cfg, err := cli.Caddy.Config(ctx, client.CaddyConfigOptions{})
if err != nil {
return false
}
+11 -12
View File
@@ -17,7 +17,6 @@ import (
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/volume"
"github.com/docker/go-units"
"github.com/psviderski/uncloud/api/pb"
"github.com/psviderski/uncloud/internal/machine/metrics"
"github.com/psviderski/uncloud/internal/machine/network"
"github.com/psviderski/uncloud/internal/secret"
@@ -316,7 +315,7 @@ func TestDeployment(t *testing.T) {
}
})
deployment, err := cli.NewCaddyDeployment("", "", api.Placement{})
deployment, err := cli.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{})
require.NoError(t, err)
deployment.Spec.UpdateConfig.MonitorPeriod = &caddyMonitorPeriod
@@ -331,7 +330,7 @@ func TestDeployment(t *testing.T) {
ctr := svc.Containers[0].Container
assert.Regexp(t, `^caddy:2\.\d+\.\d+$`, ctr.Config.Image)
config, err := cli.Caddy.GetConfig(ctx, nil)
config, err := cli.Caddy.Config(ctx, client.CaddyConfigOptions{})
require.NoError(t, err)
assert.Contains(t, config.Caddyfile, "# Caddyfile autogenerated by Uncloud")
@@ -347,8 +346,8 @@ func TestDeployment(t *testing.T) {
})
// Deploy to machine #0.
deployment, err := cli.NewCaddyDeployment("", "", api.Placement{
Machines: []string{c.Machines[0].Name},
deployment, err := cli.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{
Placement: api.Placement{Machines: []string{c.Machines[0].Name}},
})
require.NoError(t, err)
deployment.Spec.UpdateConfig.MonitorPeriod = &caddyMonitorPeriod
@@ -366,7 +365,7 @@ func TestDeployment(t *testing.T) {
// initialContainerID := svc.Containers[0].Container.ID
// Deploy to all machines without a placement constraint.
deployment, err = cli.NewCaddyDeployment(image, "", api.Placement{})
deployment, err = cli.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{Image: image})
require.NoError(t, err)
deployment.Spec.UpdateConfig.MonitorPeriod = &caddyMonitorPeriod
@@ -401,7 +400,7 @@ func TestDeployment(t *testing.T) {
// Without a Caddy container, the controller has no global config to pair with application configs. Keep any
// previously saved Caddyfile unchanged rather than publishing a new one without Caddy's global settings.
savedBefore, savedBeforeErr := cli.Caddy.GetConfig(ctx, nil)
savedBefore, savedBeforeErr := cli.Caddy.Config(ctx, client.CaddyConfigOptions{})
if savedBeforeErr != nil {
require.Equal(t, codes.NotFound, status.Code(savedBeforeErr))
}
@@ -432,7 +431,7 @@ func TestDeployment(t *testing.T) {
assertServiceMatchesSpec(t, svc, spec)
require.Never(t, func() bool {
current, currentErr := cli.Caddy.GetConfig(ctx, nil)
current, currentErr := cli.Caddy.Config(ctx, client.CaddyConfigOptions{})
if savedBeforeErr != nil {
return currentErr == nil
}
@@ -447,7 +446,7 @@ func TestDeployment(t *testing.T) {
myapp.example.com {
reverse_proxy 1.2.3.4:8000
}`
caddyDeployment, err := cli.NewCaddyDeployment("", caddyCaddyfile, api.Placement{})
caddyDeployment, err := cli.Caddy.NewDeployment(ctx, client.CaddyDeploymentOptions{Config: caddyCaddyfile})
require.NoError(t, err)
caddyDeployment.Spec.UpdateConfig.MonitorPeriod = &caddyMonitorPeriod
@@ -459,9 +458,9 @@ myapp.example.com {
assertServiceMatchesSpec(t, caddySvc, caddyDeployment.Spec)
// Wait for the Caddyfile to be regenerated with both custom configs.
var config *pb.GetCaddyConfigResponse
var config api.CaddyConfig
require.Eventually(t, func() bool {
config, err = cli.Caddy.GetConfig(ctx, nil)
config, err = cli.Caddy.Config(ctx, client.CaddyConfigOptions{})
if err != nil {
return false
}
@@ -526,7 +525,7 @@ myapp.example.com {
time.Sleep(2 * time.Second)
// Check that the Caddy config hasn't changed.
newConfig, err := cli.Caddy.GetConfig(ctx, nil)
newConfig, err := cli.Caddy.Config(ctx, client.CaddyConfigOptions{})
require.NoError(t, err)
// Compare stable parts of the Caddyfile only (skip autogenerated comment with timestamp).