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 more operations & refactor #6

Merged
merged 17 commits into from
Apr 1, 2022
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ base64 = "0.13"
base64-serde = "0.6"
chrono = { version = "0.4", features = ["serde"] }
futures = "0.3"
indexmap = { version = "1", features = ["serde"] }
humantime-serde = "1"
log = "0.4"
serde = { version = "1", features = ["derive"] }
Expand Down
2 changes: 2 additions & 0 deletions src/admin/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
//! Application administration API.
pub mod v1;
156 changes: 156 additions & 0 deletions src/admin/v1/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use super::data::*;
use crate::error::ClientError;
use crate::openid::TokenProvider;
use crate::util::Client as TraitClient;
use std::fmt::Debug;
use tracing::instrument;
use url::Url;

/// A client for drogue cloud application administration API, backed by reqwest.
#[derive(Clone, Debug)]
pub struct Client<TP>
where
TP: TokenProvider,
{
client: reqwest::Client,
api_url: Url,
token_provider: TP,
}

enum AdministrationOperation {
Transfer,
Accept,
Members,
}

type ClientResult<T> = Result<T, ClientError<reqwest::Error>>;

impl<TP> TraitClient<TP> for Client<TP>
where
TP: TokenProvider,
{
fn client(&self) -> &reqwest::Client {
&self.client
}

fn token_provider(&self) -> &TP {
&self.token_provider
}
}

impl<TP> Client<TP>
where
TP: TokenProvider,
{
/// Create a new client instance.
pub fn new(client: reqwest::Client, api_url: Url, token_provider: TP) -> Self {
Self {
client,
api_url,
token_provider,
}
}

fn url(&self, application: &str, operation: AdministrationOperation) -> ClientResult<Url> {
let mut url = self.api_url.clone();

{
let mut path = url
.path_segments_mut()
.map_err(|_| ClientError::Request("Failed to get paths".into()))?;

path.extend(&["api", "admin", "v1alpha1", "apps"]);
if !application.is_empty() {
path.push(application);
}
match operation {
AdministrationOperation::Transfer => path.push("transfer-ownership"),
AdministrationOperation::Accept => path.push("accept-ownership"),
AdministrationOperation::Members => path.push("members"),
};
}

Ok(url)
}

/// Get the application members and their roles
#[instrument]
pub async fn get_members<A>(&self, application: A) -> ClientResult<Option<Members>>
where
A: AsRef<str> + Debug,
{
self.read(self.url(application.as_ref(), AdministrationOperation::Members)?)
.await
}

/// Update the application members and their roles
#[instrument]
pub async fn update_members<A>(&self, application: A, members: Members) -> ClientResult<bool>
where
A: AsRef<str> + Debug,
{
self.update(
self.url(application.as_ref(), AdministrationOperation::Members)?,
Some(members),
)
.await
}

/// Transfer the application ownership to another user
#[instrument]
pub async fn initiate_app_transfer<A, U>(
&self,
application: A,
username: U,
) -> ClientResult<bool>
where
A: AsRef<str> + Debug,
U: AsRef<str> + Debug,
{
let payload = TransferOwnership {
new_user: username.as_ref().to_string(),
};

self.update(
self.url(application.as_ref(), AdministrationOperation::Transfer)?,
Some(payload),
)
.await
}

/// Cancel the application ownership transfer
#[instrument]
pub async fn cancel_app_transfer<A>(&self, application: A) -> ClientResult<bool>
where
A: AsRef<str> + Debug,
{
self.delete(self.url(application.as_ref(), AdministrationOperation::Transfer)?)
.await
}

/// Accept the application ownership transfer
#[instrument]
pub async fn accept_app_transfer<A>(&self, application: A) -> ClientResult<bool>
where
A: AsRef<str> + Debug,
{
self.update(
self.url(application.as_ref(), AdministrationOperation::Accept)?,
None::<()>,
)
.await
}

/// Read the application ownership transfer state
#[instrument]
pub async fn read_app_transfer<A>(
&self,
application: A,
) -> ClientResult<Option<TransferOwnership>>
where
A: AsRef<str> + Debug,
{
self.read(self.url(application.as_ref(), AdministrationOperation::Transfer)?)
.await
}
}
59 changes: 59 additions & 0 deletions src/admin/v1/data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use core::fmt::{Display, Formatter};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use std::str::FromStr;

#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct TransferOwnership {
pub new_user: String,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Members {
#[serde(skip_serializing_if = "Option::is_none")]
pub resource_version: Option<String>,
#[serde(default)]
pub members: IndexMap<String, MemberEntry>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct MemberEntry {
pub role: Role,
}

#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum Role {
/// Allow everything, including changing members
Admin,
/// Allow reading and writing, but not changing members.
Manager,
/// Allow reading only.
Reader,
}

impl Display for Role {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Admin => write!(f, "Administrator"),
Self::Manager => write!(f, "Manager"),
Self::Reader => write!(f, "Reader"),
}
}
}

impl FromStr for Role {
type Err = ();

fn from_str(input: &str) -> Result<Role, Self::Err> {
match input {
"Admin" | "admin" => Ok(Role::Admin),
"Manager" | "manager" => Ok(Role::Manager),
"Reader" | "reader" => Ok(Role::Reader),
_ => Err(()),
}
}
}
7 changes: 7 additions & 0 deletions src/admin/v1/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#[cfg(feature = "reqwest")]
mod client;
mod data;

#[cfg(feature = "reqwest")]
pub use client::*;
pub use data::*;
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
//! A client for the Drogue IoT Cloud APIs.

pub mod admin;
pub mod core;
pub mod error;
pub mod meta;
#[cfg(feature = "openid")]
pub mod openid;
pub mod registry;
pub mod tokens;

mod serde;
mod translator;
mod util;

pub use translator::*;
Loading