Compare commits

...
Author SHA1 Message Date
coop-cloud 9aa5cf6df3 set recipe label for all containers of an app
continuous-integration/drone/push Build is passing
2026-09-16 16:34:29 +02:00
3 changed files with 100 additions and 17 deletions
+50 -8
View File
@@ -10,18 +10,60 @@ import (
composetypes "github.com/docker/cli/cli/compose/types"
)
// SetRecipeLabel adds the label 'coop-cloud.${STACK_NAME}.recipe=${RECIPE}' to the app container
// to signal which recipe is connected to the deployed app
func SetRecipeLabel(compose *composetypes.Config, stackName string, recipe string) {
for _, service := range compose.Services {
if service.Name == "app" {
log.Debug(i18n.G("set recipe label 'coop-cloud.%s.recipe' to %s for %s", stackName, recipe, stackName))
labelKey := fmt.Sprintf("coop-cloud.%s.recipe", stackName)
service.Deploy.Labels[labelKey] = recipe
// setLabel writes a label to both the service object and the task template of
// every service of the stack.
//
// The task template labels end up on the containers themselves and are therefore
// visible to container-level tooling (cAdvisor, log shippers, docker events),
// which cannot read service labels at all.
//
// The service object is written as well because it is free: changing
// Spec.Labels updates the service without recreating any task and because
// `docker service ls --filter label=...` matches service labels only.
func setLabel(compose *composetypes.Config, key string, value string) {
for i := range compose.Services {
if compose.Services[i].Deploy.Labels == nil {
compose.Services[i].Deploy.Labels = composetypes.Labels{}
}
if compose.Services[i].Labels == nil {
compose.Services[i].Labels = composetypes.Labels{}
}
compose.Services[i].Deploy.Labels[key] = value
compose.Services[i].Labels[key] = value
}
}
// setAppLabel writes a label to the service object of the app service only.
func setAppLabel(compose *composetypes.Config, key string, value string) {
for i := range compose.Services {
if compose.Services[i].Name != "app" {
continue
}
if compose.Services[i].Deploy.Labels == nil {
compose.Services[i].Deploy.Labels = composetypes.Labels{}
}
compose.Services[i].Deploy.Labels[key] = value
}
}
// SetRecipeLabel adds two distinct labels to signal which recipe is connected
// to the deployed app:
// - 'coop-cloud.${STACK_NAME}.recipe=${RECIPE}' on the app service object,
// unchanged, as read back by GetLabel
// - 'coop-cloud.recipe=${RECIPE}' on every service of the stack, so that
// tooling can attribute any container to a recipe without knowing the
// stack name up front
func SetRecipeLabel(compose *composetypes.Config, stackName string, recipe string) {
log.Debug(i18n.G("set recipe labels 'coop-cloud.%s.recipe' and 'coop-cloud.recipe' to %s for %s", stackName, recipe, stackName))
setAppLabel(compose, fmt.Sprintf("coop-cloud.%s.recipe", stackName), recipe)
setLabel(compose, "coop-cloud.recipe", recipe)
}
// SetChaosLabel adds the label 'coop-cloud.${STACK_NAME}.chaos=true/false' to the app container
// to signal if the app is deployed in chaos mode
func SetChaosLabel(compose *composetypes.Config, stackName string, chaos bool) {
+41
View File
@@ -8,6 +8,7 @@ import (
testPkg "coopcloud.tech/abra/pkg/test"
stack "coopcloud.tech/abra/pkg/upstream/stack"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/stretchr/testify/assert"
)
@@ -61,3 +62,43 @@ func TestGetTimeoutFromLabel(t *testing.T) {
assert.Equal(t, timeout, test.expectedTimeout)
}
}
func TestSetRecipeLabel(t *testing.T) {
// the app service brings labels along on both levels, the db service
// has none at all, so the pre-existing and the nil map case are covered
compose := &composetypes.Config{
Services: []composetypes.ServiceConfig{
{
Name: "app",
Labels: composetypes.Labels{"example.container.label": "keep me"},
Deploy: composetypes.DeployConfig{
Labels: composetypes.Labels{"coop-cloud.backupbot.enabled": "true"},
},
},
{Name: "db"},
},
}
appPkg.SetRecipeLabel(compose, "test_example_com", "test-recipe")
services := make(map[string]composetypes.ServiceConfig)
for _, service := range compose.Services {
services[service.Name] = service
}
// the stack scoped label stays on the app service object only
assert.Equal(t, "test-recipe",
services["app"].Deploy.Labels["coop-cloud.test_example_com.recipe"])
assert.NotContains(t, services["db"].Deploy.Labels,
"coop-cloud.test_example_com.recipe")
// the static label reaches every container of the stack
for name, service := range services {
assert.Equal(t, "test-recipe", service.Labels["coop-cloud.recipe"], name)
assert.Equal(t, "test-recipe", service.Deploy.Labels["coop-cloud.recipe"], name)
}
// labels the recipe brought along are left alone
assert.Equal(t, "keep me", services["app"].Labels["example.container.label"])
assert.Equal(t, "true", services["app"].Deploy.Labels["coop-cloud.backupbot.enabled"])
}
+9 -9
View File
@@ -3047,7 +3047,7 @@ msgstr ""
msgid "generated secrets %s shown again, please take note of them %s"
msgstr ""
#: ./pkg/app/compose.go:63
#: ./pkg/app/compose.go:105
#, c-format
msgid "get label '%s'"
msgstr ""
@@ -3695,7 +3695,7 @@ msgstr ""
msgid "no %s exists, skipping reading gitignore paths"
msgstr ""
#: ./pkg/app/compose.go:69
#: ./pkg/app/compose.go:111
#, c-format
msgid "no %s label found for %s"
msgstr ""
@@ -4746,24 +4746,24 @@ msgstr ""
msgid "set 'main' as the default branch"
msgstr ""
#: ./pkg/app/compose.go:30
#: ./pkg/app/compose.go:72
#, c-format
msgid "set label 'coop-cloud.%s.chaos' to %v for %s"
msgstr ""
#: ./pkg/app/compose.go:41
#: ./pkg/app/compose.go:83
#, c-format
msgid "set label 'coop-cloud.%s.chaos-version' to %v for %s"
msgstr ""
#: ./pkg/app/compose.go:51
#: ./pkg/app/compose.go:93
#, c-format
msgid "set label 'coop-cloud.%s.version' to %v for %s"
msgstr ""
#: ./pkg/app/compose.go:18
#: ./pkg/app/compose.go:61
#, c-format
msgid "set recipe label 'coop-cloud.%s.recipe' to %s for %s"
msgid "set recipe labels 'coop-cloud.%s.recipe' and 'coop-cloud.recipe' to %s for %s"
msgstr ""
#: ./pkg/git/init.go:60
@@ -5064,7 +5064,7 @@ msgstr ""
msgid "timed out on undeploy (timeout=%v sec)"
msgstr ""
#: ./pkg/app/compose.go:80
#: ./pkg/app/compose.go:122
#, c-format
msgid "timeout label: %s"
msgstr ""
@@ -5172,7 +5172,7 @@ msgstr ""
msgid "unable to continue, input required for initial version"
msgstr ""
#: ./pkg/app/compose.go:85
#: ./pkg/app/compose.go:127
#, c-format
msgid "unable to convert timeout label %s to int: %s"
msgstr ""