mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-08 00:03:07 +00:00
feat: improve git sync and recompute dependents when common python script is redeployed (#2967)
* fix: allow extraConfig for agGrid to set onGridReady * depmap * depmap * depmap * depmap
This commit is contained in:
+1
@@ -48,6 +48,7 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
+1
@@ -69,6 +69,7 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT git_sync FROM workspace_settings WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "git_sync",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "266741894b9a6ed650cf999b6e5b7c797424f8291517d3abf096fe45548a0273"
|
||||
}
|
||||
+1
@@ -42,6 +42,7 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM dependency_map\n WHERE importer_path = $1 AND importer_kind = 'script'\n AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "30e9250edd0c4c6025322f51b758174435d6df4f835cb0883ba40897cb99a410"
|
||||
}
|
||||
+1
@@ -28,6 +28,7 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path)\n VALUES ($1, $2, 'script', $3)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5fbd5dfb8df9aecce023c5d482ee58efcbc23c8682a3f0ec94977eb56023ed72"
|
||||
}
|
||||
+1
@@ -62,6 +62,7 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "bool",
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
|
||||
+1
@@ -37,6 +37,7 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "DELETE FROM dependency_map WHERE workspace_id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "76d73f2fe5ac75e5033fb93d60f2ef5ab2b0b73a80dcb6a250c17c3b2f15c6b4"
|
||||
}
|
||||
+1
@@ -42,6 +42,7 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT importer_path FROM dependency_map\n WHERE imported_path = $1 AND importer_kind = 'script'\n AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "importer_path",
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "aa44311f9bae8253c6c483d122597ce7e470c6ba1905e7e877dca35d9d68968f"
|
||||
}
|
||||
+1
@@ -42,6 +42,7 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT content FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "content",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "deb2e4c21d4f9d0b9439780ca6ebee13450609b578c469cb437c5420ab520258"
|
||||
}
|
||||
+1
@@ -42,6 +42,7 @@
|
||||
"bash",
|
||||
"postgresql",
|
||||
"nativets",
|
||||
"Nativets",
|
||||
"bun",
|
||||
"mysql",
|
||||
"bigquery",
|
||||
|
||||
Generated
+16
@@ -9471,6 +9471,7 @@ dependencies = [
|
||||
"windmill-api",
|
||||
"windmill-api-client",
|
||||
"windmill-common",
|
||||
"windmill-git-sync",
|
||||
"windmill-queue",
|
||||
"windmill-worker",
|
||||
]
|
||||
@@ -9541,6 +9542,7 @@ dependencies = [
|
||||
"uuid 1.6.1",
|
||||
"windmill-audit",
|
||||
"windmill-common",
|
||||
"windmill-git-sync",
|
||||
"windmill-parser",
|
||||
"windmill-parser-py-imports",
|
||||
"windmill-queue",
|
||||
@@ -9610,6 +9612,19 @@ dependencies = [
|
||||
"uuid 1.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.235.0"
|
||||
dependencies = [
|
||||
"rsmq_async",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx 0.7.3",
|
||||
"tracing",
|
||||
"windmill-common",
|
||||
"windmill-queue",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.235.1"
|
||||
@@ -9810,6 +9825,7 @@ dependencies = [
|
||||
"uuid 1.6.1",
|
||||
"windmill-audit",
|
||||
"windmill-common",
|
||||
"windmill-git-sync",
|
||||
"windmill-parser",
|
||||
"windmill-parser-bash",
|
||||
"windmill-parser-go",
|
||||
|
||||
+5
-2
@@ -11,6 +11,7 @@ members = [
|
||||
"./windmill-worker",
|
||||
"./windmill-common",
|
||||
"./windmill-audit",
|
||||
"./windmill-git-sync",
|
||||
"./parsers/windmill-parser",
|
||||
"./parsers/windmill-parser-ts",
|
||||
"./parsers/windmill-parser-wasm",
|
||||
@@ -34,7 +35,7 @@ opt-level = 0
|
||||
incremental = true
|
||||
|
||||
[features]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise"]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "windmill-git-sync/enterprise"]
|
||||
enterprise_saml = ["windmill-api/enterprise_saml"]
|
||||
benchmark = ["windmill-api/benchmark", "windmill-worker/benchmark", "windmill-queue/benchmark"]
|
||||
flamegraph = ["windmill-common/flamegraph", "windmill-worker/flamegraph"]
|
||||
@@ -46,6 +47,7 @@ anyhow.workspace = true
|
||||
tokio.workspace = true
|
||||
dotenv.workspace = true
|
||||
windmill-common = { workspace = true, features = ["tracing_init"] }
|
||||
windmill-git-sync.workspace = true
|
||||
windmill-api.workspace = true
|
||||
windmill-worker.workspace = true
|
||||
futures.workspace = true
|
||||
@@ -83,6 +85,7 @@ windmill-queue = { path = "./windmill-queue" }
|
||||
windmill-worker = { path = "./windmill-worker" }
|
||||
windmill-common = { path = "./windmill-common" }
|
||||
windmill-audit = { path = "./windmill-audit" }
|
||||
windmill-git-sync = { path = "./windmill-git-sync" }
|
||||
windmill-parser = { path = "./parsers/windmill-parser" }
|
||||
windmill-parser-ts = { path = "./parsers/windmill-parser-ts" }
|
||||
windmill-parser-py = { path = "./parsers/windmill-parser-py" }
|
||||
@@ -153,7 +156,7 @@ swc_ecma_visit = "0.93.7"
|
||||
base64 = "0.21.0"
|
||||
hmac = "0.12.1"
|
||||
sha2 = "0.10.6"
|
||||
sqlx = { version = "^0", features = [
|
||||
sqlx = { version = "0.7.3", features = [
|
||||
"macros",
|
||||
"migrate",
|
||||
"uuid",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
DROP TABLE dependency_map;
|
||||
@@ -0,0 +1,14 @@
|
||||
-- Add up migration script here
|
||||
|
||||
CREATE TYPE IMPORTER_KIND AS ENUM ('script', 'flow', 'app');
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dependency_map (
|
||||
workspace_id VARCHAR(50) NOT NULL,
|
||||
importer_path VARCHAR(510) NOT NULL,
|
||||
importer_kind IMPORTER_KIND NOT NULL,
|
||||
imported_path VARCHAR(510) NOT NULL,
|
||||
PRIMARY KEY (workspace_id, importer_path, importer_kind, imported_path)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS dependency_map_imported_path_idx ON dependency_map (workspace_id, imported_path);
|
||||
|
||||
@@ -63,7 +63,7 @@ lazy_static! {
|
||||
|
||||
fn process_import(module: Option<String>, path: &str, level: usize) -> Vec<String> {
|
||||
if level > 0 {
|
||||
let mut imports = vec!["requests".to_string()];
|
||||
let mut imports = vec![];
|
||||
let splitted_path = path.split("/");
|
||||
let base = splitted_path
|
||||
.clone()
|
||||
@@ -78,10 +78,7 @@ fn process_import(module: Option<String>, path: &str, level: usize) -> Vec<Strin
|
||||
} else if let Some(module) = module {
|
||||
let imprt = module.split('.').next().unwrap_or("").replace("_", "-");
|
||||
if imprt == "u" || imprt == "f" {
|
||||
vec![
|
||||
"requests".to_string(),
|
||||
format!("relative:{}", module.replace(".", "/")),
|
||||
]
|
||||
vec![format!("relative:{}", module.replace(".", "/"))]
|
||||
} else {
|
||||
vec![imprt]
|
||||
}
|
||||
@@ -90,6 +87,57 @@ fn process_import(module: Option<String>, path: &str, level: usize) -> Vec<Strin
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_relative_imports(code: &str, path: &str) -> error::Result<Vec<String>> {
|
||||
let nimports = parse_code_for_imports(code, path)?;
|
||||
return Ok(nimports
|
||||
.into_iter()
|
||||
.filter_map(|x| {
|
||||
if x.starts_with("relative:") {
|
||||
Some(x.replace("relative:", ""))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect());
|
||||
}
|
||||
|
||||
fn parse_code_for_imports(code: &str, path: &str) -> error::Result<Vec<String>> {
|
||||
let code = code.split(DEF_MAIN).next().unwrap_or("");
|
||||
let ast = Suite::parse(code, "main.py").map_err(|e| {
|
||||
error::Error::ExecutionErr(format!("Error parsing code: {}", e.to_string()))
|
||||
})?;
|
||||
let nimports: Vec<String> = ast
|
||||
.into_iter()
|
||||
.filter_map(|x| match x {
|
||||
Stmt::Import(StmtImport { names, .. }) => Some(
|
||||
names
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let name = x.name.to_string();
|
||||
process_import(Some(name), path, 0)
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<String>>(),
|
||||
),
|
||||
Stmt::ImportFrom(StmtImportFrom { level: Some(i), module, .. }) if i.to_u32() > 0 => {
|
||||
Some(process_import(
|
||||
module.map(|x| x.to_string()),
|
||||
path,
|
||||
i.to_usize(),
|
||||
))
|
||||
}
|
||||
Stmt::ImportFrom(StmtImportFrom { level: _, module, .. }) => {
|
||||
Some(process_import(module.map(|x| x.to_string()), path, 0))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.filter(|x| !STDIMPORTS.contains(&x.as_str()))
|
||||
.unique()
|
||||
.collect();
|
||||
return Ok(nimports);
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
pub async fn parse_python_imports(
|
||||
code: &str,
|
||||
@@ -127,41 +175,7 @@ pub async fn parse_python_imports(
|
||||
imports.extend(lines);
|
||||
}
|
||||
|
||||
let code = code.split(DEF_MAIN).next().unwrap_or("");
|
||||
let ast = Suite::parse(code, "main.py").map_err(|e| {
|
||||
error::Error::ExecutionErr(format!("Error parsing code: {}", e.to_string()))
|
||||
})?;
|
||||
let nimports: Vec<String> = ast
|
||||
.into_iter()
|
||||
.filter_map(|x| match x {
|
||||
Stmt::Import(StmtImport { names, .. }) => Some(
|
||||
names
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
let name = x.name.to_string();
|
||||
process_import(Some(name), path, 0)
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<String>>(),
|
||||
),
|
||||
Stmt::ImportFrom(StmtImportFrom { level: Some(i), module, .. })
|
||||
if i.to_u32() > 0 =>
|
||||
{
|
||||
Some(process_import(
|
||||
module.map(|x| x.to_string()),
|
||||
path,
|
||||
i.to_usize(),
|
||||
))
|
||||
}
|
||||
Stmt::ImportFrom(StmtImportFrom { level: _, module, .. }) => {
|
||||
Some(process_import(module.map(|x| x.to_string()), path, 0))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.filter(|x| !STDIMPORTS.contains(&x.as_str()))
|
||||
.unique()
|
||||
.collect();
|
||||
let nimports = parse_code_for_imports(code, path)?;
|
||||
for n in nimports.iter() {
|
||||
let nested = if n.starts_with("relative:") {
|
||||
let rpath = n.replace("relative:", "");
|
||||
|
||||
@@ -9,7 +9,7 @@ name = "windmill_api"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
enterprise = ["windmill-queue/enterprise", "async-stripe", "windmill-audit/enterprise"]
|
||||
enterprise = ["windmill-queue/enterprise", "async-stripe", "windmill-audit/enterprise", "windmill-git-sync/enterprise"]
|
||||
enterprise_saml = ["samael"]
|
||||
benchmark = []
|
||||
|
||||
@@ -27,6 +27,7 @@ windmill-common = { workspace = true, features = [
|
||||
windmill-audit.workspace = true
|
||||
windmill-parser.workspace = true
|
||||
windmill-parser-py-imports.workspace = true
|
||||
windmill-git-sync.workspace = true
|
||||
tokio.workspace = true
|
||||
anyhow.workspace = true
|
||||
argon2.workspace = true
|
||||
|
||||
@@ -9,7 +9,6 @@ use std::collections::HashMap;
|
||||
*/
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
deployment_metadata_helpers,
|
||||
users::{require_owner_of_path, OptAuthed},
|
||||
variables::build_crypt,
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
@@ -588,12 +587,16 @@ async fn create_app(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut tx = PushIsolationLevel::Transaction(tx);
|
||||
let tx = PushIsolationLevel::Transaction(tx);
|
||||
let (dependency_job_uuid, new_tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
JobPayload::AppDependencies { path: app.path.clone(), version: v_id },
|
||||
JobPayload::AppDependencies {
|
||||
path: app.path.clone(),
|
||||
version: v_id,
|
||||
deployment_message: app.deployment_message,
|
||||
},
|
||||
PushArgs::empty(),
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
@@ -615,25 +618,7 @@ async fn create_app(
|
||||
.await?;
|
||||
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
|
||||
|
||||
tx = PushIsolationLevel::Transaction(new_tx);
|
||||
tx = deployment_metadata_helpers::handle_deployment_metadata(
|
||||
tx,
|
||||
&authed,
|
||||
&db,
|
||||
&w_id,
|
||||
deployment_metadata_helpers::DeployedObject::App { path: app.path.clone(), version: v_id },
|
||||
app.deployment_message,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match tx {
|
||||
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
|
||||
_ => {
|
||||
return Err(Error::InternalErr(
|
||||
"Expected a transaction here".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
new_tx.commit().await?;
|
||||
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
@@ -849,14 +834,18 @@ async fn update_app(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut tx: PushIsolationLevel<'_, rsmq_async::MultiplexedRsmq> =
|
||||
let tx: PushIsolationLevel<'_, rsmq_async::MultiplexedRsmq> =
|
||||
PushIsolationLevel::Transaction(tx);
|
||||
if let Some(v_id) = v_id {
|
||||
let (dependency_job_uuid, new_tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
&w_id,
|
||||
JobPayload::AppDependencies { path: npath.clone(), version: v_id },
|
||||
JobPayload::AppDependencies {
|
||||
path: npath.clone(),
|
||||
version: v_id,
|
||||
deployment_message: ns.deployment_message,
|
||||
},
|
||||
PushArgs::empty(),
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
@@ -877,26 +866,7 @@ async fn update_app(
|
||||
)
|
||||
.await?;
|
||||
tracing::info!("Pushed app dependency job {}", dependency_job_uuid);
|
||||
|
||||
tx = PushIsolationLevel::Transaction(new_tx);
|
||||
tx = deployment_metadata_helpers::handle_deployment_metadata(
|
||||
tx,
|
||||
&authed,
|
||||
&db,
|
||||
&w_id,
|
||||
deployment_metadata_helpers::DeployedObject::App { path: npath.clone(), version: v_id },
|
||||
ns.deployment_message,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
match tx {
|
||||
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
|
||||
_ => {
|
||||
return Err(Error::InternalErr(
|
||||
"Expected a transaction here".to_string(),
|
||||
));
|
||||
}
|
||||
new_tx.commit().await?;
|
||||
}
|
||||
|
||||
webhook.send_message(
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
use crate::deployment_metadata_helpers;
|
||||
use crate::{
|
||||
db::DB,
|
||||
schedule::clear_schedule,
|
||||
@@ -355,7 +354,7 @@ async fn create_flow(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut tx = PushIsolationLevel::Transaction(tx);
|
||||
let tx = PushIsolationLevel::Transaction(tx);
|
||||
let (dependency_job_uuid, mut new_tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
@@ -363,6 +362,7 @@ async fn create_flow(
|
||||
JobPayload::FlowDependencies {
|
||||
path: nf.path.clone(),
|
||||
dedicated_worker: nf.dedicated_worker,
|
||||
deployment_message: nf.deployment_message,
|
||||
},
|
||||
PushArgs::empty(),
|
||||
&authed.username,
|
||||
@@ -393,26 +393,7 @@ async fn create_flow(
|
||||
.execute(&mut new_tx)
|
||||
.await?;
|
||||
|
||||
tx = PushIsolationLevel::Transaction(new_tx);
|
||||
tx = deployment_metadata_helpers::handle_deployment_metadata(
|
||||
tx,
|
||||
&authed,
|
||||
&db,
|
||||
&w_id,
|
||||
deployment_metadata_helpers::DeployedObject::Flow { path: nf.path.clone() },
|
||||
nf.deployment_message,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match tx {
|
||||
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
|
||||
_ => {
|
||||
return Err(Error::InternalErr(
|
||||
"Expected a transaction here".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
new_tx.commit().await?;
|
||||
webhook.send_message(
|
||||
w_id.clone(),
|
||||
WebhookMessage::CreateFlow { workspace: w_id.clone(), path: nf.path.clone() },
|
||||
@@ -585,7 +566,7 @@ async fn update_flow(
|
||||
},
|
||||
);
|
||||
|
||||
let mut tx = PushIsolationLevel::Transaction(tx);
|
||||
let tx = PushIsolationLevel::Transaction(tx);
|
||||
|
||||
let (dependency_job_uuid, mut new_tx) = push(
|
||||
&db,
|
||||
@@ -594,6 +575,7 @@ async fn update_flow(
|
||||
JobPayload::FlowDependencies {
|
||||
path: nf.path.clone(),
|
||||
dedicated_worker: nf.dedicated_worker,
|
||||
deployment_message: nf.deployment_message,
|
||||
},
|
||||
PushArgs::empty(),
|
||||
&authed.username,
|
||||
@@ -631,25 +613,7 @@ async fn update_flow(
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx = PushIsolationLevel::Transaction(new_tx);
|
||||
tx = deployment_metadata_helpers::handle_deployment_metadata(
|
||||
tx,
|
||||
&authed,
|
||||
&db,
|
||||
&w_id,
|
||||
deployment_metadata_helpers::DeployedObject::Flow { path: nf.path.clone() },
|
||||
nf.deployment_message,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match tx {
|
||||
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
|
||||
_ => {
|
||||
return Err(Error::InternalErr(
|
||||
"Expected a transaction here".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
new_tx.commit().await?;
|
||||
|
||||
Ok(nf.path.to_string())
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ mod audit;
|
||||
mod capture;
|
||||
mod configs;
|
||||
mod db;
|
||||
mod deployment_metadata_helpers;
|
||||
mod drafts;
|
||||
pub mod ee;
|
||||
pub mod embeddings;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
use crate::{
|
||||
db::{ApiAuthed, DB},
|
||||
deployment_metadata_helpers,
|
||||
schedule::clear_schedule,
|
||||
users::{maybe_refresh_folders, require_owner_of_path, AuthCache},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
@@ -47,6 +46,7 @@ use windmill_common::{
|
||||
not_found_if_none, paginate, query_elems_from_hub, require_admin, Pagination, StripPath,
|
||||
},
|
||||
};
|
||||
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
|
||||
use windmill_queue::{
|
||||
self, schedule::push_scheduled_job, PushArgs, PushIsolationLevel, QueueTransaction,
|
||||
};
|
||||
@@ -325,7 +325,7 @@ async fn create_script(
|
||||
let script_path = ns.path.clone();
|
||||
let hash = ScriptHash(hash_script(&ns));
|
||||
let authed = maybe_refresh_folders(&ns.path, &w_id, authed, &db).await;
|
||||
let mut tx: QueueTransaction<'_, _> = (rsmq, user_db.begin(&authed).await?).into();
|
||||
let mut tx: QueueTransaction<'_, _> = (rsmq.clone(), user_db.begin(&authed).await?).into();
|
||||
|
||||
if sqlx::query_scalar!(
|
||||
"SELECT 1 FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
@@ -604,17 +604,10 @@ async fn create_script(
|
||||
);
|
||||
}
|
||||
|
||||
let mut tx = PushIsolationLevel::Transaction(tx);
|
||||
let tx = PushIsolationLevel::Transaction(tx);
|
||||
|
||||
let permissioned_as = username_to_permissioned_as(&authed.username);
|
||||
if needs_lock_gen {
|
||||
let dependencies = match ns.language {
|
||||
ScriptLang::Python3 => {
|
||||
windmill_parser_py_imports::parse_python_imports(&ns.content, &w_id, &ns.path, &db)
|
||||
.await?
|
||||
.join("\n")
|
||||
}
|
||||
_ => ns.content,
|
||||
};
|
||||
let tag = if ns.dedicated_worker.is_some_and(|x| x) {
|
||||
Some(format!("{}:{}", &w_id, &ns.path,))
|
||||
} else {
|
||||
@@ -626,15 +619,15 @@ async fn create_script(
|
||||
&w_id,
|
||||
JobPayload::Dependencies {
|
||||
hash,
|
||||
dependencies,
|
||||
language: ns.language,
|
||||
path: ns.path,
|
||||
dedicated_worker: ns.dedicated_worker,
|
||||
deployment_message: ns.deployment_message,
|
||||
},
|
||||
PushArgs::empty(),
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
permissioned_as,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
@@ -650,26 +643,18 @@ async fn create_script(
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tx = PushIsolationLevel::Transaction(new_tx);
|
||||
}
|
||||
|
||||
tx = deployment_metadata_helpers::handle_deployment_metadata(
|
||||
tx,
|
||||
&authed,
|
||||
&db,
|
||||
&w_id,
|
||||
deployment_metadata_helpers::DeployedObject::Script { hash: hash, path: script_path },
|
||||
ns.deployment_message,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match tx {
|
||||
PushIsolationLevel::Transaction(tx) => tx.commit().await?,
|
||||
_ => {
|
||||
return Err(Error::InternalErr(
|
||||
"Expected a transaction here".to_string(),
|
||||
));
|
||||
}
|
||||
new_tx.commit().await?;
|
||||
} else {
|
||||
handle_deployment_metadata(
|
||||
&authed.email,
|
||||
&authed.username,
|
||||
&db,
|
||||
&w_id,
|
||||
DeployedObject::Script { hash: hash, path: script_path },
|
||||
ns.deployment_message,
|
||||
rsmq,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok((StatusCode::CREATED, format!("{}", hash)))
|
||||
|
||||
@@ -44,6 +44,7 @@ use windmill_common::s3_helpers::LargeFileStorage;
|
||||
use windmill_common::schedule::Schedule;
|
||||
use windmill_common::users::username_to_permissioned_as;
|
||||
use windmill_common::worker::CLOUD_HOSTED;
|
||||
use windmill_common::workspaces::WorkspaceGitRepo;
|
||||
use windmill_common::{
|
||||
error::{to_anyhow, Error, JsonResult, Result},
|
||||
flows::Flow,
|
||||
@@ -1045,13 +1046,6 @@ struct EditGitSyncConfig {
|
||||
git_sync_settings: Option<WorkspaceGitRepo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct WorkspaceGitRepo {
|
||||
pub script_path: String,
|
||||
pub git_repo_resource_path: String,
|
||||
pub use_individual_branch: Option<bool>,
|
||||
}
|
||||
|
||||
async fn edit_git_sync_config(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
@@ -1505,6 +1499,9 @@ async fn delete_workspace(
|
||||
let mut tx = db.begin().await?;
|
||||
require_super_admin(&db, &email).await?;
|
||||
|
||||
sqlx::query!("DELETE FROM dependency_map WHERE workspace_id = $1", &w_id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
sqlx::query!("DELETE FROM queue WHERE workspace_id = $1", &w_id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
@@ -283,17 +283,19 @@ pub enum JobPayload {
|
||||
Dependencies {
|
||||
path: String,
|
||||
hash: ScriptHash,
|
||||
dependencies: String,
|
||||
language: ScriptLang,
|
||||
dedicated_worker: Option<bool>,
|
||||
deployment_message: Option<String>,
|
||||
},
|
||||
FlowDependencies {
|
||||
path: String,
|
||||
dedicated_worker: Option<bool>,
|
||||
deployment_message: Option<String>,
|
||||
},
|
||||
AppDependencies {
|
||||
path: String,
|
||||
version: i64,
|
||||
deployment_message: Option<String>,
|
||||
},
|
||||
Flow {
|
||||
path: String,
|
||||
|
||||
@@ -36,6 +36,7 @@ pub mod users;
|
||||
pub mod utils;
|
||||
pub mod variables;
|
||||
pub mod worker;
|
||||
pub mod workspaces;
|
||||
|
||||
#[cfg(feature = "tracing_init")]
|
||||
pub mod tracing_init;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
pub const SUPERADMIN_SECRET_EMAIL: &str = "superadmin_secret@windmill.dev";
|
||||
pub const SUPERADMIN_NOTIFICATION_EMAIL: &str = "superadmin_notification@windmill.dev";
|
||||
pub const SUPERADMIN_SYNC_EMAIL: &str = "superadmin_sync@windmill.dev";
|
||||
|
||||
pub fn username_to_permissioned_as(user: &str) -> String {
|
||||
if user.contains('@') {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct WorkspaceGitRepo {
|
||||
pub script_path: String,
|
||||
pub git_repo_resource_path: String,
|
||||
pub use_individual_branch: Option<bool>,
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "windmill-git-sync"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_git_sync"
|
||||
path = "./src/lib.rs"
|
||||
|
||||
[features]
|
||||
enterprise = ["windmill-queue/enterprise"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
sqlx.workspace = true
|
||||
serde_json.workspace = true
|
||||
tracing.workspace = true
|
||||
windmill-common = { workspace = true, features = ["axum"] }
|
||||
windmill-queue.workspace = true
|
||||
rsmq_async.workspace = true
|
||||
+68
-57
@@ -8,17 +8,18 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::db::ApiAuthed;
|
||||
use crate::db::DB;
|
||||
use crate::workspaces::{WorkspaceGitRepo, WorkspaceSettings};
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::users::SUPERADMIN_SYNC_EMAIL;
|
||||
use windmill_common::workspaces::WorkspaceGitRepo;
|
||||
|
||||
use serde_json::json;
|
||||
use windmill_common::error::{Error, Result};
|
||||
use windmill_common::jobs::JobPayload;
|
||||
use windmill_common::scripts::ScriptHash;
|
||||
use windmill_common::users::username_to_permissioned_as;
|
||||
use windmill_queue::PushIsolationLevel;
|
||||
|
||||
pub type DB = Pool<Postgres>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum DeployedObject {
|
||||
Script { hash: ScriptHash, path: String },
|
||||
@@ -37,13 +38,14 @@ impl DeployedObject {
|
||||
}
|
||||
|
||||
pub async fn handle_deployment_metadata<'c, R: rsmq_async::RsmqConnection + Send + 'c>(
|
||||
mut tx: PushIsolationLevel<'c, R>,
|
||||
authed: &ApiAuthed,
|
||||
email: &str,
|
||||
created_by: &str,
|
||||
db: &DB,
|
||||
w_id: &str,
|
||||
obj: DeployedObject,
|
||||
deployment_message: Option<String>,
|
||||
) -> Result<PushIsolationLevel<'c, R>> {
|
||||
rsmq: Option<R>,
|
||||
) -> Result<()> {
|
||||
let skip_git_sync = if obj.get_path().starts_with("u/") {
|
||||
tracing::debug!(
|
||||
"Ignoring {} from git sync as it's in a private user folder",
|
||||
@@ -54,10 +56,10 @@ pub async fn handle_deployment_metadata<'c, R: rsmq_async::RsmqConnection + Send
|
||||
false
|
||||
};
|
||||
|
||||
let workspace_git_repo_setting = sqlx::query_as::<_, WorkspaceSettings>(
|
||||
"SELECT * FROM workspace_settings WHERE workspace_id = $1",
|
||||
let workspace_git_repo_setting = sqlx::query_scalar!(
|
||||
"SELECT git_sync FROM workspace_settings WHERE workspace_id = $1",
|
||||
w_id
|
||||
)
|
||||
.bind(&w_id)
|
||||
.fetch_optional(db)
|
||||
.await?;
|
||||
|
||||
@@ -69,11 +71,10 @@ pub async fn handle_deployment_metadata<'c, R: rsmq_async::RsmqConnection + Send
|
||||
|
||||
let workspace_git_repo = workspace_git_repo_setting
|
||||
.unwrap()
|
||||
.git_sync
|
||||
.map(|conf| serde_json::from_value::<WorkspaceGitRepo>(conf).ok())
|
||||
.flatten();
|
||||
|
||||
let (git_sync_job_uuid, mut new_tx) = if !skip_git_sync && workspace_git_repo.is_some() {
|
||||
let git_sync_job_uuid = if !skip_git_sync && workspace_git_repo.is_some() {
|
||||
let workspace_git_repo = workspace_git_repo.unwrap();
|
||||
|
||||
let mut args: HashMap<String, serde_json::Value> = HashMap::new();
|
||||
@@ -84,39 +85,56 @@ pub async fn handle_deployment_metadata<'c, R: rsmq_async::RsmqConnection + Send
|
||||
.strip_prefix("$res:")),
|
||||
);
|
||||
|
||||
let default_commit_msg: String;
|
||||
match obj.clone() {
|
||||
let message = match obj.clone() {
|
||||
DeployedObject::Script { path, .. } => {
|
||||
args.insert("path".to_string(), json!(path.to_string()));
|
||||
default_commit_msg = format!("Script '{}' deployed", path);
|
||||
if deployment_message.as_ref().is_none()
|
||||
|| deployment_message.as_ref().is_some_and(|x| x.is_empty())
|
||||
{
|
||||
format!("Script '{}' deployed", path)
|
||||
} else {
|
||||
deployment_message.clone().unwrap()
|
||||
}
|
||||
}
|
||||
DeployedObject::Flow { path } => {
|
||||
args.insert("path".to_string(), json!(path.to_string()));
|
||||
default_commit_msg = format!("Flow '{}' deployed", path);
|
||||
if deployment_message.as_ref().is_none()
|
||||
|| deployment_message.as_ref().is_some_and(|x| x.is_empty())
|
||||
{
|
||||
format!("Flow '{}' deployed", path)
|
||||
} else {
|
||||
deployment_message.clone().unwrap()
|
||||
}
|
||||
}
|
||||
DeployedObject::App { path, .. } => {
|
||||
args.insert("path".to_string(), json!(path.to_string()));
|
||||
default_commit_msg = format!("App '{}' deployed", path);
|
||||
if deployment_message.as_ref().is_none()
|
||||
|| deployment_message.as_ref().is_some_and(|x| x.is_empty())
|
||||
{
|
||||
format!("App '{}' deployed", path)
|
||||
} else {
|
||||
deployment_message.clone().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
args.insert(
|
||||
"commit_msg".to_string(),
|
||||
json!(deployment_message.clone().unwrap_or(default_commit_msg)),
|
||||
);
|
||||
};
|
||||
|
||||
args.insert("commit_msg".to_string(), json!(message));
|
||||
args.insert(
|
||||
"use_individual_branch".to_string(),
|
||||
json!(workspace_git_repo.use_individual_branch.unwrap_or(false)),
|
||||
);
|
||||
|
||||
let tx: PushIsolationLevel<'_, R> = PushIsolationLevel::IsolatedRoot(db.clone(), rsmq);
|
||||
|
||||
let (job_uuid, new_tx) = windmill_queue::push(
|
||||
&db,
|
||||
db,
|
||||
tx,
|
||||
&w_id,
|
||||
JobPayload::DeploymentCallback { path: workspace_git_repo.script_path.clone() },
|
||||
args,
|
||||
&authed.username,
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
created_by,
|
||||
&email,
|
||||
SUPERADMIN_SYNC_EMAIL.to_string(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
@@ -132,16 +150,10 @@ pub async fn handle_deployment_metadata<'c, R: rsmq_async::RsmqConnection + Send
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
(Some(job_uuid), new_tx)
|
||||
new_tx.commit().await?;
|
||||
Some(job_uuid)
|
||||
} else {
|
||||
let new_tx = match tx {
|
||||
PushIsolationLevel::Isolated(user_db, authed, rsmq) => {
|
||||
(rsmq, user_db.begin(&authed).await?).into()
|
||||
}
|
||||
PushIsolationLevel::IsolatedRoot(db, rsmq) => (rsmq, db.begin().await?).into(),
|
||||
PushIsolationLevel::Transaction(tx) => tx,
|
||||
};
|
||||
(None, new_tx)
|
||||
None
|
||||
};
|
||||
|
||||
// We're not persisting the default commit msg as it's pretty useless. We will persist the ones manually set by users
|
||||
@@ -153,28 +165,27 @@ pub async fn handle_deployment_metadata<'c, R: rsmq_async::RsmqConnection + Send
|
||||
if deployment_message.is_some() || job_uuids.len() > 0 {
|
||||
// if the git sync job hasn't been triggered, and there is not custom deployment message, there's not point adding an entry to the table
|
||||
match obj.clone() {
|
||||
DeployedObject::Script { path, hash, .. } => {
|
||||
sqlx::query!(
|
||||
"INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)",
|
||||
w_id, path, hash.0, &job_uuids, deployment_message,
|
||||
)
|
||||
},
|
||||
DeployedObject::Flow { path } => {
|
||||
sqlx::query!(
|
||||
"INSERT INTO deployment_metadata (workspace_id, path, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path) WHERE script_hash IS NULL AND app_version IS NULL DO UPDATE SET callback_job_ids = $3, deployment_msg = $4",
|
||||
w_id, path, &job_uuids, deployment_message,
|
||||
)
|
||||
}
|
||||
DeployedObject::App { path, version } => {
|
||||
sqlx::query!(
|
||||
"INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)",
|
||||
w_id, path, version, &job_uuids, deployment_message,
|
||||
)
|
||||
}
|
||||
}.execute(&mut new_tx)
|
||||
.await?;
|
||||
DeployedObject::Script { path, hash, .. } => {
|
||||
sqlx::query!(
|
||||
"INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)",
|
||||
w_id, path, hash.0, &job_uuids, deployment_message,
|
||||
)
|
||||
},
|
||||
DeployedObject::Flow { path } => {
|
||||
sqlx::query!(
|
||||
"INSERT INTO deployment_metadata (workspace_id, path, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id, path) WHERE script_hash IS NULL AND app_version IS NULL DO UPDATE SET callback_job_ids = $3, deployment_msg = $4",
|
||||
w_id, path, &job_uuids, deployment_message,
|
||||
)
|
||||
}
|
||||
DeployedObject::App { path, version } => {
|
||||
sqlx::query!(
|
||||
"INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)",
|
||||
w_id, path, version, &job_uuids, deployment_message,
|
||||
)
|
||||
}
|
||||
}.execute(db)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx = PushIsolationLevel::Transaction(new_tx);
|
||||
return Ok(tx);
|
||||
return Ok(());
|
||||
}
|
||||
@@ -2496,21 +2496,23 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
dedicated_worker,
|
||||
None,
|
||||
),
|
||||
JobPayload::Dependencies { hash, dependencies, language, path, dedicated_worker } => (
|
||||
Some(hash.0),
|
||||
Some(path),
|
||||
Some((dependencies, None)),
|
||||
JobKind::Dependencies,
|
||||
None,
|
||||
None,
|
||||
Some(language),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
dedicated_worker,
|
||||
None,
|
||||
),
|
||||
JobPayload::FlowDependencies { path, dedicated_worker } => {
|
||||
JobPayload::Dependencies { hash, language, path, dedicated_worker, deployment_message } => {
|
||||
(
|
||||
Some(hash.0),
|
||||
Some(path),
|
||||
Some((deployment_message.unwrap_or_else(String::new), None)),
|
||||
JobKind::Dependencies,
|
||||
None,
|
||||
None,
|
||||
Some(language),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
dedicated_worker,
|
||||
None,
|
||||
)
|
||||
}
|
||||
JobPayload::FlowDependencies { path, dedicated_worker, deployment_message } => {
|
||||
let value_json = fetch_scalar_isolated!(
|
||||
sqlx::query_scalar!(
|
||||
"SELECT value FROM flow WHERE path = $1 AND workspace_id = $2",
|
||||
@@ -2528,7 +2530,7 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
(
|
||||
None,
|
||||
Some(path),
|
||||
None,
|
||||
Some((deployment_message.unwrap_or_else(String::new), None)),
|
||||
JobKind::FlowDependencies,
|
||||
Some(value.clone()),
|
||||
Some(FlowStatus::new(&value)), // this is a new flow being pushed, flow_status is set to flow_value
|
||||
@@ -2540,10 +2542,10 @@ pub async fn push<'c, T: Serialize + Send + Sync, R: rsmq_async::RsmqConnection
|
||||
None,
|
||||
)
|
||||
}
|
||||
JobPayload::AppDependencies { path, version } => (
|
||||
JobPayload::AppDependencies { path, version, deployment_message } => (
|
||||
Some(version),
|
||||
Some(path),
|
||||
None,
|
||||
Some((deployment_message.unwrap_or_else(String::new), None)),
|
||||
JobKind::AppDependencies,
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -10,7 +10,7 @@ path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
enterprise = ["windmill-queue/enterprise", "dep:gcp_auth", "dep:jsonwebtoken", "dep:pem", "dep:sha2", "dep:tiberius", "dep:tokio-util"]
|
||||
enterprise = ["windmill-queue/enterprise", "windmill-git-sync/enterprise", "dep:gcp_auth", "dep:jsonwebtoken", "dep:pem", "dep:sha2", "dep:tiberius", "dep:tokio-util"]
|
||||
benchmark = ["windmill-queue/benchmark"]
|
||||
flamegraph = []
|
||||
|
||||
@@ -31,6 +31,7 @@ windmill-parser-py-imports.workspace = true
|
||||
windmill-parser-bash.workspace = true
|
||||
windmill-parser-sql.workspace = true
|
||||
windmill-parser-graphql.workspace = true
|
||||
windmill-git-sync.workspace = true
|
||||
sqlx.workspace = true
|
||||
uuid.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
@@ -28,22 +28,23 @@ class WindmillFinder(MetaPathFinder):
|
||||
return ModuleSpec(name, WindmillLoader(name))
|
||||
elif l > 2:
|
||||
script_path = "/".join(splitted)
|
||||
import requests
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
headers = {"Authorization": f"Bearer {os.environ.get('WM_TOKEN')}"}
|
||||
url = f"{os.environ.get('BASE_INTERNAL_URL')}/api/w/{os.environ.get('WM_WORKSPACE')}/scripts/raw/p/{script_path}.py"
|
||||
|
||||
r = requests.get(
|
||||
url, headers={"Authorization": f"Bearer {os.environ.get('WM_TOKEN')}"}
|
||||
)
|
||||
|
||||
if r.status_code == 200:
|
||||
folder = os.getcwd() + "/tmp/" + "/".join(splitted[:-1])
|
||||
fullpath = folder + "/" + splitted[-1] + ".py"
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
with open(fullpath, "w+") as f:
|
||||
f.write(r.text)
|
||||
return ModuleSpec(name, SourceFileLoader(name, fullpath))
|
||||
else:
|
||||
req = urllib.request.Request(url, None, headers)
|
||||
try:
|
||||
with urllib.request.urlopen(req) as response:
|
||||
r = response.read().decode("utf-8")
|
||||
folder = os.getcwd() + "/tmp/" + "/".join(splitted[:-1])
|
||||
fullpath = folder + "/" + splitted[-1] + ".py"
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
with open(fullpath, "w+") as f:
|
||||
f.write(r)
|
||||
return ModuleSpec(name, SourceFileLoader(name, fullpath))
|
||||
except:
|
||||
# raise ImportError(f"Script {script_path} not found")
|
||||
return ModuleSpec(name, WindmillLoader(name))
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ use std::{
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
use windmill_git_sync::{handle_deployment_metadata, DeployedObject};
|
||||
use windmill_parser_py_imports::parse_relative_imports;
|
||||
|
||||
use uuid::Uuid;
|
||||
#[cfg(feature = "enterprise")]
|
||||
@@ -33,9 +35,9 @@ use windmill_common::{
|
||||
error::{self, to_anyhow, Error},
|
||||
flows::{FlowModule, FlowModuleValue, FlowValue},
|
||||
get_latest_deployed_hash_for_path,
|
||||
jobs::{JobKind, QueuedJob},
|
||||
jobs::{JobKind, JobPayload, QueuedJob},
|
||||
scripts::{get_full_hub_script_by_path, ScriptHash, ScriptLang},
|
||||
users::{SUPERADMIN_NOTIFICATION_EMAIL, SUPERADMIN_SECRET_EMAIL},
|
||||
users::{SUPERADMIN_NOTIFICATION_EMAIL, SUPERADMIN_SECRET_EMAIL, SUPERADMIN_SYNC_EMAIL},
|
||||
utils::{rd_string, StripPath},
|
||||
worker::{
|
||||
to_raw_value, to_raw_value_owned, update_ping, CLOUD_HOSTED, WORKER_CONFIG, WORKER_GROUP,
|
||||
@@ -149,7 +151,8 @@ pub async fn create_token_for_owner(
|
||||
.await?
|
||||
.unwrap_or(false)
|
||||
|| email == SUPERADMIN_SECRET_EMAIL
|
||||
|| email == SUPERADMIN_NOTIFICATION_EMAIL;
|
||||
|| email == SUPERADMIN_NOTIFICATION_EMAIL
|
||||
|| owner == SUPERADMIN_SYNC_EMAIL;
|
||||
|
||||
sqlx::query_scalar!(
|
||||
"INSERT INTO token
|
||||
@@ -2515,6 +2518,7 @@ async fn handle_queued_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>(
|
||||
worker_dir,
|
||||
base_internal_url,
|
||||
&client.get_token().await,
|
||||
rsmq.clone(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -2529,6 +2533,7 @@ async fn handle_queued_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>(
|
||||
worker_dir,
|
||||
base_internal_url,
|
||||
&client.get_token().await,
|
||||
rsmq.clone(),
|
||||
)
|
||||
.await
|
||||
.map(|()| serde_json::from_str("{}").unwrap()),
|
||||
@@ -2543,6 +2548,7 @@ async fn handle_queued_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>(
|
||||
worker_dir,
|
||||
base_internal_url,
|
||||
&client.get_token().await,
|
||||
rsmq.clone(),
|
||||
)
|
||||
.await
|
||||
.map(|()| serde_json::from_str("{}").unwrap()),
|
||||
@@ -3044,7 +3050,7 @@ mount {{
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn handle_dependency_job(
|
||||
async fn handle_dependency_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>(
|
||||
job: &QueuedJob,
|
||||
logs: &mut String,
|
||||
mem_peak: &mut i32,
|
||||
@@ -3055,7 +3061,17 @@ async fn handle_dependency_job(
|
||||
worker_dir: &str,
|
||||
base_internal_url: &str,
|
||||
token: &str,
|
||||
rsmq: Option<R>,
|
||||
) -> error::Result<Box<RawValue>> {
|
||||
let raw_code = sqlx::query_scalar!(
|
||||
"SELECT content FROM script WHERE hash = $1 AND workspace_id = $2",
|
||||
&job.script_hash.unwrap_or(ScriptHash(0)).0,
|
||||
&job.workspace_id
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await?
|
||||
.unwrap_or_else(|| "No script found at this hash".to_string());
|
||||
let script_path = job.script_path();
|
||||
let content = capture_dependency_job(
|
||||
&job.id,
|
||||
job.language.as_ref().map(|v| Ok(v)).unwrap_or_else(|| {
|
||||
@@ -3063,10 +3079,7 @@ async fn handle_dependency_job(
|
||||
"Job Language required for dependency jobs".to_owned(),
|
||||
))
|
||||
})?,
|
||||
job.raw_code
|
||||
.as_ref()
|
||||
.map(|a| a.as_str())
|
||||
.unwrap_or_else(|| "no raw code"),
|
||||
&raw_code,
|
||||
logs,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
@@ -3077,19 +3090,83 @@ async fn handle_dependency_job(
|
||||
worker_dir,
|
||||
base_internal_url,
|
||||
token,
|
||||
job.script_path(),
|
||||
script_path,
|
||||
)
|
||||
.await;
|
||||
match content {
|
||||
Ok(content) => {
|
||||
let hash = job.script_hash.unwrap_or(ScriptHash(0));
|
||||
let w_id = &job.workspace_id;
|
||||
sqlx::query!(
|
||||
"UPDATE script SET lock = $1 WHERE hash = $2 AND workspace_id = $3",
|
||||
&content,
|
||||
&job.script_hash.unwrap_or(ScriptHash(0)).0,
|
||||
&job.workspace_id
|
||||
&hash.0,
|
||||
w_id
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
let deployment_message = job.raw_code.as_ref().map(|x| x.clone());
|
||||
|
||||
if let Err(e) = handle_deployment_metadata(
|
||||
&job.email,
|
||||
&job.created_by,
|
||||
&db,
|
||||
&w_id,
|
||||
DeployedObject::Script { hash, path: script_path.to_string() },
|
||||
deployment_message.clone(),
|
||||
rsmq.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(%e, "error handling deployment metadata");
|
||||
}
|
||||
|
||||
if &job.language == &Some(ScriptLang::Python3) {
|
||||
if let Ok(relative_imports) = parse_relative_imports(&raw_code, script_path) {
|
||||
logs.push_str("\n--- RELATIVE IMPORTS ---\n\n");
|
||||
logs.push_str(&relative_imports.join("\n"));
|
||||
if !relative_imports.is_empty() {
|
||||
let mut tx = db.begin().await?;
|
||||
sqlx::query!(
|
||||
"DELETE FROM dependency_map
|
||||
WHERE importer_path = $1 AND importer_kind = 'script'
|
||||
AND workspace_id = $2",
|
||||
script_path,
|
||||
w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
for import in relative_imports {
|
||||
sqlx::query!(
|
||||
"INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path)
|
||||
VALUES ($1, $2, 'script', $3)",
|
||||
w_id,
|
||||
script_path,
|
||||
import
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
logs.push_str(&format!("{}\n", import));
|
||||
}
|
||||
tx.commit().await?;
|
||||
}
|
||||
if let Err(e) = trigger_python_dependents_to_recompute_dependencies(
|
||||
w_id,
|
||||
script_path,
|
||||
deployment_message,
|
||||
&job.email,
|
||||
&job.created_by,
|
||||
&job.permissioned_as,
|
||||
db,
|
||||
rsmq,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(%e, "error triggering python dependents to recompute dependencies");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(to_raw_value_owned(
|
||||
json!({ "success": "Successful lock file generation", "lock": content }),
|
||||
))
|
||||
@@ -3108,7 +3185,80 @@ async fn handle_dependency_job(
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_flow_dependency_job(
|
||||
async fn trigger_python_dependents_to_recompute_dependencies<
|
||||
R: rsmq_async::RsmqConnection + Send + Sync + Clone,
|
||||
>(
|
||||
w_id: &str,
|
||||
script_path: &str,
|
||||
deployment_message: Option<String>,
|
||||
email: &str,
|
||||
created_by: &str,
|
||||
permissioned_as: &str,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
rsmq: Option<R>,
|
||||
) -> error::Result<()> {
|
||||
let script_importers = sqlx::query_scalar!(
|
||||
"SELECT importer_path FROM dependency_map
|
||||
WHERE imported_path = $1 AND importer_kind = 'script'
|
||||
AND workspace_id = $2",
|
||||
script_path,
|
||||
w_id
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await?;
|
||||
for s in script_importers.iter() {
|
||||
let tx: PushIsolationLevel<'_, R> =
|
||||
PushIsolationLevel::IsolatedRoot(db.clone(), rsmq.clone());
|
||||
let r = get_latest_deployed_hash_for_path(db, w_id, s.as_str()).await;
|
||||
if let Ok(r) = r {
|
||||
let (job_uuid, new_tx) = windmill_queue::push(
|
||||
db,
|
||||
tx,
|
||||
&w_id,
|
||||
JobPayload::Dependencies {
|
||||
path: s.clone(),
|
||||
hash: r.0,
|
||||
language: r.5,
|
||||
dedicated_worker: r.6,
|
||||
deployment_message: deployment_message.clone(),
|
||||
},
|
||||
PushArgs::empty(),
|
||||
&created_by,
|
||||
email,
|
||||
permissioned_as.to_string(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
tracing::info!(
|
||||
"pushed dependency job due to common python path: {job_uuid} for path {path} with hash {hash}",
|
||||
path = s,
|
||||
hash = r.0
|
||||
);
|
||||
new_tx.commit().await?;
|
||||
} else {
|
||||
tracing::error!(
|
||||
"error getting latest deployed hash for path {path}: {err}",
|
||||
path = s,
|
||||
err = r.unwrap_err()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_flow_dependency_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>(
|
||||
job: &QueuedJob,
|
||||
logs: &mut String,
|
||||
mem_peak: &mut i32,
|
||||
@@ -3119,6 +3269,7 @@ async fn handle_flow_dependency_job(
|
||||
worker_dir: &str,
|
||||
base_internal_url: &str,
|
||||
token: &str,
|
||||
rsmq: Option<R>,
|
||||
) -> error::Result<()> {
|
||||
let job_path = job.script_path.clone().ok_or_else(|| {
|
||||
error::Error::InternalErr(
|
||||
@@ -3170,6 +3321,21 @@ async fn handle_flow_dependency_job(
|
||||
)
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
if let Err(e) = handle_deployment_metadata(
|
||||
&job.email,
|
||||
&job.created_by,
|
||||
&db,
|
||||
&job.workspace_id,
|
||||
DeployedObject::Flow { path: job_path },
|
||||
job.raw_code.as_ref().map(|x| x.clone()),
|
||||
rsmq.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(%e, "error handling deployment metadata");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3295,22 +3461,11 @@ async fn lock_modules(
|
||||
new_flow_modules.push(e);
|
||||
continue;
|
||||
};
|
||||
// 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.to_string()),
|
||||
&db,
|
||||
)
|
||||
.await?
|
||||
.join("\n"),
|
||||
_ => content.clone(),
|
||||
};
|
||||
|
||||
let new_lock = capture_dependency_job(
|
||||
&job.id,
|
||||
&language,
|
||||
&dependencies,
|
||||
&content,
|
||||
logs,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
@@ -3321,7 +3476,7 @@ async fn lock_modules(
|
||||
worker_dir,
|
||||
base_internal_url,
|
||||
token,
|
||||
job.script_path(),
|
||||
&path.clone().unwrap_or_else(|| job_path.to_string()),
|
||||
)
|
||||
.await;
|
||||
match new_lock {
|
||||
@@ -3396,24 +3551,11 @@ async fn lock_modules_app(
|
||||
.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,
|
||||
&content,
|
||||
logs,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
@@ -3498,7 +3640,7 @@ async fn lock_modules_app(
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_app_dependency_job(
|
||||
async fn handle_app_dependency_job<R: rsmq_async::RsmqConnection + Send + Sync + Clone>(
|
||||
job: &QueuedJob,
|
||||
logs: &mut String,
|
||||
mem_peak: &mut i32,
|
||||
@@ -3509,6 +3651,7 @@ async fn handle_app_dependency_job(
|
||||
worker_dir: &str,
|
||||
base_internal_url: &str,
|
||||
token: &str,
|
||||
rsmq: Option<R>,
|
||||
) -> error::Result<()> {
|
||||
let job_path = job.script_path.clone().ok_or_else(|| {
|
||||
error::Error::InternalErr(
|
||||
@@ -3558,6 +3701,41 @@ async fn handle_app_dependency_job(
|
||||
sqlx::query!("UPDATE app_version SET value = $1 WHERE id = $2", value, id,)
|
||||
.execute(db)
|
||||
.await?;
|
||||
|
||||
if let Err(e) = handle_deployment_metadata(
|
||||
&job.email,
|
||||
&job.created_by,
|
||||
&db,
|
||||
&job.workspace_id,
|
||||
DeployedObject::App { path: job_path, version: id },
|
||||
job.raw_code.as_ref().map(|x| x.clone()),
|
||||
rsmq.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(%e, "error handling deployment metadata");
|
||||
}
|
||||
|
||||
// tx = PushIsolationLevel::Transaction(new_tx);
|
||||
// tx = handle_deployment_metadata(
|
||||
// tx,
|
||||
// &authed,
|
||||
// &db,
|
||||
// &w_id,
|
||||
// DeployedObject::App { path: app.path.clone(), version: v_id },
|
||||
// app.deployment_message,
|
||||
// )
|
||||
// .await?;
|
||||
|
||||
// match tx {
|
||||
// PushIsolationLevel::Transaction(tx) => tx.commit().await?,
|
||||
// _ => {
|
||||
// return Err(Error::InternalErr(
|
||||
// "Expected a transaction here".to_string(),
|
||||
// ));
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
@@ -3582,10 +3760,18 @@ async fn capture_dependency_job(
|
||||
) -> error::Result<String> {
|
||||
match job_language {
|
||||
ScriptLang::Python3 => {
|
||||
let reqs = windmill_parser_py_imports::parse_python_imports(
|
||||
job_raw_code,
|
||||
&w_id,
|
||||
script_path,
|
||||
&db,
|
||||
)
|
||||
.await?
|
||||
.join("\n");
|
||||
create_dependencies_dir(job_dir).await;
|
||||
let req: std::result::Result<String, Error> = pip_compile(
|
||||
job_id,
|
||||
job_raw_code,
|
||||
&reqs,
|
||||
logs,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
if (currentId === id || allowConcurentRequests) {
|
||||
job = { ...maybe_job, id }
|
||||
await tick()
|
||||
if ('error' in (job.result ?? {})) {
|
||||
if ('error' in (job?.result ?? {})) {
|
||||
callbacks?.error()
|
||||
dispatch('doneError', {
|
||||
id,
|
||||
|
||||
@@ -373,7 +373,6 @@
|
||||
): Promise<string | undefined> {
|
||||
try {
|
||||
if (cancellableRun && !dynamicArgsOverride) {
|
||||
console.log('runComponent cancellable Run')
|
||||
await cancellableRun()
|
||||
} else {
|
||||
console.log('Run component')
|
||||
@@ -530,7 +529,6 @@
|
||||
|
||||
onMount(() => {
|
||||
cancellableRun = (inlineScript?: InlineScript, setRunnableJobEditorPanel?: boolean) => {
|
||||
console.log('cancellableRun', inlineScript)
|
||||
let rejectCb: (err: Error) => void
|
||||
let p: Partial<CancelablePromise<any>> = new Promise<void>((resolve, reject) => {
|
||||
rejectCb = reject
|
||||
|
||||
@@ -83,9 +83,7 @@
|
||||
<Tabs bind:selected={viewTab}>
|
||||
<Tab size="xs" value="result">Result</Tab>
|
||||
<Tab size="xs" value="logs">Logs</Tab>
|
||||
{#if job?.job_kind == 'dependencies'}
|
||||
<Tab size="xs" value="code">Code</Tab>
|
||||
{:else if job?.job_kind == 'preview'}
|
||||
{#if job?.job_kind == 'preview'}
|
||||
<Tab size="xs" value="code">Code</Tab>
|
||||
{/if}
|
||||
</Tabs>
|
||||
|
||||
@@ -59,8 +59,11 @@
|
||||
/>
|
||||
{#if can_write && !hideEditBtn}
|
||||
<div id="app-edit-btn" class="absolute bottom-4 z-50 right-4">
|
||||
<Button size="sm" startIcon={{ icon: Pen }} variant="border" href="/apps/edit/{app.path}"
|
||||
>Edit</Button
|
||||
<Button
|
||||
size="sm"
|
||||
startIcon={{ icon: Pen }}
|
||||
variant="border"
|
||||
href="/apps/edit/{app.path}?nodraft=true">Edit</Button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -512,9 +512,7 @@
|
||||
<Tab value="result">Result</Tab>
|
||||
<Tab value="logs">Logs</Tab>
|
||||
<Tab value="stats">Metrics</Tab>
|
||||
{#if job?.job_kind == 'dependencies'}
|
||||
<Tab value="code">Code</Tab>
|
||||
{:else if job?.job_kind == 'preview'}
|
||||
{#if job?.job_kind == 'preview'}
|
||||
<Tab value="code">Code</Tab>
|
||||
{/if}
|
||||
</Tabs>
|
||||
|
||||
@@ -389,8 +389,11 @@
|
||||
<Tab size="xs" value="users">
|
||||
<div class="flex gap-2 items-center my-1"> Users</div>
|
||||
</Tab>
|
||||
<Tab size="xs" value="git_sync">
|
||||
<div class="flex gap-2 items-center my-1">Git Sync</div>
|
||||
</Tab>
|
||||
<Tab size="xs" value="deploy_to">
|
||||
<div class="flex gap-2 items-center my-1"> Dev/Staging/Prod</div>
|
||||
<div class="flex gap-2 items-center my-1">Deployment UI</div>
|
||||
</Tab>
|
||||
{#if WORKSPACE_SHOW_SLACK_CMD}
|
||||
<Tab size="xs" value="slack">
|
||||
@@ -416,9 +419,6 @@
|
||||
<Tab size="xs" value="windmill_lfs">
|
||||
<div class="flex gap-2 items-center my-1"> S3 Storage </div>
|
||||
</Tab>
|
||||
<Tab size="xs" value="git_sync">
|
||||
<div class="flex gap-2 items-center my-1"> Git sync </div>
|
||||
</Tab>
|
||||
<Tab size="xs" value="export_delete">
|
||||
<div class="flex gap-2 items-center my-1"> Delete Workspace </div>
|
||||
</Tab>
|
||||
@@ -705,7 +705,7 @@
|
||||
/>
|
||||
</div>
|
||||
{:else if tab == 'windmill_lfs'}
|
||||
<PageHeader title="Windmill Large File Storage" primary={false} />
|
||||
<PageHeader title="S3 Storage" primary={false} />
|
||||
{#if !$enterpriseLicense}
|
||||
<Alert type="info" title="S3 storage it limited to 20 files in Windmill CE">
|
||||
Windmill S3 bucket browser will not work for buckets containing more than 20 files.
|
||||
@@ -755,6 +755,7 @@
|
||||
<Alert type="warning" title="Syncing workspace to Git is an EE feature">
|
||||
Automatically saving scripts to a Git repository on each deploy is a Windmill EE feature.
|
||||
</Alert>
|
||||
<div class="mb-1" />
|
||||
{/if}
|
||||
<Alert
|
||||
type="info"
|
||||
@@ -767,22 +768,24 @@
|
||||
Filtering out certain sensitive folders from the sync will be available soon.
|
||||
</Alert>
|
||||
<div class="flex mt-5 mb-1 gap-1">
|
||||
{#key s3ResourceInitialPath}
|
||||
<ResourcePicker
|
||||
resourceType="git_repository"
|
||||
initialValue={gitSyncSettings.git_repo_resource_path}
|
||||
on:change={(ev) => {
|
||||
editWindmillGitSyncSettings(ev.detail, gitSyncSettings.use_individual_branch)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
disabled={emptyString(gitSyncSettings.script_path)}
|
||||
btnClasses="w-32 text-center"
|
||||
color="dark"
|
||||
on:click={() => runGitSyncTestJob()}
|
||||
size="xs">Test connection</Button
|
||||
>
|
||||
{/key}
|
||||
{#if gitSyncSettings}
|
||||
{#key gitSyncSettings}
|
||||
<ResourcePicker
|
||||
resourceType="git_repository"
|
||||
initialValue={gitSyncSettings?.git_repo_resource_path}
|
||||
on:change={(ev) => {
|
||||
editWindmillGitSyncSettings(ev.detail, gitSyncSettings?.use_individual_branch)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
disabled={emptyString(gitSyncSettings?.script_path)}
|
||||
btnClasses="w-32 text-center"
|
||||
color="dark"
|
||||
on:click={() => runGitSyncTestJob()}
|
||||
size="xs">Test connection</Button
|
||||
>
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex mb-5 text-normal text-2xs gap-1">
|
||||
{#if gitSyncTestJob !== undefined}
|
||||
@@ -801,18 +804,20 @@
|
||||
</div>
|
||||
|
||||
<div class="flex mt-5 mb-1 gap-1">
|
||||
<Toggle
|
||||
disabled={emptyString(gitSyncSettings.git_repo_resource_path)}
|
||||
bind:checked={gitSyncSettings.use_individual_branch}
|
||||
on:change={(ev) => {
|
||||
editWindmillGitSyncSettings(gitSyncSettings.git_repo_resource_path, ev.detail)
|
||||
}}
|
||||
options={{
|
||||
right: 'Create one branch per deployed script/flow/app',
|
||||
rightTooltip:
|
||||
"If set, Windmill will create a unique branch per script/flow/app being pushed, prefixed with 'wm_deploy/'."
|
||||
}}
|
||||
/>
|
||||
{#if gitSyncSettings}
|
||||
<Toggle
|
||||
disabled={emptyString(gitSyncSettings?.git_repo_resource_path)}
|
||||
bind:checked={gitSyncSettings.use_individual_branch}
|
||||
on:change={(ev) => {
|
||||
editWindmillGitSyncSettings(gitSyncSettings.git_repo_resource_path, ev.detail)
|
||||
}}
|
||||
options={{
|
||||
right: 'Create one branch per deployed script/flow/app',
|
||||
rightTooltip:
|
||||
"If set, Windmill will create a unique branch per script/flow/app being pushed, prefixed with 'wm_deploy/'."
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="bg-surface-disabled p-4 rounded-md flex flex-col gap-1">
|
||||
@@ -841,12 +846,12 @@
|
||||
|
||||
<pre class="overflow-auto max-h-screen"
|
||||
><code
|
||||
>> wmill workspace add WORKSPACE_NAME WORKSPACE_ID WINDMILL_URL
|
||||
> echo 'u/' > .wmillignore
|
||||
> wmill sync pull --raw --skip-variables --skip-secrets --skip-resources
|
||||
> git add -A
|
||||
> git commit -m 'Initial commit'
|
||||
> git push</code
|
||||
>wmill workspace add {$workspaceStore} {$workspaceStore} {`${$page.url.protocol}//${$page.url.hostname}/`}
|
||||
echo 'u/' > .wmillignore
|
||||
wmill sync pull --raw --skip-variables --skip-secrets --skip-resources
|
||||
git add -A
|
||||
git commit -m 'Initial commit'
|
||||
git push</code
|
||||
></pre
|
||||
>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user