Merge nucleic/mellow-dewy-falcon-rjhr into main
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import Foundation
|
||||
|
||||
/// The set of labels this host's runners advertise, used to decide whether a
|
||||
/// queued Gitea job is ours to pick up.
|
||||
///
|
||||
/// ## Bare names only
|
||||
///
|
||||
/// Gitea's label syntax at *registration* time is `name:schema` (for example
|
||||
/// `macos-arm64:host`), where the schema defaults to `host` when omitted. The
|
||||
/// schema is a runner-side execution hint — it tells `gitea-runner` to run the
|
||||
/// job directly on the machine instead of inside a container. **The server only
|
||||
/// ever stores and reports the bare name.** A workflow's `runs-on:` value, and
|
||||
/// therefore the `labels` array on a queued job, likewise contains bare names.
|
||||
///
|
||||
/// So: pass `macos-arm64:host` to `gitea-runner register --labels`, but match
|
||||
/// against `macos-arm64` here. Matching is case-sensitive, because Gitea's own
|
||||
/// comparison is.
|
||||
///
|
||||
/// - Warning: If the guest's `.runner`/`config.yaml` sets `runner.labels`, it
|
||||
/// silently overrides whatever `--labels` was passed at registration. The
|
||||
/// guest must therefore never ship a config file containing labels.
|
||||
public struct LabelSet: Sendable, Equatable, Hashable {
|
||||
/// The bare label names this host serves, e.g. `["macos-arm64", "macos"]`.
|
||||
public let names: Set<String>
|
||||
|
||||
/// Creates a label set from bare names.
|
||||
///
|
||||
/// Any `:schema` suffix present in `names` is stripped, so it is safe to
|
||||
/// hand this the same array that is written into the config file.
|
||||
///
|
||||
/// - Parameter names: Label names, with or without a `:schema` suffix.
|
||||
public init(_ names: [String]) {
|
||||
self.names = Set(
|
||||
names
|
||||
.map(LabelSet.bareName)
|
||||
.filter { !$0.isEmpty }
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether a queued job's `labels` array can be satisfied by this host.
|
||||
///
|
||||
/// Returns `true` if and only if `jobLabels` is non-empty *and* every entry
|
||||
/// is a member of ``names``. An empty job label array is treated as "no
|
||||
/// declared requirement" and is deliberately **not** matched — a job that
|
||||
/// asks for nothing must not be scheduled onto a scarce macOS VM.
|
||||
///
|
||||
/// The job side is put through ``bareName(_:)`` too. The server normally
|
||||
/// stores bare names, so this changes nothing in the common case — but a
|
||||
/// workflow that writes `runs-on: [macos-arm64:host]` would otherwise never
|
||||
/// match anything and its job would be skipped with no log line at all.
|
||||
///
|
||||
/// - Parameter jobLabels: The `labels` array from a `WorkflowJob`.
|
||||
/// - Returns: `true` when this host should boot a VM for the job.
|
||||
public func matches(jobLabels: [String]) -> Bool {
|
||||
guard !jobLabels.isEmpty else { return false }
|
||||
let wanted = Set(jobLabels.map(LabelSet.bareName).filter { !$0.isEmpty })
|
||||
guard !wanted.isEmpty else { return false }
|
||||
return wanted.isSubset(of: names)
|
||||
}
|
||||
|
||||
/// The value to pass to `gitea-runner register --labels`, i.e. each bare
|
||||
/// name suffixed with the given schema and joined by commas.
|
||||
///
|
||||
/// - Parameter schema: The execution schema; `host` for a bare-metal guest.
|
||||
/// - Returns: For example `"macos-arm64:host,macos:host"`.
|
||||
public func registrationArgument(schema: String = "host") -> String {
|
||||
// Sorted so the argument is stable across process runs — a `Set` has no
|
||||
// inherent order, and an unstable registration argument would make the
|
||||
// guest command line (and its logs) needlessly non-reproducible.
|
||||
names.sorted()
|
||||
.map { schema.isEmpty ? $0 : "\($0):\(schema)" }
|
||||
.joined(separator: ",")
|
||||
}
|
||||
|
||||
/// Strips an optional `:schema` suffix from a single label token.
|
||||
///
|
||||
/// - Parameter label: A label such as `macos-arm64:host` or `macos-arm64`.
|
||||
/// - Returns: The bare name.
|
||||
public static func bareName(_ label: String) -> String {
|
||||
let trimmed = label.trimmingCharacters(in: .whitespaces)
|
||||
guard let colon = trimmed.firstIndex(of: ":") else { return trimmed }
|
||||
return String(trimmed[trimmed.startIndex..<colon])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user