mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-22 00:01:34 +00:00
refactor: extract object store into dedicated crate with filesystem backend (#7996)
* refactor: extract object store code into windmill-object-store crate with filesystem backend Consolidate all object_store-dependent code from windmill-common into a new windmill-object-store crate. Add a filesystem-backed object store implementation using LocalFileSystem for dev/testing without cloud credentials. Includes 30 comprehensive tests covering render_endpoint, lfs_to_object_store_resource, duckdb_connection_settings, error mapping, and filesystem-backed integration tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * all * all * all * all * fix: fix raw_app hardcoded path, add missing ObjectStoreResource import, and add tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: move S3ModeFormat to windmill-types, make windmill-parser-sql optional, restore debug logs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * all --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Generated
+46
-31
@@ -15741,7 +15741,6 @@ dependencies = [
|
||||
"gethostname",
|
||||
"git-version",
|
||||
"lazy_static",
|
||||
"object_store",
|
||||
"once_cell",
|
||||
"prometheus",
|
||||
"rand 0.9.0",
|
||||
@@ -15777,10 +15776,10 @@ dependencies = [
|
||||
"windmill-dep-map",
|
||||
"windmill-git-sync",
|
||||
"windmill-indexer",
|
||||
"windmill-object-store",
|
||||
"windmill-operator",
|
||||
"windmill-queue",
|
||||
"windmill-runtime-nativets",
|
||||
"windmill-sandbox",
|
||||
"windmill-test-utils",
|
||||
"windmill-worker",
|
||||
"windows-service",
|
||||
@@ -15847,7 +15846,6 @@ dependencies = [
|
||||
"matchit 0.7.3",
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
"object_store",
|
||||
"openidconnect",
|
||||
"openssl",
|
||||
"pin-project",
|
||||
@@ -15917,13 +15915,13 @@ dependencies = [
|
||||
"windmill-mcp",
|
||||
"windmill-native-triggers",
|
||||
"windmill-oauth",
|
||||
"windmill-object-store",
|
||||
"windmill-parser",
|
||||
"windmill-parser-py",
|
||||
"windmill-parser-py-imports",
|
||||
"windmill-parser-sql",
|
||||
"windmill-parser-ts",
|
||||
"windmill-queue",
|
||||
"windmill-sandbox",
|
||||
"windmill-store",
|
||||
"windmill-trigger",
|
||||
"windmill-trigger-email",
|
||||
@@ -15935,6 +15933,7 @@ dependencies = [
|
||||
"windmill-trigger-postgres",
|
||||
"windmill-trigger-sqs",
|
||||
"windmill-trigger-websocket",
|
||||
"windmill-types",
|
||||
"windmill-worker",
|
||||
]
|
||||
|
||||
@@ -16275,6 +16274,7 @@ dependencies = [
|
||||
"windmill-common",
|
||||
"windmill-dep-map",
|
||||
"windmill-git-sync",
|
||||
"windmill-object-store",
|
||||
"windmill-parser-py",
|
||||
"windmill-parser-ts",
|
||||
"windmill-queue",
|
||||
@@ -16291,7 +16291,6 @@ dependencies = [
|
||||
"chrono",
|
||||
"futures",
|
||||
"lazy_static",
|
||||
"object_store",
|
||||
"regex",
|
||||
"rsa",
|
||||
"serde",
|
||||
@@ -16305,6 +16304,7 @@ dependencies = [
|
||||
"windmill-alerting",
|
||||
"windmill-api-auth",
|
||||
"windmill-common",
|
||||
"windmill-object-store",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -16383,6 +16383,7 @@ dependencies = [
|
||||
"windmill-dep-map",
|
||||
"windmill-git-sync",
|
||||
"windmill-queue",
|
||||
"windmill-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -16462,7 +16463,6 @@ dependencies = [
|
||||
"magic-crypt",
|
||||
"mail-send",
|
||||
"native-tls",
|
||||
"object_store",
|
||||
"once_cell",
|
||||
"openidconnect",
|
||||
"opentelemetry 0.27.1",
|
||||
@@ -16564,7 +16564,6 @@ dependencies = [
|
||||
"flume",
|
||||
"futures",
|
||||
"lazy_static",
|
||||
"object_store",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
@@ -16574,6 +16573,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"uuid",
|
||||
"windmill-common",
|
||||
"windmill-object-store",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -16685,6 +16685,40 @@ dependencies = [
|
||||
"windmill-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-object-store"
|
||||
version = "1.638.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"aws-config",
|
||||
"aws-credential-types",
|
||||
"aws-sdk-sts",
|
||||
"aws-smithy-types-convert",
|
||||
"axum 0.7.9",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"datafusion",
|
||||
"futures",
|
||||
"globset",
|
||||
"lazy_static",
|
||||
"object_store",
|
||||
"quick_cache",
|
||||
"reqwest 0.13.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"windmill-common",
|
||||
"windmill-parser-sql",
|
||||
"windmill-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-operator"
|
||||
version = "1.638.4"
|
||||
@@ -16874,6 +16908,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sqlparser 0.59.0",
|
||||
"windmill-parser",
|
||||
"windmill-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -16981,26 +17016,6 @@ dependencies = [
|
||||
"windmill-queue",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sandbox"
|
||||
version = "1.638.3"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"chrono",
|
||||
"flate2",
|
||||
"object_store",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"sqlx",
|
||||
"tar",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"windmill-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.638.4"
|
||||
@@ -17182,6 +17197,7 @@ dependencies = [
|
||||
"windmill-common",
|
||||
"windmill-git-sync",
|
||||
"windmill-trigger",
|
||||
"windmill-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -17346,6 +17362,7 @@ name = "windmill-types"
|
||||
version = "1.638.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.4",
|
||||
"chrono",
|
||||
"hex",
|
||||
"itertools 0.14.0",
|
||||
@@ -17383,7 +17400,6 @@ dependencies = [
|
||||
"derive_more 1.0.0",
|
||||
"dotenv",
|
||||
"eventsource-stream",
|
||||
"flate2",
|
||||
"flume",
|
||||
"futures",
|
||||
"gcp_auth",
|
||||
@@ -17403,7 +17419,6 @@ dependencies = [
|
||||
"mysql_async",
|
||||
"native-tls",
|
||||
"nix 0.27.1",
|
||||
"object_store",
|
||||
"once_cell",
|
||||
"opentelemetry 0.27.1",
|
||||
"opentelemetry-proto 0.29.0",
|
||||
@@ -17425,7 +17440,6 @@ dependencies = [
|
||||
"sha2 0.10.9",
|
||||
"sqlx",
|
||||
"tar",
|
||||
"tempfile",
|
||||
"tiberius",
|
||||
"tokio",
|
||||
"tokio-postgres 0.7.13",
|
||||
@@ -17444,6 +17458,7 @@ dependencies = [
|
||||
"windmill-jseval",
|
||||
"windmill-macros",
|
||||
"windmill-mcp",
|
||||
"windmill-object-store",
|
||||
"windmill-parser",
|
||||
"windmill-parser-bash",
|
||||
"windmill-parser-csharp",
|
||||
@@ -17461,7 +17476,7 @@ dependencies = [
|
||||
"windmill-parser-yaml",
|
||||
"windmill-queue",
|
||||
"windmill-runtime-nativets",
|
||||
"windmill-sandbox",
|
||||
"windmill-types",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
|
||||
+7
-8
@@ -7,6 +7,7 @@ edition.workspace = true
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"./windmill-object-store",
|
||||
"./windmill-api",
|
||||
"./windmill-api-scripts",
|
||||
"./windmill-api-flows",
|
||||
@@ -71,7 +72,6 @@ members = [
|
||||
"./parsers/windmill-parser-yaml", "windmill-macros", "parsers/windmill-parser-nu",
|
||||
"./windmill-test-utils",
|
||||
"./windmill-api-integration-tests",
|
||||
"./windmill-sandbox",
|
||||
]
|
||||
exclude = ["./windmill-duckdb-ffi-internal"]
|
||||
|
||||
@@ -97,19 +97,19 @@ lto = "thin"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
private = ["windmill-api/private", "windmill-api-agent-workers?/private", "windmill-autoscaling/private", "windmill-common/private", "windmill-git-sync/private", "windmill-indexer/private", "windmill-operator?/private", "windmill-queue/private", "windmill-worker/private", "windmill-sandbox/private", "windmill-test-utils/private"]
|
||||
private = ["windmill-api/private", "windmill-api-agent-workers?/private", "windmill-autoscaling/private", "windmill-common/private", "windmill-object-store/private", "windmill-git-sync/private", "windmill-indexer/private", "windmill-operator?/private", "windmill-queue/private", "windmill-worker/private", "windmill-test-utils/private"]
|
||||
agent_worker_server = ["windmill-api/agent_worker_server", "dep:windmill-api-agent-workers", "windmill-test-utils/agent_worker_server"]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "windmill-api-agent-workers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise", "windmill-sandbox/enterprise"]
|
||||
enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "windmill-api-agent-workers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise", "windmill-object-store/enterprise"]
|
||||
local_reports = ["windmill-common/local_reports"]
|
||||
enterprise_saml = ["windmill-api/enterprise_saml", "oauth2"]
|
||||
stripe = ["windmill-api/stripe"]
|
||||
benchmark = ["windmill-api/benchmark", "windmill-worker/benchmark", "windmill-queue/benchmark", "windmill-common/benchmark"]
|
||||
embedding = ["windmill-api/embedding"]
|
||||
parquet = ["windmill-api/parquet", "windmill-common/parquet", "windmill-worker/parquet", "windmill-sandbox/parquet", "dep:object_store"]
|
||||
parquet = ["windmill-api/parquet", "windmill-common/parquet", "windmill-object-store/parquet", "windmill-worker/parquet"]
|
||||
prometheus = ["windmill-common/prometheus", "windmill-api/prometheus", "windmill-worker/prometheus", "windmill-queue/prometheus", "dep:prometheus"]
|
||||
flow_testing = ["windmill-worker/flow_testing"]
|
||||
quickjs = ["windmill-worker/quickjs", "windmill-api/quickjs"]
|
||||
openidconnect = ["windmill-api/openidconnect", "windmill-common/openidconnect"]
|
||||
openidconnect = ["windmill-api/openidconnect", "windmill-common/openidconnect", "windmill-object-store/openidconnect"]
|
||||
cloud = ["windmill-queue/cloud", "windmill-worker/cloud", "windmill-common/cloud", "windmill-api/cloud"]
|
||||
jemalloc = ["windmill-common/jemalloc", "dep:tikv-jemallocator", "dep:tikv-jemalloc-sys", "dep:tikv-jemalloc-ctl"]
|
||||
tantivy = ["dep:windmill-indexer", "windmill-api/tantivy", "windmill-indexer/enterprise", "windmill-indexer/parquet", "windmill-common/tantivy", "enterprise", "parquet"]
|
||||
@@ -200,12 +200,12 @@ tokio-stream.workspace = true
|
||||
dotenv.workspace = true
|
||||
windmill-queue.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-object-store.workspace = true
|
||||
windmill-git-sync.workspace = true
|
||||
windmill-api = { workspace = true, default-features = false }
|
||||
windmill-api-agent-workers = { workspace = true, optional = true }
|
||||
windmill-api-settings.workspace = true
|
||||
windmill-worker.workspace = true
|
||||
windmill-sandbox.workspace = true
|
||||
windmill-indexer = { workspace = true, optional = true }
|
||||
windmill-autoscaling = { workspace = true, optional = true }
|
||||
windmill-operator = { workspace = true, optional = true }
|
||||
@@ -230,7 +230,6 @@ serde_derive.workspace = true
|
||||
serde_yml.workspace = true
|
||||
serde.workspace = true
|
||||
windmill-runtime-nativets = { workspace = true, optional = true }
|
||||
object_store = { workspace = true, optional = true }
|
||||
sha1 = { workspace = true, optional = true }
|
||||
constant_time_eq = { workspace = true, optional = true }
|
||||
rustls.workspace = true
|
||||
@@ -270,6 +269,7 @@ windmill-worker = { path = "./windmill-worker" }
|
||||
windmill-dep-map = { path = "./windmill-dep-map" }
|
||||
windmill-types = { path = "./windmill-types" }
|
||||
windmill-common = { path = "./windmill-common", default-features = false }
|
||||
windmill-object-store = { path = "./windmill-object-store" }
|
||||
windmill-audit = { path = "./windmill-audit" }
|
||||
windmill-git-sync = { path = "./windmill-git-sync" }
|
||||
windmill-autoscaling = { path = "./windmill-autoscaling" }
|
||||
@@ -330,7 +330,6 @@ windmill-jseval = { path = "./windmill-jseval" }
|
||||
windmill-runtime-nativets = { path = "./windmill-runtime-nativets" }
|
||||
windmill-api-client = { path = "./windmill-api-client" }
|
||||
windmill-test-utils = { path = "./windmill-test-utils" }
|
||||
windmill-sandbox = { path = "./windmill-sandbox" }
|
||||
|
||||
reqwest-retry = "^0"
|
||||
reqwest-middleware = { version = "^0", features = ["json"] }
|
||||
|
||||
@@ -1 +1 @@
|
||||
592848d59ca2304926fb2bd85d000668a7f46a77
|
||||
0eccae6a9a9ecde09816cd4d88ca9ab305659e4c
|
||||
|
||||
@@ -16,6 +16,7 @@ regex.workspace = true
|
||||
|
||||
[dependencies]
|
||||
windmill-parser.workspace = true
|
||||
windmill-types.workspace = true
|
||||
anyhow.workspace = true
|
||||
lazy_static.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -141,19 +141,7 @@ pub fn parse_db_resource(code: &str) -> Option<String> {
|
||||
cap.map(|x| x.get(1).map(|x| x.as_str().to_string()).unwrap())
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum S3ModeFormat {
|
||||
Json,
|
||||
Csv,
|
||||
Parquet,
|
||||
}
|
||||
pub fn s3_mode_extension(format: S3ModeFormat) -> &'static str {
|
||||
match format {
|
||||
S3ModeFormat::Json => "json",
|
||||
S3ModeFormat::Csv => "csv",
|
||||
S3ModeFormat::Parquet => "parquet",
|
||||
}
|
||||
}
|
||||
pub use windmill_types::s3::{s3_mode_extension, S3ModeFormat};
|
||||
pub struct S3ModeArgs {
|
||||
pub prefix: Option<String>,
|
||||
pub storage: Option<String>,
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ use crate::monitor::{
|
||||
};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::s3_helpers::reload_object_store_setting;
|
||||
use windmill_object_store::reload_object_store_setting;
|
||||
|
||||
const DEFAULT_NUM_WORKERS: usize = 1;
|
||||
const DEFAULT_PORT: u16 = 8000;
|
||||
|
||||
@@ -40,7 +40,7 @@ use windmill_common::ee_oss::{jobs_waiting_alerts, worker_groups_alerts};
|
||||
#[cfg(feature = "oauth2")]
|
||||
use windmill_common::global_settings::OAUTH_SETTING;
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::s3_helpers::reload_object_store_setting;
|
||||
use windmill_object_store::reload_object_store_setting;
|
||||
use windmill_common::{
|
||||
agent_workers::DECODED_AGENT_TOKEN,
|
||||
apps::APP_WORKSPACED_ROUTE,
|
||||
@@ -95,7 +95,7 @@ use windmill_worker::{
|
||||
};
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::s3_helpers::ObjectStoreReload;
|
||||
use windmill_object_store::ObjectStoreReload;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use crate::ee_oss::verify_license_key;
|
||||
@@ -712,7 +712,7 @@ async fn send_log_file_to_object_store(
|
||||
}
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
let s3_client = windmill_common::s3_helpers::get_object_store().await;
|
||||
let s3_client = windmill_object_store::get_object_store().await;
|
||||
#[cfg(feature = "parquet")]
|
||||
if let Some(s3_client) = s3_client {
|
||||
let path = std::path::Path::new(TMP_WINDMILL_LOGS_SERVICE)
|
||||
@@ -725,7 +725,7 @@ async fn send_log_file_to_object_store(
|
||||
tracing::error!("Error reading log file: {:?}", e);
|
||||
return;
|
||||
}
|
||||
let path = object_store::path::Path::from_url_path(format!(
|
||||
let path = windmill_object_store::object_store_reexports::Path::from_url_path(format!(
|
||||
"{}{hostname}/{highest_file}",
|
||||
windmill_common::tracing_init::LOGS_SERVICE
|
||||
));
|
||||
@@ -1174,7 +1174,7 @@ async fn delete_log_files_from_disk_and_store(
|
||||
_s3_prefix: &str,
|
||||
) {
|
||||
#[cfg(feature = "parquet")]
|
||||
let os = windmill_common::s3_helpers::get_object_store().await;
|
||||
let os = windmill_object_store::get_object_store().await;
|
||||
#[cfg(not(feature = "parquet"))]
|
||||
let os: Option<()> = None;
|
||||
|
||||
@@ -1204,7 +1204,7 @@ async fn delete_log_files_from_disk_and_store(
|
||||
#[cfg(feature = "parquet")]
|
||||
if _should_del_from_store {
|
||||
if let Some(os) = _os2 {
|
||||
let p = object_store::path::Path::from(format!("{}{}", _s3_prefix, path));
|
||||
let p = windmill_object_store::object_store_reexports::Path::from(format!("{}{}", _s3_prefix, path));
|
||||
if let Err(e) = os.delete(&p).await {
|
||||
tracing::error!("Failed to delete from object store {}: {e}", p.to_string())
|
||||
} else {
|
||||
|
||||
@@ -53,8 +53,13 @@ if [[ $EE_CODE_DIR == /* ]]; then
|
||||
else
|
||||
EE_CODE_DIR="${root_dirpath}/${EE_CODE_DIR}"
|
||||
fi
|
||||
echo "EE code directory = ${EE_CODE_DIR} | Revert = ${REVERT}"
|
||||
|
||||
# Fallback to ~/windmill-ee-private if the default location doesn't exist
|
||||
if [ ! -d "${EE_CODE_DIR}" ] && [ "${EE_CODE_DIR}" == "${root_dirpath}/../windmill-ee-private/" ]; then
|
||||
EE_CODE_DIR="${HOME}/windmill-ee-private"
|
||||
fi
|
||||
|
||||
echo "EE code directory = ${EE_CODE_DIR} | Revert = ${REVERT}"
|
||||
|
||||
if [ ! -d "${EE_CODE_DIR}" ]; then
|
||||
echo "Windmill EE repo not found, please clone it next to this repository (or use the --dir option) and try again"
|
||||
|
||||
@@ -16,6 +16,7 @@ python = ["dep:windmill-parser-py"]
|
||||
|
||||
[dependencies]
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-object-store.workspace = true
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-queue.workspace = true
|
||||
windmill-audit.workspace = true
|
||||
|
||||
@@ -51,11 +51,11 @@ use windmill_common::{
|
||||
runnable_settings::{
|
||||
min_version_supports_runnable_settings_v0, RunnableSettings, RunnableSettingsTrait,
|
||||
},
|
||||
s3_helpers::upload_artifact_to_store,
|
||||
scripts::{hash_script, ScriptRunnableSettingsHandle, ScriptRunnableSettingsInline},
|
||||
utils::{paginate_without_limits, WarnAfterExt},
|
||||
worker::CLOUD_HOSTED,
|
||||
};
|
||||
use windmill_object_store::upload_artifact_to_store;
|
||||
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
@@ -497,7 +497,7 @@ async fn create_snapshot_script(
|
||||
|
||||
uploaded = true;
|
||||
|
||||
let path = windmill_common::s3_helpers::bundle(&w_id, &hash);
|
||||
let path = windmill_object_store::bundle(&w_id, &hash);
|
||||
upload_artifact_to_store(
|
||||
&path,
|
||||
data,
|
||||
|
||||
@@ -12,7 +12,7 @@ path = "src/lib.rs"
|
||||
default = []
|
||||
enterprise = []
|
||||
private = ["windmill-common/private"]
|
||||
parquet = ["dep:object_store", "windmill-common/parquet"]
|
||||
parquet = ["windmill-common/parquet", "windmill-object-store/parquet"]
|
||||
license = ["dep:rsa"]
|
||||
|
||||
[dependencies]
|
||||
@@ -36,4 +36,4 @@ uuid.workspace = true
|
||||
base64.workspace = true
|
||||
sha2.workspace = true
|
||||
rsa = { workspace = true, optional = true }
|
||||
object_store = { workspace = true, optional = true }
|
||||
windmill-object-store.workspace = true
|
||||
|
||||
@@ -152,10 +152,10 @@ pub async fn test_email(
|
||||
}
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::s3_helpers::ObjectSettings;
|
||||
use windmill_object_store::ObjectSettings;
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::s3_helpers::build_object_store_from_settings;
|
||||
use windmill_object_store::build_object_store_from_settings;
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
pub async fn test_s3_bucket(
|
||||
@@ -170,7 +170,7 @@ pub async fn test_s3_bucket(
|
||||
.await?
|
||||
.store;
|
||||
|
||||
let mut list = client.list(Some(&object_store::path::Path::from("".to_string())));
|
||||
let mut list = client.list(Some(&windmill_object_store::object_store_reexports::Path::from("".to_string())));
|
||||
let first_file = list.next().await;
|
||||
if first_file.is_some() {
|
||||
if let Err(e) = first_file.as_ref().unwrap() {
|
||||
@@ -182,13 +182,13 @@ pub async fn test_s3_bucket(
|
||||
tracing::info!("No files in blob storage");
|
||||
}
|
||||
|
||||
let path = object_store::path::Path::from(format!(
|
||||
let path = windmill_object_store::object_store_reexports::Path::from(format!(
|
||||
"/test-s3-bucket-{uuid}",
|
||||
uuid = uuid::Uuid::new_v4()
|
||||
));
|
||||
tracing::info!("Testing blob storage at path: {path}");
|
||||
client
|
||||
.put(&path, object_store::PutPayload::from_static(b"hello"))
|
||||
.put(&path, windmill_object_store::object_store_reexports::PutPayload::from_static(b"hello"))
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("error writing file to {path}: {e:#}"))?;
|
||||
let content = client
|
||||
|
||||
@@ -17,6 +17,7 @@ no_auth = ["windmill-api-auth/no_auth"]
|
||||
|
||||
[dependencies]
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-types.workspace = true
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-api-users.workspace = true
|
||||
windmill-audit.workspace = true
|
||||
|
||||
@@ -30,7 +30,7 @@ use uuid::Uuid;
|
||||
use windmill_audit::audit_oss::{audit_log, AuditAuthorable};
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::db::UserDB;
|
||||
use windmill_common::s3_helpers::LargeFileStorage;
|
||||
use windmill_types::s3::LargeFileStorage;
|
||||
use windmill_common::users::username_to_permissioned_as;
|
||||
use windmill_common::variables::{build_crypt, decrypt, encrypt, WORKSPACE_CRYPT_CACHE};
|
||||
use windmill_common::worker::{to_raw_value, CLOUD_HOSTED};
|
||||
|
||||
@@ -10,15 +10,15 @@ path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
private = ["windmill-audit/private", "windmill-common/private", "windmill-sandbox/private", "windmill-api-auth/private", "windmill-store/private", "windmill-api-users/private", "windmill-api-workspaces/private", "windmill-api-groups/private", "windmill-api-configs/private", "windmill-api-settings/private", "windmill-api-agent-workers?/private", "windmill-trigger-kafka?/private", "windmill-trigger-postgres?/private", "windmill-trigger-mqtt?/private", "windmill-trigger-websocket?/private", "windmill-trigger-nats?/private", "windmill-trigger-sqs?/private", "windmill-trigger-gcp?/private", "windmill-trigger-email?/private"]
|
||||
enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-sandbox/enterprise", "windmill-worker?/enterprise", "windmill-api-auth/enterprise", "windmill-store/enterprise", "windmill-api-jobs/enterprise", "windmill-api-scripts/enterprise", "windmill-api-flows/enterprise", "windmill-api-users/enterprise", "windmill-api-workspaces/enterprise", "windmill-api-groups/enterprise", "windmill-api-configs/enterprise", "windmill-api-settings/enterprise", "windmill-api-agent-workers?/enterprise", "windmill-trigger/enterprise", "windmill-trigger-kafka?/enterprise", "windmill-trigger-postgres?/enterprise", "windmill-trigger-mqtt?/enterprise", "windmill-trigger-websocket?/enterprise", "windmill-trigger-email?/enterprise", "windmill-trigger-nats?/enterprise", "windmill-trigger-sqs?/enterprise", "windmill-trigger-gcp?/enterprise", "windmill-trigger-http?/enterprise", "windmill-native-triggers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise"]
|
||||
private = ["windmill-audit/private", "windmill-common/private", "windmill-api-auth/private", "windmill-store/private", "windmill-api-users/private", "windmill-api-workspaces/private", "windmill-api-groups/private", "windmill-api-configs/private", "windmill-api-settings/private", "windmill-api-agent-workers?/private", "windmill-trigger-kafka?/private", "windmill-trigger-postgres?/private", "windmill-trigger-mqtt?/private", "windmill-trigger-websocket?/private", "windmill-trigger-nats?/private", "windmill-trigger-sqs?/private", "windmill-trigger-gcp?/private", "windmill-trigger-email?/private"]
|
||||
enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker?/enterprise", "windmill-api-auth/enterprise", "windmill-store/enterprise", "windmill-api-jobs/enterprise", "windmill-api-scripts/enterprise", "windmill-api-flows/enterprise", "windmill-api-users/enterprise", "windmill-api-workspaces/enterprise", "windmill-api-groups/enterprise", "windmill-api-configs/enterprise", "windmill-api-settings/enterprise", "windmill-api-agent-workers?/enterprise", "windmill-trigger/enterprise", "windmill-trigger-kafka?/enterprise", "windmill-trigger-postgres?/enterprise", "windmill-trigger-mqtt?/enterprise", "windmill-trigger-websocket?/enterprise", "windmill-trigger-email?/enterprise", "windmill-trigger-nats?/enterprise", "windmill-trigger-sqs?/enterprise", "windmill-trigger-gcp?/enterprise", "windmill-trigger-http?/enterprise", "windmill-native-triggers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise"]
|
||||
stripe = []
|
||||
inline_preview = ["dep:windmill-worker", "windmill-api-configs/inline_preview"]
|
||||
agent_worker_server = ["dep:windmill-worker", "dep:windmill-api-agent-workers"]
|
||||
enterprise_saml = ["dep:samael", "dep:libxml"]
|
||||
benchmark = []
|
||||
embedding = ["windmill-api-embeddings/embedding"]
|
||||
parquet = ["dep:datafusion", "dep:object_store", "windmill-common/parquet", "windmill-sandbox/parquet", "windmill-worker?/parquet", "windmill-api-users/parquet", "windmill-api-settings/parquet", "dep:aws-sigv4", "dep:aws-sdk-config"]
|
||||
parquet = ["dep:datafusion", "windmill-common/parquet", "windmill-object-store/parquet", "windmill-worker?/parquet", "windmill-api-users/parquet", "windmill-api-settings/parquet", "dep:aws-sigv4", "dep:aws-sdk-config"]
|
||||
prometheus = ["windmill-common/prometheus", "windmill-queue/prometheus", "dep:prometheus", "windmill-worker?/prometheus"]
|
||||
openidconnect = ["dep:openidconnect", "windmill-common/openidconnect", "windmill-store/openidconnect"]
|
||||
tantivy = ["dep:windmill-indexer"]
|
||||
@@ -58,7 +58,8 @@ windmill-trigger.workspace = true
|
||||
windmill-store.workspace = true
|
||||
windmill-queue.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-sandbox.workspace = true
|
||||
windmill-types.workspace = true
|
||||
windmill-object-store.workspace = true
|
||||
windmill-audit.workspace = true
|
||||
windmill-parser.workspace = true
|
||||
windmill-parser-sql.workspace = true
|
||||
@@ -130,7 +131,6 @@ async-recursion.workspace = true
|
||||
rsa = { workspace = true, optional = true}
|
||||
uuid.workspace = true
|
||||
datafusion = { workspace = true, optional = true}
|
||||
object_store = { workspace = true, optional = true}
|
||||
openidconnect = { workspace = true, optional = true}
|
||||
jsonwebtoken = { workspace = true }
|
||||
matchit = { workspace = true, optional = true }
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fn main() {
|
||||
// rust-embed requires the embedded folder to exist at compile time.
|
||||
// When building without a prior frontend build, create the directory
|
||||
// so the derive macro doesn't panic.
|
||||
let dir = std::env::var("FRONTEND_BUILD_DIR")
|
||||
.unwrap_or_else(|_| "../../frontend/build/".to_string());
|
||||
let path = std::path::Path::new(&dir);
|
||||
if !path.exists() {
|
||||
std::fs::create_dir_all(path).ok();
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ use itertools::Itertools;
|
||||
use lazy_static::lazy_static;
|
||||
use magic_crypt::MagicCryptTrait;
|
||||
#[cfg(feature = "parquet")]
|
||||
use object_store::{Attribute, Attributes};
|
||||
use windmill_object_store::object_store_reexports::{Attribute, Attributes};
|
||||
#[cfg(feature = "parquet")]
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -78,9 +78,10 @@ use hmac::Mac;
|
||||
use windmill_common::{
|
||||
jwt,
|
||||
oauth2::HmacSha256,
|
||||
s3_helpers::{build_object_store_client, S3Object, S3Permission},
|
||||
variables::get_workspace_key,
|
||||
};
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_types::s3::{S3Object, S3Permission};
|
||||
|
||||
pub fn workspaced_service() -> Router {
|
||||
Router::new()
|
||||
@@ -406,7 +407,7 @@ async fn get_raw_app_data(
|
||||
Extension(db): Extension<DB>,
|
||||
) -> Result<Response> {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
let object_store = windmill_common::s3_helpers::get_object_store().await;
|
||||
let object_store = windmill_object_store::get_object_store().await;
|
||||
|
||||
// tracing::info!("secret_with_ext: {}", secret_with_ext);
|
||||
let mut splitted = secret_with_ext.split('.');
|
||||
@@ -442,10 +443,12 @@ async fn get_raw_app_data(
|
||||
if let Some(os) = object_store {
|
||||
let path = format!("/app_bundles/{}/{}.{}", w_id, id, file_type);
|
||||
let stream = os
|
||||
.get(&object_store::path::Path::from(path))
|
||||
.await?
|
||||
.get(&windmill_object_store::object_store_reexports::Path::from(path))
|
||||
.await
|
||||
.map_err(windmill_object_store::object_store_error_to_error)?
|
||||
.bytes()
|
||||
.await?;
|
||||
.await
|
||||
.map_err(windmill_object_store::object_store_error_to_error)?;
|
||||
tracing::info!("stream: {}", stream.len());
|
||||
body = Some(Body::from(stream));
|
||||
}
|
||||
@@ -949,13 +952,13 @@ async fn store_raw_app_file<'a>(
|
||||
) -> Result<()> {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
{
|
||||
let object_store = windmill_common::s3_helpers::get_object_store().await;
|
||||
let object_store = windmill_object_store::get_object_store().await;
|
||||
|
||||
let path: String = format!("/app_bundles/{}/{}.{}", w_id, id, file_type);
|
||||
|
||||
if let Some(os) = object_store {
|
||||
if let Err(e) = os
|
||||
.put(&object_store::path::Path::from(path.clone()), data.into())
|
||||
.put(&windmill_object_store::object_store_reexports::Path::from(path.clone()), data.into())
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to put snapshot to s3 at {path}: {:?}", e);
|
||||
@@ -2480,7 +2483,7 @@ async fn upload_s3_file_from_app(
|
||||
let s3_resource = s3_resource_opt.ok_or(Error::internal_err(
|
||||
"No files storage resource defined at the workspace level".to_string(),
|
||||
))?;
|
||||
let s3_client = build_object_store_client(&s3_resource).await?;
|
||||
let s3_client = windmill_object_store::build_object_store_client(&s3_resource).await?;
|
||||
|
||||
let options = Attributes::from_iter(vec![
|
||||
(
|
||||
@@ -2537,7 +2540,7 @@ async fn delete_s3_file_from_app(
|
||||
..
|
||||
} = jwt::decode_with_internal_secret::<S3DeleteTokenClaims>(&query.delete_token).await?;
|
||||
|
||||
let path = object_store::path::Path::parse(file_key.as_str())
|
||||
let path = windmill_object_store::object_store_reexports::Path::parse(file_key.as_str())
|
||||
.map_err(|e| Error::internal_err(format!("Error parsing file key: {}", e)))?;
|
||||
|
||||
if workspace != w_id {
|
||||
@@ -2581,7 +2584,7 @@ async fn delete_s3_file_from_app(
|
||||
))?
|
||||
};
|
||||
|
||||
let s3_client = build_object_store_client(&s3_resource).await?;
|
||||
let s3_client = windmill_object_store::build_object_store_client(&s3_resource).await?;
|
||||
|
||||
s3_client.delete(&path).await.map_err(|err| {
|
||||
tracing::error!("Error deleting file: {:?}", err);
|
||||
|
||||
@@ -91,8 +91,8 @@ impl RawWebhookArgs {
|
||||
get_random_file_name, get_workspace_s3_resource, upload_file_internal,
|
||||
};
|
||||
use futures::TryStreamExt;
|
||||
use object_store::{Attribute, Attributes};
|
||||
use windmill_common::s3_helpers::build_object_store_client;
|
||||
use windmill_object_store::object_store_reexports::{Attribute, Attributes};
|
||||
use windmill_object_store::build_object_store_client;
|
||||
|
||||
let (_, s3_resource) = get_workspace_s3_resource(authed, db, None, w_id, None).await?;
|
||||
|
||||
|
||||
@@ -7,18 +7,20 @@ use axum::Router;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use uuid::Uuid;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use windmill_common::s3_helpers::StorageResourceType;
|
||||
use windmill_types::s3::StorageResourceType;
|
||||
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
use crate::db::{ApiAuthed, OptJobAuthed, DB};
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
use object_store::{ObjectStore, PutMultipartOpts, PutResult};
|
||||
use windmill_object_store::object_store_reexports::{ObjectStore, PutMultipartOpts, PutResult};
|
||||
#[cfg(not(feature = "private"))]
|
||||
use windmill_object_store::ObjectStoreResource;
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
use std::sync::Arc;
|
||||
#[cfg(not(feature = "private"))]
|
||||
use windmill_common::error;
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
use windmill_common::{db::UserDB, s3_helpers::ObjectStoreResource};
|
||||
use windmill_common::db::UserDB;
|
||||
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
use bytes::Bytes;
|
||||
@@ -155,11 +157,11 @@ pub async fn get_workspace_s3_resource_and_check_paths<'c>(
|
||||
_authed_api: Option<&ApiAuthed>,
|
||||
_w_id: &str,
|
||||
_storage: Option<String>,
|
||||
_paths: &[(&str, windmill_common::s3_helpers::S3Permission)],
|
||||
_paths: &[(&str, windmill_types::s3::S3Permission)],
|
||||
_job_id: Option<uuid::Uuid>,
|
||||
) -> windmill_common::error::Result<(
|
||||
Option<bool>,
|
||||
Option<windmill_common::s3_helpers::ObjectStoreResource>,
|
||||
Option<windmill_types::s3::ObjectStoreResource>,
|
||||
)> {
|
||||
Err(windmill_common::error::Error::internal_err(
|
||||
"Not implemented in Windmill's Open Source repository".to_string(),
|
||||
|
||||
@@ -42,7 +42,8 @@ use windmill_common::runnable_settings::{
|
||||
};
|
||||
#[cfg(feature = "inline_preview")]
|
||||
use windmill_common::runtime_assets::{register_runtime_asset, InsertRuntimeAssetParams};
|
||||
use windmill_common::s3_helpers::{upload_artifact_to_store, BundleFormat};
|
||||
use windmill_types::s3::BundleFormat;
|
||||
use windmill_object_store::upload_artifact_to_store;
|
||||
use windmill_common::scripts::ScriptRunnableSettingsInline;
|
||||
use windmill_common::triggers::TriggerMetadata;
|
||||
use windmill_common::utils::{RunnableKind, WarnAfterExt};
|
||||
@@ -1384,40 +1385,15 @@ async fn get_logs_from_store(
|
||||
logs: &str,
|
||||
log_file_index: &Option<Vec<String>>,
|
||||
) -> Option<error::Result<Body>> {
|
||||
if log_offset > 0 {
|
||||
if let Some(file_index) = log_file_index.clone() {
|
||||
tracing::debug!("Getting logs from store: {file_index:?}");
|
||||
if let Some(os) = windmill_common::s3_helpers::get_object_store().await {
|
||||
tracing::debug!("object store client present, streaming from there");
|
||||
|
||||
let logs = logs.to_string();
|
||||
let stream = async_stream::stream! {
|
||||
yield Ok(bytes::Bytes::from(
|
||||
r#"to remove ansi colors, use: | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g'
|
||||
"#
|
||||
.to_string(),
|
||||
));
|
||||
for file_p in file_index.clone() {
|
||||
let file_p_2 = file_p.clone();
|
||||
let file = os.get(&object_store::path::Path::from(file_p)).await;
|
||||
if let Ok(file) = file {
|
||||
if let Ok(bytes) = file.bytes().await {
|
||||
yield Ok(bytes::Bytes::from(bytes)) as object_store::Result<bytes::Bytes>;
|
||||
}
|
||||
} else {
|
||||
tracing::debug!("error getting file from store: {file_p_2}: {}", file.err().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
yield Ok(bytes::Bytes::from(logs))
|
||||
};
|
||||
return Some(Ok(Body::from_stream(stream)));
|
||||
} else {
|
||||
tracing::debug!("object store client not present, cannot stream logs from store");
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
use futures::StreamExt;
|
||||
let stream = windmill_object_store::get_logs_from_store(log_offset, logs, log_file_index).await?;
|
||||
let header = bytes::Bytes::from(
|
||||
r#"to remove ansi colors, use: | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g'
|
||||
"#
|
||||
.to_string(),
|
||||
);
|
||||
let prefixed_stream = futures::stream::once(async { Ok(header) }).chain(stream);
|
||||
Some(Ok(Body::from_stream(prefixed_stream)))
|
||||
}
|
||||
|
||||
async fn get_logs_from_disk(
|
||||
@@ -4840,7 +4816,7 @@ async fn run_bundle_preview_script(
|
||||
|
||||
uploaded = true;
|
||||
|
||||
let path = windmill_common::s3_helpers::bundle(&w_id, &id);
|
||||
let path = windmill_object_store::bundle(&w_id, &id);
|
||||
upload_artifact_to_store(
|
||||
&path,
|
||||
data,
|
||||
@@ -5794,9 +5770,9 @@ async fn get_log_file(Path((_w_id, file_p)): Path<(String, String)>) -> error::R
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = windmill_common::s3_helpers::get_object_store().await {
|
||||
if let Some(os) = windmill_object_store::get_object_store().await {
|
||||
let file = os
|
||||
.get(&object_store::path::Path::from(format!("logs/{file_p}")))
|
||||
.get(&windmill_object_store::object_store_reexports::Path::from(format!("logs/{file_p}")))
|
||||
.await;
|
||||
if let Ok(file) = file {
|
||||
if let Ok(bytes) = file.bytes().await {
|
||||
|
||||
@@ -98,11 +98,11 @@ async fn get_log_file(
|
||||
require_devops_role(&db, &email).await?;
|
||||
let path = path.to_path();
|
||||
#[cfg(feature = "parquet")]
|
||||
let s3_client = windmill_common::s3_helpers::get_object_store().await;
|
||||
let s3_client = windmill_object_store::get_object_store().await;
|
||||
#[cfg(feature = "parquet")]
|
||||
if let Some(s3_client) = s3_client {
|
||||
let path = format!("{}{}", windmill_common::tracing_init::LOGS_SERVICE, path);
|
||||
let file = s3_client.get(&object_store::path::Path::from(path)).await;
|
||||
let file = s3_client.get(&windmill_object_store::object_store_reexports::Path::from(path)).await;
|
||||
match file {
|
||||
Ok(file) => {
|
||||
let bytes = file.bytes().await;
|
||||
|
||||
@@ -35,7 +35,7 @@ use windmill_trigger::TriggerMode;
|
||||
#[cfg(feature = "parquet")]
|
||||
use {
|
||||
crate::job_helpers_oss::get_workspace_s3_resource,
|
||||
windmill_common::s3_helpers::build_object_store_client,
|
||||
windmill_object_store::build_object_store_client,
|
||||
};
|
||||
|
||||
async fn conditional_cors_middleware(
|
||||
@@ -365,13 +365,13 @@ async fn route_job(
|
||||
} else {
|
||||
config.s3.clone()
|
||||
};
|
||||
let path = object_store::path::Path::from(path);
|
||||
let path = windmill_object_store::object_store_reexports::Path::from(path);
|
||||
let s3_object = s3_client.get(&path).await;
|
||||
|
||||
let s3_object = match s3_object {
|
||||
Err(object_store::Error::NotFound { .. }) if trigger.is_static_website => {
|
||||
Err(windmill_object_store::object_store_reexports::ObjectStoreError::NotFound { .. }) if trigger.is_static_website => {
|
||||
// fallback to index.html if the file is not found
|
||||
let path = object_store::path::Path::from(format!(
|
||||
let path = windmill_object_store::object_store_reexports::Path::from(format!(
|
||||
"{}/index.html",
|
||||
config.s3.trim_end_matches('/')
|
||||
));
|
||||
@@ -410,7 +410,7 @@ async fn route_job(
|
||||
"content-type",
|
||||
s3_object
|
||||
.attributes
|
||||
.get(&object_store::Attribute::ContentType)
|
||||
.get(&windmill_object_store::object_store_reexports::Attribute::ContentType)
|
||||
.map(|s| s.parse().ok())
|
||||
.flatten()
|
||||
.unwrap_or("application/octet-stream".parse().unwrap()),
|
||||
@@ -422,7 +422,7 @@ async fn route_job(
|
||||
|| {
|
||||
s3_object
|
||||
.attributes
|
||||
.get(&object_store::Attribute::ContentDisposition)
|
||||
.get(&windmill_object_store::object_store_reexports::Attribute::ContentDisposition)
|
||||
.map(|s| s.parse().ok())
|
||||
.flatten()
|
||||
.unwrap_or("inline".parse().unwrap())
|
||||
|
||||
@@ -14,7 +14,7 @@ jemalloc = ["dep:tikv-jemalloc-ctl"]
|
||||
tantivy = []
|
||||
prometheus = ["dep:prometheus"]
|
||||
benchmark = []
|
||||
parquet = ["dep:object_store", "dep:aws-sdk-sts", "dep:aws-smithy-types-convert", "dep:datafusion", "dep:aws-config", "dep:aws-credential-types"]
|
||||
parquet = []
|
||||
aws_auth = ["dep:aws-sdk-sts", "dep:aws-config"]
|
||||
otel = ["dep:opentelemetry-semantic-conventions", "dep:opentelemetry-otlp", "dep:opentelemetry_sdk",
|
||||
"dep:tracing-opentelemetry", "dep:opentelemetry-appender-tracing", "dep:tonic", "dep:opentelemetry"]
|
||||
@@ -64,7 +64,6 @@ regex.workspace = true
|
||||
git-version.workspace = true
|
||||
cron.workspace = true
|
||||
magic-crypt.workspace = true
|
||||
object_store = { workspace = true, optional = true }
|
||||
prometheus = { workspace = true, optional = true }
|
||||
aws-config = { workspace = true, optional = true }
|
||||
aws-sdk-sts = { workspace = true, optional = true }
|
||||
|
||||
@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::value::RawValue;
|
||||
|
||||
use crate::flow_status::AgentAction;
|
||||
use crate::s3_helpers::S3Object;
|
||||
use windmill_types::s3::S3Object;
|
||||
|
||||
// ============================================================================
|
||||
// Shared Types for OpenAI-compatible message format
|
||||
|
||||
@@ -313,42 +313,3 @@ where
|
||||
Self(err.into())
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "parquet")]
|
||||
impl From<object_store::Error> for Error {
|
||||
fn from(err: object_store::Error) -> Self {
|
||||
use object_store::Error::*;
|
||||
match err {
|
||||
Generic { store, source } => Error::Generic(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Generic {} error: {}", store, source),
|
||||
),
|
||||
NotFound { path, source } => Error::NotFound(format!("{}: {}", path, source)),
|
||||
InvalidPath { source } => Error::BadRequest(format!("Invalid path: {}", source)),
|
||||
JoinError { source } => Error::InternalErr(format!("Join error: {}", source)),
|
||||
NotSupported { source } => {
|
||||
Error::BadRequest(format!("Operation not supported: {}", source))
|
||||
}
|
||||
AlreadyExists { path, source } => {
|
||||
Error::BadRequest(format!("Object at {} already exists: {}", path, source))
|
||||
}
|
||||
Precondition { path, source } => {
|
||||
Error::BadRequest(format!("Precondition failed at {}: {}", path, source))
|
||||
}
|
||||
NotModified { path, source } => {
|
||||
Error::ExecutionErr(format!("Not modified at {}: {}", path, source))
|
||||
}
|
||||
NotImplemented => Error::BadRequest("Operation not yet implemented.".to_string()),
|
||||
PermissionDenied { path, source } => {
|
||||
Error::PermissionDenied(format!("Permission denied at {}: {}", path, source))
|
||||
}
|
||||
Unauthenticated { path, source } => {
|
||||
Error::NotAuthorized(format!("Unauthenticated for {}: {}", path, source))
|
||||
}
|
||||
UnknownConfigurationKey { store, key } => Error::BadConfig(format!(
|
||||
"Invalid config key '{}' for store '{}'",
|
||||
key, store
|
||||
)),
|
||||
_ => Error::InternalErr(format!("Object store error: {}", err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,42 +250,6 @@ pub async fn get_logs_from_disk(
|
||||
return None;
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
pub async fn get_logs_from_store(
|
||||
log_offset: i32,
|
||||
logs: &str,
|
||||
log_file_index: &Option<Vec<String>>,
|
||||
) -> Option<impl Stream<Item = Result<Bytes, object_store::Error>>> {
|
||||
use crate::s3_helpers::get_object_store;
|
||||
|
||||
if log_offset > 0 {
|
||||
if let Some(file_index) = log_file_index.clone() {
|
||||
if let Some(os) = get_object_store().await {
|
||||
let logs = logs.to_string();
|
||||
let stream = async_stream::stream! {
|
||||
for file_p in file_index.clone() {
|
||||
let file_p_2 = file_p.clone();
|
||||
let file = os.get(&object_store::path::Path::from(file_p)).await;
|
||||
if let Ok(file) = file {
|
||||
if let Ok(bytes) = file.bytes().await {
|
||||
yield Ok(bytes::Bytes::from(bytes)) as object_store::Result<bytes::Bytes>;
|
||||
}
|
||||
} else {
|
||||
tracing::debug!("error getting file from store: {file_p_2}: {}", file.err().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
yield Ok(bytes::Bytes::from(logs))
|
||||
};
|
||||
return Some(stream);
|
||||
} else {
|
||||
tracing::debug!("object store client not present, cannot stream logs from store");
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref TAGS_ARE_SENSITIVE: bool = std::env::var("TAGS_ARE_SENSITIVE").map(
|
||||
|v| v.parse().unwrap()
|
||||
|
||||
@@ -57,10 +57,6 @@ pub mod global_settings;
|
||||
pub mod indexer;
|
||||
pub mod instance_config;
|
||||
pub mod job_metrics;
|
||||
#[cfg(all(feature = "parquet", feature = "private"))]
|
||||
pub mod job_s3_helpers_ee;
|
||||
#[cfg(feature = "parquet")]
|
||||
pub mod job_s3_helpers_oss;
|
||||
pub mod min_version;
|
||||
pub mod notify_events;
|
||||
pub mod runtime_assets;
|
||||
@@ -83,7 +79,6 @@ pub mod otel_oss;
|
||||
pub mod queue;
|
||||
pub mod result_stream;
|
||||
pub mod runnable_settings;
|
||||
pub mod s3_helpers;
|
||||
pub mod schedule;
|
||||
pub mod schema;
|
||||
pub mod scripts;
|
||||
|
||||
@@ -902,137 +902,6 @@ pub fn copy_dir_recursively(src: &Path, dst: &Path) -> error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn load_cache(bin_path: &str, _remote_path: &str, is_dir: bool) -> (bool, String) {
|
||||
if tokio::fs::metadata(&bin_path).await.is_ok() {
|
||||
(true, format!("loaded from local cache: {}\n", bin_path))
|
||||
} else {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = crate::s3_helpers::get_object_store().await {
|
||||
let started = std::time::Instant::now();
|
||||
use crate::s3_helpers::attempt_fetch_bytes;
|
||||
|
||||
if let Ok(mut x) = attempt_fetch_bytes(os, _remote_path).await {
|
||||
if is_dir {
|
||||
if let Err(e) = extract_tar(x, bin_path).await {
|
||||
tracing::error!("could not write tar archive locally: {e:?}");
|
||||
return (
|
||||
false,
|
||||
"error writing tar archive from object store".to_string(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if let Err(e) = write_binary_file(bin_path, &mut x) {
|
||||
tracing::error!("could not write bundle/bin file locally: {e:?}");
|
||||
return (
|
||||
false,
|
||||
"error writing bundle/bin file from object store".to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
tracing::info!("loaded from object store {}", bin_path);
|
||||
return (
|
||||
true,
|
||||
format!(
|
||||
"loaded bin/bundle from object store {} in {}ms",
|
||||
bin_path,
|
||||
started.elapsed().as_millis()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
let _ = is_dir;
|
||||
(false, "".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn exists_in_cache(bin_path: &str, _remote_path: &str) -> bool {
|
||||
if tokio::fs::metadata(&bin_path).await.is_ok() {
|
||||
return true;
|
||||
} else {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = crate::s3_helpers::get_object_store().await {
|
||||
return os
|
||||
.get(&object_store::path::Path::from(_remote_path))
|
||||
.await
|
||||
.is_ok();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn save_cache(
|
||||
local_cache_path: &str,
|
||||
_remote_cache_path: &str,
|
||||
origin: &str,
|
||||
is_dir: bool,
|
||||
) -> crate::error::Result<String> {
|
||||
let mut _cached_to_s3 = false;
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = crate::s3_helpers::get_object_store().await {
|
||||
use object_store::path::Path;
|
||||
let file_to_cache = if is_dir {
|
||||
let tar_path = format!(
|
||||
"{ROOT_CACHE_DIR}/tar/{}_tar.tar",
|
||||
local_cache_path
|
||||
.split("/")
|
||||
.last()
|
||||
.unwrap_or(&uuid::Uuid::new_v4().to_string())
|
||||
);
|
||||
let tar_file = std::fs::File::create(&tar_path)?;
|
||||
let mut tar = tar::Builder::new(tar_file);
|
||||
tar.append_dir_all(".", &origin)?;
|
||||
let tar_metadata = tokio::fs::metadata(&tar_path).await;
|
||||
if tar_metadata.is_err() || tar_metadata.as_ref().unwrap().len() == 0 {
|
||||
tracing::info!("Failed to tar cache: {origin}");
|
||||
return Err(error::Error::ExecutionErr(format!(
|
||||
"Failed to tar cache: {origin}"
|
||||
)));
|
||||
}
|
||||
tar_path
|
||||
} else {
|
||||
origin.to_owned()
|
||||
};
|
||||
|
||||
if let Err(e) = os
|
||||
.put(
|
||||
&Path::from(_remote_cache_path),
|
||||
std::fs::read(&file_to_cache)?.into(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to put go bin to object store: {_remote_cache_path}. Error: {:?}",
|
||||
e
|
||||
);
|
||||
} else {
|
||||
_cached_to_s3 = true;
|
||||
if is_dir {
|
||||
tokio::fs::remove_dir_all(&file_to_cache).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if !*CLOUD_HOSTED {
|
||||
if true {
|
||||
if is_dir {
|
||||
copy_dir_recursively(&PathBuf::from(origin), &PathBuf::from(local_cache_path))?;
|
||||
} else {
|
||||
std::fs::copy(origin, local_cache_path)?;
|
||||
}
|
||||
Ok(format!(
|
||||
"\nwrote cached binary: {} (backed by EE distributed object store: {_cached_to_s3})\n",
|
||||
local_cache_path
|
||||
))
|
||||
} else if _cached_to_s3 {
|
||||
Ok(format!(
|
||||
"wrote cached binary to object store {}\n",
|
||||
local_cache_path
|
||||
))
|
||||
} else {
|
||||
Ok("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
pub async fn extract_tar(tar: bytes::Bytes, folder: &str) -> error::Result<()> {
|
||||
use std::time::Instant;
|
||||
@@ -1059,7 +928,7 @@ pub async fn extract_tar(tar: bytes::Bytes, folder: &str) -> error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
fn write_binary_file(main_path: &str, byts: &mut bytes::Bytes) -> error::Result<()> {
|
||||
pub fn write_binary_file(main_path: &str, byts: &mut bytes::Bytes) -> error::Result<()> {
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
|
||||
@@ -10,12 +10,13 @@ path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
parquet = ["dep:object_store", "windmill-common/parquet"]
|
||||
parquet = ["windmill-common/parquet", "windmill-object-store/parquet"]
|
||||
private = ["windmill-common/private"]
|
||||
enterprise = ["windmill-common/enterprise"]
|
||||
enterprise = ["windmill-common/enterprise", "windmill-object-store/enterprise"]
|
||||
|
||||
[dependencies]
|
||||
windmill-common.workspace = true
|
||||
windmill-object-store.workspace = true
|
||||
tantivy.workspace = true
|
||||
tokio.workspace = true
|
||||
sqlx.workspace = true
|
||||
@@ -28,7 +29,6 @@ uuid.workspace = true
|
||||
futures.workspace = true
|
||||
tempfile.workspace = true
|
||||
bytes.workspace = true
|
||||
object_store = { workspace = true, optional = true}
|
||||
astral-tokio-tar.workspace = true
|
||||
lazy_static.workspace = true
|
||||
const_format.workspace = true
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
[package]
|
||||
name = "windmill-object-store"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
private = []
|
||||
enterprise = []
|
||||
parquet = [
|
||||
"windmill-common/parquet",
|
||||
"dep:object_store",
|
||||
"dep:aws-sdk-sts",
|
||||
"dep:aws-smithy-types-convert",
|
||||
"dep:datafusion",
|
||||
"dep:aws-config",
|
||||
"dep:aws-credential-types",
|
||||
"dep:windmill-parser-sql",
|
||||
]
|
||||
openidconnect = ["windmill-common/openidconnect"]
|
||||
|
||||
[lib]
|
||||
name = "windmill_object_store"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-types.workspace = true
|
||||
anyhow.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sqlx.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-stream.workspace = true
|
||||
tracing.workspace = true
|
||||
reqwest.workspace = true
|
||||
uuid.workspace = true
|
||||
chrono.workspace = true
|
||||
bytes.workspace = true
|
||||
lazy_static.workspace = true
|
||||
quick_cache.workspace = true
|
||||
async-trait.workspace = true
|
||||
futures.workspace = true
|
||||
async-stream.workspace = true
|
||||
windmill-parser-sql = { workspace = true, optional = true }
|
||||
globset.workspace = true
|
||||
axum.workspace = true
|
||||
|
||||
object_store = { workspace = true, optional = true }
|
||||
aws-sdk-sts = { workspace = true, optional = true }
|
||||
aws-smithy-types-convert = { workspace = true, optional = true }
|
||||
datafusion = { workspace = true, optional = true }
|
||||
aws-config = { workspace = true, optional = true }
|
||||
aws-credential-types = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||
object_store.workspace = true
|
||||
+7
-7
@@ -3,21 +3,21 @@
|
||||
pub use crate::job_s3_helpers_ee::*;
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
use crate::s3_helpers::{ObjectStoreResource, StorageResourceType};
|
||||
use windmill_types::s3::{ObjectStoreResource, StorageResourceType};
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub async fn get_s3_resource_internal<'c>(
|
||||
_resource_type: StorageResourceType,
|
||||
_s3_resource_value_raw: serde_json::Value,
|
||||
_gen_token: TokenGenerator<'c>,
|
||||
_db: &crate::DB,
|
||||
) -> crate::error::Result<ObjectStoreResource> {
|
||||
_db: &windmill_common::DB,
|
||||
) -> windmill_common::error::Result<ObjectStoreResource> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "private"))]
|
||||
pub enum TokenGenerator<'c> {
|
||||
AsClient(&'c crate::client::AuthedClient),
|
||||
AsClient(&'c windmill_common::client::AuthedClient),
|
||||
AsServerInstance(),
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ impl<'c> TokenGenerator<'c> {
|
||||
pub async fn gen_token(
|
||||
&self,
|
||||
_audience: &str,
|
||||
_db: Option<&crate::DB>,
|
||||
_db: Option<&windmill_common::DB>,
|
||||
) -> anyhow::Result<String> {
|
||||
todo!()
|
||||
}
|
||||
@@ -34,9 +34,9 @@ impl<'c> TokenGenerator<'c> {
|
||||
|
||||
#[cfg(all(feature = "parquet", not(feature = "private")))]
|
||||
pub(crate) async fn generate_s3_aws_oidc_resource<'c>(
|
||||
_clone: crate::s3_helpers::S3AwsOidcResource,
|
||||
_clone: windmill_types::s3::S3AwsOidcResource,
|
||||
_token_generator: TokenGenerator<'c>,
|
||||
_init_private_key: Option<&sqlx::Pool<sqlx::Postgres>>,
|
||||
) -> crate::error::Result<ObjectStoreResource> {
|
||||
) -> windmill_common::error::Result<ObjectStoreResource> {
|
||||
todo!()
|
||||
}
|
||||
+1064
-764
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@ cloud = ["windmill-common/cloud"]
|
||||
[dependencies]
|
||||
windmill-api-auth.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-types.workspace = true
|
||||
windmill-trigger.workspace = true
|
||||
windmill-audit.workspace = true
|
||||
windmill-git-sync.workspace = true
|
||||
|
||||
@@ -7,11 +7,11 @@ use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use windmill_common::{
|
||||
error::{Error, Result},
|
||||
flows::Retry,
|
||||
s3_helpers::S3Object,
|
||||
utils::ExpiringCacheEntry,
|
||||
worker::CLOUD_HOSTED,
|
||||
DB,
|
||||
};
|
||||
use windmill_types::s3::S3Object;
|
||||
|
||||
use windmill_api_auth::ApiAuthed;
|
||||
use windmill_trigger::TriggerMode;
|
||||
|
||||
@@ -20,3 +20,4 @@ anyhow.workspace = true
|
||||
tracing.workspace = true
|
||||
itertools.workspace = true
|
||||
strum.workspace = true
|
||||
bitflags.workspace = true
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod flows;
|
||||
pub mod jobs;
|
||||
pub mod more_serde;
|
||||
pub mod runnable_settings;
|
||||
pub mod s3;
|
||||
pub mod schedule;
|
||||
pub mod scripts;
|
||||
pub mod triggers;
|
||||
|
||||
@@ -0,0 +1,479 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::de::Visitor;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct FilesystemSettings {
|
||||
pub root_path: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct FilesystemStorage {
|
||||
pub root_path: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub public_resource: Option<bool>,
|
||||
pub advanced_permissions: Option<Vec<S3PermissionRule>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum LargeFileStorage {
|
||||
S3Storage(S3Storage),
|
||||
AzureBlobStorage(AzureBlobStorage),
|
||||
S3AwsOidc(S3Storage),
|
||||
AzureWorkloadIdentity(AzureBlobStorage),
|
||||
GoogleCloudStorage(GoogleCloudStorage),
|
||||
FilesystemStorage(FilesystemStorage),
|
||||
}
|
||||
|
||||
impl LargeFileStorage {
|
||||
pub fn get_s3_resource_path(&self) -> &str {
|
||||
match self {
|
||||
LargeFileStorage::S3Storage(s3_lfs) => &s3_lfs.s3_resource_path,
|
||||
LargeFileStorage::S3AwsOidc(s3_lfs) => &s3_lfs.s3_resource_path,
|
||||
LargeFileStorage::AzureBlobStorage(az_lfs) => &az_lfs.azure_blob_resource_path,
|
||||
LargeFileStorage::AzureWorkloadIdentity(az_lfs) => &az_lfs.azure_blob_resource_path,
|
||||
LargeFileStorage::GoogleCloudStorage(gcs_lfs) => &gcs_lfs.gcs_resource_path,
|
||||
LargeFileStorage::FilesystemStorage(fs_lfs) => &fs_lfs.root_path,
|
||||
}
|
||||
}
|
||||
pub fn is_public_resource(&self) -> bool {
|
||||
match self {
|
||||
LargeFileStorage::S3Storage(lfs) => lfs.public_resource,
|
||||
LargeFileStorage::S3AwsOidc(lfs) => lfs.public_resource,
|
||||
LargeFileStorage::AzureBlobStorage(lfs) => lfs.public_resource,
|
||||
LargeFileStorage::AzureWorkloadIdentity(lfs) => lfs.public_resource,
|
||||
LargeFileStorage::GoogleCloudStorage(glfs) => glfs.public_resource,
|
||||
LargeFileStorage::FilesystemStorage(fs_lfs) => fs_lfs.public_resource,
|
||||
}
|
||||
.unwrap_or(false)
|
||||
}
|
||||
pub fn get_advanced_permissions(&self) -> Option<&Vec<S3PermissionRule>> {
|
||||
match self {
|
||||
LargeFileStorage::S3Storage(lfs) => lfs.advanced_permissions.as_ref(),
|
||||
LargeFileStorage::S3AwsOidc(lfs) => lfs.advanced_permissions.as_ref(),
|
||||
LargeFileStorage::AzureBlobStorage(lfs) => lfs.advanced_permissions.as_ref(),
|
||||
LargeFileStorage::AzureWorkloadIdentity(lfs) => lfs.advanced_permissions.as_ref(),
|
||||
LargeFileStorage::GoogleCloudStorage(glfs) => glfs.advanced_permissions.as_ref(),
|
||||
LargeFileStorage::FilesystemStorage(fs_lfs) => fs_lfs.advanced_permissions.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct S3Storage {
|
||||
pub s3_resource_path: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub public_resource: Option<bool>,
|
||||
pub advanced_permissions: Option<Vec<S3PermissionRule>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct AzureBlobStorage {
|
||||
pub azure_blob_resource_path: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub public_resource: Option<bool>,
|
||||
pub advanced_permissions: Option<Vec<S3PermissionRule>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct GoogleCloudStorage {
|
||||
pub gcs_resource_path: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub public_resource: Option<bool>,
|
||||
pub advanced_permissions: Option<Vec<S3PermissionRule>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct S3PermissionRule {
|
||||
pub pattern: String,
|
||||
pub allow: S3Permission,
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct S3Permission: u8 {
|
||||
const READ = 0b0001;
|
||||
const WRITE = 0b0010;
|
||||
const DELETE = 0b0100;
|
||||
const LIST = 0b1000;
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for S3Permission {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut perms = Vec::new();
|
||||
if self.contains(S3Permission::READ) {
|
||||
perms.push("read");
|
||||
}
|
||||
if self.contains(S3Permission::WRITE) {
|
||||
perms.push("write");
|
||||
}
|
||||
if self.contains(S3Permission::DELETE) {
|
||||
perms.push("delete");
|
||||
}
|
||||
if self.contains(S3Permission::LIST) {
|
||||
perms.push("list");
|
||||
}
|
||||
let perms = perms.join(",");
|
||||
perms.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for S3Permission {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct PermVisitor;
|
||||
impl<'de> Visitor<'de> for PermVisitor {
|
||||
type Value = S3Permission;
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("comma separated list of permissions: read, write, delete, list")
|
||||
}
|
||||
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
||||
let mut perms = S3Permission::empty();
|
||||
for value in v.split(',') {
|
||||
perms |= match value {
|
||||
"read" => S3Permission::READ,
|
||||
"write" => S3Permission::WRITE,
|
||||
"delete" => S3Permission::DELETE,
|
||||
"list" => S3Permission::LIST,
|
||||
_ => S3Permission::empty(),
|
||||
};
|
||||
}
|
||||
Ok(perms)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(PermVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ObjectStoreResource {
|
||||
S3(S3Resource),
|
||||
Azure(AzureBlobResource),
|
||||
Gcs(GcsResource),
|
||||
Filesystem(FilesystemSettings),
|
||||
}
|
||||
|
||||
impl ObjectStoreResource {
|
||||
pub fn expiration(&self) -> Option<DateTime<Utc>> {
|
||||
match self {
|
||||
ObjectStoreResource::S3(s3_resource) => s3_resource.expiration,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub enum StorageResourceType {
|
||||
S3,
|
||||
AzureBlob,
|
||||
S3AwsOidc,
|
||||
AzureWorkloadIdentity,
|
||||
GoogleCloudStorage,
|
||||
Filesystem,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct S3Resource {
|
||||
pub bucket: String,
|
||||
pub region: String,
|
||||
#[serde(rename = "endPoint")]
|
||||
pub endpoint: String,
|
||||
#[serde(rename = "useSSL")]
|
||||
pub use_ssl: bool,
|
||||
#[serde(rename = "accessKey")]
|
||||
pub access_key: Option<String>,
|
||||
#[serde(rename = "secretKey")]
|
||||
pub secret_key: Option<String>,
|
||||
#[serde(rename = "pathStyle")]
|
||||
pub path_style: Option<bool>,
|
||||
pub token: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub expiration: Option<DateTime<Utc>>,
|
||||
pub port: Option<u16>,
|
||||
}
|
||||
|
||||
impl S3Resource {
|
||||
pub fn endpoint_with_region_fallback(&self, region_fallback: Option<String>) -> String {
|
||||
if self.endpoint.is_empty() {
|
||||
let final_region = if self.region.is_empty() {
|
||||
region_fallback.unwrap_or_else(|| "us-east-1".to_string())
|
||||
} else {
|
||||
self.region.clone()
|
||||
};
|
||||
format!("s3.{}.amazonaws.com", final_region)
|
||||
} else {
|
||||
self.endpoint.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct AzureBlobResource {
|
||||
pub endpoint: Option<String>,
|
||||
#[serde(rename = "useSSL")]
|
||||
pub use_ssl: Option<bool>,
|
||||
#[serde(rename = "accountName")]
|
||||
pub account_name: String,
|
||||
#[serde(rename = "tenantId")]
|
||||
pub tenant_id: Option<String>,
|
||||
#[serde(rename = "clientId")]
|
||||
pub client_id: Option<String>,
|
||||
#[serde(rename = "containerName")]
|
||||
pub container_name: String,
|
||||
#[serde(rename = "accessKey")]
|
||||
pub access_key: Option<String>,
|
||||
#[serde(rename = "federatedTokenFile")]
|
||||
pub federated_token_file: Option<String>,
|
||||
}
|
||||
|
||||
fn as_string<'de, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
let v: serde_json::Value = Deserialize::deserialize(deserializer)?;
|
||||
serde_json::to_string(&v).map_err(serde::de::Error::custom)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct GcsResource {
|
||||
pub bucket: String,
|
||||
#[serde(rename = "serviceAccountKey")]
|
||||
#[serde(deserialize_with = "as_string")]
|
||||
pub service_account_key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Hash)]
|
||||
pub struct S3AwsOidcResource {
|
||||
#[serde(rename = "bucket")]
|
||||
pub bucket: String,
|
||||
pub region: Option<String>,
|
||||
#[serde(rename = "roleArn")]
|
||||
pub role_arn: String,
|
||||
pub audience: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct S3Object {
|
||||
pub s3: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub storage: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub filename: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub presigned: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BundleFormat {
|
||||
Esm,
|
||||
Cjs,
|
||||
}
|
||||
|
||||
impl BundleFormat {
|
||||
pub fn from_string(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"esm" => Some(Self::Esm),
|
||||
"cjs" => Some(Self::Cjs),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "typ", content = "value")]
|
||||
pub enum ObjectStoreSettings {
|
||||
S3(S3Settings),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ObjectSettings {
|
||||
S3(S3Settings),
|
||||
Azure(AzureBlobResource),
|
||||
AwsOidc(S3AwsOidcResource),
|
||||
Gcs(GcsResource),
|
||||
Filesystem(FilesystemSettings),
|
||||
}
|
||||
|
||||
impl ObjectSettings {
|
||||
pub fn get_bucket(&self) -> Option<&String> {
|
||||
match self {
|
||||
ObjectSettings::S3(s3_settings) => s3_settings.bucket.as_ref(),
|
||||
ObjectSettings::Azure(azure_settings) => Some(&azure_settings.container_name),
|
||||
ObjectSettings::AwsOidc(s3_aws_oidc_settings) => Some(&s3_aws_oidc_settings.bucket),
|
||||
ObjectSettings::Gcs(gcs_settings) => Some(&gcs_settings.bucket),
|
||||
ObjectSettings::Filesystem(fs_settings) => Some(&fs_settings.root_path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct S3Settings {
|
||||
pub bucket: Option<String>,
|
||||
pub region: Option<String>,
|
||||
pub access_key: Option<String>,
|
||||
pub secret_key: Option<String>,
|
||||
pub endpoint: Option<String>,
|
||||
pub allow_http: Option<bool>,
|
||||
pub path_style: Option<bool>,
|
||||
pub store_logs: Option<bool>,
|
||||
pub port: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct DuckdbConnectionSettingsResponse {
|
||||
pub connection_settings_str: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub azure_container_path: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub s3_bucket: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct DuckdbConnectionSettingsQueryV2 {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub s3_resource_path: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub storage: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum S3ModeFormat {
|
||||
Json,
|
||||
Csv,
|
||||
Parquet,
|
||||
}
|
||||
|
||||
pub fn s3_mode_extension(format: S3ModeFormat) -> &'static str {
|
||||
match format {
|
||||
S3ModeFormat::Json => "json",
|
||||
S3ModeFormat::Csv => "csv",
|
||||
S3ModeFormat::Parquet => "parquet",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_s3_permission_serde_custom_format() {
|
||||
let perm = S3Permission::READ | S3Permission::WRITE;
|
||||
let serialized = serde_json::to_string(&perm).unwrap();
|
||||
assert_eq!(serialized, "\"read,write\"");
|
||||
|
||||
let deserialized: S3Permission = serde_json::from_str("\"read,write\"").unwrap();
|
||||
assert_eq!(deserialized, S3Permission::READ | S3Permission::WRITE);
|
||||
|
||||
// Unknown permissions are silently ignored
|
||||
let deserialized: S3Permission =
|
||||
serde_json::from_str("\"read,unknown,delete\"").unwrap();
|
||||
assert_eq!(deserialized, S3Permission::READ | S3Permission::DELETE);
|
||||
|
||||
// All four permissions
|
||||
let all: S3Permission =
|
||||
serde_json::from_str("\"read,write,delete,list\"").unwrap();
|
||||
assert_eq!(
|
||||
all,
|
||||
S3Permission::READ | S3Permission::WRITE | S3Permission::DELETE | S3Permission::LIST
|
||||
);
|
||||
|
||||
// Empty string gives empty permission set
|
||||
let empty: S3Permission = serde_json::from_str("\"\"").unwrap();
|
||||
assert_eq!(empty, S3Permission::empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_s3_resource_endpoint_fallback() {
|
||||
let resource = S3Resource {
|
||||
bucket: "b".to_string(),
|
||||
region: "".to_string(),
|
||||
endpoint: "".to_string(),
|
||||
use_ssl: true,
|
||||
access_key: None,
|
||||
secret_key: None,
|
||||
path_style: None,
|
||||
token: None,
|
||||
expiration: None,
|
||||
port: None,
|
||||
};
|
||||
|
||||
// Both empty, no fallback → defaults to us-east-1
|
||||
assert_eq!(
|
||||
resource.endpoint_with_region_fallback(None),
|
||||
"s3.us-east-1.amazonaws.com"
|
||||
);
|
||||
|
||||
// Both empty, fallback provided
|
||||
assert_eq!(
|
||||
resource.endpoint_with_region_fallback(Some("eu-west-1".to_string())),
|
||||
"s3.eu-west-1.amazonaws.com"
|
||||
);
|
||||
|
||||
// Region set, endpoint empty → use region
|
||||
let with_region = S3Resource {
|
||||
region: "ap-southeast-1".to_string(),
|
||||
..resource.clone()
|
||||
};
|
||||
assert_eq!(
|
||||
with_region.endpoint_with_region_fallback(Some("ignored".to_string())),
|
||||
"s3.ap-southeast-1.amazonaws.com"
|
||||
);
|
||||
|
||||
// Endpoint set → return as-is
|
||||
let with_endpoint = S3Resource {
|
||||
endpoint: "custom.s3.endpoint.com".to_string(),
|
||||
..resource.clone()
|
||||
};
|
||||
assert_eq!(
|
||||
with_endpoint.endpoint_with_region_fallback(Some("ignored".to_string())),
|
||||
"custom.s3.endpoint.com"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lfs_methods_filesystem() {
|
||||
let rules = vec![S3PermissionRule {
|
||||
pattern: "**/*.csv".to_string(),
|
||||
allow: S3Permission::READ,
|
||||
}];
|
||||
let lfs = LargeFileStorage::FilesystemStorage(FilesystemStorage {
|
||||
root_path: "/data/workspace".to_string(),
|
||||
public_resource: Some(true),
|
||||
advanced_permissions: Some(rules.clone()),
|
||||
});
|
||||
|
||||
assert_eq!(lfs.get_s3_resource_path(), "/data/workspace");
|
||||
assert!(lfs.is_public_resource());
|
||||
let perms = lfs.get_advanced_permissions().unwrap();
|
||||
assert_eq!(perms.len(), 1);
|
||||
assert_eq!(perms[0].pattern, "**/*.csv");
|
||||
|
||||
// Default public_resource = false when None
|
||||
let lfs_default = LargeFileStorage::FilesystemStorage(FilesystemStorage {
|
||||
root_path: "/tmp".to_string(),
|
||||
public_resource: None,
|
||||
advanced_permissions: None,
|
||||
});
|
||||
assert!(!lfs_default.is_public_resource());
|
||||
assert!(lfs_default.get_advanced_permissions().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bundle_format_from_string() {
|
||||
assert_eq!(BundleFormat::from_string("esm"), Some(BundleFormat::Esm));
|
||||
assert_eq!(BundleFormat::from_string("cjs"), Some(BundleFormat::Cjs));
|
||||
assert_eq!(BundleFormat::from_string("unknown"), None);
|
||||
assert_eq!(BundleFormat::from_string(""), None);
|
||||
assert_eq!(BundleFormat::from_string("ESM"), None); // case sensitive
|
||||
}
|
||||
}
|
||||
@@ -10,16 +10,16 @@ path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
private = ["windmill-sandbox/private"]
|
||||
private = []
|
||||
mcp = ["dep:windmill-mcp"]
|
||||
prometheus = ["dep:prometheus", "windmill-common/prometheus"]
|
||||
enterprise = ["windmill-queue/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-sandbox/enterprise", "dep:pem", "dep:tokio-util", "dep:opentelemetry-proto", "dep:prost", "dep:hudsucker", "dep:rcgen", "dep:hyper-http-proxy", "dep:hyper-tls", "dep:hyper-util"]
|
||||
enterprise = ["windmill-queue/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "dep:pem", "dep:tokio-util", "dep:opentelemetry-proto", "dep:prost", "dep:hudsucker", "dep:rcgen", "dep:hyper-http-proxy", "dep:hyper-tls", "dep:hyper-util"]
|
||||
mssql = ["dep:tiberius"]
|
||||
mssql-kerberos = ["mssql", "tiberius/integrated-auth-gssapi"] # Linux/Unix integrated auth
|
||||
mssql-winauth = ["mssql", "tiberius/winauth"] # Windows integrated auth
|
||||
bigquery = ["dep:gcp_auth"]
|
||||
benchmark = ["windmill-queue/benchmark", "windmill-common/benchmark"]
|
||||
parquet = ["windmill-common/parquet", "windmill-sandbox/parquet", "dep:object_store"]
|
||||
parquet = ["windmill-common/parquet", "windmill-object-store/parquet"]
|
||||
flow_testing = []
|
||||
cloud = []
|
||||
sqlx = []
|
||||
@@ -45,7 +45,8 @@ windmill-queue.workspace = true
|
||||
windmill-dep-map.workspace = true
|
||||
windmill-audit.workspace = true # there isn't really a reason for audit-worth actions to happen in the worker.
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-sandbox.workspace = true
|
||||
windmill-types.workspace = true
|
||||
windmill-object-store.workspace = true
|
||||
windmill-jseval.workspace = true
|
||||
windmill-runtime-nativets = { workspace = true, optional = true }
|
||||
windmill-mcp = { workspace = true, optional = true }
|
||||
@@ -120,9 +121,6 @@ hex.workspace = true
|
||||
tiberius = { workspace = true, optional = true }
|
||||
tokio-util = { workspace = true, optional = true }
|
||||
tar.workspace = true
|
||||
flate2.workspace = true
|
||||
tempfile.workspace = true
|
||||
object_store = { workspace = true, optional = true}
|
||||
convert_case.workspace = true
|
||||
yaml-rust.workspace = true
|
||||
backon.workspace = true
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use base64::Engine;
|
||||
use futures;
|
||||
use ulid;
|
||||
use windmill_common::{client::AuthedClient, error::Error, s3_helpers::S3Object};
|
||||
use windmill_common::{client::AuthedClient, error::Error};
|
||||
use windmill_types::s3::S3Object;
|
||||
use windmill_queue::MiniPulledJob;
|
||||
|
||||
use crate::ai::types::*;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use async_trait::async_trait;
|
||||
use windmill_common::{
|
||||
client::AuthedClient, error::Error, s3_helpers::S3Object, worker::Connection,
|
||||
client::AuthedClient, error::Error, worker::Connection,
|
||||
};
|
||||
use windmill_queue::MiniPulledJob;
|
||||
use windmill_types::s3::S3Object;
|
||||
|
||||
use crate::{
|
||||
ai::{
|
||||
|
||||
@@ -19,8 +19,8 @@ use windmill_common::{
|
||||
error::Error,
|
||||
flow_status::AgentAction,
|
||||
flows::FlowModule,
|
||||
s3_helpers::S3Object,
|
||||
};
|
||||
use windmill_types::s3::S3Object;
|
||||
use windmill_parser::Typ;
|
||||
|
||||
// Re-export shared types from windmill_common::ai_types
|
||||
|
||||
@@ -6,7 +6,7 @@ use reqwest::Client;
|
||||
use serde_json::{json, value::RawValue, Value};
|
||||
use windmill_common::client::AuthedClient;
|
||||
use windmill_common::error::to_anyhow;
|
||||
use windmill_common::s3_helpers::convert_json_line_stream;
|
||||
use windmill_object_store::convert_json_line_stream;
|
||||
use windmill_common::worker::{Connection, SqlResultCollectionStrategy};
|
||||
use windmill_common::{error::Error, worker::to_raw_value};
|
||||
use windmill_parser_sql::{
|
||||
|
||||
@@ -27,11 +27,11 @@ use crate::{
|
||||
};
|
||||
use windmill_common::{
|
||||
client::AuthedClient,
|
||||
s3_helpers::BundleFormat,
|
||||
scripts::{id_to_codebase_info, CodebaseInfo, ScriptLang},
|
||||
utils::WarnAfterExt,
|
||||
workspace_dependencies::WorkspaceDependenciesPrefetched,
|
||||
};
|
||||
use windmill_types::s3::BundleFormat;
|
||||
|
||||
#[cfg(windows)]
|
||||
use crate::SYSTEM_ROOT;
|
||||
@@ -43,12 +43,13 @@ use tokio::io::AsyncReadExt;
|
||||
use windmill_common::{
|
||||
error::{self, Result},
|
||||
get_latest_hash_for_path,
|
||||
worker::{exists_in_cache, save_cache, write_file, Connection, DISABLE_BUNDLING},
|
||||
worker::{write_file, Connection, DISABLE_BUNDLING},
|
||||
DB,
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
use windmill_common::s3_helpers::attempt_fetch_bytes;
|
||||
use windmill_object_store::attempt_fetch_bytes;
|
||||
use crate::global_cache::{exists_in_cache, save_cache};
|
||||
|
||||
use windmill_parser::Typ;
|
||||
|
||||
@@ -699,7 +700,7 @@ struct PulledCodebase {
|
||||
is_esm: bool,
|
||||
}
|
||||
async fn pull_codebase(w_id: &str, id: &str, job_dir: &str) -> Result<PulledCodebase> {
|
||||
let path = windmill_common::s3_helpers::bundle(&w_id, &id);
|
||||
let path = windmill_object_store::bundle(&w_id, &id);
|
||||
let CodebaseInfo { is_tar, is_esm } = id_to_codebase_info(id);
|
||||
|
||||
let bun_cache_path = format!(
|
||||
@@ -719,7 +720,7 @@ async fn pull_codebase(w_id: &str, id: &str, job_dir: &str) -> Result<PulledCode
|
||||
extract_saved_codebase(job_dir, &bun_cache_path, is_tar, &dst, false)?;
|
||||
} else {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
let object_store = windmill_common::s3_helpers::get_object_store().await;
|
||||
let object_store = windmill_object_store::get_object_store().await;
|
||||
|
||||
#[cfg(not(all(feature = "enterprise", feature = "parquet")))]
|
||||
let object_store: Option<()> = None;
|
||||
@@ -977,7 +978,7 @@ pub async fn handle_bun_job(
|
||||
};
|
||||
|
||||
let (cache, logs) =
|
||||
windmill_common::worker::load_cache(&local_path, &remote_path, false).await;
|
||||
crate::global_cache::load_cache(&local_path, &remote_path, false).await;
|
||||
(cache, logs, local_path, remote_path)
|
||||
} else {
|
||||
(false, "".to_string(), "".to_string(), "".to_string())
|
||||
|
||||
@@ -16,9 +16,9 @@ use tokio::{fs::File, io::AsyncReadExt};
|
||||
|
||||
use windmill_common::flows::Step;
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_common::s3_helpers::{
|
||||
get_etag_or_empty, LargeFileStorage, ObjectStoreResource, S3Object,
|
||||
};
|
||||
use windmill_types::s3::{LargeFileStorage, ObjectStoreResource, S3Object};
|
||||
#[cfg(feature = "parquet")]
|
||||
use windmill_object_store::get_etag_or_empty;
|
||||
use windmill_common::variables::{build_crypt_with_key_suffix, decrypt};
|
||||
use windmill_common::worker::{
|
||||
to_raw_value, update_ping_for_failed_init_script_query, write_file, Connection, Ping, PingType,
|
||||
@@ -893,9 +893,8 @@ async fn get_workspace_s3_resource_path(
|
||||
storage: Option<&String>,
|
||||
job_id: &Uuid,
|
||||
) -> windmill_common::error::Result<Option<ObjectStoreResource>> {
|
||||
use windmill_common::{
|
||||
job_s3_helpers_oss::get_s3_resource_internal, s3_helpers::StorageResourceType,
|
||||
};
|
||||
use windmill_object_store::job_s3_helpers_oss::get_s3_resource_internal;
|
||||
use windmill_types::s3::StorageResourceType;
|
||||
|
||||
let raw_lfs_opt = if let Some(storage) = storage {
|
||||
sqlx::query_scalar!(
|
||||
@@ -948,7 +947,10 @@ async fn get_workspace_s3_resource_path(
|
||||
resource_path.to_string(),
|
||||
)
|
||||
}
|
||||
Some(LargeFileStorage::FilesystemStorage(_)) | None => {
|
||||
Some(LargeFileStorage::FilesystemStorage(fs)) => {
|
||||
(StorageResourceType::Filesystem, fs.root_path.clone())
|
||||
}
|
||||
None => {
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
@@ -962,21 +964,22 @@ async fn get_workspace_s3_resource_path(
|
||||
let object_store_resource = get_s3_resource_internal(
|
||||
rt,
|
||||
s3_resource_value_raw,
|
||||
windmill_common::job_s3_helpers_oss::TokenGenerator::AsClient(client),
|
||||
windmill_object_store::job_s3_helpers_oss::TokenGenerator::AsClient(client),
|
||||
db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Check bucket workspace restrictions
|
||||
use windmill_common::s3_helpers::ObjectStoreResource;
|
||||
use windmill_object_store::ObjectStoreResource;
|
||||
let bucket_name = match &object_store_resource {
|
||||
ObjectStoreResource::S3(s3_resource) => Some(&s3_resource.bucket),
|
||||
ObjectStoreResource::Azure(azure_resource) => Some(&azure_resource.container_name),
|
||||
ObjectStoreResource::Gcs(gcs_resource) => Some(&gcs_resource.bucket),
|
||||
ObjectStoreResource::Filesystem(_) => None,
|
||||
};
|
||||
|
||||
if let Some(bucket) = bucket_name {
|
||||
windmill_common::s3_helpers::check_bucket_workspace_restriction(bucket, workspace_id)?;
|
||||
windmill_object_store::check_bucket_workspace_restriction(bucket, workspace_id)?;
|
||||
}
|
||||
|
||||
Ok(Some(object_store_resource))
|
||||
@@ -1364,8 +1367,8 @@ impl S3ModeWorkerData {
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn to_return_s3_obj(&self) -> windmill_common::s3_helpers::S3Object {
|
||||
windmill_common::s3_helpers::S3Object {
|
||||
pub fn to_return_s3_obj(&self) -> windmill_types::s3::S3Object {
|
||||
windmill_types::s3::S3Object {
|
||||
s3: self.object_key.clone(),
|
||||
storage: self.storage.clone(),
|
||||
..Default::default()
|
||||
@@ -1554,3 +1557,4 @@ mod tests {
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,11 +15,13 @@ use tokio::{fs::File, io::AsyncReadExt, process::Command};
|
||||
#[cfg(feature = "csharp")]
|
||||
use windmill_common::{
|
||||
utils::calculate_hash,
|
||||
worker::{save_cache, write_file},
|
||||
worker::write_file,
|
||||
};
|
||||
|
||||
use windmill_common::error::{self, Error};
|
||||
#[cfg(feature = "csharp")]
|
||||
use crate::global_cache::save_cache;
|
||||
#[cfg(feature = "csharp")]
|
||||
use windmill_queue::append_logs;
|
||||
|
||||
use windmill_queue::CanceledBy;
|
||||
@@ -518,7 +520,7 @@ pub async fn handle_csharp_job(
|
||||
let remote_path = format!("{CSHARP_OBJECT_STORE_PREFIX}{hash}");
|
||||
|
||||
let (cache, cache_logs) =
|
||||
windmill_common::worker::load_cache(&bin_path, &remote_path, false).await;
|
||||
crate::global_cache::load_cache(&bin_path, &remote_path, false).await;
|
||||
|
||||
let cache_logs = if cache {
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -10,7 +10,8 @@ use serde_json::value::RawValue;
|
||||
use serde_json::{json, Value};
|
||||
use uuid::Uuid;
|
||||
use windmill_common::error::{to_anyhow, Error, Result};
|
||||
use windmill_common::s3_helpers::{S3Object, S3_PROXY_LAST_ERRORS_CACHE};
|
||||
use windmill_types::s3::S3Object;
|
||||
use windmill_object_store::S3_PROXY_LAST_ERRORS_CACHE;
|
||||
use windmill_common::utils::sanitize_string_from_password;
|
||||
use windmill_common::worker::{Connection, SqlResultCollectionStrategy};
|
||||
use windmill_common::workspaces::{
|
||||
@@ -29,7 +30,7 @@ use crate::mysql_executor::MysqlDatabase;
|
||||
use crate::sanitized_sql_params::sanitize_and_interpolate_unsafe_sql_args;
|
||||
use crate::sql_utils::remove_comments;
|
||||
use windmill_common::client::AuthedClient;
|
||||
use windmill_common::s3_helpers::DEFAULT_STORAGE;
|
||||
use windmill_object_store::DEFAULT_STORAGE;
|
||||
|
||||
pub async fn do_duckdb(
|
||||
job: &MiniPulledJob,
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
// #[cfg(feature = "enterprise")]
|
||||
// use rand::Rng;
|
||||
|
||||
use tokio::time::Instant;
|
||||
use windmill_common::error;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
use object_store::ObjectStore;
|
||||
use windmill_object_store::object_store_reexports::ObjectStore;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
use std::sync::Arc;
|
||||
@@ -21,7 +18,7 @@ pub async fn build_tar_and_push(
|
||||
custom_folder_name: Option<String>,
|
||||
platform_agnostic: bool,
|
||||
) -> error::Result<()> {
|
||||
use object_store::path::Path;
|
||||
use windmill_object_store::object_store_reexports::Path;
|
||||
use tokio::fs::create_dir_all;
|
||||
|
||||
use crate::TAR_PYBASE_CACHE_DIR;
|
||||
@@ -94,7 +91,7 @@ pub async fn pull_from_tar(
|
||||
custom_folder_name: Option<String>,
|
||||
platform_agnostic: bool,
|
||||
) -> error::Result<()> {
|
||||
use windmill_common::s3_helpers::attempt_fetch_bytes;
|
||||
use windmill_object_store::attempt_fetch_bytes;
|
||||
|
||||
let folder_name = if let Some(name) = custom_folder_name {
|
||||
name
|
||||
@@ -147,3 +144,142 @@ pub fn extract_tar(tar: bytes::Bytes, folder: &str) -> error::Result<()> {
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Two-tier cache load: check local disk first, then fall back to instance object store.
|
||||
/// Returns `(hit, log_message)`.
|
||||
pub async fn load_cache(bin_path: &str, _remote_path: &str, is_dir: bool) -> (bool, String) {
|
||||
if tokio::fs::metadata(&bin_path).await.is_ok() {
|
||||
(true, format!("loaded from local cache: {}\n", bin_path))
|
||||
} else {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = windmill_object_store::get_object_store().await {
|
||||
let started = std::time::Instant::now();
|
||||
|
||||
if let Ok(mut x) = windmill_object_store::attempt_fetch_bytes(os, _remote_path).await {
|
||||
if is_dir {
|
||||
if let Err(e) = windmill_common::worker::extract_tar(x, bin_path).await {
|
||||
tracing::error!("could not write tar archive locally: {e:?}");
|
||||
return (
|
||||
false,
|
||||
"error writing tar archive from object store".to_string(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if let Err(e) = windmill_common::worker::write_binary_file(bin_path, &mut x) {
|
||||
tracing::error!("could not write bundle/bin file locally: {e:?}");
|
||||
return (
|
||||
false,
|
||||
"error writing bundle/bin file from object store".to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
tracing::info!("loaded from object store {}", bin_path);
|
||||
return (
|
||||
true,
|
||||
format!(
|
||||
"loaded bin/bundle from object store {} in {}ms",
|
||||
bin_path,
|
||||
started.elapsed().as_millis()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
let _ = is_dir;
|
||||
(false, "".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether a binary/bundle exists in local cache or instance object store.
|
||||
pub async fn exists_in_cache(bin_path: &str, _remote_path: &str) -> bool {
|
||||
if tokio::fs::metadata(&bin_path).await.is_ok() {
|
||||
return true;
|
||||
} else {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = windmill_object_store::get_object_store().await {
|
||||
return os
|
||||
.get(&windmill_object_store::object_store_reexports::Path::from(_remote_path))
|
||||
.await
|
||||
.is_ok();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Two-tier cache write: upload to instance object store, then copy to local disk.
|
||||
pub async fn save_cache(
|
||||
local_cache_path: &str,
|
||||
_remote_cache_path: &str,
|
||||
origin: &str,
|
||||
is_dir: bool,
|
||||
) -> windmill_common::error::Result<String> {
|
||||
use std::path::PathBuf;
|
||||
|
||||
let mut _cached_to_s3 = false;
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = windmill_object_store::get_object_store().await {
|
||||
use windmill_object_store::object_store_reexports::Path;
|
||||
let file_to_cache = if is_dir {
|
||||
let tar_path = format!(
|
||||
"{}/tar/{}_tar.tar",
|
||||
windmill_common::worker::ROOT_CACHE_DIR,
|
||||
local_cache_path
|
||||
.split("/")
|
||||
.last()
|
||||
.unwrap_or(&uuid::Uuid::new_v4().to_string())
|
||||
);
|
||||
let tar_file = std::fs::File::create(&tar_path)?;
|
||||
let mut tar = tar::Builder::new(tar_file);
|
||||
tar.append_dir_all(".", &origin)?;
|
||||
let tar_metadata = tokio::fs::metadata(&tar_path).await;
|
||||
if tar_metadata.is_err() || tar_metadata.as_ref().unwrap().len() == 0 {
|
||||
tracing::info!("Failed to tar cache: {origin}");
|
||||
return Err(error::Error::ExecutionErr(format!(
|
||||
"Failed to tar cache: {origin}"
|
||||
)));
|
||||
}
|
||||
tar_path
|
||||
} else {
|
||||
origin.to_owned()
|
||||
};
|
||||
|
||||
if let Err(e) = os
|
||||
.put(
|
||||
&Path::from(_remote_cache_path),
|
||||
std::fs::read(&file_to_cache)?.into(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to put bin to object store: {_remote_cache_path}. Error: {:?}",
|
||||
e
|
||||
);
|
||||
} else {
|
||||
_cached_to_s3 = true;
|
||||
if is_dir {
|
||||
tokio::fs::remove_dir_all(&file_to_cache).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if true {
|
||||
if is_dir {
|
||||
windmill_common::worker::copy_dir_recursively(
|
||||
&PathBuf::from(origin),
|
||||
&PathBuf::from(local_cache_path),
|
||||
)?;
|
||||
} else {
|
||||
std::fs::copy(origin, local_cache_path)?;
|
||||
}
|
||||
Ok(format!(
|
||||
"\nwrote cached binary: {} (backed by EE distributed object store: {_cached_to_s3})\n",
|
||||
local_cache_path
|
||||
))
|
||||
} else if _cached_to_s3 {
|
||||
Ok(format!(
|
||||
"wrote cached binary to object store {}\n",
|
||||
local_cache_path
|
||||
))
|
||||
} else {
|
||||
Ok("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@ use uuid::Uuid;
|
||||
use windmill_common::{
|
||||
error::{self, Error},
|
||||
utils::calculate_hash,
|
||||
worker::{save_cache, write_file, Connection, GoAnnotations},
|
||||
worker::{write_file, Connection, GoAnnotations},
|
||||
};
|
||||
use crate::global_cache::save_cache;
|
||||
use windmill_parser_go::{parse_go_imports, REQUIRE_PARSE};
|
||||
use windmill_queue::{append_logs, CanceledBy, MiniPulledJob};
|
||||
|
||||
@@ -110,7 +111,7 @@ pub async fn handle_go_job(
|
||||
let bin_path = format!("{}/{hash}", GO_BIN_CACHE_DIR);
|
||||
let remote_path = format!("{GO_OBJECT_STORE_PREFIX}{hash}");
|
||||
let (cache, cache_logs) =
|
||||
windmill_common::worker::load_cache(&bin_path, &remote_path, false).await;
|
||||
crate::global_cache::load_cache(&bin_path, &remote_path, false).await;
|
||||
|
||||
let (skip_go_mod, skip_tidy) = if cache {
|
||||
(true, true)
|
||||
|
||||
@@ -13,8 +13,9 @@ use uuid::Uuid;
|
||||
use windmill_common::{
|
||||
error::{self, Error},
|
||||
utils::calculate_hash,
|
||||
worker::{copy_dir_recursively, save_cache, write_file, Connection},
|
||||
worker::{copy_dir_recursively, write_file, Connection},
|
||||
};
|
||||
use crate::global_cache::save_cache;
|
||||
use windmill_parser::Arg;
|
||||
use windmill_parser_java::parse_java_sig_meta;
|
||||
use windmill_queue::{append_logs, CanceledBy, MiniPulledJob};
|
||||
@@ -466,7 +467,7 @@ async fn compile<'a>(
|
||||
let hash = compute_hash(inner_content, *requirements_o);
|
||||
let bin_path = format!("{}/{hash}", JAVA_CACHE_DIR);
|
||||
let remote_path = format!("java_jar/{hash}");
|
||||
let (cache, ..) = windmill_common::worker::load_cache(&bin_path, &remote_path, true).await;
|
||||
let (cache, ..) = crate::global_cache::load_cache(&bin_path, &remote_path, true).await;
|
||||
|
||||
if cache {
|
||||
let target = format!("{job_dir}/target");
|
||||
|
||||
@@ -11,7 +11,7 @@ use tiberius::{
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::compat::TokioAsyncWriteCompatExt;
|
||||
use uuid::Uuid;
|
||||
use windmill_common::s3_helpers::convert_json_line_stream;
|
||||
use windmill_object_store::convert_json_line_stream;
|
||||
use windmill_common::utils::merge_raw_values_to_object;
|
||||
use windmill_common::worker::SqlResultCollectionStrategy;
|
||||
use windmill_common::{
|
||||
|
||||
@@ -15,9 +15,9 @@ use tokio::sync::Mutex;
|
||||
use windmill_common::{
|
||||
client::AuthedClient,
|
||||
error::{to_anyhow, Error},
|
||||
s3_helpers::convert_json_line_stream,
|
||||
worker::{to_raw_value, Connection, SqlResultCollectionStrategy},
|
||||
};
|
||||
use windmill_object_store::convert_json_line_stream;
|
||||
use windmill_parser_sql::{
|
||||
parse_db_resource, parse_mysql_sig, parse_s3_mode, parse_sql_blocks,
|
||||
parse_sql_statement_named_params, RE_ARG_MYSQL_NAMED,
|
||||
|
||||
@@ -10,9 +10,9 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, value::RawValue, Value};
|
||||
use windmill_common::{
|
||||
error::{to_anyhow, Error},
|
||||
s3_helpers::convert_json_line_stream,
|
||||
worker::{to_raw_value, Connection, SqlResultCollectionStrategy},
|
||||
};
|
||||
use windmill_object_store::convert_json_line_stream;
|
||||
use windmill_queue::MiniPulledJob;
|
||||
|
||||
use windmill_parser_sql::{
|
||||
|
||||
@@ -24,7 +24,7 @@ use tokio_postgres::{
|
||||
use uuid::Uuid;
|
||||
use windmill_common::error::to_anyhow;
|
||||
use windmill_common::error::{self, Error};
|
||||
use windmill_common::s3_helpers::convert_json_line_stream;
|
||||
use windmill_object_store::convert_json_line_stream;
|
||||
use windmill_common::worker::{
|
||||
to_raw_value, Connection, SqlResultCollectionStrategy, CLOUD_HOSTED,
|
||||
};
|
||||
|
||||
@@ -93,7 +93,7 @@ struct PiptarUploadTask {
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet", unix))]
|
||||
async fn handle_piptar_uploads(mut rx: tokio::sync::mpsc::UnboundedReceiver<PiptarUploadTask>) {
|
||||
use crate::global_cache::build_tar_and_push;
|
||||
use windmill_common::s3_helpers::get_object_store;
|
||||
use windmill_object_store::get_object_store;
|
||||
|
||||
while let Some(task) = rx.recv().await {
|
||||
if let Some(os) = get_object_store().await {
|
||||
@@ -123,7 +123,7 @@ const RELATIVE_PYTHON_LOADER: &str = include_str!("../loader.py");
|
||||
use crate::global_cache::pull_from_tar;
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet", unix))]
|
||||
use windmill_common::s3_helpers::OBJECT_STORE_SETTINGS;
|
||||
use windmill_object_store::OBJECT_STORE_SETTINGS;
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
@@ -1920,7 +1920,7 @@ pub async fn handle_python_reqs(
|
||||
let start = std::time::Instant::now();
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet", unix))]
|
||||
if is_not_pro {
|
||||
if let Some(os) = windmill_common::s3_helpers::get_object_store().await {
|
||||
if let Some(os) = windmill_object_store::get_object_store().await {
|
||||
tokio::select! {
|
||||
// Cancel was called on the job
|
||||
_ = kill_rx.recv() => return Err(Error::from(anyhow::anyhow!("S3 pull was canceled"))),
|
||||
|
||||
@@ -14,8 +14,9 @@ use tokio::{
|
||||
use windmill_common::{
|
||||
error::{self, Error},
|
||||
utils::calculate_hash,
|
||||
worker::{save_cache, write_file, Connection},
|
||||
worker::{write_file, Connection},
|
||||
};
|
||||
use crate::global_cache::save_cache;
|
||||
use windmill_queue::MiniPulledJob;
|
||||
use windmill_queue::{append_logs, CanceledBy};
|
||||
|
||||
@@ -611,7 +612,7 @@ pub async fn handle_rust_job(
|
||||
get_reserved_variables(job, &client.token, conn, parent_runnable_path).await?;
|
||||
|
||||
let (cache, cache_logs) =
|
||||
windmill_common::worker::load_cache(&bin_path, &remote_path, false).await;
|
||||
crate::global_cache::load_cache(&bin_path, &remote_path, false).await;
|
||||
|
||||
let cache_logs = if cache {
|
||||
let target = format!("{job_dir}/main");
|
||||
|
||||
@@ -9,7 +9,7 @@ use serde_json::{json, value::RawValue, Value};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::HashMap;
|
||||
use windmill_common::error::to_anyhow;
|
||||
use windmill_common::s3_helpers::convert_json_line_stream;
|
||||
use windmill_object_store::convert_json_line_stream;
|
||||
use windmill_common::worker::{Connection, SqlResultCollectionStrategy};
|
||||
|
||||
use windmill_common::{error::Error, worker::to_raw_value};
|
||||
|
||||
@@ -175,7 +175,7 @@ pub async fn par_install_language_dependencies_all_at_once<
|
||||
mark_success(path.clone(), job_id, w_id).await;
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
{
|
||||
if let Some(os) = windmill_common::s3_helpers::get_object_store().await {
|
||||
if let Some(os) = windmill_object_store::get_object_store().await {
|
||||
let language_name = _language_name.to_owned();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = crate::global_cache::build_tar_and_push(
|
||||
@@ -524,7 +524,7 @@ async fn try_install_one_detached<'a, T: Clone + std::marker::Send + Sync + 'a +
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
let s3_pull_future = if is_not_pro {
|
||||
if let Some(os) = windmill_common::s3_helpers::get_object_store().await {
|
||||
if let Some(os) = windmill_object_store::get_object_store().await {
|
||||
Some(crate::global_cache::pull_from_tar(
|
||||
os,
|
||||
dep.path.clone(),
|
||||
@@ -624,7 +624,7 @@ async fn try_install_one_detached<'a, T: Clone + std::marker::Send + Sync + 'a +
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
{
|
||||
if let Some(os) = windmill_common::s3_helpers::get_object_store().await {
|
||||
if let Some(os) = windmill_object_store::get_object_store().await {
|
||||
let language_name = _language_name.to_string();
|
||||
let platform_agnostic = _platform_agnostic;
|
||||
let path = dep.path.clone();
|
||||
@@ -685,7 +685,7 @@ async fn print_success(
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if windmill_common::s3_helpers::OBJECT_STORE_SETTINGS
|
||||
if windmill_object_store::OBJECT_STORE_SETTINGS
|
||||
.read()
|
||||
.await
|
||||
.is_none()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Navigate to the target directory
|
||||
cd ../../windmill-ee-private || { echo "Directory not found"; exit 1; }
|
||||
cd ../../windmill-ee-private || cd ~/windmill-ee-private || { echo "Directory not found"; exit 1; }
|
||||
|
||||
# Get the current commit hash
|
||||
commit_hash=$(git rev-parse HEAD)
|
||||
@@ -13,4 +13,4 @@ cd - || exit
|
||||
echo -n "$commit_hash" > ./ee-repo-ref.txt
|
||||
|
||||
# Confirmation message
|
||||
echo "Commit hash $commit_hash written to ee-repo-ref.txt"
|
||||
echo "Commit hash $commit_hash written to ee-repo-ref.txt"
|
||||
|
||||
Reference in New Issue
Block a user