Skip to content

Commit

Permalink
Add support for KDL configuration (#42)
Browse files Browse the repository at this point in the history
This PR adds support for KDL-formatted configuration, in anticipation of supporting more complex load balancing configuration.

We'll review whether to switch to KDL as a primary format, revert to only TOML support, or supporting both prior to the 0.3 release.
  • Loading branch information
jamesmunns authored Jun 10, 2024
1 parent ba1d6a8 commit 0c32967
Show file tree
Hide file tree
Showing 13 changed files with 1,141 additions and 60 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = [
members = [ "experiments/kdl-experiment",
"source/river",
]

Expand Down
13 changes: 13 additions & 0 deletions experiments/kdl-experiment/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "kdl-experiment"
version = "0.1.0"
edition = "2021"
publish = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
kdl = "4.6.0"
miette = { version = "5.10.0", features = ["fancy"] }
pingora = "0.2.0"
thiserror = "1.0.61"
38 changes: 38 additions & 0 deletions experiments/kdl-experiment/reference.kdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
system {
threads-per-service 8
}

services {
Example1 {
listeners {
"0.0.0.0:8080"
"0.0.0.0:4443" cert-path="./assets/test.crt" key-path="./assets/test.key"
"0.0.0.0:8443" cert-path="./assets/test.crt" key-path="./assets/test.key"
}

connectors {
"91.107.223.4:443" tls-sni="onevariable.com"
}

path-control {
upstream-request {
filter kind="remove-header-key-regex" pattern=".*SECRET.*"
filter kind="remove-header-key-regex" pattern=".*secret.*"
filter kind="upsert-header" key="x-proxy-friend" value="river"
}
upstream-response {
filter kind="remove-header-key-regex" pattern=".*ETag.*"
filter kind="upsert-header" key="x-with-love-from" value="river"
}
}
}

Example2 {
listeners {
"0.0.0.0:8000"
}
connectors {
"91.107.223.4:80"
}
}
}
85 changes: 85 additions & 0 deletions experiments/kdl-experiment/reference.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Test configuration file
#
# This is more in depth than the example.
#
# NOTE: Until we have made a release, configuration format should
# be considered entirely unstable, and likely to make breaking changes
# commit-to-commit!

[system]
# Threads per service
threads-per-service = 8

# Specify a Basic Proxy service, which features minimal configuration.
#
# Note that indentation isn't significant in TOML, it is only done
# here to note the structure of the data.
#
# TODO: This data is very structured, and TOML might not be a good fit.
[[basic-proxy]]
name = "Example1"

# Each `basic-proxy` can have one or more Listeners, or downstream
# connections. If you provide zero, the basic proxy will terminate
# immediately.
[[basic-proxy.listeners]]
# TODO: "listeners" only has one field, we might want to use
# serde flatten
[basic-proxy.listeners.source]
# Listeners can have kind of "Tcp" (w/ or w/o TLS) or "Uds"
# for "Unix Domain Sockets", which cannot have TLS.
kind = "Tcp"
[basic-proxy.listeners.source.value]
# TCP must specify the address, which includes the port to bind to
addr = "0.0.0.0:8080"

# `basic-proxy`s can have multiple listeners
[[basic-proxy.listeners]]

[basic-proxy.listeners.source]
kind = "Tcp"

[basic-proxy.listeners.source.value]
addr = "0.0.0.0:4443"

# To enable TLS, specify the path to the certificate and key
[basic-proxy.listeners.source.value.tls]
cert_path = "./assets/test.crt"
key_path = "./assets/test.key"

# Each `basic proxy` must have exactly one "connector", or the upstream
# server they will proxy to.
#
# To use TLS for upstream connections, specify the SNI of the connection
[basic-proxy.connector]
proxy_addr = "91.107.223.4:443"
tls_sni = "onevariable.com"

# "Path Control" affects requests and responses as they are proxied
[basic-proxy.path-control]
# upstream request filters specifically allow for the cancellation or modification
# of requests, as they are being made.
#
# Filters are applied in the order they are specified. Multiple instances of
# each filter may be provided.
upstream-request-filters = [
# Remove any headers with keys matching `pattern`
{ kind = "remove-header-key-regex", pattern = ".*(secret|SECRET).*" },
# Add or replace (e.g. "Upsert") a fixed header with the given key and value
{ kind = "upsert-header", key = "x-proxy-friend", value = "river" },
]

upstream-response-filters = [
# Remove any headers with keys matching `pattern`
{ kind = "remove-header-key-regex", pattern = ".*ETag.*" },
# Add or replace (e.g. "Upsert") a fixed header with the given key and value
{ kind = "upsert-header", key = "x-with-love-from", value = "river" },
]

# We specify a second basic proxy as well. Here we use the other table syntax
[[basic-proxy]]
name = "Example2"
listeners = [
{ source = { kind = "Tcp", value = { addr = "0.0.0.0:8000" } } }
]
connector = { proxy_addr = "91.107.223.4:80" }
104 changes: 104 additions & 0 deletions experiments/kdl-experiment/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@

use std::{collections::BTreeMap, path::PathBuf};

use pingora::{
server::configuration::{Opt as PingoraOpt, ServerConf as PingoraServerConf},
upstreams::peer::HttpPeer,
};

/// River's internal configuration
#[derive(Debug, Clone)]
pub struct Config {
pub validate_configs: bool,
pub threads_per_service: usize,
pub basic_proxies: Vec<ProxyConfig>,
}

impl Config {
/// Get the [`Opt`][PingoraOpt] field for Pingora
pub fn pingora_opt(&self) -> PingoraOpt {
// TODO
PingoraOpt {
upgrade: false,
daemon: false,
nocapture: false,
test: self.validate_configs,
conf: None,
}
}

/// Get the [`ServerConf`][PingoraServerConf] field for Pingora
pub fn pingora_server_conf(&self) -> PingoraServerConf {
PingoraServerConf {
daemon: false,
error_log: None,
// TODO: These are bad assumptions - non-developers will not have "target"
// files, and we shouldn't necessarily use utf-8 strings with fixed separators
// here.
pid_file: String::from("./target/pidfile"),
upgrade_sock: String::from("./target/upgrade"),
user: None,
group: None,
threads: self.threads_per_service,
work_stealing: true,
ca_file: None,
..PingoraServerConf::default()
}
}

pub fn validate(&self) {
// TODO: validation logic
}
}

/// Add Path Control Modifiers
///
/// Note that we use `BTreeMap` and NOT `HashMap`, as we want to maintain the
/// ordering from the configuration file.
#[derive(Default, Debug, Clone)]
pub struct PathControl {
pub(crate) upstream_request_filters: Vec<BTreeMap<String, String>>,
pub(crate) upstream_response_filters: Vec<BTreeMap<String, String>>,
}

//
// Basic Proxy Configuration
//

#[derive(Debug, Clone)]
pub struct ProxyConfig {
pub(crate) name: String,
pub(crate) listeners: Vec<ListenerConfig>,
pub(crate) upstream: HttpPeer,
pub(crate) path_control: PathControl,
}

#[derive(Debug, PartialEq, Clone)]
pub struct TlsConfig {
pub(crate) cert_path: PathBuf,
pub(crate) key_path: PathBuf,
}

#[derive(Debug, PartialEq, Clone)]
pub struct ListenerConfig {
pub(crate) source: ListenerKind,
}

#[derive(Debug, PartialEq, Clone)]
pub enum ListenerKind {
Tcp {
addr: String,
tls: Option<TlsConfig>,
},
Uds(PathBuf),
}

impl Default for Config {
fn default() -> Self {
Self {
validate_configs: false,
threads_per_service: 8,
basic_proxies: vec![],
}
}
}
Loading

0 comments on commit 0c32967

Please sign in to comment.