From 824fb9ef7622303f5dc35aa1821c4c4570e07afb Mon Sep 17 00:00:00 2001 From: GoncaloGarcia Date: Mon, 3 Jun 2024 15:34:34 +0100 Subject: [PATCH] Add support for Cfd Tunnels, now that Argo Tunnel is a deprecated API --- .../src/endpoints/cfd_tunnel/create_tunnel.rs | 51 +++++++++ .../endpoints/cfd_tunnel/data_structures.rs | 103 ++++++++++++++++++ .../src/endpoints/cfd_tunnel/delete_tunnel.rs | 36 ++++++ .../src/endpoints/cfd_tunnel/list_tunnels.rs | 48 ++++++++ cloudflare/src/endpoints/cfd_tunnel/mod.rs | 8 ++ .../src/endpoints/cfd_tunnel/route_dns.rs | 39 +++++++ .../src/endpoints/cfd_tunnel/update_tunnel.rs | 53 +++++++++ cloudflare/src/endpoints/mod.rs | 1 + cloudflare/src/framework/endpoint.rs | 7 +- 9 files changed, 341 insertions(+), 5 deletions(-) create mode 100644 cloudflare/src/endpoints/cfd_tunnel/create_tunnel.rs create mode 100644 cloudflare/src/endpoints/cfd_tunnel/data_structures.rs create mode 100644 cloudflare/src/endpoints/cfd_tunnel/delete_tunnel.rs create mode 100644 cloudflare/src/endpoints/cfd_tunnel/list_tunnels.rs create mode 100644 cloudflare/src/endpoints/cfd_tunnel/mod.rs create mode 100644 cloudflare/src/endpoints/cfd_tunnel/route_dns.rs create mode 100644 cloudflare/src/endpoints/cfd_tunnel/update_tunnel.rs diff --git a/cloudflare/src/endpoints/cfd_tunnel/create_tunnel.rs b/cloudflare/src/endpoints/cfd_tunnel/create_tunnel.rs new file mode 100644 index 00000000..7172218e --- /dev/null +++ b/cloudflare/src/endpoints/cfd_tunnel/create_tunnel.rs @@ -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). +/// +#[derive(Debug)] +pub struct CreateTunnel<'a> { + pub account_identifier: &'a str, + pub params: Params<'a>, +} + +impl<'a> EndpointSpec 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 { + 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")] + pub tunnel_secret: &'a Vec, + + pub config_src: &'a ConfigurationSrc, + + /// Arbitrary metadata for the tunnel. + pub metadata: Option, +} diff --git a/cloudflare/src/endpoints/cfd_tunnel/data_structures.rs b/cloudflare/src/endpoints/cfd_tunnel/data_structures.rs new file mode 100644 index 00000000..997e04a1 --- /dev/null +++ b/cloudflare/src/endpoints/cfd_tunnel/data_structures.rs @@ -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, + pub deleted_at: Option>, + pub name: String, + pub connections: Vec, + 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, + pub deleted_at: Option>, + pub name: String, + pub connections: Vec, + pub conns_active_at: Option>, + pub conns_inactive_at: Option>, + // 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, +} + +#[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, + pub client_id: Uuid, + pub client_version: String, +} + +impl ApiResult for Tunnel {} +impl ApiResult for Vec {} + +/// 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 {} diff --git a/cloudflare/src/endpoints/cfd_tunnel/delete_tunnel.rs b/cloudflare/src/endpoints/cfd_tunnel/delete_tunnel.rs new file mode 100644 index 00000000..d7348bb4 --- /dev/null +++ b/cloudflare/src/endpoints/cfd_tunnel/delete_tunnel.rs @@ -0,0 +1,36 @@ +use crate::framework::endpoint::{serialize_query, EndpointSpec, Method}; +use serde::Serialize; + +use super::Tunnel; + +/// Delete a tunnel +/// +#[derive(Debug)] +pub struct DeleteTunnel<'a> { + pub account_identifier: &'a str, + pub tunnel_id: &'a str, + pub params: Params, +} + +impl<'a> EndpointSpec 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 { + 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, +} diff --git a/cloudflare/src/endpoints/cfd_tunnel/list_tunnels.rs b/cloudflare/src/endpoints/cfd_tunnel/list_tunnels.rs new file mode 100644 index 00000000..7562cd66 --- /dev/null +++ b/cloudflare/src/endpoints/cfd_tunnel/list_tunnels.rs @@ -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. +/// +#[derive(Debug)] +pub struct ListTunnels<'a> { + pub account_identifier: &'a str, + pub params: Params, +} + +impl<'a> EndpointSpec> 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 { + 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, + pub uuid: Option, + pub is_deleted: Option, + pub existed_at: Option>, + pub was_active_at: Option>, + pub include_prefix: Option, + pub was_inactive_at: Option>, + pub exclude_prefix: Option, + #[serde(flatten)] + pub pagination_params: Option, +} + +#[derive(Serialize, Clone, Debug)] +pub struct PaginationParams { + pub page: u64, + pub per_page: u64, +} diff --git a/cloudflare/src/endpoints/cfd_tunnel/mod.rs b/cloudflare/src/endpoints/cfd_tunnel/mod.rs new file mode 100644 index 00000000..1841ae5e --- /dev/null +++ b/cloudflare/src/endpoints/cfd_tunnel/mod.rs @@ -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::*; diff --git a/cloudflare/src/endpoints/cfd_tunnel/route_dns.rs b/cloudflare/src/endpoints/cfd_tunnel/route_dns.rs new file mode 100644 index 00000000..823101d3 --- /dev/null +++ b/cloudflare/src/endpoints/cfd_tunnel/route_dns.rs @@ -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 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 { + 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 }, +} diff --git a/cloudflare/src/endpoints/cfd_tunnel/update_tunnel.rs b/cloudflare/src/endpoints/cfd_tunnel/update_tunnel.rs new file mode 100644 index 00000000..b2c5aba8 --- /dev/null +++ b/cloudflare/src/endpoints/cfd_tunnel/update_tunnel.rs @@ -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). +/// +#[derive(Debug)] +pub struct UpdateTunnel<'a> { + pub account_identifier: &'a str, + pub tunnel_id: &'a str, + pub params: Params<'a>, +} + +impl<'a> EndpointSpec 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 { + 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")] + pub tunnel_secret: &'a Vec, + + /// Arbitrary metadata for the tunnel. + pub metadata: Option, +} diff --git a/cloudflare/src/endpoints/mod.rs b/cloudflare/src/endpoints/mod.rs index 19548a96..a423977c 100644 --- a/cloudflare/src/endpoints/mod.rs +++ b/cloudflare/src/endpoints/mod.rs @@ -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; diff --git a/cloudflare/src/framework/endpoint.rs b/cloudflare/src/framework/endpoint.rs index ce579c68..b013467d 100644 --- a/cloudflare/src/framework/endpoint.rs +++ b/cloudflare/src/framework/endpoint.rs @@ -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. @@ -74,6 +71,6 @@ pub trait Endpoint: spec::EndpointSpec {} /// A utility function for serializing parameters into a URL query string. #[inline] -pub(crate) fn serialize_query(q: &Q) -> Option { +pub fn serialize_query(q: &Q) -> Option { serde_urlencoded::to_string(q).ok() }