Files
abra/pkg/app/compose.go
coop-cloud 9aa5cf6df3
continuous-integration/drone/push Build is passing
set recipe label for all containers of an app
2026-09-16 16:34:29 +02:00

133 lines
4.8 KiB
Go

package app
import (
"errors"
"fmt"
"strconv"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
composetypes "github.com/docker/cli/cli/compose/types"
)
// 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) {
for _, service := range compose.Services {
if service.Name == "app" {
log.Debug(i18n.G("set label 'coop-cloud.%s.chaos' to %v for %s", stackName, chaos, stackName))
labelKey := fmt.Sprintf("coop-cloud.%s.chaos", stackName)
service.Deploy.Labels[labelKey] = strconv.FormatBool(chaos)
}
}
}
// SetChaosVersionLabel adds the label 'coop-cloud.${STACK_NAME}.chaos-version=$(GIT_COMMIT)' to the app container
func SetChaosVersionLabel(compose *composetypes.Config, stackName string, chaosVersion string) {
for _, service := range compose.Services {
if service.Name == "app" {
log.Debug(i18n.G("set label 'coop-cloud.%s.chaos-version' to %v for %s", stackName, chaosVersion, stackName))
labelKey := fmt.Sprintf("coop-cloud.%s.chaos-version", stackName)
service.Deploy.Labels[labelKey] = chaosVersion
}
}
}
func SetVersionLabel(compose *composetypes.Config, stackName string, version string) {
for _, service := range compose.Services {
if service.Name == "app" {
log.Debug(i18n.G("set label 'coop-cloud.%s.version' to %v for %s", stackName, version, stackName))
labelKey := fmt.Sprintf("coop-cloud.%s.version", stackName)
service.Deploy.Labels[labelKey] = version
}
}
}
// GetLabel reads docker labels in the format of "coop-cloud.${STACK_NAME}.${LABEL}" from the local compose files
func GetLabel(compose *composetypes.Config, stackName string, label string) string {
for _, service := range compose.Services {
if service.Name == "app" {
labelKey := fmt.Sprintf("coop-cloud.%s.%s", stackName, label)
log.Debug(i18n.G("get label '%s'", labelKey))
if labelValue, ok := service.Deploy.Labels[labelKey]; ok {
return labelValue
}
}
}
log.Debug(i18n.G("no %s label found for %s", label, stackName))
return ""
}
// GetTimeoutFromLabel reads the timeout value from docker label
// `coop-cloud.${STACK_NAME}.timeout=...` if present. A value is present if the
// operator uses a `TIMEOUT=...` in their app env.
func GetTimeoutFromLabel(compose *composetypes.Config, stackName string) (int, error) {
var timeout int
if timeoutLabel := GetLabel(compose, stackName, "timeout"); timeoutLabel != "" {
log.Debug(i18n.G("timeout label: %s", timeoutLabel))
var err error
timeout, err = strconv.Atoi(timeoutLabel)
if err != nil {
return timeout, errors.New(i18n.G("unable to convert timeout label %s to int: %s", timeoutLabel, err))
}
}
return timeout, nil
}