mirror of
https://github.com/psviderski/uncloud.git
synced 2026-11-10 12:44:16 +00:00
feat(caddy): make cluster storage for Caddy default, remember 'uc caddy deploy' options
This commit is contained in:
@@ -134,6 +134,22 @@ func (sp *ServicePlan) Format() string {
|
||||
specTable.Row(mod, "image:", formatImageDiff(oldSpec.Container.Image, sp.Spec.Container.Image))
|
||||
}
|
||||
|
||||
// An existing service without an old spec only has new containers added to up-to-date ones so its Caddy config and
|
||||
// labels didn't change.
|
||||
specChanged := sp.IsNewService || oldSpec != nil
|
||||
var old api.ServiceSpec
|
||||
if oldSpec != nil {
|
||||
old = *oldSpec
|
||||
}
|
||||
|
||||
// Caddy config row.
|
||||
if specChanged {
|
||||
mod, value := formatCaddyConfigDiff(old.CaddyConfig(), sp.Spec.CaddyConfig(), sp.IsNewService)
|
||||
if value != "" {
|
||||
specTable.Row(mod, "caddy config:", value)
|
||||
}
|
||||
}
|
||||
|
||||
// Replicas row for replicated services.
|
||||
if sp.Spec.Mode == api.ServiceModeReplicated {
|
||||
replicasStr := fmt.Sprintf("%d", sp.Spec.Replicas)
|
||||
@@ -153,15 +169,11 @@ func (sp *ServicePlan) Format() string {
|
||||
|
||||
specDiff := specTable.String() + "\n"
|
||||
|
||||
// Render labels as nested blocks outside the table to not widen its attribute column. An existing service without
|
||||
// an old spec only has new containers added to up-to-date ones so its labels didn't change.
|
||||
if sp.IsNewService || oldSpec != nil {
|
||||
var oldLabels, oldContainerLabels map[string]string
|
||||
if oldSpec != nil {
|
||||
oldLabels, oldContainerLabels = oldSpec.Labels, oldSpec.Container.Labels
|
||||
}
|
||||
specDiff += formatLabelsDiff("labels:", oldLabels, sp.Spec.Labels, sp.IsNewService)
|
||||
specDiff += formatLabelsDiff("container labels:", oldContainerLabels, sp.Spec.Container.Labels, sp.IsNewService)
|
||||
// Render the labels outside the table because they span multiple lines.
|
||||
if specChanged {
|
||||
specDiff += formatLabelsDiff("labels:", old.Labels, sp.Spec.Labels, sp.IsNewService)
|
||||
specDiff += formatLabelsDiff("container labels:", old.Container.Labels, sp.Spec.Container.Labels,
|
||||
sp.IsNewService)
|
||||
}
|
||||
|
||||
treePrefix := tui.Faint.Render(" │ ")
|
||||
@@ -288,6 +300,65 @@ func formatImageDiff(oldImage, newImage string) string {
|
||||
tui.FormatImage(newImage, tui.Green)
|
||||
}
|
||||
|
||||
// formatCaddyConfigDiff returns the modifier and the number of added and removed lines in the Caddy config. It omits
|
||||
// the config content because it can be large. It returns an empty value if the configs are the same.
|
||||
func formatCaddyConfigDiff(oldConfig, newConfig string, isNewService bool) (mod, value string) {
|
||||
if oldConfig == newConfig {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// Count lines regardless of their order, which is enough for a summary and doesn't require a diff algorithm.
|
||||
remaining := make(map[string]int)
|
||||
var removed int
|
||||
if oldConfig != "" {
|
||||
for line := range strings.SplitSeq(oldConfig, "\n") {
|
||||
remaining[line]++
|
||||
removed++
|
||||
}
|
||||
}
|
||||
var added int
|
||||
if newConfig != "" {
|
||||
for line := range strings.SplitSeq(newConfig, "\n") {
|
||||
if remaining[line] > 0 {
|
||||
remaining[line]--
|
||||
removed--
|
||||
} else {
|
||||
added++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var parts []string
|
||||
if added > 0 {
|
||||
parts = append(parts, tui.Green.Render("+"+strconv.Itoa(added)))
|
||||
}
|
||||
if removed > 0 {
|
||||
parts = append(parts, tui.Red.Render("-"+strconv.Itoa(removed)))
|
||||
}
|
||||
|
||||
switch {
|
||||
case added+removed == 0:
|
||||
value = "changed"
|
||||
case added+removed == 1:
|
||||
value = parts[0] + " line"
|
||||
default:
|
||||
value = strings.Join(parts, " ") + " lines"
|
||||
}
|
||||
|
||||
switch {
|
||||
case isNewService:
|
||||
mod = ""
|
||||
case oldConfig == "":
|
||||
mod = tui.Green.Render("+")
|
||||
case newConfig == "":
|
||||
mod = tui.Red.Render("-")
|
||||
default:
|
||||
mod = "~"
|
||||
}
|
||||
|
||||
return mod, value
|
||||
}
|
||||
|
||||
// formatLabelsDiff renders a block with the attribute name followed by a nested line for each added, changed, or
|
||||
// removed label, sorted by key. It returns an empty string if the labels are the same.
|
||||
func formatLabelsDiff(attribute string, oldLabels, newLabels map[string]string, isNewService bool) string {
|
||||
|
||||
@@ -83,6 +83,21 @@ func TestServicePlan_Format(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
withCaddy := func(config string) api.ServiceSpec {
|
||||
s := spec("nginx:1.25", nil, nil)
|
||||
s.Caddy = &api.CaddySpec{Config: config}
|
||||
return s
|
||||
}
|
||||
replaceFrom := func(old api.ServiceSpec) func(api.ServiceSpec) []operation.Operation {
|
||||
return func(new api.ServiceSpec) []operation.Operation {
|
||||
return []operation.Operation{&operation.ReplaceContainerOperation{
|
||||
Spec: new,
|
||||
MachineName: "machine-1",
|
||||
OldContainer: newContainer(old),
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
isNewService bool
|
||||
@@ -90,6 +105,112 @@ func TestServicePlan_Format(t *testing.T) {
|
||||
ops func(spec api.ServiceSpec) []operation.Operation
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "caddy config lines added and removed",
|
||||
spec: withCaddy("{\n\tstorage uncloud\n}"),
|
||||
ops: replaceFrom(withCaddy("{\n\tdebug\n\tauto_https off\n}")),
|
||||
expected: []string{
|
||||
"~ update service web",
|
||||
" │ image: nginx:1.25",
|
||||
" │ ~ caddy config: +1 -2 lines",
|
||||
" │",
|
||||
" ╰── +/- replace container web/0f6df6593e1b on machine-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "caddy config single line added",
|
||||
spec: withCaddy("{\n\tstorage uncloud\n\tdebug\n}"),
|
||||
ops: replaceFrom(withCaddy("{\n\tstorage uncloud\n}")),
|
||||
expected: []string{
|
||||
"~ update service web",
|
||||
" │ image: nginx:1.25",
|
||||
" │ ~ caddy config: +1 line",
|
||||
" │",
|
||||
" ╰── +/- replace container web/0f6df6593e1b on machine-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "caddy config added to existing service",
|
||||
spec: withCaddy("{\n\tstorage uncloud\n}"),
|
||||
ops: replaceFrom(spec("nginx:1.25", nil, nil)),
|
||||
expected: []string{
|
||||
"~ update service web",
|
||||
" │ image: nginx:1.25",
|
||||
" │ + caddy config: +3 lines",
|
||||
" │",
|
||||
" ╰── +/- replace container web/0f6df6593e1b on machine-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "caddy config removed",
|
||||
spec: spec("nginx:1.25", nil, nil),
|
||||
ops: replaceFrom(withCaddy("{\n\tstorage uncloud\n}")),
|
||||
expected: []string{
|
||||
"~ update service web",
|
||||
" │ image: nginx:1.25",
|
||||
" │ - caddy config: -3 lines",
|
||||
" │",
|
||||
" ╰── +/- replace container web/0f6df6593e1b on machine-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "caddy config with only reordered lines",
|
||||
spec: withCaddy("a {\n}\nb {\n}"),
|
||||
ops: replaceFrom(withCaddy("b {\n}\na {\n}")),
|
||||
expected: []string{
|
||||
"~ update service web",
|
||||
" │ image: nginx:1.25",
|
||||
" │ ~ caddy config: changed",
|
||||
" │",
|
||||
" ╰── +/- replace container web/0f6df6593e1b on machine-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "caddy config unchanged ignoring surrounding whitespace",
|
||||
spec: withCaddy("{\n\tdebug\n}"),
|
||||
ops: replaceFrom(withCaddy("\n{\n\tdebug\n}\n")),
|
||||
expected: []string{
|
||||
"~ update service web",
|
||||
" │ image: nginx:1.25",
|
||||
" │",
|
||||
" ╰── +/- replace container web/0f6df6593e1b on machine-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "new service with caddy config",
|
||||
isNewService: true,
|
||||
spec: withCaddy("{\n\tstorage uncloud\n}"),
|
||||
ops: func(new api.ServiceSpec) []operation.Operation {
|
||||
return []operation.Operation{&operation.RunContainerOperation{Spec: new, MachineName: "machine-1"}}
|
||||
},
|
||||
expected: []string{
|
||||
"+ create service web",
|
||||
" │ image: nginx:1.25",
|
||||
" │ caddy config: +3 lines",
|
||||
" │",
|
||||
" ╰── + run container web on machine-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "caddy config before replicas",
|
||||
isNewService: true,
|
||||
spec: func() api.ServiceSpec {
|
||||
s := withCaddy("{\n\tstorage uncloud\n}")
|
||||
s.Replicas = 3
|
||||
return s
|
||||
}(),
|
||||
ops: func(new api.ServiceSpec) []operation.Operation {
|
||||
return []operation.Operation{&operation.RunContainerOperation{Spec: new, MachineName: "machine-1"}}
|
||||
},
|
||||
expected: []string{
|
||||
"+ create service web",
|
||||
" │ image: nginx:1.25",
|
||||
" │ caddy config: +3 lines",
|
||||
" │ replicas: 3",
|
||||
" │",
|
||||
" ╰── + run container web on machine-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "service labels added, changed, and removed in place",
|
||||
spec: spec("nginx:1.25", map[string]string{"env": "prod", "team": "platform", "same": "1"}, nil),
|
||||
|
||||
Reference in New Issue
Block a user