Skip to content

Commit

Permalink
Add support for Cfd Tunnels, now that Argo Tunnel is a deprecated API
Browse files Browse the repository at this point in the history
  • Loading branch information
GoncaloGarcia committed Jun 6, 2024
1 parent b4fa5ef commit 824fb9e
Show file tree
Hide file tree
Showing 9 changed files with 341 additions and 5 deletions.
51 changes: 51 additions & 0 deletions cloudflare/src/endpoints/cfd_tunnel/create_tunnel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use crate::endpoints::cfd_tunnel::{ConfigurationSrc, Tunnel};
use serde::Serialize;
use serde_with::{
base64::{Base64, Standard},
formats::Padded,
serde_as,
};

use crate::framework::endpoint::{EndpointSpec, Method};

/// Create a Cfd Tunnel
/// This creates the Tunnel, which can then be routed and ran. Creating the Tunnel per se is only
/// a metadata operation (i.e. no Tunnel is running at this point).
/// <https://developers.cloudflare.com/api/operations/cloudflare-tunnel-create-a-cloudflare-tunnel>
#[derive(Debug)]
pub struct CreateTunnel<'a> {
pub account_identifier: &'a str,
pub params: Params<'a>,
}

impl<'a> EndpointSpec<Tunnel> for CreateTunnel<'a> {
fn method(&self) -> Method {
Method::POST
}
fn path(&self) -> String {
format!("accounts/{}/cfd_tunnel", self.account_identifier)
}
#[inline]
fn body(&self) -> Option<String> {
let body = serde_json::to_string(&self.params).unwrap();
Some(body)
}
}

/// Params for creating a Named Argo Tunnel
#[serde_as]
#[serde_with::skip_serializing_none]
#[derive(Serialize, Clone, Debug)]
pub struct Params<'a> {
/// The name for the Tunnel to be created. It must be unique within the account.
pub name: &'a str,
/// The byte array (with 32 or more bytes) representing a secret for the tunnel. This is
/// encoded into JSON as a base64 String. This secret is necessary to run the tunnel.
#[serde_as(as = "Base64<Standard, Padded>")]
pub tunnel_secret: &'a Vec<u8>,

pub config_src: &'a ConfigurationSrc,

/// Arbitrary metadata for the tunnel.
pub metadata: Option<serde_json::Value>,
}
103 changes: 103 additions & 0 deletions cloudflare/src/endpoints/cfd_tunnel/data_structures.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use chrono::{offset::Utc, DateTime};
use serde::{Deserialize, Serialize};
use std::net::IpAddr;
use uuid::Uuid;

use crate::framework::response::ApiResult;

/// A Cfd Tunnel
/// This is an Cfd Tunnel that has been created. It can be used for routing and subsequent running.
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct Tunnel {
pub id: Uuid,
pub created_at: DateTime<Utc>,
pub deleted_at: Option<DateTime<Utc>>,
pub name: String,
pub connections: Vec<ActiveConnection>,
pub metadata: serde_json::Value,
}

#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)]
pub struct TunnelWithConnections {
pub id: Uuid,
pub account_tag: String,
pub created_at: DateTime<Utc>,
pub deleted_at: Option<DateTime<Utc>>,
pub name: String,
pub connections: Vec<ActiveConnection>,
pub conns_active_at: Option<DateTime<Utc>>,
pub conns_inactive_at: Option<DateTime<Utc>>,
// tun_type can be inferred from metadata
#[serde(flatten)]
pub metadata: serde_json::Value,
pub status: TunnelStatusType,
// This field is only present for tunnels that make sense to report (e.g: Cfd_Tunnel), which
// are the ones that can be managed via UI or dash in terms of their YAML file.
#[serde(skip_serializing_if = "Option::is_none")]
pub remote_config: Option<bool>,
}

#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Copy, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum TunnelStatusType {
Inactive, // Tunnel has been created but a connection has yet to be registered
Down, // Tunnel is down and all connections are unregistered
Degraded, // Tunnel health is degraded but still serving connections
Healthy, // Tunnel is healthy
}

#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub enum ConfigurationSrc {
#[serde(rename = "local")]
#[default]
Local,
#[serde(rename = "cloudflare")]
Cloudflare,
}
/// An active connection for a Cfd Tunnel
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Hash)]
pub struct ActiveConnection {
pub colo_name: String,
/// Deprecated, use `id` instead.
pub uuid: Uuid,
pub id: Uuid,
pub is_pending_reconnect: bool,
pub origin_ip: IpAddr,
pub opened_at: DateTime<Utc>,
pub client_id: Uuid,
pub client_version: String,
}

impl ApiResult for Tunnel {}
impl ApiResult for Vec<Tunnel> {}

/// The result of a route request for a Cfd Tunnel
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
#[serde(untagged)]
pub enum RouteResult {
Dns(DnsRouteResult),
Lb(LoadBalancerRouteResult),
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct DnsRouteResult {
pub cname: Change,
pub name: String,
pub dns_tag: String,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct LoadBalancerRouteResult {
pub load_balancer: Change,
pub pool: Change,
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum Change {
Unchanged,
New,
Updated,
}

impl ApiResult for RouteResult {}
36 changes: 36 additions & 0 deletions cloudflare/src/endpoints/cfd_tunnel/delete_tunnel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use crate::framework::endpoint::{serialize_query, EndpointSpec, Method};
use serde::Serialize;

use super::Tunnel;

/// Delete a tunnel
/// <https://developers.cloudflare.com/api/operations/cloudflare-tunnel-delete-a-cloudflare-tunnel>
#[derive(Debug)]
pub struct DeleteTunnel<'a> {
pub account_identifier: &'a str,
pub tunnel_id: &'a str,
pub params: Params,
}

impl<'a> EndpointSpec<Tunnel> for DeleteTunnel<'a> {
fn method(&self) -> Method {
Method::DELETE
}
fn path(&self) -> String {
format!(
"accounts/{}/cfd_tunnel/{}",
self.account_identifier, self.tunnel_id
)
}
#[inline]
fn query(&self) -> Option<String> {
serialize_query(&self.params)
}
}

#[serde_with::skip_serializing_none]
#[derive(Serialize, Clone, Debug, Default)]
pub struct Params {
// should delete tunnel connections if any exists
pub cascade: bool,
}
48 changes: 48 additions & 0 deletions cloudflare/src/endpoints/cfd_tunnel/list_tunnels.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use crate::endpoints::cfd_tunnel::Tunnel;
use chrono::{DateTime, Utc};
use serde::Serialize;

use crate::framework::endpoint::{serialize_query, EndpointSpec, Method};

/// List/search tunnels in an account.
/// <https://developers.cloudflare.com/api/operations/cloudflare-tunnel-list-cloudflare-tunnels>
#[derive(Debug)]
pub struct ListTunnels<'a> {
pub account_identifier: &'a str,
pub params: Params,
}

impl<'a> EndpointSpec<Vec<Tunnel>> for ListTunnels<'a> {
fn method(&self) -> Method {
Method::GET
}
fn path(&self) -> String {
format!("accounts/{}/cfd_tunnel", self.account_identifier)
}
#[inline]
fn query(&self) -> Option<String> {
serialize_query(&self.params)
}
}

/// Params for filtering listed tunnels
#[serde_with::skip_serializing_none]
#[derive(Serialize, Clone, Debug, Default)]
pub struct Params {
pub name: Option<String>,
pub uuid: Option<String>,
pub is_deleted: Option<bool>,
pub existed_at: Option<DateTime<Utc>>,
pub was_active_at: Option<DateTime<Utc>>,
pub include_prefix: Option<String>,
pub was_inactive_at: Option<DateTime<Utc>>,
pub exclude_prefix: Option<String>,
#[serde(flatten)]
pub pagination_params: Option<PaginationParams>,
}

#[derive(Serialize, Clone, Debug)]
pub struct PaginationParams {
pub page: u64,
pub per_page: u64,
}
8 changes: 8 additions & 0 deletions cloudflare/src/endpoints/cfd_tunnel/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pub mod create_tunnel;
mod data_structures;
pub mod delete_tunnel;
pub mod list_tunnels;
pub mod route_dns;
pub mod update_tunnel;

pub use data_structures::*;
39 changes: 39 additions & 0 deletions cloudflare/src/endpoints/cfd_tunnel/route_dns.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use crate::framework::endpoint::{EndpointSpec, Method};

use super::RouteResult;
use serde::Serialize;
use uuid::Uuid;

/// Route for a Named Argo Tunnel
/// This creates a new route for the identified Tunnel. More than 1 route may co-exist for the same
/// Tunnel.
/// Note that this modifies only metadata on Cloudflare side to route traffic to the Tunnel, but
/// it is still up to the user to run the Tunnel to receive that traffic.
#[derive(Debug)]
pub struct RouteTunnel<'a> {
pub zone_tag: &'a str,
pub tunnel_id: Uuid,
pub params: Params<'a>,
}

impl<'a> EndpointSpec<RouteResult> for RouteTunnel<'a> {
fn method(&self) -> Method {
Method::PUT
}
fn path(&self) -> String {
format!("zones/{}/tunnels/{}/routes", self.zone_tag, self.tunnel_id)
}
#[inline]
fn body(&self) -> Option<String> {
let body = serde_json::to_string(&self.params).unwrap();
Some(body)
}
}

/// Params for routing a Named Argo Tunnel
#[derive(Serialize, Clone, Debug)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum Params<'a> {
Dns { user_hostname: &'a str },
Lb { lb_name: &'a str, lb_pool: &'a str },
}
53 changes: 53 additions & 0 deletions cloudflare/src/endpoints/cfd_tunnel/update_tunnel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use crate::endpoints::cfd_tunnel::Tunnel;
use serde::Serialize;
use serde_with::{
base64::{Base64, Standard},
formats::Padded,
serde_as,
};

use crate::framework::endpoint::{EndpointSpec, Method};

/// Create a Cfd Tunnel
/// This creates the Tunnel, which can then be routed and ran. Creating the Tunnel per se is only
/// a metadata operation (i.e. no Tunnel is running at this point).
/// <https://developers.cloudflare.com/api/operations/cloudflare-tunnel-create-a-cloudflare-tunnel>
#[derive(Debug)]
pub struct UpdateTunnel<'a> {
pub account_identifier: &'a str,
pub tunnel_id: &'a str,
pub params: Params<'a>,
}

impl<'a> EndpointSpec<Tunnel> for UpdateTunnel<'a> {
fn method(&self) -> Method {
Method::PATCH
}
fn path(&self) -> String {
format!(
"accounts/{}/cfd_tunnel/{}",
self.account_identifier, self.tunnel_id
)
}
#[inline]
fn body(&self) -> Option<String> {
let body = serde_json::to_string(&self.params).unwrap();
Some(body)
}
}

/// Params for updating a Cfd Tunnel
#[serde_as]
#[serde_with::skip_serializing_none]
#[derive(Serialize, Clone, Debug)]
pub struct Params<'a> {
/// The new name for the Tunnel
pub name: &'a str,
/// The byte array (with 32 or more bytes) representing a secret for the tunnel. This is
/// encoded into JSON as a base64 String. This secret is necessary to run the tunnel.
#[serde_as(as = "Base64<Standard, Padded>")]
pub tunnel_secret: &'a Vec<u8>,

/// Arbitrary metadata for the tunnel.
pub metadata: Option<serde_json::Value>,
}
1 change: 1 addition & 0 deletions cloudflare/src/endpoints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.
*/
pub mod account;
pub mod argo_tunnel;
pub mod cfd_tunnel;
pub mod dns;
pub mod load_balancing;
pub mod plan;
Expand Down
7 changes: 2 additions & 5 deletions cloudflare/src/framework/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ pub use spec::EndpointSpec;
#[cfg(not(feature = "endpoint-spec"))]
pub(crate) use spec::EndpointSpec;

// This is the internal-only representation. To avoid bloat from monomorphization, the query
// string, body, etc are generally not exposed publicly, though it can be exposed via the
// "endpoint-spec" feature.
mod spec {
pub mod spec {
use super::*;

/// Represents a specification for an API call that can be built into an HTTP request and sent.
Expand Down Expand Up @@ -74,6 +71,6 @@ pub trait Endpoint<ResultType: ApiResult>: spec::EndpointSpec<ResultType> {}

/// A utility function for serializing parameters into a URL query string.
#[inline]
pub(crate) fn serialize_query<Q: Serialize>(q: &Q) -> Option<String> {
pub fn serialize_query<Q: Serialize>(q: &Q) -> Option<String> {
serde_urlencoded::to_string(q).ok()
}

0 comments on commit 824fb9e

Please sign in to comment.