diff --git a/lib/backend-api/schema.graphql b/lib/backend-api/schema.graphql index 2c58375a4bb..281826450c7 100644 --- a/lib/backend-api/schema.graphql +++ b/lib/backend-api/schema.graphql @@ -43,6 +43,7 @@ type User implements Node & PackageOwner & Owner { id: ID! globalName: String! globalId: ID! + viewerCan(action: OwnerAction!): Boolean! avatar(size: Int = 80): String! isViewer: Boolean! hasUsablePassword: Boolean @@ -76,12 +77,19 @@ type User implements Node & PackageOwner & Owner { interface PackageOwner { globalName: String! globalId: ID! + viewerCan(action: OwnerAction!): Boolean! +} + +enum OwnerAction { + DEPLOY_APP + PUBLISH_PACKAGE } """An owner of a package.""" interface Owner { globalName: String! globalId: ID! + viewerCan(action: OwnerAction!): Boolean! } """ @@ -204,6 +212,7 @@ type Namespace implements Node & PackageOwner & Owner { userSet(offset: Int, before: String, after: String, first: Int, last: Int): UserConnection! globalName: String! globalId: ID! + viewerCan(action: OwnerAction!): Boolean! avatar: String! packages(offset: Int, before: String, after: String, first: Int, last: Int): PackageConnection! apps(sortBy: DeployAppsSortBy, offset: Int, before: String, after: String, first: Int, last: Int): DeployAppConnection! @@ -372,6 +381,7 @@ type Package implements Likeable & Node & PackageOwner { viewerHasLiked: Boolean! globalName: String! globalId: ID! + viewerCan(action: OwnerAction!): Boolean! alias: String displayName: String! @@ -926,6 +936,7 @@ type DeployApp implements Node & Owner { activeVersion: DeployAppVersion! globalName: String! globalId: ID! + viewerCan(action: OwnerAction!): Boolean! url: String! adminUrl: String! permalink: String! @@ -2388,6 +2399,7 @@ type Query { getPackageRelease(hash: String!): PackageWebc getPackageInstanceByVersionOrHash(name: String!, version: String, hash: String): PackageInstance categories(offset: Int, before: String, after: String, first: Int, last: Int): CategoryConnection! + viewerCan(action: OwnerAction!, ownerName: String!): Boolean! blogposts(tags: [String!], before: String, after: String, first: Int, last: Int): BlogPostConnection! getBlogpost(slug: String, featured: Boolean): BlogPost allBlogpostTags(offset: Int, before: String, after: String, first: Int, last: Int): BlogPostTagConnection diff --git a/lib/backend-api/src/query.rs b/lib/backend-api/src/query.rs index 3b5a43174b3..3ee3852852f 100644 --- a/lib/backend-api/src/query.rs +++ b/lib/backend-api/src/query.rs @@ -15,6 +15,19 @@ use crate::{ GraphQLApiFailure, WasmerClient, }; +pub async fn viewer_can_deploy_to_namespace( + client: &WasmerClient, + owner_name: &str, +) -> Result { + client + .run_graphql_strict(types::ViewerCan::build(ViewerCanVariables { + action: OwnerAction::DeployApp, + owner_name, + })) + .await + .map(|v| v.viewer_can) +} + pub async fn redeploy_app_by_id( client: &WasmerClient, app_id: impl Into, diff --git a/lib/backend-api/src/types.rs b/lib/backend-api/src/types.rs index c7ef5b2b36a..dcec89d7fae 100644 --- a/lib/backend-api/src/types.rs +++ b/lib/backend-api/src/types.rs @@ -41,6 +41,25 @@ mod queries { Viewer, } + #[derive(cynic::QueryVariables, Debug)] + pub struct ViewerCanVariables<'a> { + pub action: OwnerAction, + pub owner_name: &'a str, + } + + #[derive(cynic::QueryFragment, Debug)] + #[cynic(graphql_type = "Query", variables = "ViewerCanVariables")] + pub struct ViewerCan { + #[arguments(action: $action, ownerName: $owner_name)] + pub viewer_can: bool, + } + + #[derive(cynic::Enum, Clone, Copy, Debug)] + pub enum OwnerAction { + DeployApp, + PublishPackage, + } + #[derive(cynic::QueryVariables, Debug)] pub struct RevokeTokenVariables { pub token: String, diff --git a/lib/cli/src/commands/app/deploy.rs b/lib/cli/src/commands/app/deploy.rs index c503e74d234..6f229520247 100644 --- a/lib/cli/src/commands/app/deploy.rs +++ b/lib/cli/src/commands/app/deploy.rs @@ -279,10 +279,37 @@ impl AsyncCliCommand for CmdAppDeploy { None }; - let owner = self + let mut owner = self .get_owner(&client, &mut app_yaml, maybe_edge_app.as_ref()) .await?; + if !wasmer_api::query::viewer_can_deploy_to_namespace(&client, &owner).await? { + eprintln!("It seems you don't have access to {}", owner.bold()); + if self.non_interactive { + anyhow::bail!("Please, change the owner before deploying or check your current user with `{} whoami`.", std::env::args().next().unwrap_or("wasmer".into())); + } else { + let user = wasmer_api::query::current_user_with_namespaces(&client, None).await?; + owner = crate::utils::prompts::prompt_for_namespace( + "Who should own this app?", + None, + Some(&user), + )?; + + app_yaml + .as_mapping_mut() + .unwrap() + .insert("owner".into(), owner.clone().into()); + + if app_yaml.get("app_id").is_some() { + app_yaml.as_mapping_mut().unwrap().remove("app_id"); + } + + if app_yaml.get("name").is_some() { + app_yaml.as_mapping_mut().unwrap().remove("name"); + } + } + } + if app_yaml.get("name").is_none() && self.app_name.is_some() { app_yaml.as_mapping_mut().unwrap().insert( "name".into(),