Skip to content

Commit

Permalink
feat(api): add endpoint to delete app
Browse files Browse the repository at this point in the history
  • Loading branch information
leroyguillaume committed Jul 11, 2024
1 parent 1eadd3b commit 6b17d05
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 6 deletions.
11 changes: 11 additions & 0 deletions charts/simpaas/crds/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ spec:
oneOf:
- required:
- createApp
- required:
- deleteApp
- required:
- inviteUsers
- required:
Expand All @@ -35,10 +37,19 @@ spec:
createApp:
description: Allow role to create app.
type: object
deleteApp:
description: Allow role to delete app.
properties:
name:
default: .*
description: Pattern that matches app name.
type: string
type: object
inviteUsers:
description: Allow role to invite users.
type: object
updateApp:
description: Allow roel to update app.
properties:
name:
default: .*
Expand Down
4 changes: 2 additions & 2 deletions charts/simpaas/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ api:
name: admin
permissions:
- createApp: {}
- deleteApp: {}
- inviteUsers: {}
- updateApp:
name: .*
- updateApp: {}

admin:
create: true
Expand Down
26 changes: 26 additions & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ fn create_router<
.api_route("/_health", aide::axum::routing::get(health))
.api_route("/app", aide::axum::routing::post(create_app))
.api_route("/app/:name", aide::axum::routing::put(update_app))
.api_route("/app/:name", aide::axum::routing::delete(delete_app))
.api_route(
"/auth",
aide::axum::routing::post(authenticate_with_password),
Expand Down Expand Up @@ -370,6 +371,31 @@ async fn create_app<J: JwtEncoder, K: KubeClient, M: MailSender, P: PasswordEnco
.await
}

async fn delete_app<J: JwtEncoder, K: KubeClient, M: MailSender, P: PasswordEncoder>(
headers: HeaderMap,
State(ctx): State<Arc<ApiContext<J, K, M, P>>>,
Path(name): Path<String>,
) -> Result<StatusCode> {
let (username, user) = authenticated_user(&headers, &ctx.jwt_encoder, &ctx.kube).await?;
let app = ctx
.kube
.get_app(&name)
.await?
.ok_or(Error::ResourceNotFound)?;
if app.spec.owner != username {
check_permission(&user, Action::DeleteApp(&name), &ctx.kube).await?;
}
let span = info_span!("delete_app", app.name = name,);
async {
debug!("deleting app");
ctx.kube.delete_app(&name).await?;
info!("app deleted");
Ok(StatusCode::NO_CONTENT)
}
.instrument(span)
.await
}

#[instrument(skip(api))]
async fn doc(Extension(api): Extension<OpenApi>) -> Json<OpenApi> {
Json(api)
Expand Down
29 changes: 25 additions & 4 deletions src/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub struct PermissionError(
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Action<'a> {
CreateApp,
DeleteApp(&'a str),
InviteUsers,
UpdateApp(&'a str),
}
Expand All @@ -30,6 +31,7 @@ impl Display for Action<'_> {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
Self::CreateApp => write!(f, "create_app"),
Self::DeleteApp(_) => write!(f, "delete_app"),
Self::InviteUsers => write!(f, "invite_users"),
Self::UpdateApp(_) => write!(f, "update_app"),
}
Expand Down Expand Up @@ -112,8 +114,15 @@ pub struct Ingress {
pub enum Permission {
/// Allow role to create app.
CreateApp {},
/// Allow role to delete app.
DeleteApp {
/// Pattern that matches app name.
#[serde(default = "default_perm_pattern")]
name: String,
},
/// Allow role to invite users.
InviteUsers {},
/// Allow roel to update app.
UpdateApp {
/// Pattern that matches app name.
#[serde(default = "default_perm_pattern")]
Expand All @@ -125,23 +134,35 @@ impl Permission {
pub fn allows(&self, action: Action) -> Result<bool, PermissionError> {
match self {
Self::CreateApp {} => Ok(matches!(action, Action::CreateApp)),
Self::DeleteApp { name: pattern } => {
if let Action::DeleteApp(name) = action {
Self::name_matches(name, pattern)
} else {
Ok(false)
}
}
Self::InviteUsers {} => Ok(matches!(action, Action::InviteUsers)),
Self::UpdateApp { name } => {
if let Action::UpdateApp(app) = action {
let regex = Regex::new(name)?;
Ok(regex.is_match(app))
Self::UpdateApp { name: pattern } => {
if let Action::UpdateApp(name) = action {
Self::name_matches(name, pattern)
} else {
Ok(false)
}
}
}
}

fn name_matches(name: &str, pattern: &str) -> Result<bool, PermissionError> {
let regex = Regex::new(pattern)?;
Ok(regex.is_match(name))
}
}

impl Display for Permission {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
Self::CreateApp {} => write!(f, "create_app"),
Self::DeleteApp { .. } => write!(f, "delete_app"),
Self::InviteUsers {} => write!(f, "invite_users"),
Self::UpdateApp { .. } => write!(f, "update_app"),
}
Expand Down
9 changes: 9 additions & 0 deletions src/kube/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ impl ApiKubeClient {
}

impl KubeClient for ApiKubeClient {
#[instrument(skip(self, name), fields(app.name = name))]
async fn delete_app(&self, name: &str) -> Result {
let api: Api<App> = Api::default_namespaced(self.0.clone());
let params = DeleteParams::background();
debug!("deleting app");
api.delete(name, &params).await?;
Ok(())
}

#[instrument(skip(self, token), fields(invit.token = token))]
async fn delete_invitation(&self, token: &str) -> Result {
let api: Api<Invitation> = Api::default_namespaced(self.0.clone());
Expand Down
2 changes: 2 additions & 0 deletions src/kube/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub struct DomainUsage {
}

pub trait KubeClient: Send + Sync {
fn delete_app(&self, name: &str) -> impl Future<Output = Result> + Send;

fn delete_invitation(&self, token: &str) -> impl Future<Output = Result> + Send;

fn delete_namespace(&self, namespace: &str) -> impl Future<Output = Result> + Send;
Expand Down

0 comments on commit 6b17d05

Please sign in to comment.