defmodule GitGud.Workflows.Runner do
use Ecto.Schema
import Ecto.Changeset
alias GitGud.Accounts.User
alias GitGud.Organizations.Organization
alias GitGud.Repositories.Repository
@valid_statuses ~w(active offline disabled)
schema "runners" do
field :uuid, Ecto.UUID
field :name, :string
field :version, :string
# Operator-assigned. Source of truth for `claim_next_job/1`
# routing. Set via the offline-mint UI / mix task and never
# touched by the runner's `Declare` RPC.
field :labels, :map, default: %{"labels" => ["ubuntu-latest"]}
# Runner-self-reported. The runner re-asserts these every
# `Declare`; we record but don't route on them. Surfaced in
# the UI for operator visibility.
field :agent_labels, :map, default: %{"labels" => []}
field :token_hash, :binary
field :last_seen_at, :utc_datetime
field :status, :string, default: "active"
belongs_to :owner, User
belongs_to :repository, Repository
belongs_to :organization, Organization
timestamps(type: :utc_datetime)
end
def changeset(runner, attrs) do
runner
|> cast(attrs, [
:name,
:version,
:labels,
:agent_labels,
:status,
:repository_id,
:organization_id,
:uuid
])
|> validate_required([:name])
|> validate_inclusion(:status, @valid_statuses)
|> validate_length(:name, max: 100)
|> check_constraint(:base,
name: :runners_scope_xor,
message: "runner may be scoped to either a repository or an organization, not both"
)
end
@doc "Human label for the runner's scope."
def scope_label(%__MODULE__{repository_id: r}) when not is_nil(r), do: "repo"
def scope_label(%__MODULE__{organization_id: o}) when not is_nil(o), do: "org"
def scope_label(_), do: "global"
def valid_statuses, do: @valid_statuses
end
1.8 KiB · text
5af55d9