mirror of
https://github.com/psviderski/uncloud.git
synced 2026-11-10 12:44:16 +00:00
feat(labels): support service and container labels, update labels and cpu/memory in-place
This commit is contained in:
@@ -55,7 +55,7 @@ func (p *Plan) Format() string {
|
||||
|
||||
// formatSummary counts all operations across the plan and renders the summary footer.
|
||||
func (p *Plan) formatSummary() string {
|
||||
var createCount, startFirstCount, stopFirstCount, removeCount int
|
||||
var createCount, startFirstCount, stopFirstCount, updateCount, removeCount int
|
||||
machines := make(map[string]struct{})
|
||||
|
||||
for _, op := range p.Volumes {
|
||||
@@ -76,6 +76,9 @@ func (p *Plan) formatSummary() string {
|
||||
} else {
|
||||
startFirstCount++
|
||||
}
|
||||
case *operation.UpdateContainerOperation:
|
||||
machines[o.MachineID] = struct{}{}
|
||||
updateCount++
|
||||
case *operation.RemoveContainerOperation:
|
||||
machines[o.MachineID] = struct{}{}
|
||||
removeCount++
|
||||
@@ -99,6 +102,10 @@ func (p *Plan) formatSummary() string {
|
||||
parts = append(parts,
|
||||
tui.BoldYellow.Render(strconv.Itoa(stopFirstCount))+" "+tui.Yellow.Render("replace (stop-first)"))
|
||||
}
|
||||
if updateCount > 0 {
|
||||
parts = append(parts,
|
||||
tui.BoldYellow.Render(strconv.Itoa(updateCount))+" "+tui.Yellow.Render("update (in place)"))
|
||||
}
|
||||
if removeCount > 0 {
|
||||
parts = append(parts,
|
||||
tui.BoldRed.Render(strconv.Itoa(removeCount))+" "+tui.Red.Render("remove"))
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
"github.com/psviderski/uncloud/pkg/client/deploy"
|
||||
"github.com/psviderski/uncloud/pkg/client/operation"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPlan_FormatSummary(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
web := &deploy.ServicePlan{}
|
||||
web.Operations = []operation.Operation{
|
||||
&operation.UpdateContainerOperation{MachineID: "m-1"},
|
||||
&operation.RemoveContainerOperation{MachineID: "m-2"},
|
||||
}
|
||||
db := &deploy.ServicePlan{}
|
||||
db.Operations = []operation.Operation{
|
||||
&operation.UpdateContainerOperation{MachineID: "m-2"},
|
||||
}
|
||||
plan := &Plan{
|
||||
Volumes: []*operation.CreateVolumeOperation{{MachineID: "m-3"}},
|
||||
Services: []*deploy.ServicePlan{web, db},
|
||||
}
|
||||
|
||||
assert.Equal(t, "1 create · 2 update (in place) · 1 remove · across 3 machines", ansi.Strip(plan.formatSummary()))
|
||||
}
|
||||
@@ -344,20 +344,6 @@ configs:
|
||||
warnCount: 1,
|
||||
warnContains: []string{"container_name"},
|
||||
},
|
||||
{
|
||||
name: "deploy labels",
|
||||
composeYAML: `services:
|
||||
app:
|
||||
image: myapp:latest
|
||||
deploy:
|
||||
mode: replicated
|
||||
labels:
|
||||
- foo
|
||||
replicas: 2
|
||||
`,
|
||||
warnCount: 1,
|
||||
warnContains: []string{"deploy labels"},
|
||||
},
|
||||
{
|
||||
name: "deploy restart_policy",
|
||||
composeYAML: `services:
|
||||
|
||||
@@ -66,6 +66,10 @@ func ServiceSpecFromCompose(project *types.Project, serviceName string) (api.Ser
|
||||
Mode: api.ServiceModeReplicated,
|
||||
}
|
||||
|
||||
if labels := mergeLabels(service.Labels, service.CustomLabels); len(labels) > 0 {
|
||||
spec.Container.Labels = labels
|
||||
}
|
||||
|
||||
// Map x-caddy extension to spec.Caddy if specified.
|
||||
if caddy, ok := service.Extensions[CaddyExtensionKey].(Caddy); ok && caddy.Config != "" {
|
||||
spec.Caddy = &api.CaddySpec{
|
||||
@@ -124,6 +128,10 @@ func ServiceSpecFromCompose(project *types.Project, serviceName string) (api.Ser
|
||||
d := time.Duration(cfg.Monitor)
|
||||
spec.UpdateConfig.MonitorPeriod = &d
|
||||
}
|
||||
|
||||
if len(service.Deploy.Labels) > 0 {
|
||||
spec.Labels = maps.Clone(service.Deploy.Labels)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: can service.tmpfs be handled as tmpfs volume mounts as well?
|
||||
@@ -480,9 +488,6 @@ func validateServicesFeatures(project *types.Project) []error {
|
||||
if service.DNSSearch != nil {
|
||||
errs = append(errs, err(service.Name, "dns_search"))
|
||||
}
|
||||
if service.Labels != nil {
|
||||
errs = append(errs, err(service.Name, "labels"))
|
||||
}
|
||||
if service.Links != nil {
|
||||
errs = append(errs, err(service.Name, "links"))
|
||||
}
|
||||
@@ -517,9 +522,6 @@ func validateServicesFeatures(project *types.Project) []error {
|
||||
if service.Deploy.RollbackConfig != nil {
|
||||
errs = append(errs, err(service.Name, "deploy rollback_config"))
|
||||
}
|
||||
if len(service.Deploy.Labels) != 0 {
|
||||
errs = append(errs, err(service.Name, "deploy labels"))
|
||||
}
|
||||
}
|
||||
// we only allow the 'default' network, nothing else.
|
||||
if x := service.Networks; x != nil {
|
||||
|
||||
@@ -875,6 +875,81 @@ services:
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceSpecFromCompose_Labels(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
composeYAML string
|
||||
expectedContainerLabels map[string]string
|
||||
expectedServiceLabels map[string]string
|
||||
}{
|
||||
{
|
||||
name: "no labels",
|
||||
composeYAML: `
|
||||
services:
|
||||
web:
|
||||
image: nginx
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "container labels as list",
|
||||
composeYAML: `
|
||||
services:
|
||||
web:
|
||||
image: nginx
|
||||
labels:
|
||||
- com.example.team=web
|
||||
- com.example.empty
|
||||
`,
|
||||
expectedContainerLabels: map[string]string{
|
||||
"com.example.team": "web",
|
||||
"com.example.empty": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "container and service labels as maps",
|
||||
composeYAML: `
|
||||
services:
|
||||
web:
|
||||
image: nginx
|
||||
labels:
|
||||
com.example.team: web
|
||||
deploy:
|
||||
labels:
|
||||
tier: frontend
|
||||
`,
|
||||
expectedContainerLabels: map[string]string{"com.example.team": "web"},
|
||||
expectedServiceLabels: map[string]string{"tier": "frontend"},
|
||||
},
|
||||
{
|
||||
name: "service labels only",
|
||||
composeYAML: `
|
||||
services:
|
||||
web:
|
||||
image: nginx
|
||||
deploy:
|
||||
labels:
|
||||
- tier=frontend
|
||||
`,
|
||||
expectedServiceLabels: map[string]string{"tier": "frontend"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
project, err := LoadProjectFromContent(context.Background(), tt.composeYAML)
|
||||
require.NoError(t, err)
|
||||
|
||||
spec, err := ServiceSpecFromCompose(project, "web")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.expectedContainerLabels, spec.Container.Labels)
|
||||
assert.Equal(t, tt.expectedServiceLabels, spec.Labels)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceSpecFromCompose_UpdateConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user