feat: lock inline scripts for apps on deploy
This commit is contained in:
@@ -39,7 +39,8 @@
|
||||
"http",
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop"
|
||||
"noop",
|
||||
"appdependencies"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -59,6 +60,7 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
@@ -44,7 +44,8 @@
|
||||
"http",
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop"
|
||||
"noop",
|
||||
"appdependencies"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -66,6 +67,7 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
22
backend/.sqlx/query-29fce7e56bb4a874b4c1e3a6454273203aab7344abd03df4d86e4c5f5b9a394a.json
generated
Normal file
22
backend/.sqlx/query-29fce7e56bb4a874b4c1e3a6454273203aab7344abd03df4d86e4c5f5b9a394a.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT value FROM app_version WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "value",
|
||||
"type_info": "Json"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "29fce7e56bb4a874b4c1e3a6454273203aab7344abd03df4d86e4c5f5b9a394a"
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"http",
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop"
|
||||
"noop",
|
||||
"appdependencies"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
15
backend/.sqlx/query-6fb904692513c453989ffedb14b1eb737da453e9fcac6cfa098296d6c211d2a9.json
generated
Normal file
15
backend/.sqlx/query-6fb904692513c453989ffedb14b1eb737da453e9fcac6cfa098296d6c211d2a9.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE app_version SET value = $1 WHERE id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Json",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6fb904692513c453989ffedb14b1eb737da453e9fcac6cfa098296d6c211d2a9"
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
"http",
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop"
|
||||
"noop",
|
||||
"appdependencies"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -59,7 +60,8 @@
|
||||
"http",
|
||||
"graphql",
|
||||
"postgresql",
|
||||
"noop"
|
||||
"noop",
|
||||
"appdependencies"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
2
backend/migrations/20230826001320_appdependencies.up.sql
Normal file
2
backend/migrations/20230826001320_appdependencies.up.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TYPE JOB_KIND ADD VALUE IF NOT EXISTS 'appdependencies';
|
||||
@@ -1873,7 +1873,8 @@ paths:
|
||||
|
||||
/w/{workspace}/resources/get_value_interpolated/{path}:
|
||||
get:
|
||||
summary: get resource interpolated (variables and resources are fully unrolled)
|
||||
summary:
|
||||
get resource interpolated (variables and resources are fully unrolled)
|
||||
operationId: getResourceValueInterpolated
|
||||
tags:
|
||||
- resource
|
||||
@@ -2328,7 +2329,8 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
- name: first_parent_hash
|
||||
description: mask to filter scripts whom first direct parent has exact hash
|
||||
description:
|
||||
mask to filter scripts whom first direct parent has exact hash
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -2496,7 +2498,8 @@ paths:
|
||||
|
||||
/workers/custom_tags:
|
||||
get:
|
||||
summary: get all instance custom tags (tags are used to dispatch jobs to
|
||||
summary:
|
||||
get all instance custom tags (tags are used to dispatch jobs to
|
||||
different worker groups)
|
||||
operationId: getCustomTags
|
||||
tags:
|
||||
@@ -2547,7 +2550,8 @@ paths:
|
||||
|
||||
/w/{workspace}/scripts/delete/h/{hash}:
|
||||
post:
|
||||
summary: delete script by hash (erase content but keep hash, require admin)
|
||||
summary:
|
||||
delete script by hash (erase content but keep hash, require admin)
|
||||
operationId: deleteScriptByHash
|
||||
tags:
|
||||
- script
|
||||
@@ -2739,14 +2743,16 @@ paths:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: scheduled_in_secs
|
||||
description: schedule the script to execute in the number of seconds starting now
|
||||
description:
|
||||
schedule the script to execute in the number of seconds starting now
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
- $ref: "#/components/parameters/ParentJob"
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- name: invisible_to_owner
|
||||
description: make the run invisible to the the script owner (default false)
|
||||
description:
|
||||
make the run invisible to the the script owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -3624,7 +3630,8 @@ paths:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: scheduled_in_secs
|
||||
description: schedule the script to execute in the number of seconds starting now
|
||||
description:
|
||||
schedule the script to execute in the number of seconds starting now
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
@@ -3632,7 +3639,8 @@ paths:
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description: make the run invisible to the the flow owner (default false)
|
||||
description:
|
||||
make the run invisible to the the flow owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -3670,7 +3678,8 @@ paths:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: scheduled_in_secs
|
||||
description: schedule the script to execute in the number of seconds starting now
|
||||
description:
|
||||
schedule the script to execute in the number of seconds starting now
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
@@ -3678,7 +3687,8 @@ paths:
|
||||
- $ref: "#/components/parameters/NewJobId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description: make the run invisible to the the script owner (default false)
|
||||
description:
|
||||
make the run invisible to the the script owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -3709,7 +3719,8 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description: make the run invisible to the the script owner (default false)
|
||||
description:
|
||||
make the run invisible to the the script owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -3742,7 +3753,8 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/IncludeHeader"
|
||||
- name: invisible_to_owner
|
||||
description: make the run invisible to the the script owner (default false)
|
||||
description:
|
||||
make the run invisible to the the script owner (default false)
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
@@ -3935,7 +3947,8 @@ paths:
|
||||
- job
|
||||
responses:
|
||||
"200":
|
||||
description: the timestamp of the db that can be used to compute the drift
|
||||
description:
|
||||
the timestamp of the db that can be used to compute the drift
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -4168,7 +4181,8 @@ paths:
|
||||
|
||||
/w/{workspace}/jobs/resume_urls/{id}/{resume_id}:
|
||||
get:
|
||||
summary: get resume urls given a job_id, resume_id and a nonce to resume a flow
|
||||
summary:
|
||||
get resume urls given a job_id, resume_id and a nonce to resume a flow
|
||||
operationId: getResumeUrls
|
||||
tags:
|
||||
- job
|
||||
@@ -4681,7 +4695,8 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: only_member_of
|
||||
in: query
|
||||
description: only list the groups the user is member of (default false)
|
||||
description:
|
||||
only list the groups the user is member of (default false)
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
@@ -4869,7 +4884,8 @@ paths:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- name: only_member_of
|
||||
in: query
|
||||
description: only list the folders the user is member of (default false)
|
||||
description:
|
||||
only list the folders the user is member of (default false)
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
@@ -5507,7 +5523,8 @@ components:
|
||||
type: integer
|
||||
PerPage:
|
||||
name: per_page
|
||||
description: number of items to return for a given page (default 30, max 100)
|
||||
description:
|
||||
number of items to return for a given page (default 30, max 100)
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
@@ -5641,7 +5658,8 @@ components:
|
||||
type: boolean
|
||||
ArgsFilter:
|
||||
name: args
|
||||
description: filter on jobs containing those args as a json subset (@> in postgres)
|
||||
description:
|
||||
filter on jobs containing those args as a json subset (@> in postgres)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -5653,7 +5671,8 @@ components:
|
||||
type: string
|
||||
ResultFilter:
|
||||
name: result
|
||||
description: filter on jobs containing those result as a json subset (@> in postgres)
|
||||
description:
|
||||
filter on jobs containing those result as a json subset (@> in postgres)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
@@ -6016,6 +6035,7 @@ components:
|
||||
"preview",
|
||||
"dependencies",
|
||||
"flowdependencies",
|
||||
"appdependencies",
|
||||
"flow",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
@@ -6121,6 +6141,7 @@ components:
|
||||
"dependencies",
|
||||
"flow",
|
||||
"flowdependencies",
|
||||
"appdependencies",
|
||||
"flowpreview",
|
||||
"script_hub",
|
||||
"identity",
|
||||
|
||||
@@ -38,7 +38,7 @@ use windmill_common::{
|
||||
http_get_from_hub, list_elems_from_hub, not_found_if_none, paginate, Pagination, StripPath,
|
||||
},
|
||||
};
|
||||
use windmill_queue::push;
|
||||
use windmill_queue::{push, PushIsolationLevel, QueueTransaction};
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
@@ -372,14 +372,16 @@ async fn get_secret_id(
|
||||
async fn create_app(
|
||||
authed: ApiAuthed,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(mut app): Json<CreateApp>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.clone().begin(&authed).await?).into();
|
||||
|
||||
app.policy.on_behalf_of = Some(username_to_permissioned_as(&authed.username));
|
||||
app.policy.on_behalf_of_email = Some(authed.email);
|
||||
app.policy.on_behalf_of_email = Some(authed.email.clone());
|
||||
|
||||
if &app.path == "" {
|
||||
return Err(Error::BadRequest("App path cannot be empty".to_string()));
|
||||
@@ -390,7 +392,7 @@ async fn create_app(
|
||||
&app.path,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.fetch_one(&mut tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
@@ -406,7 +408,7 @@ async fn create_app(
|
||||
&app.path,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
let id = sqlx::query_scalar!(
|
||||
@@ -419,7 +421,7 @@ async fn create_app(
|
||||
json!(app.policy),
|
||||
app.draft_only,
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
|
||||
let v_id = sqlx::query_scalar!(
|
||||
@@ -431,7 +433,7 @@ async fn create_app(
|
||||
serde_json::to_string(&app.value).unwrap(),
|
||||
authed.username,
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
@@ -439,11 +441,11 @@ async fn create_app(
|
||||
v_id,
|
||||
id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&mut tx,
|
||||
&authed.username,
|
||||
"apps.create",
|
||||
ActionKind::Create,
|
||||
@@ -453,6 +455,31 @@ async fn create_app(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let tx = PushIsolationLevel::Transaction(tx);
|
||||
let (dependency_job_uuid, tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
JobPayload::AppDependencies { path: app.path.clone(), version: v_id },
|
||||
serde_json::Map::new(),
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
windmill_common::users::username_to_permissioned_as(&authed.username),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
|
||||
tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
@@ -541,8 +568,10 @@ async fn delete_app(
|
||||
|
||||
async fn update_app(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Extension(rsmq): Extension<Option<rsmq_async::MultiplexedRsmq>>,
|
||||
Path((w_id, path)): Path<(String, StripPath)>,
|
||||
Json(ns): Json<EditApp>,
|
||||
) -> Result<String> {
|
||||
@@ -550,7 +579,7 @@ async fn update_app(
|
||||
|
||||
let path = path.to_path();
|
||||
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.clone().begin(&authed).await?).into();
|
||||
|
||||
let npath = if ns.policy.is_some() || ns.path.is_some() || ns.summary.is_some() {
|
||||
let mut sqlb = SqlBuilder::update_table("app");
|
||||
@@ -567,7 +596,7 @@ async fn update_app(
|
||||
npath,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.fetch_one(&mut tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
@@ -587,7 +616,7 @@ async fn update_app(
|
||||
|
||||
if let Some(mut npolicy) = ns.policy {
|
||||
npolicy.on_behalf_of = Some(username_to_permissioned_as(&authed.username));
|
||||
npolicy.on_behalf_of_email = Some(authed.email);
|
||||
npolicy.on_behalf_of_email = Some(authed.email.clone());
|
||||
sqlb.set(
|
||||
"policy",
|
||||
&format!(
|
||||
@@ -602,18 +631,18 @@ async fn update_app(
|
||||
sqlb.returning("path");
|
||||
|
||||
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
|
||||
let npath_o: Option<String> = sqlx::query_scalar(&sql).fetch_optional(&mut *tx).await?;
|
||||
let npath_o: Option<String> = sqlx::query_scalar(&sql).fetch_optional(&mut tx).await?;
|
||||
not_found_if_none(npath_o, "App", path)?
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
if let Some(nvalue) = &ns.value {
|
||||
let v_id = if let Some(nvalue) = &ns.value {
|
||||
let app_id = sqlx::query_scalar!(
|
||||
"SELECT id FROM app WHERE path = $1 AND workspace_id = $2",
|
||||
npath,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
|
||||
let v_id = sqlx::query_scalar!(
|
||||
@@ -625,7 +654,7 @@ async fn update_app(
|
||||
serde_json::to_string(&nvalue).unwrap(),
|
||||
authed.username,
|
||||
)
|
||||
.fetch_one(&mut *tx)
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
@@ -634,20 +663,23 @@ async fn update_app(
|
||||
npath,
|
||||
w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
}
|
||||
Some(v_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM draft WHERE path = $1 AND workspace_id = $2 AND typ = 'app'",
|
||||
path,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&mut tx,
|
||||
&authed.username,
|
||||
"apps.update",
|
||||
ActionKind::Update,
|
||||
@@ -656,7 +688,39 @@ async fn update_app(
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(v_id) = v_id {
|
||||
let tx: PushIsolationLevel<'_, rsmq_async::MultiplexedRsmq> =
|
||||
PushIsolationLevel::Transaction(tx);
|
||||
|
||||
let (dependency_job_uuid, tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
JobPayload::AppDependencies { path: npath.clone(), version: v_id },
|
||||
serde_json::Map::new(),
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
windmill_common::users::username_to_permissioned_as(&authed.username),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
|
||||
tx.commit().await?;
|
||||
} else {
|
||||
tx.commit().await?;
|
||||
}
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::UpdateApp {
|
||||
|
||||
@@ -244,11 +244,6 @@ async fn create_flow(
|
||||
)
|
||||
.await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateFlow { workspace: w_id.clone(), path: nf.path.clone() },
|
||||
);
|
||||
|
||||
let tx = PushIsolationLevel::Transaction(tx);
|
||||
let (dependency_job_uuid, mut tx) = push(
|
||||
&db,
|
||||
@@ -284,6 +279,11 @@ async fn create_flow(
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateFlow { workspace: w_id.clone(), path: nf.path.clone() },
|
||||
);
|
||||
|
||||
Ok((StatusCode::CREATED, nf.path.to_string()))
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,9 @@ pub async fn audit_log<'c, E: sqlx::Executor<'c, Database = Postgres>>(
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
let _resource: Option<&str> = Some("EE only");
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
let username: &str = "redacted";
|
||||
|
||||
tracing::info!(
|
||||
operation = operation,
|
||||
action_kind = ?action_kind,
|
||||
|
||||
@@ -23,6 +23,7 @@ pub enum JobKind {
|
||||
FlowPreview,
|
||||
Identity,
|
||||
FlowDependencies,
|
||||
AppDependencies,
|
||||
Noop,
|
||||
}
|
||||
|
||||
@@ -180,6 +181,10 @@ pub enum JobPayload {
|
||||
FlowDependencies {
|
||||
path: String,
|
||||
},
|
||||
AppDependencies {
|
||||
path: String,
|
||||
version: i64,
|
||||
},
|
||||
Flow(String),
|
||||
RawFlow {
|
||||
value: FlowValue,
|
||||
|
||||
@@ -1359,6 +1359,7 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
|
||||
if usage > MAX_FREE_EXECS
|
||||
&& !matches!(job_payload, JobPayload::Dependencies { .. })
|
||||
&& !matches!(job_payload, JobPayload::FlowDependencies { .. })
|
||||
&& !matches!(job_payload, JobPayload::AppDependencies { .. })
|
||||
{
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"User {email} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
|
||||
@@ -1492,6 +1493,16 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
|
||||
None,
|
||||
)
|
||||
}
|
||||
JobPayload::AppDependencies { path, version } => (
|
||||
Some(version),
|
||||
Some(path),
|
||||
None,
|
||||
JobKind::AppDependencies,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
JobPayload::RawFlow { value, path } => (
|
||||
None,
|
||||
path,
|
||||
@@ -1700,6 +1711,7 @@ pub async fn push<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
|
||||
JobKind::Identity => "jobs.run.identity",
|
||||
JobKind::Noop => "jobs.run.noop",
|
||||
JobKind::FlowDependencies => "jobs.run.flow_dependencies",
|
||||
JobKind::AppDependencies => "jobs.run.app_dependencies",
|
||||
};
|
||||
|
||||
audit_log(
|
||||
|
||||
@@ -1079,6 +1079,11 @@ async fn handle_queued_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>(
|
||||
handle_flow_dependency_job(&job, &mut logs, job_dir, db, worker_name, worker_dir, base_internal_url, &client.get_token().await)
|
||||
.await
|
||||
.map(|()| Value::Null)
|
||||
},
|
||||
JobKind::AppDependencies => {
|
||||
handle_app_dependency_job(&job, &mut logs, job_dir, db, worker_name, worker_dir, base_internal_url, &client.get_token().await)
|
||||
.await
|
||||
.map(|()| Value::Null)
|
||||
}
|
||||
JobKind::Identity => match job.args.clone() {
|
||||
Some(Value::Object(args))
|
||||
@@ -1540,7 +1545,6 @@ async fn handle_dependency_job(
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn handle_flow_dependency_job(
|
||||
job: &QueuedJob,
|
||||
logs: &mut String,
|
||||
@@ -1563,7 +1567,7 @@ async fn handle_flow_dependency_job(
|
||||
})?;
|
||||
let mut flow = serde_json::from_value::<FlowValue>(raw_flow).map_err(to_anyhow)?;
|
||||
|
||||
flow.modules = lock_modules(flow.modules, job, logs, job_dir, db, worker_name, worker_dir, job_path.clone(), base_internal_url, token).await?;
|
||||
flow.modules = lock_modules(flow.modules, job, logs, job_dir, db, worker_name, worker_dir, &job_path, base_internal_url, token).await?;
|
||||
let new_flow_value = serde_json::to_value(flow).map_err(to_anyhow)?;
|
||||
|
||||
// Re-check cancelation to ensure we don't accidentially override a flow.
|
||||
@@ -1599,7 +1603,7 @@ async fn lock_modules(
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
worker_name: &str,
|
||||
worker_dir: &str,
|
||||
job_path: String,
|
||||
job_path: &str,
|
||||
base_internal_url: &str,
|
||||
token: &str) -> Result<Vec<FlowModule>> {
|
||||
let mut new_flow_modules = Vec::new();
|
||||
@@ -1635,7 +1639,7 @@ async fn lock_modules(
|
||||
};
|
||||
// sync with windmill-api/scripts
|
||||
let dependencies = match language {
|
||||
ScriptLang::Python3 => windmill_parser_py_imports::parse_python_imports(&content, &job.workspace_id, &path.clone().unwrap_or_else(|| job_path.clone()), &db).await?.join("\n"),
|
||||
ScriptLang::Python3 => windmill_parser_py_imports::parse_python_imports(&content, &job.workspace_id, &path.clone().unwrap_or_else(|| job_path.to_string()), &db).await?.join("\n"),
|
||||
_ => content.clone(),
|
||||
};
|
||||
let new_lock = capture_dependency_job(
|
||||
@@ -1694,6 +1698,142 @@ async fn lock_modules(
|
||||
}
|
||||
Ok(new_flow_modules)
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
async fn lock_modules_app(
|
||||
value: Value,
|
||||
job: &QueuedJob,
|
||||
logs: &mut String,
|
||||
job_dir: &str,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
worker_name: &str,
|
||||
worker_dir: &str,
|
||||
job_path: &str,
|
||||
base_internal_url: &str,
|
||||
token: &str) -> Result<Value> {
|
||||
match value {
|
||||
Value::Object(mut m) => {
|
||||
if m.contains_key("inlineScript") {
|
||||
let v = m.get_mut("inlineScript").unwrap();
|
||||
if let Some(v) = v.as_object_mut() {
|
||||
if v.contains_key("content") && v.contains_key("language") {
|
||||
if let Ok(language) = serde_json::from_value::<ScriptLang>(v.get("language").unwrap().clone()) {
|
||||
let content = v.get("content").unwrap().as_str().unwrap_or_default().to_string();
|
||||
let dependencies = match language {
|
||||
ScriptLang::Python3 => windmill_parser_py_imports::parse_python_imports(&content, &job.workspace_id, job_path, &db).await?.join("\n"),
|
||||
_ => content.clone(),
|
||||
};
|
||||
logs.push_str("Found lockable inline script. Generating lock...\n");
|
||||
let new_lock = capture_dependency_job(
|
||||
&job.id,
|
||||
&language,
|
||||
&dependencies,
|
||||
logs,
|
||||
job_dir,
|
||||
db,
|
||||
worker_name,
|
||||
&job.workspace_id,
|
||||
worker_dir,
|
||||
base_internal_url,
|
||||
token,
|
||||
job.script_path()
|
||||
)
|
||||
.await;
|
||||
match new_lock {
|
||||
Ok(new_lock) => {
|
||||
v.insert("lock".to_string(), serde_json::Value::String(new_lock));
|
||||
return Ok(Value::Object(m.clone()))
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
language = ?language,
|
||||
error = ?e,
|
||||
logs = ?logs,
|
||||
"Failed to generate flow lock for inline script"
|
||||
);
|
||||
()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (a, b) in m.clone().into_iter() {
|
||||
m.insert(
|
||||
a.clone(),
|
||||
lock_modules_app(b, job, logs, job_dir, db, worker_name, worker_dir, job_path, base_internal_url, token).await?,
|
||||
);
|
||||
}
|
||||
Ok(Value::Object(m))
|
||||
},
|
||||
Value::Array(a) => {
|
||||
let mut nv = vec![];
|
||||
for b in a.clone().into_iter() {
|
||||
nv.push(lock_modules_app(b, job, logs, job_dir, db, worker_name, worker_dir, job_path, base_internal_url, token).await?);
|
||||
}
|
||||
Ok(Value::Array(nv))
|
||||
},
|
||||
a @ _ => Ok(a),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async fn handle_app_dependency_job(
|
||||
job: &QueuedJob,
|
||||
logs: &mut String,
|
||||
job_dir: &str,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
worker_name: &str,
|
||||
worker_dir: &str,
|
||||
base_internal_url: &str,
|
||||
token: &str,
|
||||
) -> error::Result<()> {
|
||||
|
||||
let job_path = job.script_path.clone().ok_or_else(|| {
|
||||
error::Error::InternalErr(
|
||||
"Cannot resolve flow dependencies for flow without path".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let id = job.script_hash.clone().ok_or_else(|| {
|
||||
Error::InternalErr(
|
||||
"Flow Dependency requires script hash".to_owned(),
|
||||
)})?.0;
|
||||
let value = sqlx::query_scalar!("SELECT value FROM app_version WHERE id = $1", id)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
|
||||
if let Some(value) = value {
|
||||
let value = lock_modules_app(value, job, logs, job_dir, db, worker_name, worker_dir, &job_path, base_internal_url, token).await?;
|
||||
|
||||
|
||||
// Re-check cancelation to ensure we don't accidentially override a flow.
|
||||
if sqlx::query_scalar!("SELECT canceled FROM queue WHERE id = $1", job.id)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
.map(|v| Some(true) == v)
|
||||
.unwrap_or_else(|err| {
|
||||
tracing::error!(%job.id, %err, "error checking cancelation for job {0}: {err}", job.id);
|
||||
false
|
||||
})
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE app_version SET value = $1 WHERE id = $2",
|
||||
value,
|
||||
id,
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async fn capture_dependency_job(
|
||||
job_id: &Uuid,
|
||||
job_language: &ScriptLang,
|
||||
|
||||
@@ -246,7 +246,8 @@
|
||||
requestBody['raw_code'] = {
|
||||
content: inlineScript.content,
|
||||
language: inlineScript.language,
|
||||
path: inlineScript.path
|
||||
path: inlineScript.path,
|
||||
lock: inlineScript.lock
|
||||
}
|
||||
}
|
||||
} else if (runnable?.type === 'runnableByPath') {
|
||||
|
||||
@@ -262,6 +262,9 @@
|
||||
}}
|
||||
on:change={async (e) => {
|
||||
if (inlineScript && inlineScript.language != 'frontend') {
|
||||
if (inlineScript.lock) {
|
||||
inlineScript.lock = undefined
|
||||
}
|
||||
const oldSchema = JSON.stringify(inlineScript.schema)
|
||||
if (inlineScript.schema == undefined) {
|
||||
inlineScript.schema = emptySchema()
|
||||
|
||||
@@ -297,21 +297,6 @@
|
||||
{/key}
|
||||
</PanelSection>
|
||||
{/if}
|
||||
{#if Object.values(initialConfiguration).length > 0}
|
||||
<PanelSection title="Configuration">
|
||||
<InputsSpecsEditor
|
||||
id={component.id}
|
||||
inputSpecsConfiguration={initialConfiguration}
|
||||
bind:inputSpecs={componentSettings.item.data.configuration}
|
||||
userInputEnabled={false}
|
||||
/>
|
||||
</PanelSection>
|
||||
{:else}
|
||||
<div class="h-full w-full text-sm text-tertiary text-center py-8 px-2"
|
||||
>{ccomponents[component.type].name} has no configuration</div
|
||||
>
|
||||
{/if}
|
||||
|
||||
{#if componentSettings.item.data.type === 'tabscomponent'}
|
||||
<GridTab
|
||||
bind:tabs={componentSettings.item.data.tabs}
|
||||
@@ -339,6 +324,21 @@
|
||||
<TableActions id={component.id} bind:components={componentSettings.item.data.actionButtons} />
|
||||
{/if}
|
||||
|
||||
{#if Object.values(initialConfiguration).length > 0}
|
||||
<PanelSection title="Configuration">
|
||||
<InputsSpecsEditor
|
||||
id={component.id}
|
||||
inputSpecsConfiguration={initialConfiguration}
|
||||
bind:inputSpecs={componentSettings.item.data.configuration}
|
||||
userInputEnabled={false}
|
||||
/>
|
||||
</PanelSection>
|
||||
{:else}
|
||||
<div class="h-full w-full text-sm text-tertiary text-center py-8 px-2"
|
||||
>{ccomponents[component.type].name} has no configuration</div
|
||||
>
|
||||
{/if}
|
||||
|
||||
{#if (`recomputeIds` in componentSettings.item.data && Array.isArray(componentSettings.item.data.recomputeIds)) || componentSettings.item.data.type === 'buttoncomponent' || componentSettings.item.data.type === 'formcomponent' || componentSettings.item.data.type === 'formbuttoncomponent' || componentSettings.item.data.type === 'checkboxcomponent'}
|
||||
<Recompute
|
||||
bind:recomputeIds={componentSettings.item.data.recomputeIds}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<PanelSection title={`Table actions`}>
|
||||
<PanelSection title={`Table Actions`}>
|
||||
{#if components.length == 0}
|
||||
<span class="text-xs text-tertiary">No action buttons</span>
|
||||
{/if}
|
||||
|
||||
@@ -106,6 +106,7 @@ export type InlineScript = {
|
||||
language: Preview.language | 'frontend'
|
||||
path?: string
|
||||
schema?: Schema
|
||||
lock?: String
|
||||
refreshOn?: { id: string; key: string }[]
|
||||
suggestedRefreshOn?: { id: string; key: string }[]
|
||||
}
|
||||
|
||||
@@ -177,6 +177,7 @@
|
||||
JOBS_RUN_IDENTITY: 'jobs.run.identity',
|
||||
JOBS_RUN_NOOP: 'jobs.run.noop',
|
||||
JOBS_FLOW_DEPENDENCIES: 'jobs.flow_dependencies',
|
||||
JOBS_APP_DEPENDENCIES: 'jobs.app_dependencies',
|
||||
JOBS: 'jobs',
|
||||
JOBS_CANCEL: 'jobs.cancel',
|
||||
JOBS_FORCE_CANCEL: 'jobs.force_cancel',
|
||||
|
||||
@@ -53,9 +53,9 @@
|
||||
|
||||
function computeJobKinds(jobKindsCat: string | undefined): string {
|
||||
if (jobKindsCat == 'all') {
|
||||
return `${CompletedJob.job_kind.SCRIPT},${CompletedJob.job_kind.FLOW},${CompletedJob.job_kind.DEPENDENCIES},${CompletedJob.job_kind.FLOWDEPENDENCIES},${CompletedJob.job_kind.PREVIEW},${CompletedJob.job_kind.FLOWPREVIEW},${CompletedJob.job_kind.SCRIPT_HUB}`
|
||||
return `${CompletedJob.job_kind.SCRIPT},${CompletedJob.job_kind.FLOW},${CompletedJob.job_kind.DEPENDENCIES},${CompletedJob.job_kind.FLOWDEPENDENCIES},${CompletedJob.job_kind.APPDEPENDENCIES},${CompletedJob.job_kind.PREVIEW},${CompletedJob.job_kind.FLOWPREVIEW},${CompletedJob.job_kind.SCRIPT_HUB}`
|
||||
} else if (jobKindsCat == 'dependencies') {
|
||||
return `${CompletedJob.job_kind.DEPENDENCIES},${CompletedJob.job_kind.FLOWDEPENDENCIES}`
|
||||
return `${CompletedJob.job_kind.DEPENDENCIES},${CompletedJob.job_kind.FLOWDEPENDENCIES},${CompletedJob.job_kind.APPDEPENDENCIES}`
|
||||
} else if (jobKindsCat == 'previews') {
|
||||
return `${CompletedJob.job_kind.PREVIEW},${CompletedJob.job_kind.FLOWPREVIEW}`
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user