initial commit
This commit is contained in:
commit
a4ca8d02f8
8 changed files with 3993 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
3719
Cargo.lock
generated
Normal file
3719
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
19
Cargo.toml
Normal file
19
Cargo.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "reggie"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-web = "4.13.0"
|
||||||
|
k8s-openapi = { version = "0.27.1", features = ["v1_33"] }
|
||||||
|
kube = { version = "3.1.0", features = ["runtime", "derive"] }
|
||||||
|
log = "0.4.29"
|
||||||
|
macro_rules_attribute = "0.2.2"
|
||||||
|
schemars = "1.2.1"
|
||||||
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
serde_json = "1.0.149"
|
||||||
|
simplelog = "0.12.2"
|
||||||
|
sqlx = "0.8.6"
|
||||||
|
tokio = { version = "1.52.2", features = ["macros", "rt-multi-thread"] }
|
||||||
|
utoipa = "5.5.0"
|
||||||
|
yaml_serde = "0.10.4"
|
||||||
25
kube/GameServers.spfn.net.yaml
Normal file
25
kube/GameServers.spfn.net.yaml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: GameServers.spfn.net
|
||||||
|
spec:
|
||||||
|
group: spfn.net
|
||||||
|
names:
|
||||||
|
kind: GameServer
|
||||||
|
shortNames:
|
||||||
|
- gserv
|
||||||
|
versions:
|
||||||
|
- name: v1
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
spec: # There is only one (required) field named "replicas" specifying how many pods are created by the Operator
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
replicas:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
required: ["replicas"]
|
||||||
51
src/crds.rs
Normal file
51
src/crds.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition;
|
||||||
|
use kube::{
|
||||||
|
Api, Client, CustomResource, CustomResourceExt,
|
||||||
|
api::{Patch, PatchParams},
|
||||||
|
};
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
|
use crate::kube::SELF_MANAGER_NAME;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema, ToSchema)]
|
||||||
|
enum GameServerIdent {
|
||||||
|
WiiU { game_server_id: String },
|
||||||
|
N3DS { title_id: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(CustomResource, Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema, ToSchema)]
|
||||||
|
#[kube(
|
||||||
|
group = "spbr.net",
|
||||||
|
version = "v1",
|
||||||
|
kind = "GameServer",
|
||||||
|
derive = "PartialEq"
|
||||||
|
)]
|
||||||
|
pub struct GameServerSpec {
|
||||||
|
game_name: String,
|
||||||
|
tag: String,
|
||||||
|
game_server_idents: Vec<GameServerIdent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameServer {
|
||||||
|
fn get_auth_daemonset(&self, client: &kube::Client) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn apply_app_crds(client: &Client) -> Result<(), kube::Error> {
|
||||||
|
let crds: Api<CustomResourceDefinition> = Api::all(client.clone());
|
||||||
|
macro_rules! sync_crd {
|
||||||
|
($($t:tt)*) => {
|
||||||
|
crds.patch(
|
||||||
|
$($t)*::crd_name(),
|
||||||
|
&PatchParams::apply(SELF_MANAGER_NAME),
|
||||||
|
&Patch::Apply($($t)*::crd()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_crd!(GameServer);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
39
src/kube.rs
Normal file
39
src/kube.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use kube::{
|
||||||
|
Api, Client,
|
||||||
|
runtime::{Controller, controller::Action, watcher::Config},
|
||||||
|
};
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
use crate::crds::{GameServer, apply_app_crds};
|
||||||
|
|
||||||
|
pub const SELF_MANAGER_NAME: &str = "reggie-manager";
|
||||||
|
|
||||||
|
struct ControllerContext {
|
||||||
|
client: kube::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start_kubernetes_operator(client: Client) {
|
||||||
|
apply_app_crds(&client).await.expect("failed to apply crds");
|
||||||
|
|
||||||
|
sleep(Duration::from_secs_f32(5.0)).await;
|
||||||
|
|
||||||
|
let gs_api: Api<GameServer> = Api::all(client.clone());
|
||||||
|
|
||||||
|
let controller_context = ControllerContext {
|
||||||
|
client: client.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
let controller_context = Arc::new(controller_context);
|
||||||
|
|
||||||
|
Controller::new(gs_api, Config::default())
|
||||||
|
.run(
|
||||||
|
|o, ctx| async { Ok(()) },
|
||||||
|
|o, e, ctx| async { Ok(Action::await_change()) },
|
||||||
|
controller_context.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
*/
|
||||||
|
}
|
||||||
26
src/main.rs
Normal file
26
src/main.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
use ::kube::Client;
|
||||||
|
use simplelog::{Config, TerminalMode};
|
||||||
|
use tokio::{join, main};
|
||||||
|
|
||||||
|
mod crds;
|
||||||
|
mod kube;
|
||||||
|
mod web;
|
||||||
|
|
||||||
|
#[main]
|
||||||
|
async fn main() {
|
||||||
|
simplelog::TermLogger::init(
|
||||||
|
log::LevelFilter::Debug,
|
||||||
|
Config::default(),
|
||||||
|
TerminalMode::Mixed,
|
||||||
|
simplelog::ColorChoice::Auto,
|
||||||
|
)
|
||||||
|
.expect("unable to set up logger");
|
||||||
|
let client: Client = Client::try_default()
|
||||||
|
.await
|
||||||
|
.expect("Expected valid KUBECONFIG environment variable");
|
||||||
|
|
||||||
|
join![
|
||||||
|
web::start_webserver(client.clone()),
|
||||||
|
kube::start_kubernetes_operator(client.clone())
|
||||||
|
];
|
||||||
|
}
|
||||||
113
src/web.rs
Normal file
113
src/web.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
use std::{any::Any, clone, env, fmt::Display, sync::LazyLock};
|
||||||
|
|
||||||
|
use actix_web::{
|
||||||
|
App, HttpResponse, HttpResponseBuilder, HttpServer, Responder, ResponseError, Result, get,
|
||||||
|
http::{self, StatusCode},
|
||||||
|
web,
|
||||||
|
};
|
||||||
|
use kube::{Api, Resource, api::ListParams, core::object::HasSpec};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::crds::GameServer;
|
||||||
|
|
||||||
|
static SELF_ENDPOINT: LazyLock<String> =
|
||||||
|
LazyLock::new(|| env::var("SELF_ENDPOINT").expect("SELF_ENDPOINT is not specified"));
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
struct ResourceNameDescriptor {
|
||||||
|
name: String,
|
||||||
|
resource: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct KubeError(kube::Error);
|
||||||
|
|
||||||
|
impl Display for KubeError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseError for KubeError {
|
||||||
|
fn status_code(&self) -> http::StatusCode {
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_response(&self) -> HttpResponse<actix_web::body::BoxBody> {
|
||||||
|
HttpResponseBuilder::new(self.status_code()).body(self.0.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct HashParam {
|
||||||
|
hash: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/gameserver/discover")]
|
||||||
|
async fn gameservers(kube_client: web::Data<kube::Client>) -> Result<impl Responder> {
|
||||||
|
let servers: Api<GameServer> = Api::all(kube_client.get_ref().clone());
|
||||||
|
|
||||||
|
let servers = servers
|
||||||
|
.list_metadata(&ListParams::default())
|
||||||
|
.await
|
||||||
|
.map_err(KubeError)?;
|
||||||
|
|
||||||
|
let list: Vec<_> = servers
|
||||||
|
.into_iter()
|
||||||
|
.map(|v| ResourceNameDescriptor {
|
||||||
|
name: v.metadata.name.clone().unwrap(),
|
||||||
|
resource: format!(
|
||||||
|
"{}/gameserver/{}",
|
||||||
|
&*SELF_ENDPOINT,
|
||||||
|
v.metadata.name.unwrap()
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(web::Json(list))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/gameserver/{game_name}")]
|
||||||
|
async fn get_gameserver(
|
||||||
|
kube_client: web::Data<kube::Client>,
|
||||||
|
path: web::Path<(String)>,
|
||||||
|
) -> Result<impl Responder> {
|
||||||
|
let (game_name) = path.into_inner();
|
||||||
|
let servers: Api<GameServer> = Api::all(kube_client.get_ref().clone());
|
||||||
|
|
||||||
|
let game_server = servers.get(&game_name).await.map_err(KubeError)?;
|
||||||
|
let game_server_spec = game_server.spec();
|
||||||
|
|
||||||
|
Ok(web::Json(game_server_spec.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/gameserver/{game_name}/auth_proxy")]
|
||||||
|
async fn get_gameserver_proxy(
|
||||||
|
kube_client: web::Data<kube::Client>,
|
||||||
|
path: web::Path<String>,
|
||||||
|
param: web::Query<HashParam>,
|
||||||
|
) -> Result<impl Responder> {
|
||||||
|
let game_name = path.into_inner();
|
||||||
|
let hash = param.0.hash.unwrap_or(0);
|
||||||
|
let servers: Api<GameServer> = Api::all(kube_client.get_ref().clone());
|
||||||
|
|
||||||
|
let game_server = servers.get(&game_name).await.map_err(KubeError)?;
|
||||||
|
let game_server_spec = game_server.spec();
|
||||||
|
|
||||||
|
Ok(web::Json(game_server_spec.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start_webserver(kube_client: kube::Client) {
|
||||||
|
HttpServer::new(move || {
|
||||||
|
App::new()
|
||||||
|
.app_data(web::Data::new(kube_client.clone()))
|
||||||
|
.service(gameservers)
|
||||||
|
.service(get_gameserver)
|
||||||
|
.service(get_gameserver_proxy)
|
||||||
|
})
|
||||||
|
.bind(("127.0.0.1", 8080))
|
||||||
|
.expect("failed to start webserver")
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
.expect("error occurred whilest running webserver");
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue