diff --git a/server/graphman/src/resolvers/deployment_mutation.rs b/server/graphman/src/resolvers/deployment_mutation.rs index 4b6da18b935..983897391cf 100644 --- a/server/graphman/src/resolvers/deployment_mutation.rs +++ b/server/graphman/src/resolvers/deployment_mutation.rs @@ -10,7 +10,9 @@ use crate::entities::EmptyResponse; use crate::entities::ExecutionId; use crate::resolvers::context::GraphmanContext; +mod create; mod pause; +mod remove; mod restart; mod resume; @@ -65,4 +67,18 @@ impl DeploymentMutation { restart::run_in_background(ctx, store, deployment, delay_seconds).await } + + /// Create a subgraph + pub async fn create(&self, ctx: &Context<'_>, name: String) -> Result { + let ctx = GraphmanContext::new(ctx)?; + create::run(&ctx, &name)?; + Ok(EmptyResponse::new()) + } + + /// Remove a subgraph + pub async fn remove(&self, ctx: &Context<'_>, name: String) -> Result { + let ctx = GraphmanContext::new(ctx)?; + remove::run(&ctx, &name)?; + Ok(EmptyResponse::new()) + } } diff --git a/server/graphman/src/resolvers/deployment_mutation/create.rs b/server/graphman/src/resolvers/deployment_mutation/create.rs new file mode 100644 index 00000000000..0488c094535 --- /dev/null +++ b/server/graphman/src/resolvers/deployment_mutation/create.rs @@ -0,0 +1,26 @@ +use anyhow::anyhow; +use async_graphql::Result; +use graph::prelude::SubgraphName; +use graph_store_postgres::command_support::catalog; + +use crate::resolvers::context::GraphmanContext; +use graphman::GraphmanError; + +pub fn run(ctx: &GraphmanContext, name: &String) -> Result<()> { + let primary_pool = ctx.primary_pool.get().map_err(GraphmanError::from)?; + let mut catalog_conn = catalog::Connection::new(primary_pool); + + let name = match SubgraphName::new(name) { + Ok(name) => name, + Err(_) => { + return Err(GraphmanError::Store(anyhow!( + "Subgraph name must contain only a-z, A-Z, 0-9, '-' and '_'" + )) + .into()) + } + }; + + catalog_conn.create_subgraph(&name)?; + + Ok(()) +} diff --git a/server/graphman/src/resolvers/deployment_mutation/remove.rs b/server/graphman/src/resolvers/deployment_mutation/remove.rs new file mode 100644 index 00000000000..0e5c02fea40 --- /dev/null +++ b/server/graphman/src/resolvers/deployment_mutation/remove.rs @@ -0,0 +1,27 @@ +use anyhow::anyhow; +use async_graphql::Result; +use graph::prelude::{StoreEvent, SubgraphName}; +use graph_store_postgres::command_support::catalog; + +use crate::resolvers::context::GraphmanContext; +use graphman::GraphmanError; + +pub fn run(ctx: &GraphmanContext, name: &String) -> Result<()> { + let primary_pool = ctx.primary_pool.get().map_err(GraphmanError::from)?; + let mut catalog_conn = catalog::Connection::new(primary_pool); + + let name = match SubgraphName::new(name) { + Ok(name) => name, + Err(_) => { + return Err(GraphmanError::Store(anyhow!( + "Subgraph name must contain only a-z, A-Z, 0-9, '-' and '_'" + )) + .into()) + } + }; + + let changes = catalog_conn.remove_subgraph(name)?; + catalog_conn.send_store_event(&ctx.notification_sender, &StoreEvent::new(changes))?; + + Ok(()) +} diff --git a/server/graphman/tests/deployment_mutation.rs b/server/graphman/tests/deployment_mutation.rs index dbc1f891323..927cf5bc87a 100644 --- a/server/graphman/tests/deployment_mutation.rs +++ b/server/graphman/tests/deployment_mutation.rs @@ -266,3 +266,127 @@ fn graphql_allows_tracking_restart_deployment_executions() { assert_eq!(resp, expected_resp); }); } + +#[test] +fn graphql_can_create_new_subgraph() { + run_test(|| async { + let resp = send_graphql_request( + json!({ + "query": r#"mutation CreateSubgraph { + deployment { + create(name: "subgraph_1") { + success + } + } + }"# + }), + VALID_TOKEN, + ) + .await; + + let expected_resp = json!({ + "data": { + "deployment": { + "create": { + "success": true, + } + } + } + }); + + assert_eq!(resp, expected_resp); + }); +} + +#[test] +fn graphql_cannot_create_new_subgraph_with_invalid_name() { + run_test(|| async { + let resp = send_graphql_request( + json!({ + "query": r#"mutation CreateInvalidSubgraph { + deployment { + create(name: "*@$%^subgraph") { + success + } + } + }"# + }), + VALID_TOKEN, + ) + .await; + + let success_resp = json!({ + "data": { + "deployment": { + "create": { + "success": true, + } + } + } + }); + + assert_ne!(resp, success_resp); + }); +} + +#[test] +fn graphql_can_remove_subgraph() { + run_test(|| async { + let resp = send_graphql_request( + json!({ + "query": r#"mutation RemoveSubgraph { + deployment { + remove(name: "subgraph_1") { + success + } + } + }"# + }), + VALID_TOKEN, + ) + .await; + + let expected_resp = json!({ + "data": { + "deployment": { + "remove": { + "success": true, + } + } + } + }); + + assert_eq!(resp, expected_resp); + }); +} + +#[test] +fn graphql_cannot_remove_subgraph_with_invalid_name() { + run_test(|| async { + let resp = send_graphql_request( + json!({ + "query": r#"mutation RemoveInvalidSubgraph { + deployment { + remove(name: "*@$%^subgraph") { + success + } + } + }"# + }), + VALID_TOKEN, + ) + .await; + + let success_resp = json!({ + "data": { + "deployment": { + "remove": { + "success": true, + } + } + } + }); + + assert_ne!(resp, success_resp); + }); +}