// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT // Package labels provides utilities for parsing and managing runner labels. package labels import ( "fmt" "strings" ) // Label scheme constants define the execution environments. const ( SchemeHost = "host" SchemeDocker = "docker" ) // Label represents a parsed runner label with name, schema, and optional argument. type Label struct { Name string Schema string Arg string } // Parse parses a label string in the format "name:schema:arg" and returns a Label. func Parse(str string) (*Label, error) { splits := strings.SplitN(str, ":", 3) label := &Label{ Name: splits[0], Schema: "host", Arg: "", } if len(splits) >= 2 { label.Schema = splits[1] } if len(splits) >= 3 { label.Arg = splits[2] } if label.Schema != SchemeHost && label.Schema != SchemeDocker { return nil, fmt.Errorf("unsupported schema: %s", label.Schema) } return label, nil } // Labels is a slice of Label pointers. type Labels []*Label // RequireDocker returns true if any label uses the docker schema. func (l Labels) RequireDocker() bool { for _, label := range l { if label.Schema == SchemeDocker { return true } } return false } // PickPlatform selects the appropriate platform based on the runsOn requirements. // Returns empty string if no matching label is found, which will cause the job to fail. // If runs-on includes a schema (e.g., "linux:host" or "linux:docker"), it must match // the runner's configured schema for that label. func (l Labels) PickPlatform(runsOn []string) string { // Build maps for both platform values and schemas platforms := make(map[string]string, len(l)) schemas := make(map[string]string, len(l)) for _, label := range l { switch label.Schema { case SchemeDocker: platforms[label.Name] = strings.TrimPrefix(label.Arg, "//") schemas[label.Name] = SchemeDocker case SchemeHost: platforms[label.Name] = "-self-hosted" schemas[label.Name] = SchemeHost default: continue } } for _, v := range runsOn { name := v requestedSchema := "" // Parse schema if present (e.g., "germany-linux:host" -> name="germany-linux", schema="host") if idx := strings.Index(v, ":"); idx != -1 { name = v[:idx] requestedSchema = v[idx+1:] // Handle docker:// prefix if strings.HasPrefix(requestedSchema, "docker") { requestedSchema = SchemeDocker } } if platform, ok := platforms[name]; ok { // If schema was specified, validate it matches if requestedSchema != "" && requestedSchema != schemas[name] { // Schema mismatch - workflow asked for different mode than runner provides continue } return platform } } // No matching label found return "" } // Names returns the names of all labels. func (l Labels) Names() []string { names := make([]string, 0, len(l)) for _, label := range l { names = append(names, label.Name) } return names } // ToStrings converts labels back to their string representation. func (l Labels) ToStrings() []string { ls := make([]string, 0, len(l)) for _, label := range l { lbl := label.Name if label.Schema != "" { lbl += ":" + label.Schema if label.Arg != "" { lbl += ":" + label.Arg } } ls = append(ls, lbl) } return ls }