From ebc002ef7cca25b5a0d34da2f7f7245402a0754f Mon Sep 17 00:00:00 2001 From: Paulo Bressan Date: Thu, 11 Jan 2024 14:31:26 -0300 Subject: [PATCH] feat: added secondary route with key on host * chore: adjusted handlers * feat: added plugin to accept dmtr key on hostname * feat: added secondary route with key on hostname --- bootstrap/crds/main.tf | 15 +- operator/src/controller.rs | 44 ++++-- operator/src/handlers/auth.rs | 259 +++++++++++++++++-------------- operator/src/handlers/gateway.rs | 145 +++++++++-------- operator/src/helpers/mod.rs | 14 +- operator/yaml/crd.yaml | 11 +- 6 files changed, 286 insertions(+), 202 deletions(-) diff --git a/bootstrap/crds/main.tf b/bootstrap/crds/main.tf index fbf5809..fba0f8a 100644 --- a/bootstrap/crds/main.tf +++ b/bootstrap/crds/main.tf @@ -33,6 +33,11 @@ resource "kubernetes_manifest" "customresourcedefinition_ogmiosports_demeter_run "name" = "Endpoint URL" "type" = "string" }, + { + "jsonPath" = ".status.endpoint_key_url" + "name" = "Endpoint Key URL" + "type" = "string" + }, { "jsonPath" = ".status.authToken" "name" = "Auth Token" @@ -71,14 +76,20 @@ resource "kubernetes_manifest" "customresourcedefinition_ogmiosports_demeter_run "nullable" = true "properties" = { "authToken" = { - "nullable" = true + "type" = "string" + } + "endpointKeyUrl" = { "type" = "string" } "endpointUrl" = { - "nullable" = true "type" = "string" } } + "required" = [ + "authToken", + "endpointKeyUrl", + "endpointUrl", + ] "type" = "object" } } diff --git a/operator/src/controller.rs b/operator/src/controller.rs index e5f4420..7722f5a 100644 --- a/operator/src/controller.rs +++ b/operator/src/controller.rs @@ -2,7 +2,7 @@ use futures::StreamExt; use kube::{ api::ListParams, runtime::{controller::Action, watcher::Config as WatcherConfig, Controller}, - Api, Client, CustomResource, ResourceExt, + Api, Client, CustomResource, CustomResourceExt, ResourceExt, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -11,9 +11,8 @@ use tracing::{error, info, instrument}; use crate::{ auth::handle_auth, - build_private_dns_service_name, - gateway::{handle_http_route, handle_reference_grant}, - Error, Metrics, Network, Result, State, + gateway::{handle_http_route, handle_http_route_key, handle_reference_grant}, + patch_resource_status, Error, Metrics, Network, Result, State, }; pub static OGMIOS_PORT_FINALIZER: &str = "ogmiosports.demeter.run"; @@ -30,6 +29,7 @@ pub static OGMIOS_PORT_FINALIZER: &str = "ogmiosports.demeter.run"; {"name": "Network", "jsonPath": ".spec.network", "type": "string"}, {"name": "Version", "jsonPath": ".spec.version", "type": "number"}, {"name": "Endpoint URL", "jsonPath": ".status.endpointUrl", "type": "string"}, + {"name": "Endpoint Key URL", "jsonPath": ".status.endpoint_key_url", "type": "string"}, {"name": "Auth Token", "jsonPath": ".status.authToken", "type": "string"} "#)] #[serde(rename_all = "camelCase")] @@ -41,10 +41,9 @@ pub struct OgmiosPortSpec { #[derive(Deserialize, Serialize, Clone, Default, Debug, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct OgmiosPortStatus { - #[serde(skip_serializing_if = "Option::is_none")] - pub endpoint_url: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub auth_token: Option, + pub endpoint_url: String, + pub endpoint_key_url: String, + pub auth_token: String, } struct Context { @@ -58,14 +57,31 @@ impl Context { } async fn reconcile(crd: Arc, ctx: Arc) -> Result { - let client = ctx.client.clone(); + handle_reference_grant(&ctx.client, &crd).await?; + + let key = handle_auth(&ctx.client, &crd).await?; + let hostname = handle_http_route(&ctx.client, &crd).await?; + let hostname_key = handle_http_route_key(&ctx.client, &crd, &key).await?; + + let status = OgmiosPortStatus { + endpoint_url: format!("https://{hostname}"), + endpoint_key_url: format!("https://{hostname_key}"), + auth_token: key, + }; + let namespace = crd.namespace().unwrap(); + let ogmios_port = OgmiosPort::api_resource(); + + patch_resource_status( + ctx.client.clone(), + &namespace, + ogmios_port, + &crd.name_any(), + serde_json::to_value(status)?, + ) + .await?; - let private_dns_service_name = - build_private_dns_service_name(&crd.spec.network, &crd.spec.version); - handle_reference_grant(client.clone(), &namespace, &crd, &private_dns_service_name).await?; - handle_http_route(client.clone(), &namespace, &crd, &private_dns_service_name).await?; - handle_auth(client.clone(), &namespace, &crd).await?; + info!(resource = crd.name_any(), "Reconcile completed"); Ok(Action::await_change()) } diff --git a/operator/src/handlers/auth.rs b/operator/src/handlers/auth.rs index de1781c..e376734 100644 --- a/operator/src/handlers/auth.rs +++ b/operator/src/handlers/auth.rs @@ -8,7 +8,7 @@ use k8s_openapi::{api::core::v1::Secret, apimachinery::pkg::apis::meta::v1::Owne use kube::{ api::{Patch, PatchParams, PostParams}, core::ObjectMeta, - Api, Client, CustomResourceExt, Resource, ResourceExt, + Api, Client, Resource, ResourceExt, }; use serde::Deserialize; use serde_json::{json, Value as JsonValue}; @@ -16,104 +16,101 @@ use std::collections::BTreeMap; use tracing::info; use crate::{ - create_resource, get_acl_name, get_auth_name, get_config, get_resource, kong_consumer, - kong_plugin, patch_resource, patch_resource_status, Error, OgmiosPort, OgmiosPortStatus, + create_resource, get_acl_name, get_auth_name, get_config, get_host_key_name, get_resource, + kong_consumer, kong_plugin, patch_resource, Error, OgmiosPort, }; -pub async fn handle_auth( - client: Client, - namespace: &str, - resource: &OgmiosPort, -) -> Result<(), Error> { - handle_auth_secret(client.clone(), namespace, resource).await?; - handle_auth_plugin(client.clone(), namespace, resource).await?; - handle_acl_secret(client.clone(), namespace, resource).await?; - handle_acl_plugin(client.clone(), namespace, resource).await?; - handle_consumer(client.clone(), namespace, resource).await?; - Ok(()) -} +pub async fn handle_auth(client: &Client, crd: &OgmiosPort) -> Result { + let key = generate_api_key(crd).await?; + + handle_auth_secret(client, crd, &key).await?; + handle_auth_plugin(client, crd).await?; + handle_host_key_plugin(client, crd).await?; -async fn handle_auth_secret( - client: Client, - namespace: &str, - resource: &OgmiosPort, -) -> Result<(), Error> { - let name = get_auth_name(&resource.name_any()); - let api_key = generate_api_key(&name, namespace).await?; - let ogmios_port = OgmiosPort::api_resource(); + handle_acl_secret(client, crd).await?; + handle_acl_plugin(client, crd).await?; + + handle_consumer(client, crd).await?; + + Ok(key) +} - let api = Api::::namespaced(client.clone(), namespace); +async fn handle_auth_secret(client: &Client, crd: &OgmiosPort, key: &str) -> Result<(), Error> { + let namespace = crd.namespace().unwrap(); + let name = get_auth_name(&crd.name_any()); + let secret = build_auth_secret(crd, key); - let secret = auth_secret(&name, &api_key, resource.clone()); + let api = Api::::namespaced(client.clone(), &namespace); let result = api.get_opt(&name).await?; if result.is_some() { - info!(resource = resource.name_any(), "Updating auth secret"); + info!(resource = crd.name_any(), "Updating auth secret"); let patch_params = PatchParams::default(); - api.patch(&name, &patch_params, &Patch::Merge(secret)) - .await?; + let patch_data = Patch::Merge(secret); + api.patch(&name, &patch_params, &patch_data).await?; } else { - info!(resource = resource.name_any(), "Creating auth secret"); + info!(resource = crd.name_any(), "Creating auth secret"); let post_params = PostParams::default(); api.create(&post_params, &secret).await?; } - let status = OgmiosPortStatus { - auth_token: Some(api_key), - ..Default::default() - }; - - patch_resource_status( - client.clone(), - namespace, - ogmios_port, - &resource.name_any(), - serde_json::to_value(status)?, - ) - .await?; Ok(()) } -async fn handle_auth_plugin( - client: Client, - namespace: &str, - resource: &OgmiosPort, -) -> Result<(), Error> { - let name = get_auth_name(&resource.name_any()); +async fn handle_auth_plugin(client: &Client, crd: &OgmiosPort) -> Result<(), Error> { + let namespace = crd.namespace().unwrap(); + let name = get_auth_name(&crd.name_any()); let kong_plugin = kong_plugin(); - let result = get_resource(client.clone(), namespace, &kong_plugin, &name).await?; - let (metadata, data, raw) = auth_plugin(resource.clone())?; + let (metadata, data, raw) = build_auth_plugin(crd)?; + + let result = get_resource(client.clone(), &namespace, &kong_plugin, &name).await?; if result.is_some() { - info!(resource = resource.name_any(), "Updating auth plugin"); - patch_resource(client.clone(), namespace, kong_plugin, &name, raw).await?; + info!(resource = crd.name_any(), "Updating auth plugin"); + patch_resource(client.clone(), &namespace, kong_plugin, &name, raw).await?; } else { - info!(resource = resource.name_any(), "Creating auth plugin"); - create_resource(client.clone(), namespace, kong_plugin, metadata, data).await?; + info!(resource = crd.name_any(), "Creating auth plugin"); + create_resource(client.clone(), &namespace, kong_plugin, metadata, data).await?; } Ok(()) } -async fn handle_acl_secret( - client: Client, - namespace: &str, - resource: &OgmiosPort, -) -> Result<(), Error> { - let name = get_acl_name(&resource.name_any()); +async fn handle_host_key_plugin(client: &Client, crd: &OgmiosPort) -> Result<(), Error> { + let namespace = crd.namespace().unwrap(); + let name = get_host_key_name(&crd.name_any()); + let kong_plugin = kong_plugin(); + + let (metadata, data, raw) = build_host_key_plugin(crd)?; - let api = Api::::namespaced(client.clone(), namespace); + let result = get_resource(client.clone(), &namespace, &kong_plugin, &name).await?; - let secret = acl_secret(&name, resource.clone()); + if result.is_some() { + info!(resource = crd.name_any(), "Updating host key plugin"); + patch_resource(client.clone(), &namespace, kong_plugin, &name, raw).await?; + } else { + info!(resource = crd.name_any(), "Creating host key plugin"); + create_resource(client.clone(), &namespace, kong_plugin, metadata, data).await?; + } + + Ok(()) +} + +async fn handle_acl_secret(client: &Client, crd: &OgmiosPort) -> Result<(), Error> { + let namespace = crd.namespace().unwrap(); + let name = get_acl_name(&crd.name_any()); + let secret = build_acl_secret(crd); + + let api = Api::::namespaced(client.clone(), &namespace); let result = api.get_opt(&name).await?; if result.is_some() { - info!(resource = resource.name_any(), "Updating acl secret"); + info!(resource = crd.name_any(), "Updating acl secret"); let patch_params = PatchParams::default(); - api.patch(&name, &patch_params, &Patch::Merge(secret)) - .await?; + let patch_data = Patch::Merge(secret); + api.patch(&name, &patch_params, &patch_data).await?; } else { - info!(resource = resource.name_any(), "Creating acl secret"); + info!(resource = crd.name_any(), "Creating acl secret"); let post_params = PostParams::default(); api.create(&post_params, &secret).await?; } @@ -121,49 +118,48 @@ async fn handle_acl_secret( Ok(()) } -async fn handle_acl_plugin( - client: Client, - namespace: &str, - resource: &OgmiosPort, -) -> Result<(), Error> { - let name = get_acl_name(&resource.name_any()); +async fn handle_acl_plugin(client: &Client, crd: &OgmiosPort) -> Result<(), Error> { + let namespace = crd.namespace().unwrap(); + let name = get_acl_name(&crd.name_any()); let kong_plugin = kong_plugin(); - let result = get_resource(client.clone(), namespace, &kong_plugin, &name).await?; - let (metadata, data, raw) = acl_plugin(resource.clone())?; + let (metadata, data, raw) = build_acl_plugin(crd)?; + + let result = get_resource(client.clone(), &namespace, &kong_plugin, &name).await?; if result.is_some() { - info!(resource = resource.name_any(), "Updating acl plugin"); - patch_resource(client.clone(), namespace, kong_plugin, &name, raw).await?; + info!(resource = crd.name_any(), "Updating acl plugin"); + patch_resource(client.clone(), &namespace, kong_plugin, &name, raw).await?; } else { - info!(resource = resource.name_any(), "Creating acl plugin"); - create_resource(client.clone(), namespace, kong_plugin, metadata, data).await?; + info!(resource = crd.name_any(), "Creating acl plugin"); + create_resource(client.clone(), &namespace, kong_plugin, metadata, data).await?; } Ok(()) } -async fn handle_consumer( - client: Client, - namespace: &str, - resource: &OgmiosPort, -) -> Result<(), Error> { - let name = get_auth_name(&resource.name_any()); +async fn handle_consumer(client: &Client, crd: &OgmiosPort) -> Result<(), Error> { + let namespace = crd.namespace().unwrap(); + let name = get_auth_name(&crd.name_any()); let kong_consumer = kong_consumer(); - let result = get_resource(client.clone(), namespace, &kong_consumer, &name).await?; - let (metadata, data, raw) = consumer(resource.clone())?; + let (metadata, data, raw) = build_consumer(crd)?; + + let result = get_resource(client.clone(), &namespace, &kong_consumer, &name).await?; if result.is_some() { - info!(resource = resource.name_any(), "Updating consumer"); - patch_resource(client.clone(), namespace, kong_consumer, &name, raw).await?; + info!(resource = crd.name_any(), "Updating consumer"); + patch_resource(client.clone(), &namespace, kong_consumer, &name, raw).await?; } else { - info!(resource = resource.name_any(), "Creating consumer"); - create_resource(client.clone(), namespace, kong_consumer, metadata, data).await?; + info!(resource = crd.name_any(), "Creating consumer"); + create_resource(client.clone(), &namespace, kong_consumer, metadata, data).await?; } Ok(()) } -async fn generate_api_key(name: &str, namespace: &str) -> Result { +async fn generate_api_key(crd: &OgmiosPort) -> Result { + let namespace = crd.namespace().unwrap(); + let name = get_auth_name(&crd.name_any()); + let password = format!("{}{}", name, namespace).as_bytes().to_vec(); let config = get_config(); @@ -174,14 +170,13 @@ async fn generate_api_key(name: &str, namespace: &str) -> Result let argon2 = Argon2::default(); argon2.hash_password_into(password.as_slice(), salt, &mut output)?; - // Encode the hash using Bech32 let base64 = general_purpose::URL_SAFE_NO_PAD.encode(output); let with_bech = bech32::encode("dmtr_ogmios", base64.to_base32(), bech32::Variant::Bech32)?; Ok(with_bech) } -fn auth_secret(name: &str, api_key: &str, owner: OgmiosPort) -> Secret { +fn build_auth_secret(crd: &OgmiosPort, api_key: &str) -> Secret { let mut string_data = BTreeMap::new(); string_data.insert("key".into(), api_key.into()); @@ -189,12 +184,12 @@ fn auth_secret(name: &str, api_key: &str, owner: OgmiosPort) -> Secret { labels.insert("konghq.com/credential".into(), "key-auth".into()); let metadata = ObjectMeta { - name: Some(name.to_string()), + name: Some(get_auth_name(&crd.name_any())), owner_references: Some(vec![OwnerReference { api_version: OgmiosPort::api_version(&()).to_string(), kind: OgmiosPort::kind(&()).to_string(), - name: owner.name_any(), - uid: owner.uid().unwrap(), + name: crd.name_any(), + uid: crd.uid().unwrap(), ..Default::default() }]), labels: Some(labels), @@ -209,18 +204,17 @@ fn auth_secret(name: &str, api_key: &str, owner: OgmiosPort) -> Secret { } } -fn auth_plugin(owner: OgmiosPort) -> Result<(ObjectMeta, JsonValue, JsonValue), Error> { +fn build_auth_plugin(crd: &OgmiosPort) -> Result<(ObjectMeta, JsonValue, JsonValue), Error> { let kong_plugin = kong_plugin(); let metadata = ObjectMeta::deserialize(&json!({ - "name": get_auth_name(&owner.name_any()), - + "name": get_auth_name(&crd.name_any()), "ownerReferences": [ { "apiVersion": OgmiosPort::api_version(&()).to_string(), // @TODO: try to grab this from the owner "kind": OgmiosPort::kind(&()).to_string(), // @TODO: try to grab this from the owner - "name": owner.name_any(), - "uid": owner.uid() + "name": crd.name_any(), + "uid": crd.uid() } ] }))?; @@ -243,20 +237,51 @@ fn auth_plugin(owner: OgmiosPort) -> Result<(ObjectMeta, JsonValue, JsonValue), Ok((metadata, data, raw)) } -fn acl_secret(name: &str, owner: OgmiosPort) -> Secret { +fn build_host_key_plugin(crd: &OgmiosPort) -> Result<(ObjectMeta, JsonValue, JsonValue), Error> { + let kong_plugin = kong_plugin(); + + let metadata = ObjectMeta::deserialize(&json!({ + "name": get_host_key_name(&crd.name_any()), + "ownerReferences": [ + { + "apiVersion": OgmiosPort::api_version(&()).to_string(), + "kind": OgmiosPort::kind(&()).to_string(), + "name": crd.name_any(), + "uid": crd.uid() + } + ] + }))?; + + let data = json!({ + "plugin": "key-to-header", + "config": {} + }); + + let raw = json!({ + "apiVersion": kong_plugin.api_version, + "kind": kong_plugin.kind, + "metadata": metadata, + "plugin": data["plugin"], + "config": data["config"] + }); + + Ok((metadata, data, raw)) +} + +fn build_acl_secret(crd: &OgmiosPort) -> Secret { let mut string_data = BTreeMap::new(); - string_data.insert("group".into(), owner.name_any()); + string_data.insert("group".into(), crd.name_any()); let mut labels = BTreeMap::new(); labels.insert("konghq.com/credential".into(), "acl".into()); let metadata = ObjectMeta { - name: Some(name.to_string()), + name: Some(get_auth_name(&crd.name_any())), owner_references: Some(vec![OwnerReference { api_version: OgmiosPort::api_version(&()).to_string(), kind: OgmiosPort::kind(&()).to_string(), - name: owner.name_any(), - uid: owner.uid().unwrap(), + name: crd.name_any(), + uid: crd.uid().unwrap(), ..Default::default() }]), labels: Some(labels), @@ -271,17 +296,17 @@ fn acl_secret(name: &str, owner: OgmiosPort) -> Secret { } } -fn acl_plugin(owner: OgmiosPort) -> Result<(ObjectMeta, JsonValue, JsonValue), Error> { +fn build_acl_plugin(crd: &OgmiosPort) -> Result<(ObjectMeta, JsonValue, JsonValue), Error> { let kong_plugin = kong_plugin(); let metadata = ObjectMeta::deserialize(&json!({ - "name": get_acl_name(&owner.name_any()), + "name": get_acl_name(&crd.name_any()), "ownerReferences": [ { "apiVersion": OgmiosPort::api_version(&()).to_string(), // @TODO: try to grab this from the owner "kind": OgmiosPort::kind(&()).to_string(), // @TODO: try to grab this from the owner - "name": owner.name_any(), - "uid": owner.uid() + "name": crd.name_any(), + "uid": crd.uid() } ] }))?; @@ -289,7 +314,7 @@ fn acl_plugin(owner: OgmiosPort) -> Result<(ObjectMeta, JsonValue, JsonValue), E let data = json!({ "plugin": "acl", "config": { - "allow": [owner.name_any()] + "allow": [crd.name_any()] } }); @@ -304,12 +329,12 @@ fn acl_plugin(owner: OgmiosPort) -> Result<(ObjectMeta, JsonValue, JsonValue), E Ok((metadata, data, raw)) } -fn consumer(owner: OgmiosPort) -> Result<(ObjectMeta, JsonValue, JsonValue), Error> { +fn build_consumer(crd: &OgmiosPort) -> Result<(ObjectMeta, JsonValue, JsonValue), Error> { let kong_consumer = kong_consumer(); let config = get_config(); let metadata = ObjectMeta::deserialize(&json!({ - "name": get_auth_name(&owner.name_any()), + "name": get_auth_name(&crd.name_any()), "annotations": { "kubernetes.io/ingress.class": config.ingress_class, }, @@ -318,15 +343,15 @@ fn consumer(owner: OgmiosPort) -> Result<(ObjectMeta, JsonValue, JsonValue), Err { "apiVersion": OgmiosPort::api_version(&()).to_string(), // @TODO: try to grab this from the owner "kind": OgmiosPort::kind(&()).to_string(), // @TODO: try to grab this from the owner - "name": owner.name_any(), - "uid": owner.uid() + "name": crd.name_any(), + "uid": crd.uid() } ] }))?; let data = json!({ - "username": owner.name_any(), - "credentials": [get_auth_name(&owner.name_any()), get_acl_name(&owner.name_any())] + "username": crd.name_any(), + "credentials": [get_auth_name(&crd.name_any()), get_acl_name(&crd.name_any())] }); let raw = json!({ diff --git a/operator/src/handlers/gateway.rs b/operator/src/handlers/gateway.rs index 6de8ad1..5142179 100644 --- a/operator/src/handlers/gateway.rs +++ b/operator/src/handlers/gateway.rs @@ -1,67 +1,78 @@ -use kube::{core::ObjectMeta, Client, CustomResourceExt, Resource, ResourceExt}; +use kube::{core::ObjectMeta, Client, Resource, ResourceExt}; use serde::Deserialize; use serde_json::{json, Value as JsonValue}; use tracing::info; use crate::{ - create_resource, get_acl_name, get_auth_name, get_config, get_resource, http_route, - patch_resource, patch_resource_status, reference_grant, Error, OgmiosPort, OgmiosPortStatus, + create_resource, get_acl_name, get_auth_name, get_config, get_http_route_key_name, + get_http_route_name, get_resource, http_route, ogmios_service_name, patch_resource, + reference_grant, Error, OgmiosPort, }; -pub async fn handle_http_route( - client: Client, - namespace: &str, - resource: &OgmiosPort, - private_dns_service_name: &str, -) -> Result<(), Error> { - let name = format!("ogmios-{}", resource.name_any()); - let host_name = build_host(&resource.name_any(), &namespace_to_slug(namespace)); +pub async fn handle_http_route(client: &Client, crd: &OgmiosPort) -> Result { + let namespace = crd.namespace().unwrap(); + let ogmios_service = ogmios_service_name(&crd.spec.network, &crd.spec.version); + + let name = get_http_route_name(&crd.name_any()); + let host_name = build_hostname(&crd.name_any(), &project_id(&namespace), None); let http_route = http_route(); - let ogmios_port = OgmiosPort::api_resource(); - let result = get_resource(client.clone(), namespace, &http_route, &name).await?; + let result = get_resource(client.clone(), &namespace, &http_route, &name).await?; - let (metadata, data, raw) = route(&name, &host_name, resource, private_dns_service_name)?; + let (metadata, data, raw) = build_route(&name, &host_name, crd, &ogmios_service)?; if result.is_some() { - info!(resource = resource.name_any(), "Updating http route"); - patch_resource(client.clone(), namespace, http_route, &name, raw).await?; + info!(resource = crd.name_any(), "Updating http route"); + patch_resource(client.clone(), &namespace, http_route, &name, raw).await?; } else { - info!(resource = resource.name_any(), "Creating http route"); - create_resource(client.clone(), namespace, http_route, metadata, data).await?; + info!(resource = crd.name_any(), "Creating http route"); + create_resource(client.clone(), &namespace, http_route, metadata, data).await?; } - let status = OgmiosPortStatus { - endpoint_url: Some(format!("https://{}", host_name)), - ..Default::default() - }; - patch_resource_status( - client.clone(), - namespace, - ogmios_port, - &resource.name_any(), - serde_json::to_value(status)?, - ) - .await?; - Ok(()) + Ok(host_name) } -pub async fn handle_reference_grant( - client: Client, - namespace: &str, - resource: &OgmiosPort, - private_dns_service_name: &str, -) -> Result<(), Error> { - let name = format!("{}-{}-http", namespace, resource.name_any()); +pub async fn handle_http_route_key( + client: &Client, + crd: &OgmiosPort, + key: &str, +) -> Result { + let namespace = crd.namespace().unwrap(); + let ogmios_service = ogmios_service_name(&crd.spec.network, &crd.spec.version); + + let name = get_http_route_key_name(&crd.name_any()); + let host_name = build_hostname(&crd.name_any(), &project_id(&namespace), Some(key)); + let http_route = http_route(); + + let result = get_resource(client.clone(), &namespace, &http_route, &name).await?; + + let (metadata, data, raw) = build_route(&name, &host_name, crd, &ogmios_service)?; + + if result.is_some() { + info!(resource = crd.name_any(), "Updating http route"); + patch_resource(client.clone(), &namespace, http_route, &name, raw).await?; + } else { + info!(resource = crd.name_any(), "Creating http route"); + create_resource(client.clone(), &namespace, http_route, metadata, data).await?; + } + + Ok(host_name) +} + +pub async fn handle_reference_grant(client: &Client, crd: &OgmiosPort) -> Result<(), Error> { + let namespace = crd.namespace().unwrap(); + let ogmios_service = ogmios_service_name(&crd.spec.network, &crd.spec.version); + + let name = format!("{}-{}-http", namespace, crd.name_any()); let reference_grant = reference_grant(); let config = get_config(); let result = get_resource(client.clone(), &config.namespace, &reference_grant, &name).await?; - let (metadata, data, raw) = grant(&name, private_dns_service_name, namespace)?; + let (metadata, data, raw) = build_grant(&name, &ogmios_service, &namespace)?; if result.is_some() { - info!(resource = resource.name_any(), "Updating reference grant"); + info!(resource = crd.name_any(), "Updating reference grant"); patch_resource( client.clone(), &config.namespace, @@ -71,8 +82,7 @@ pub async fn handle_reference_grant( ) .await?; } else { - info!(resource = resource.name_any(), "Creating reference grant"); - // we need to get the deserialized payload + info!(resource = crd.name_any(), "Creating reference grant"); create_resource( client.clone(), &config.namespace, @@ -85,31 +95,18 @@ pub async fn handle_reference_grant( Ok(()) } -fn build_host(name: &str, project_slug: &str) -> String { - let config = get_config(); - - format!( - "{}-{}.{}.{}", - name, project_slug, config.ingress_class, config.dns_zone - ) -} - -fn namespace_to_slug(namespace: &str) -> String { - namespace.split_once('-').unwrap().1.to_string() -} - -fn route( +fn build_route( name: &str, hostname: &str, - owner: &OgmiosPort, - private_dns_service_name: &str, + crd: &OgmiosPort, + ogmios_service: &str, ) -> Result<(ObjectMeta, JsonValue, JsonValue), Error> { let config = get_config(); let http_route = http_route(); let plugins = format!( "{},{}", - get_auth_name(&owner.name_any()), - get_acl_name(&owner.name_any()), + get_auth_name(&crd.name_any()), + get_acl_name(&crd.name_any()), ); let metadata = ObjectMeta::deserialize(&json!({ @@ -126,8 +123,8 @@ fn route( { "apiVersion": OgmiosPort::api_version(&()).to_string(), // @TODO: try to grab this from the owner "kind": OgmiosPort::kind(&()).to_string(), // @TODO: try to grab this from the owner - "name": owner.name_any(), - "uid": owner.uid() + "name": crd.name_any(), + "uid": crd.uid() } ] }))?; @@ -146,7 +143,7 @@ fn route( "backendRefs": [ { "kind": "Service", - "name": private_dns_service_name, + "name": ogmios_service, "port": config.http_port.parse::()?, "namespace": config.namespace } @@ -166,9 +163,9 @@ fn route( Ok((metadata, data, raw)) } -fn grant( +fn build_grant( name: &str, - private_dns_service_name: &str, + ogmios_service: &str, project_namespace: &str, ) -> Result<(ObjectMeta, JsonValue, JsonValue), Error> { let reference_grant = reference_grant(); @@ -191,7 +188,7 @@ fn grant( { "group": "", "kind": "Service", - "name": private_dns_service_name, + "name": ogmios_service, }, ], } @@ -206,3 +203,19 @@ fn grant( Ok((metadata, data, raw)) } + +fn build_hostname(crd_name: &str, project_id: &str, key: Option<&str>) -> String { + let config = get_config(); + let ingress_class = &config.ingress_class; + let dns_zone = &config.dns_zone; + + if let Some(key) = key { + return format!("{key}.{crd_name}-{project_id}.{ingress_class}.{dns_zone}"); + } + + format!("{crd_name}-{project_id}.{ingress_class}.{dns_zone}") +} + +fn project_id(namespace: &str) -> String { + namespace.split_once('-').unwrap().1.to_string() +} diff --git a/operator/src/helpers/mod.rs b/operator/src/helpers/mod.rs index a1bb53d..4826d78 100644 --- a/operator/src/helpers/mod.rs +++ b/operator/src/helpers/mod.rs @@ -108,14 +108,26 @@ pub async fn patch_resource_status( Ok(()) } +pub fn get_http_route_name(name: &str) -> String { + format!("ogmios-http-route-{}", name) +} + +pub fn get_http_route_key_name(name: &str) -> String { + format!("ogmios-http-route-key-{}", name) +} + pub fn get_auth_name(name: &str) -> String { format!("ogmios-auth-{name}") } +pub fn get_host_key_name(name: &str) -> String { + format!("ogmios-host-key-{name}") +} + pub fn get_acl_name(name: &str) -> String { format!("ogmios-acl-{name}") } -pub fn build_private_dns_service_name(network: &Network, version: &u8) -> String { +pub fn ogmios_service_name(network: &Network, version: &u8) -> String { format!("ogmios-{network}-{version}") } diff --git a/operator/yaml/crd.yaml b/operator/yaml/crd.yaml index 294afc3..2b51b6d 100644 --- a/operator/yaml/crd.yaml +++ b/operator/yaml/crd.yaml @@ -22,6 +22,9 @@ spec: - jsonPath: .status.endpointUrl name: Endpoint URL type: string + - jsonPath: .status.endpoint_key_url + name: Endpoint Key URL + type: string - jsonPath: .status.authToken name: Auth Token type: string @@ -51,11 +54,15 @@ spec: nullable: true properties: authToken: - nullable: true + type: string + endpointKeyUrl: type: string endpointUrl: - nullable: true type: string + required: + - authToken + - endpointKeyUrl + - endpointUrl type: object required: - spec