feat(apis): add MinecraftServer CRD types (v1alpha1)
Kubernetes API types for the MinecraftServer custom resource, the lifecycle source of truth (spec §1). Leaf package with no internal dependencies.
This commit is contained in:
3 files changed
+428
No files matched your search
@@ -0,0 +1,36 @@
|
||||
// Package v1alpha1 contains the felis.lolicon.best/v1alpha1 API group, whose
|
||||
// MinecraftServer kind is the lifecycle source-of-truth for Felis (spec §1, §4).
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
// GroupName is the hardcoded software-identity API group (spec §2, §4).
|
||||
GroupName = "felis.lolicon.best"
|
||||
// Version is the API version served by this package.
|
||||
Version = "v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion is group version used to register these objects.
|
||||
GroupVersion = schema.GroupVersion{Group: GroupName, Version: Version}
|
||||
|
||||
// SchemeBuilder registers the API types into a runtime.Scheme.
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
|
||||
// AddToScheme adds the types in this group-version to the given scheme.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(GroupVersion,
|
||||
&MinecraftServer{},
|
||||
&MinecraftServerList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, GroupVersion)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Label and annotation keys owned by the operator (spec §4, §5). All keys are
|
||||
// scoped under the software-identity group so they never collide with the
|
||||
// deployment domain.
|
||||
const (
|
||||
// LabelServer marks every operator-managed object with its owning
|
||||
// MinecraftServer name (used as the StatefulSet/Service/PVC selector).
|
||||
LabelServer = GroupName + "/server"
|
||||
// LabelManagedBy marks objects reconciled by felis-operator.
|
||||
LabelManagedBy = GroupName + "/managed-by"
|
||||
// LabelComponent distinguishes the workload role (server, rcon, ...).
|
||||
LabelComponent = GroupName + "/component"
|
||||
)
|
||||
|
||||
// DesiredState is the operator-facing intent toggle (spec §4 spec.desiredState).
|
||||
type DesiredState string
|
||||
|
||||
const (
|
||||
// DesiredRunning asks the operator to bring the server up.
|
||||
DesiredRunning DesiredState = "Running"
|
||||
// DesiredStopped asks the operator to scale the server down to zero.
|
||||
DesiredStopped DesiredState = "Stopped"
|
||||
)
|
||||
|
||||
// Phase is the observed lifecycle phase (spec §4 status.phase).
|
||||
type Phase string
|
||||
|
||||
const (
|
||||
PhaseUnknown Phase = "Unknown"
|
||||
PhaseStopped Phase = "Stopped"
|
||||
PhaseStarting Phase = "Starting"
|
||||
PhaseRunning Phase = "Running"
|
||||
PhaseStopping Phase = "Stopping"
|
||||
PhaseFailed Phase = "Failed"
|
||||
)
|
||||
|
||||
// AutostartPolicy controls who may wake a stopped server (spec §4, §8). It only
|
||||
// has meaning when the Velocity proxy runs with online-mode=true.
|
||||
type AutostartPolicy string
|
||||
|
||||
const (
|
||||
// AutostartPublic lets any authenticated player wake the server.
|
||||
AutostartPublic AutostartPolicy = "public"
|
||||
// AutostartAllowlist restricts waking to entries in server_allowlist.
|
||||
AutostartAllowlist AutostartPolicy = "allowlist"
|
||||
// AutostartOwnerOnly restricts waking to the claimed owner.
|
||||
AutostartOwnerOnly AutostartPolicy = "ownerOnly"
|
||||
)
|
||||
|
||||
// EndpointMode describes how the proxy reaches a Running server (spec §4
|
||||
// status.endpoint.mode).
|
||||
type EndpointMode string
|
||||
|
||||
const (
|
||||
// EndpointDirect means the proxy dials the Service ClusterIP directly.
|
||||
EndpointDirect EndpointMode = "direct"
|
||||
// EndpointFallback means traffic is routed to the configured fallback.
|
||||
EndpointFallback EndpointMode = "fallback"
|
||||
)
|
||||
|
||||
// Condition type strings surfaced on status.conditions (spec §4).
|
||||
const (
|
||||
ConditionReady = "Ready"
|
||||
ConditionRconReached = "RconReached"
|
||||
ConditionProvisioned = "Provisioned"
|
||||
)
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
|
||||
// MinecraftServer is the lifecycle source-of-truth for a single managed
|
||||
// Minecraft server (spec §4). The operator reconciles the StatefulSet, Service
|
||||
// and PVC from this object; readiness is gated exclusively on an RCON probe.
|
||||
type MinecraftServer struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec MinecraftServerSpec `json:"spec,omitempty"`
|
||||
Status MinecraftServerStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// MinecraftServerList is a list of MinecraftServer objects.
|
||||
type MinecraftServerList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []MinecraftServer `json:"items"`
|
||||
}
|
||||
|
||||
// MinecraftServerSpec is the desired state (spec §4 spec.*).
|
||||
type MinecraftServerSpec struct {
|
||||
// Subdomain is the per-server label under the deployment zone. It is the
|
||||
// only routing identity; the operator never hardcodes the parent domain.
|
||||
Subdomain string `json:"subdomain"`
|
||||
// DisplayName is the human-facing name shown in the panel and MOTD.
|
||||
DisplayName string `json:"displayName,omitempty"`
|
||||
// Description is free-form operator/owner notes.
|
||||
Description string `json:"description,omitempty"`
|
||||
// ReaperExempt opts this server out of the world reaper entirely (spec §18).
|
||||
ReaperExempt bool `json:"reaperExempt,omitempty"`
|
||||
|
||||
// DesiredState toggles the server up or down (default Stopped).
|
||||
// +kubebuilder:validation:Enum=Running;Stopped
|
||||
DesiredState DesiredState `json:"desiredState,omitempty"`
|
||||
// AutostartPolicy controls who may wake the server (spec §8).
|
||||
// +kubebuilder:validation:Enum=public;allowlist;ownerOnly
|
||||
AutostartPolicy AutostartPolicy `json:"autostartPolicy,omitempty"`
|
||||
// FallbackServer is the Velocity server name traffic routes to while this
|
||||
// server is stopped or starting.
|
||||
FallbackServer string `json:"fallbackServer,omitempty"`
|
||||
|
||||
// Image is the fully-qualified container image (loader-agnostic).
|
||||
Image string `json:"image"`
|
||||
// Jar is the server jar path/name inside the image, if the entrypoint
|
||||
// needs it explicitly.
|
||||
Jar string `json:"jar,omitempty"`
|
||||
// JavaMemory is the heap sizing passed as -Xmx/-Xms (e.g. "4G").
|
||||
JavaMemory string `json:"javaMemory,omitempty"`
|
||||
// JavaFlags are additional JVM flags (e.g. Aikar's flags).
|
||||
JavaFlags []string `json:"javaFlags,omitempty"`
|
||||
// Args are extra arguments appended after the jar.
|
||||
Args []string `json:"args,omitempty"`
|
||||
// Env are extra environment variables injected into the server container.
|
||||
Env []EnvVar `json:"env,omitempty"`
|
||||
|
||||
// OnlineMode mirrors server.properties online-mode. Wake/claim semantics
|
||||
// only hold when the Velocity proxy enforces online-mode=true (spec §8).
|
||||
OnlineMode bool `json:"onlineMode,omitempty"`
|
||||
// Motd holds the per-phase MOTD strings surfaced to status pings.
|
||||
Motd MotdSpec `json:"motd,omitempty"`
|
||||
|
||||
// Rcon configures the RCON endpoint the operator probes for readiness and
|
||||
// uses for graceful shutdown (spec §5, §7).
|
||||
Rcon RconSpec `json:"rcon,omitempty"`
|
||||
// Storage configures the world PVC.
|
||||
Storage StorageSpec `json:"storage,omitempty"`
|
||||
// Resources are the container resource requests/limits.
|
||||
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
|
||||
// Lifecycle tunes graceful shutdown (spec §7).
|
||||
Lifecycle LifecycleSpec `json:"lifecycle,omitempty"`
|
||||
// Startup bounds how long Starting may last before Failed (spec §5).
|
||||
Startup StartupSpec `json:"startup,omitempty"`
|
||||
// Idle configures empty-server auto-stop (spec §8).
|
||||
Idle IdleSpec `json:"idle,omitempty"`
|
||||
}
|
||||
|
||||
// EnvVar is a name/value pair injected into the server container. It is a
|
||||
// deliberately narrow subset of corev1.EnvVar (no valueFrom) so the CRD cannot
|
||||
// be used to exfiltrate arbitrary cluster secrets.
|
||||
type EnvVar struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// MotdSpec holds per-phase MOTD strings (spec §4 spec.motd).
|
||||
type MotdSpec struct {
|
||||
Running string `json:"running,omitempty"`
|
||||
Stopped string `json:"stopped,omitempty"`
|
||||
Starting string `json:"starting,omitempty"`
|
||||
Failed string `json:"failed,omitempty"`
|
||||
}
|
||||
|
||||
// RconSpec configures RCON (spec §4 spec.rcon).
|
||||
type RconSpec struct {
|
||||
// Enabled must be true for readiness probing and graceful shutdown.
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
// Port is the RCON TCP port (default 25575).
|
||||
Port int32 `json:"port,omitempty"`
|
||||
// SecretRef points at the Secret holding the RCON password.
|
||||
SecretRef SecretKeyRef `json:"secretRef,omitempty"`
|
||||
}
|
||||
|
||||
// SecretKeyRef references a single key within a Secret in the same namespace.
|
||||
type SecretKeyRef struct {
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// StorageSpec configures the world PVC (spec §4 spec.storage).
|
||||
type StorageSpec struct {
|
||||
// Size is the requested PVC capacity (e.g. "10Gi").
|
||||
Size string `json:"size,omitempty"`
|
||||
// StorageClassName selects the StorageClass; empty uses the default.
|
||||
StorageClassName string `json:"storageClassName,omitempty"`
|
||||
// RetainOnDelete keeps the PVC when the MinecraftServer is deleted.
|
||||
RetainOnDelete bool `json:"retainOnDelete,omitempty"`
|
||||
}
|
||||
|
||||
// LifecycleSpec tunes graceful shutdown (spec §7). The operator injects a
|
||||
// preStop RCON "save-all flush; stop" hook when PreStopSaveAndStop is set.
|
||||
type LifecycleSpec struct {
|
||||
// TerminationGracePeriodSeconds is the pod grace period (default 300).
|
||||
TerminationGracePeriodSeconds int64 `json:"terminationGracePeriodSeconds,omitempty"`
|
||||
// PreStopSaveAndStop enables the operator-injected RCON save+stop preStop.
|
||||
PreStopSaveAndStop bool `json:"preStopSaveAndStop,omitempty"`
|
||||
}
|
||||
|
||||
// StartupSpec bounds the Starting phase (spec §5).
|
||||
type StartupSpec struct {
|
||||
// TimeoutSeconds is the overall budget before the server is marked Failed.
|
||||
TimeoutSeconds int32 `json:"timeoutSeconds,omitempty"`
|
||||
// ReadinessTimeoutSeconds is the budget for the first successful RCON probe.
|
||||
ReadinessTimeoutSeconds int32 `json:"readinessTimeoutSeconds,omitempty"`
|
||||
}
|
||||
|
||||
// IdleSpec configures empty-server auto-stop (spec §8).
|
||||
type IdleSpec struct {
|
||||
// AutoStopEnabled turns on idle auto-stop.
|
||||
AutoStopEnabled bool `json:"autoStopEnabled,omitempty"`
|
||||
// EmptySecondsBeforeStop is how long the server may sit empty before the
|
||||
// operator scales it down.
|
||||
EmptySecondsBeforeStop int32 `json:"emptySecondsBeforeStop,omitempty"`
|
||||
}
|
||||
|
||||
// MinecraftServerStatus is the observed state (spec §4 status.*).
|
||||
type MinecraftServerStatus struct {
|
||||
// Phase is the coarse lifecycle phase.
|
||||
Phase Phase `json:"phase,omitempty"`
|
||||
// Ready is true only after a successful RCON probe (loader-agnostic; a
|
||||
// status ping is never sufficient — spec §5).
|
||||
Ready bool `json:"ready,omitempty"`
|
||||
// Endpoint is where the proxy should route traffic.
|
||||
Endpoint EndpointStatus `json:"endpoint,omitempty"`
|
||||
// Players is the last observed player count.
|
||||
Players PlayersStatus `json:"players,omitempty"`
|
||||
// LiveMotd is the MOTD currently advertised for the active phase.
|
||||
LiveMotd string `json:"liveMotd,omitempty"`
|
||||
// ReadySignalAt is when the first RCON probe succeeded.
|
||||
ReadySignalAt *metav1.Time `json:"readySignalAt,omitempty"`
|
||||
// ObservedGeneration is the spec generation this status reflects.
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||
// Conditions are the standard metav1 conditions (Ready, RconReached, ...).
|
||||
// +patchMergeKey=type
|
||||
// +patchStrategy=merge
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
|
||||
}
|
||||
|
||||
// EndpointStatus is the resolved routing target (spec §4 status.endpoint).
|
||||
type EndpointStatus struct {
|
||||
// Mode is "direct" or "fallback".
|
||||
Mode EndpointMode `json:"mode,omitempty"`
|
||||
// Address is the host:port the proxy should dial.
|
||||
Address string `json:"address,omitempty"`
|
||||
}
|
||||
|
||||
// PlayersStatus is the last observed player count (spec §4 status.players).
|
||||
type PlayersStatus struct {
|
||||
Online int32 `json:"online"`
|
||||
Max int32 `json:"max"`
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
//go:build !ignore_autogenerated
|
||||
|
||||
// Code in this file is hand-written to the same contract controller-gen would
|
||||
// emit (DeepCopyInto/DeepCopy/DeepCopyObject) so the package needs no codegen
|
||||
// toolchain to compile and satisfy runtime.Object.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto copies the receiver into out.
|
||||
func (in *MinecraftServer) DeepCopyInto(out *MinecraftServer) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy returns a deep copy of the receiver.
|
||||
func (in *MinecraftServer) DeepCopy() *MinecraftServer {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MinecraftServer)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject implements runtime.Object.
|
||||
func (in *MinecraftServer) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto copies the receiver into out.
|
||||
func (in *MinecraftServerList) DeepCopyInto(out *MinecraftServerList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
l := make([]MinecraftServer, len(in.Items))
|
||||
for i := range in.Items {
|
||||
in.Items[i].DeepCopyInto(&l[i])
|
||||
}
|
||||
out.Items = l
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy returns a deep copy of the receiver.
|
||||
func (in *MinecraftServerList) DeepCopy() *MinecraftServerList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MinecraftServerList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject implements runtime.Object.
|
||||
func (in *MinecraftServerList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto copies the receiver into out.
|
||||
func (in *MinecraftServerSpec) DeepCopyInto(out *MinecraftServerSpec) {
|
||||
*out = *in
|
||||
if in.JavaFlags != nil {
|
||||
l := make([]string, len(in.JavaFlags))
|
||||
copy(l, in.JavaFlags)
|
||||
out.JavaFlags = l
|
||||
}
|
||||
if in.Args != nil {
|
||||
l := make([]string, len(in.Args))
|
||||
copy(l, in.Args)
|
||||
out.Args = l
|
||||
}
|
||||
if in.Env != nil {
|
||||
l := make([]EnvVar, len(in.Env))
|
||||
copy(l, in.Env)
|
||||
out.Env = l
|
||||
}
|
||||
out.Motd = in.Motd
|
||||
out.Rcon = in.Rcon
|
||||
out.Storage = in.Storage
|
||||
in.Resources.DeepCopyInto(&out.Resources)
|
||||
out.Lifecycle = in.Lifecycle
|
||||
out.Startup = in.Startup
|
||||
out.Idle = in.Idle
|
||||
}
|
||||
|
||||
// DeepCopy returns a deep copy of the receiver.
|
||||
func (in *MinecraftServerSpec) DeepCopy() *MinecraftServerSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MinecraftServerSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto copies the receiver into out.
|
||||
func (in *MinecraftServerStatus) DeepCopyInto(out *MinecraftServerStatus) {
|
||||
*out = *in
|
||||
out.Endpoint = in.Endpoint
|
||||
out.Players = in.Players
|
||||
if in.ReadySignalAt != nil {
|
||||
out.ReadySignalAt = in.ReadySignalAt.DeepCopy()
|
||||
}
|
||||
if in.Conditions != nil {
|
||||
l := make([]metav1.Condition, len(in.Conditions))
|
||||
for i := range in.Conditions {
|
||||
in.Conditions[i].DeepCopyInto(&l[i])
|
||||
}
|
||||
out.Conditions = l
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy returns a deep copy of the receiver.
|
||||
func (in *MinecraftServerStatus) DeepCopy() *MinecraftServerStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MinecraftServerStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
Reference in new issue
Block a user