Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support providing external servers #631

Merged
merged 4 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions kube/helm/templates/crds/shulkermc.io_minecraftclusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,28 @@ spec:
properties:
spec:
properties:
externalServers:
description: List of servers that should be registered on the proxies that are not managed by Shulker
items:
properties:
address:
description: Address of the server, may contain a port after a colon
type: string
name:
description: Name of the server, as the proxies will register it. Allowed names only are lowercased, dash-separated alphanumerical string
pattern: ^[a-z0-9\-]+$
type: string
tags:
description: Tags associated to the server
items:
type: string
type: array
required:
- address
- name
type: object
nullable: true
type: array
networkAdmins:
description: List of player UUIDs that are automatically promoted as network administrators, which are granted all the permissions by default on all the proxies and servers
items:
Expand Down
22 changes: 22 additions & 0 deletions packages/shulker-crds/src/v1alpha1/minecraft_cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ pub struct MinecraftClusterSpec {
/// for the different Shulker components
#[serde(skip_serializing_if = "Option::is_none")]
pub redis: Option<MinecraftClusterRedisSpec>,

/// List of servers that should be registered on the proxies
/// that are not managed by Shulker
#[serde(skip_serializing_if = "Option::is_none")]
pub external_servers: Option<Vec<MinecraftClusterExternalServerSpec>>,
}

#[derive(Deserialize, Serialize, Clone, Debug, Default, JsonSchema)]
Expand Down Expand Up @@ -72,6 +77,23 @@ impl MinecraftClusterRedisProvidedSpec {
}
}

#[derive(Deserialize, Serialize, Clone, Debug, Default, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct MinecraftClusterExternalServerSpec {
/// Name of the server, as the proxies will register it.
/// Allowed names only are lowercased, dash-separated
/// alphanumerical string
#[schemars(regex(pattern = r"^[a-z0-9\-]+$"))]
pub name: String,

/// Address of the server, may contain a port after a colon
pub address: String,

/// Tags associated to the server
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
}

/// The status object of `MinecraftCluster`
#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)]
#[serde(rename_all = "camelCase")]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use std::collections::BTreeMap;

use k8s_openapi::api::core::v1::ConfigMap;
use kube::core::ObjectMeta;
use kube::Api;
use kube::Client;
use kube::ResourceExt;

use shulker_crds::v1alpha1::minecraft_cluster::MinecraftCluster;
use shulker_kube_utils::reconcilers::builder::ResourceBuilder;

use super::MinecraftClusterReconciler;

pub struct ExternalServersConfigMapBuilder {
client: Client,
}

#[async_trait::async_trait]
impl<'a> ResourceBuilder<'a> for ExternalServersConfigMapBuilder {
type OwnerType = MinecraftCluster;
type ResourceType = ConfigMap;
type Context = ();

fn name(cluster: &Self::OwnerType) -> String {
format!("{}-external-servers", cluster.name_any())
}

fn api(&self, cluster: &Self::OwnerType) -> kube::Api<Self::ResourceType> {
Api::namespaced(self.client.clone(), cluster.namespace().as_ref().unwrap())

Check warning on line 29 in packages/shulker-operator/src/reconcilers/minecraft_cluster/external_servers_config_map.rs

View check run for this annotation

Codecov / codecov/patch

packages/shulker-operator/src/reconcilers/minecraft_cluster/external_servers_config_map.rs#L28-L29

Added lines #L28 - L29 were not covered by tests
}

fn is_needed(&self, cluster: &Self::OwnerType) -> bool {
cluster
.spec
.external_servers

Check warning on line 35 in packages/shulker-operator/src/reconcilers/minecraft_cluster/external_servers_config_map.rs

View check run for this annotation

Codecov / codecov/patch

packages/shulker-operator/src/reconcilers/minecraft_cluster/external_servers_config_map.rs#L32-L35

Added lines #L32 - L35 were not covered by tests
.as_ref()
.map_or(false, |list| !list.is_empty())

Check warning on line 37 in packages/shulker-operator/src/reconcilers/minecraft_cluster/external_servers_config_map.rs

View check run for this annotation

Codecov / codecov/patch

packages/shulker-operator/src/reconcilers/minecraft_cluster/external_servers_config_map.rs#L37

Added line #L37 was not covered by tests
}

async fn build(
&self,
cluster: &Self::OwnerType,
name: &str,
_existing_config_map: Option<&Self::ResourceType>,
_context: Option<Self::Context>,
) -> Result<Self::ResourceType, anyhow::Error> {
let config_map = ConfigMap {
metadata: ObjectMeta {
name: Some(name.to_string()),
namespace: Some(cluster.namespace().unwrap().clone()),
labels: Some(MinecraftClusterReconciler::get_labels(
cluster,
"external-servers".to_string(),
"proxy".to_string(),
)),
..ObjectMeta::default()
},
data: Some(BTreeMap::from([(
"external-servers.yaml".to_string(),
ExternalServersConfigMapBuilder::get_content_from_server_list(cluster),
)])),
..ConfigMap::default()
};

Ok(config_map)
}
}

impl ExternalServersConfigMapBuilder {
pub fn new(client: Client) -> Self {
ExternalServersConfigMapBuilder { client }
}

fn get_content_from_server_list(cluster: &MinecraftCluster) -> String {
serde_yaml::to_string(cluster.spec.external_servers.as_ref().unwrap()).unwrap()
}
}

#[cfg(test)]
mod tests {
use shulker_kube_utils::reconcilers::builder::ResourceBuilder;

use crate::reconcilers::minecraft_cluster::fixtures::{create_client_mock, TEST_CLUSTER};

#[test]
fn name_contains_cluster_name() {
// W
let name = super::ExternalServersConfigMapBuilder::name(&TEST_CLUSTER);

// T
assert_eq!(name, "my-cluster-external-servers");
}

#[tokio::test]
async fn build_snapshot() {
// G
let client = create_client_mock();
let builder = super::ExternalServersConfigMapBuilder::new(client);
let name = super::ExternalServersConfigMapBuilder::name(&TEST_CLUSTER);

// W
let config_map = builder
.build(&TEST_CLUSTER, &name, None, None)
.await
.unwrap();

// T
insta::assert_yaml_snapshot!(config_map);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use http::{Request, Response};
use kube::client::Body;
use kube::{core::ObjectMeta, Client};
use lazy_static::lazy_static;
use shulker_crds::v1alpha1::minecraft_cluster::{MinecraftCluster, MinecraftClusterSpec};
use shulker_crds::v1alpha1::minecraft_cluster::{
MinecraftCluster, MinecraftClusterExternalServerSpec, MinecraftClusterSpec,
};

lazy_static! {
pub static ref TEST_CLUSTER: MinecraftCluster = MinecraftCluster {
Expand All @@ -13,7 +15,12 @@ lazy_static! {
},
spec: MinecraftClusterSpec {
network_admins: None,
redis: None
redis: None,
external_servers: Some(vec![MinecraftClusterExternalServerSpec {
name: "my-external-server".to_string(),
address: "127.0.0.1:25565".to_string(),
tags: vec!["game".to_string()]
}])
},
status: None,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::{collections::BTreeMap, sync::Arc, time::Duration};

use external_servers_config_map::ExternalServersConfigMapBuilder;
use futures::StreamExt;
use k8s_openapi::api::{
apps::v1::StatefulSet,
core::v1::{Secret, Service, ServiceAccount},
core::v1::{ConfigMap, Secret, Service, ServiceAccount},
rbac::v1::{Role, RoleBinding},
};
use kube::{
Expand Down Expand Up @@ -35,6 +36,7 @@

use super::Result;

pub mod external_servers_config_map;
mod forwarding_secret;
mod headless_service;
mod minecraft_server_role;
Expand Down Expand Up @@ -65,6 +67,7 @@
minecraft_server_role_binding_builder: MinecraftServerRoleBindingBuilder,
redis_service_builder: RedisServiceBuilder,
redis_stateful_set_builder: RedisStatefulSetBuilder,
external_servers_config_map_builder: ExternalServersConfigMapBuilder,
}

impl MinecraftClusterReconciler {
Expand Down Expand Up @@ -111,6 +114,13 @@
reconcile_builder(&self.redis_stateful_set_builder, cluster.as_ref(), None)
.await
.map_err(ReconcilerError::BuilderError)?;
reconcile_builder(
&self.external_servers_config_map_builder,
cluster.as_ref(),
None,

Check warning on line 120 in packages/shulker-operator/src/reconcilers/minecraft_cluster/mod.rs

View check run for this annotation

Codecov / codecov/patch

packages/shulker-operator/src/reconcilers/minecraft_cluster/mod.rs#L118-L120

Added lines #L118 - L120 were not covered by tests
)
.await
.map_err(ReconcilerError::BuilderError)?;

Check warning on line 123 in packages/shulker-operator/src/reconcilers/minecraft_cluster/mod.rs

View check run for this annotation

Codecov / codecov/patch

packages/shulker-operator/src/reconcilers/minecraft_cluster/mod.rs#L122-L123

Added lines #L122 - L123 were not covered by tests

Ok(Action::requeue(Duration::from_secs(5 * 60)))
}
Expand Down Expand Up @@ -210,9 +220,15 @@
),
redis_service_builder: RedisServiceBuilder::new(client.clone()),
redis_stateful_set_builder: RedisStatefulSetBuilder::new(client.clone()),

external_servers_config_map_builder: ExternalServersConfigMapBuilder::new(client.clone()),

Check warning on line 224 in packages/shulker-operator/src/reconcilers/minecraft_cluster/mod.rs

View check run for this annotation

Codecov / codecov/patch

packages/shulker-operator/src/reconcilers/minecraft_cluster/mod.rs#L224

Added line #L224 was not covered by tests
};

Controller::new(clusters_api, Config::default().any_semantic())
.owns(
Api::<ConfigMap>::all(client.clone()),
Config::default().any_semantic(),

Check warning on line 230 in packages/shulker-operator/src/reconcilers/minecraft_cluster/mod.rs

View check run for this annotation

Codecov / codecov/patch

packages/shulker-operator/src/reconcilers/minecraft_cluster/mod.rs#L229-L230

Added lines #L229 - L230 were not covered by tests
)
.owns(
Api::<Secret>::all(client.clone()),
Config::default().any_semantic(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
source: packages/shulker-operator/src/reconcilers/minecraft_cluster/external_servers_config_map.rs
expression: config_map
---
apiVersion: v1
kind: ConfigMap
data:
external-servers.yaml: "- name: my-external-server\n address: 127.0.0.1:25565\n tags:\n - game\n"
metadata:
labels:
app.kubernetes.io/component: proxy
app.kubernetes.io/instance: external-servers-my-cluster
app.kubernetes.io/managed-by: shulker-operator
app.kubernetes.io/name: external-servers
app.kubernetes.io/part-of: cluster-my-cluster
minecraftcluster.shulkermc.io/name: my-cluster
name: my-cluster-external-servers
namespace: default
Loading
Loading