package discovery import ( "fmt" "strings" ) // ComposeLabels captures the four Compose-managed labels we need // to drive a `docker compose pull`/`up -d` against the right stack. type ComposeLabels struct { Project string Service string WorkingDir string ConfigFiles []string } const ( labelProject = "com.docker.compose.project" labelService = "com.docker.compose.service" labelWorkingDir = "com.docker.compose.project.working_dir" labelConfigFiles = "com.docker.compose.project.config_files" ) // ParseComposeLabels extracts the Compose labels we need. Returns an // error naming the missing label if any required field is absent — // in practice this should only happen if the container was not // started by Compose. func ParseComposeLabels(labels map[string]string) (ComposeLabels, error) { get := func(key string) (string, error) { v, ok := labels[key] if !ok || v == "" { return "", fmt.Errorf("missing required label: %s", key) } return v, nil } project, err := get(labelProject) if err != nil { return ComposeLabels{}, err } service, err := get(labelService) if err != nil { return ComposeLabels{}, err } workingDir, err := get(labelWorkingDir) if err != nil { return ComposeLabels{}, err } configFilesRaw, err := get(labelConfigFiles) if err != nil { return ComposeLabels{}, err } files := strings.Split(configFilesRaw, ",") for i, f := range files { files[i] = strings.TrimSpace(f) } return ComposeLabels{ Project: project, Service: service, WorkingDir: workingDir, ConfigFiles: files, }, nil } // HasOptIn reports whether the labels include the opt-in marker with // value "true" (exact, case-sensitive — anything else is excluded). func HasOptIn(labels map[string]string, key string) bool { return labels[key] == "true" }