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

Add support for Cfd Tunnels #237

Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
name: Formatter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Check Formatting
run: cargo fmt --all -- --check
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
rust: [stable]

steps:
- uses: hecrj/setup-rust-action@v1
- uses: hecrj/setup-rust-action@v2
with:
rust-version: ${{ matrix.rust }}
- uses: actions/checkout@master
Expand Down
9 changes: 6 additions & 3 deletions cloudflare-examples/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ struct Section<'a> {
function: SectionFunction<HttpApiClient>,
}

fn print_response<T: ApiResult>(response: ApiResponse<T>) {
fn print_response<T>(response: ApiResponse<T>)
where
T: ApiResult,
{
match response {
Ok(success) => println!("Success: {success:#?}"),
Err(e) => match e {
Expand All @@ -43,9 +46,9 @@ fn print_response<T: ApiResult>(response: ApiResponse<T>) {
}

/// Sometimes you want to pipe results to jq etc
fn print_response_json<T: ApiResult>(response: ApiResponse<T>)
fn print_response_json<T>(response: ApiResponse<T>)
where
T: Serialize,
T: ApiResult + Serialize,
{
match response {
Ok(success) => println!("{}", serde_json::to_string(&success.result).unwrap()),
Expand Down
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
Loading