feat: add workspace specific tags

This commit is contained in:
Ruben Fiszel
2023-07-26 14:58:51 +02:00
parent 0b550fc626
commit 52f28b5173
8 changed files with 117 additions and 28 deletions
+46 -1
View File
@@ -11,6 +11,7 @@ use crate::{
users::{check_scopes, require_owner_of_path, Authed, OptAuthed},
utils::require_super_admin,
variables::get_workspace_key,
workers::{CUSTOM_TAGS, CUSTOM_TAGS_PER_WORKSPACE},
BASE_URL,
};
use anyhow::Context;
@@ -1403,6 +1404,33 @@ fn add_raw_string(
return args;
}
fn check_tag_available_for_workspace(w_id: &str, tag: &Option<String>) -> error::Result<()> {
if let Some(tag) = tag {
if tag == "" {
return Ok(());
}
let custom_tags_per_w = &*CUSTOM_TAGS_PER_WORKSPACE;
if custom_tags_per_w.0.contains(&tag.to_string()) {
Ok(())
} else if custom_tags_per_w.1.contains_key(tag)
&& custom_tags_per_w
.1
.get(tag)
.unwrap()
.contains(&w_id.to_string())
{
Ok(())
} else {
return Err(error::Error::BadRequest(format!(
"Tag {tag} cannot be used on workspace {w_id}: (CUSTOM_TAGS: {:?})",
*CUSTOM_TAGS
)));
}
} else {
Ok(())
}
}
pub async fn run_flow_by_path(
authed: Authed,
Extension(user_db): Extension<UserDB>,
@@ -1424,6 +1452,7 @@ pub async fn run_flow_by_path(
.fetch_optional(&mut tx)
.await?
.flatten();
check_tag_available_for_workspace(&w_id, &tag)?;
let scheduled_for = run_query.get_scheduled_for(tx.transaction_mut()).await?;
let args = run_query.add_include_headers(headers, args.unwrap_or_default());
let args = add_raw_string(raw_string, args);
@@ -1469,6 +1498,7 @@ pub async fn run_job_by_path(
let scheduled_for = run_query.get_scheduled_for(tx.transaction_mut()).await?;
let args = run_query.add_include_headers(headers, args.unwrap_or_default());
let args = add_raw_string(raw_string, args);
check_tag_available_for_workspace(&w_id, &tag)?;
let (uuid, tx) = push(
tx,
@@ -1669,6 +1699,7 @@ pub async fn run_wait_result_job_by_path_get(
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.clone().begin(&authed).await?).into();
let (job_payload, tag) =
script_path_to_payload(script_path, tx.transaction_mut(), &w_id).await?;
check_tag_available_for_workspace(&w_id, &tag)?;
let (uuid, tx) = push(
tx,
@@ -1798,6 +1829,7 @@ async fn run_wait_result_script_by_path_internal(
let args = run_query.add_include_headers(headers, args.unwrap_or_default());
let args = add_raw_string(raw_string, args);
check_tag_available_for_workspace(&w_id, &tag)?;
let (uuid, tx) = push(
tx,
@@ -1851,6 +1883,7 @@ pub async fn run_wait_result_script_by_hash(
let args = run_query.add_include_headers(headers, args.unwrap_or_default());
let args = add_raw_string(raw_string, args);
check_tag_available_for_workspace(&w_id, &tag)?;
let (uuid, tx) = push(
tx,
@@ -1951,6 +1984,15 @@ async fn run_wait_result_flow_by_path_internal(
let scheduled_for = run_query.get_scheduled_for(tx.transaction_mut()).await?;
let args = run_query.add_include_headers(headers, args.unwrap_or_default());
let args = add_raw_string(raw_string, args);
let tag = sqlx::query_scalar!(
"SELECT tag from flow WHERE path = $1 and workspace_id = $2",
flow_path,
w_id
)
.fetch_optional(&mut tx)
.await?
.flatten();
check_tag_available_for_workspace(&w_id, &tag)?;
let (uuid, tx) = push(
tx,
@@ -1969,7 +2011,7 @@ async fn run_wait_result_flow_by_path_internal(
false,
None,
!run_query.invisible_to_owner.unwrap_or(false),
None,
tag,
)
.await?;
tx.commit().await?;
@@ -2002,6 +2044,7 @@ async fn run_preview_job(
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let scheduled_for = run_query.get_scheduled_for(tx.transaction_mut()).await?;
let args = run_query.add_include_headers(headers, preview.args.unwrap_or_default());
check_tag_available_for_workspace(&w_id, &preview.tag)?;
let (uuid, tx) = push(
tx,
@@ -2096,6 +2139,7 @@ async fn run_preview_flow_job(
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
let scheduled_for = run_query.get_scheduled_for(tx.transaction_mut()).await?;
let args = run_query.add_include_headers(headers, raw_flow.args.unwrap_or_default());
check_tag_available_for_workspace(&w_id, &raw_flow.tag)?;
let (uuid, tx) = push(
tx,
@@ -2140,6 +2184,7 @@ pub async fn run_job_by_hash(
let scheduled_for = run_query.get_scheduled_for(tx.transaction_mut()).await?;
let args = run_query.add_include_headers(headers, args.unwrap_or_default());
let args = add_raw_string(raw_string, args);
check_tag_available_for_workspace(&w_id, &tag)?;
let (uuid, tx) = push(
tx,
+6 -2
View File
@@ -9,6 +9,7 @@
use crate::oauth2::AllClients;
use crate::saml::{SamlSsoLogin, ServiceProviderExt};
use crate::scim::has_scim_token;
use crate::tracing_init::MyOnFailure;
use crate::{
db::UserDB,
oauth2::{build_oauth_clients, SlackVerifier},
@@ -16,6 +17,7 @@ use crate::{
users::{Authed, OptAuthed},
webhook_util::WebhookShared,
};
use anyhow::Context;
use argon2::Argon2;
use axum::extract::DefaultBodyLimit;
use axum::{middleware::from_extractor, routing::get, Extension, Router};
@@ -160,7 +162,8 @@ pub async fn run_server(
TraceLayer::new_for_http()
.on_response(MyOnResponse {})
.make_span_with(MyMakeSpan {})
.on_request(()),
.on_request(())
.on_failure(MyOnFailure {}),
)
.layer(Extension(db.clone()))
.layer(Extension(rsmq))
@@ -296,7 +299,8 @@ async fn is_up_to_date() -> Result<String, AppError> {
let version = HTTP_CLIENT
.get("https://api.github.com/repos/windmill-labs/windmill/releases/latest")
.send()
.await?
.await
.context("Impossible to reach api.github")?
.json::<serde_json::Value>()
.await?
.get("tag_name")
+19 -10
View File
@@ -1,3 +1,5 @@
#![allow(non_snake_case)]
/*
* Author: Ruben Fiszel
* Copyright: Windmill Labs, Inc 2023
@@ -7,11 +9,11 @@
*/
use axum::{
extract::{Path, Query},
extract::Query,
middleware::Next,
response::{IntoResponse, Response},
routing::{get, post},
Extension, Json, Router,
routing::get,
Extension, Router,
};
use bytes::{BufMut, BytesMut};
use hyper::{header, http::HeaderValue, Request, StatusCode};
@@ -19,10 +21,16 @@ use mime_guess::mime;
use serde::{Deserialize, Serialize};
use serde_json::json;
use sql_builder::SqlBuilder;
use windmill_common::{
error::{Error, Result},
utils::not_found_if_none,
};
use windmill_common::error::{Error, Result};
#[cfg(feature = "enterprise")]
use axum::{extract::Path, Json};
#[cfg(feature = "enterprise")]
use windmill_common::utils::not_found_if_none;
#[cfg(feature = "enterprise")]
use axum::routing::post;
use crate::db::DB;
@@ -78,8 +86,6 @@ pub async fn has_scim_token<B>(request: Request<B>, next: Next<B>) -> Response {
.into_response();
}
pub type JsonScimResult<T> = std::result::Result<JsonScim<T>, Error>;
impl<T> IntoResponse for JsonScim<T>
where
T: Serialize,
@@ -170,11 +176,12 @@ pub async fn get_users(
))
}
#[cfg(feature = "enterprise")]
#[derive(Deserialize, Debug)]
pub struct CreateUser {
userName: String,
}
// #[cfg(feature = "enterprise")]
#[cfg(feature = "enterprise")]
pub async fn create_user(
Extension(db): Extension<DB>,
Json(body): Json<CreateUser>,
@@ -382,6 +389,7 @@ pub async fn update_group(
}
}
#[cfg(feature = "enterprise")]
pub async fn delete_group(Extension(db): Extension<DB>, Path(id): Path<String>) -> Result<()> {
tracing::info!("SCIM delete group: {:?}", id);
sqlx::query!("DELETE FROM email_to_igroup WHERE igroup = $1", id)
@@ -393,6 +401,7 @@ pub async fn delete_group(Extension(db): Extension<DB>, Path(id): Path<String>)
Ok(())
}
#[cfg(feature = "enterprise")]
fn convert_name(name: &str) -> String {
name.replace(" ", "_").to_lowercase()
}
-7
View File
@@ -45,13 +45,6 @@ use windmill_common::{
};
use windmill_queue::{self, schedule::push_scheduled_job, QueueTransaction};
lazy_static::lazy_static! {
pub static ref CUSTOM_TAGS: Vec<String> = std::env::var("CUSTOM_TAGS")
.ok()
.map(|x| x.split(',').map(|x| x.to_string()).filter(|x| !x.is_empty()).collect::<Vec<_>>()).unwrap_or_default();
}
const MAX_HASH_HISTORY_LENGTH_STORED: usize = 20;
#[derive(Serialize, sqlx::FromRow)]
+9 -1
View File
@@ -8,7 +8,7 @@
use ::tracing::{field, Span};
use hyper::Response;
use tower_http::trace::{MakeSpan, OnResponse};
use tower_http::trace::{MakeSpan, OnFailure, OnResponse};
#[derive(Clone)]
pub struct MyOnResponse {}
@@ -28,6 +28,14 @@ impl<B> OnResponse<B> for MyOnResponse {
}
}
#[derive(Clone)]
pub struct MyOnFailure {}
impl<B> OnFailure<B> for MyOnFailure {
fn on_failure(&mut self, _b: B, _latency: std::time::Duration, _span: &tracing::Span) {
// tracing::error!(latency = latency.as_millis(), "response")
}
}
#[derive(Clone)]
pub struct MyMakeSpan {}
+29 -1
View File
@@ -13,6 +13,8 @@ use axum::{
Json, Router,
};
use itertools::Itertools;
use regex::Regex;
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use windmill_common::{
@@ -20,6 +22,7 @@ use windmill_common::{
utils::{paginate, Pagination},
};
use std::collections::HashMap;
#[cfg(feature = "benchmark")]
use std::sync::atomic::Ordering;
#[cfg(feature = "benchmark")]
@@ -45,6 +48,31 @@ lazy_static::lazy_static! {
.ok()
.map(|x| x.split(',').map(|x| x.to_string()).collect::<Vec<_>>()).unwrap_or_default();
pub static ref CUSTOM_TAGS_PER_WORKSPACE: (Vec<String>, HashMap<String, Vec<String>>) = process_custom_tags(std::env::var("CUSTOM_TAGS")
.ok());
pub static ref ALL_TAGS: Vec<String> = [CUSTOM_TAGS_PER_WORKSPACE.0.clone(), CUSTOM_TAGS_PER_WORKSPACE.1.keys().map(|x| x.to_string()).collect_vec()].concat();
}
fn process_custom_tags(o: Option<String>) -> (Vec<String>, HashMap<String, Vec<String>>) {
let regex = Regex::new(r"^(\w+)\(((?:\w+)\+?)+\)$").unwrap();
if let Some(s) = o {
let mut global = vec![];
let mut specific: HashMap<String, Vec<String>> = HashMap::new();
for e in s.split(",") {
if let Some(cap) = regex.captures(e) {
let tag = cap.get(1).unwrap().as_str().to_string();
let workspaces = cap.get(2).unwrap().as_str().split("+");
specific.insert(tag, workspaces.map(|x| x.to_string()).collect_vec());
} else {
global.push(e.to_string());
}
}
(global, specific)
} else {
(vec![], HashMap::new())
}
}
#[derive(FromRow, Serialize, Deserialize)]
@@ -91,5 +119,5 @@ async fn toggle(Query(query): Query<EnableWorkerQuery>) -> JsonResult<bool> {
}
async fn get_custom_tags() -> Json<Vec<String>> {
Json(CUSTOM_TAGS.clone())
Json(ALL_TAGS.clone())
}
+7 -5
View File
@@ -115,11 +115,13 @@ pub struct AppError(anyhow::Error);
// Tell axum how to convert `AppError` into a response.
impl IntoResponse for AppError {
fn into_response(self) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", self.0),
)
.into_response()
let body = body::boxed(body::Full::from(self.0.to_string()));
tracing::error!(error = self.0.to_string());
axum::response::Response::builder()
.header("Content-Type", "text/plain")
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(body)
.unwrap()
}
}
@@ -1,4 +1,4 @@
use std::{fmt::{Debug}};
use std::fmt::Debug;
use futures_core::{future::BoxFuture, stream::BoxStream};
use rsmq_async::{RedisBytes, RsmqConnection};