Skip to content

Commit 09ac06a

Browse files
authored
IceHut Service Daemon (#177)
* IceHut CLI * Renaming binary * Fixing edition * Formatting fixes
1 parent c7ad440 commit 09ac06a

File tree

9 files changed

+218
-96
lines changed

9 files changed

+218
-96
lines changed

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
[workspace]
2+
default-members = ["bin/icehutd"]
23
members = [
4+
"bin/icehutd",
35
"crates/utils",
46
"crates/catalog",
57
"crates/control_plane",
68
"crates/nexus",
7-
"crates/runtime",
9+
"crates/runtime",
810
]
911
resolver = "2"
1012

@@ -30,7 +32,6 @@ utoipa = { version = "5.0.0-beta.0", features = ["uuid", "chrono"] }
3032
utoipa-axum = { version = "0.1.0-beta.2" }
3133
utoipa-swagger-ui = { version = "7.1.1-beta.0", features = ["axum"] }
3234
lazy_static = { version = "1.5" }
33-
dotenv = { version = "0.15.0" }
3435
datafusion = { version = "43" }
3536
snafu = { version = "0.8.5", features = ["futures"] }
3637
tracing = { version = "0.1" }

bin/icehutd/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "icehutd"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
tokio = { workspace = true }
8+
object_store = { workspace = true }
9+
dotenv = { version = "0.15.0" }
10+
clap = { version = "4.5.27", features = ["env", "derive"] }
11+
nexus = { path = "../../crates/nexus" }
12+
tracing = { version = "0.1" }
13+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
14+
15+
[lints]
16+
workspace = true

bin/icehutd/src/cli.rs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use clap::{Parser, ValueEnum};
2+
use object_store::{
3+
aws::AmazonS3Builder, aws::S3ConditionalPut, local::LocalFileSystem, memory::InMemory,
4+
ObjectStore, Result as ObjectStoreResult,
5+
};
6+
use std::path::PathBuf;
7+
8+
#[derive(Parser)]
9+
#[command(version, about, long_about=None)]
10+
pub struct IceHutOpts {
11+
#[arg(
12+
short,
13+
long,
14+
value_enum,
15+
env = "OBJECT_STORE_BACKEND",
16+
help = "Backend to use for state storage"
17+
)]
18+
backend: StoreBackend,
19+
20+
#[arg(
21+
long,
22+
env = "AWS_ACCESS_KEY_ID",
23+
required_if_eq("backend", "s3"),
24+
help = "AWS Access Key ID",
25+
help_heading = "S3 Backend Options"
26+
)]
27+
access_key_id: Option<String>,
28+
#[arg(
29+
long,
30+
env = "AWS_SECRET_ACCESS_KEY",
31+
required_if_eq("backend", "s3"),
32+
help = "AWS Secret Access Key",
33+
help_heading = "S3 Backend Options"
34+
)]
35+
secret_access_key: Option<String>,
36+
#[arg(
37+
long,
38+
env = "AWS_REGION",
39+
required_if_eq("backend", "s3"),
40+
help = "AWS Region",
41+
help_heading = "S3 Backend Options"
42+
)]
43+
region: Option<String>,
44+
#[arg(
45+
long,
46+
env = "S3_BUCKET",
47+
required_if_eq("backend", "s3"),
48+
help = "S3 Bucket Name",
49+
help_heading = "S3 Backend Options"
50+
)]
51+
bucket: Option<String>,
52+
#[arg(
53+
long,
54+
env = "S3_ENDPOINT",
55+
help = "S3 Endpoint (Optional)",
56+
help_heading = "S3 Backend Options"
57+
)]
58+
endpoint: Option<String>,
59+
#[arg(
60+
long,
61+
env = "S3_ALLOW_HTTP",
62+
help = "Allow HTTP for S3 (Optional)",
63+
help_heading = "S3 Backend Options"
64+
)]
65+
allow_http: Option<bool>,
66+
67+
#[arg(long, env="FILE_STORAGE_PATH",
68+
required_if_eq("backend", "file"),
69+
conflicts_with_all(["access_key_id", "secret_access_key", "region", "bucket", "endpoint", "allow_http"]),
70+
help_heading="File Backend Options",
71+
help="Path to the directory where files will be stored"
72+
)]
73+
file_storage_path: Option<PathBuf>,
74+
75+
#[arg(short, long, env = "SLATEDB_PREFIX")]
76+
pub slatedb_prefix: String,
77+
78+
#[arg(
79+
long,
80+
env = "ICEHUT_HOST",
81+
default_value = "127.0.0.1",
82+
help = "Host to bind to"
83+
)]
84+
pub host: Option<String>,
85+
86+
#[arg(
87+
long,
88+
env = "ICEHUT_PORT",
89+
default_value = "3000",
90+
help = "Port to bind to"
91+
)]
92+
pub port: Option<u16>,
93+
94+
#[arg(long, default_value = "false")]
95+
use_fs: Option<bool>,
96+
}
97+
98+
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
99+
enum StoreBackend {
100+
S3,
101+
File,
102+
Memory,
103+
}
104+
105+
impl IceHutOpts {
106+
#[allow(clippy::unwrap_used, clippy::as_conversions)]
107+
pub fn object_store_backend(self) -> ObjectStoreResult<Box<dyn ObjectStore>> {
108+
// TODO: Hacky workaround for now, need to figure out a better way to pass this
109+
// TODO: Really, seriously remove this. This is a hack.
110+
unsafe {
111+
let use_fs = self.use_fs.unwrap_or(false);
112+
std::env::set_var("USE_FILE_SYSTEM_INSTEAD_OF_CLOUD", use_fs.to_string());
113+
}
114+
match self.backend {
115+
StoreBackend::S3 => {
116+
let s3_allow_http = self.allow_http.unwrap_or(false);
117+
118+
let s3_builder = AmazonS3Builder::new()
119+
.with_access_key_id(self.access_key_id.unwrap())
120+
.with_secret_access_key(self.secret_access_key.unwrap())
121+
.with_region(self.region.unwrap())
122+
.with_bucket_name(self.bucket.unwrap())
123+
.with_conditional_put(S3ConditionalPut::ETagMatch);
124+
125+
if let Some(endpoint) = self.endpoint {
126+
s3_builder
127+
.with_endpoint(&endpoint)
128+
.with_allow_http(s3_allow_http)
129+
.build()
130+
.map(|s3| Box::new(s3) as Box<dyn ObjectStore>)
131+
} else {
132+
s3_builder
133+
.build()
134+
.map(|s3| Box::new(s3) as Box<dyn ObjectStore>)
135+
}
136+
}
137+
StoreBackend::File => {
138+
let file_storage_path = self.file_storage_path.unwrap();
139+
LocalFileSystem::new_with_prefix(file_storage_path)
140+
.map(|fs| Box::new(fs) as Box<dyn ObjectStore>)
141+
}
142+
StoreBackend::Memory => Ok(Box::new(InMemory::new()) as Box<dyn ObjectStore>),
143+
}
144+
}
145+
}

bin/icehutd/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
//use nexus::run_icehut;

bin/icehutd/src/main.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
pub(crate) mod cli;
2+
3+
use clap::Parser;
4+
use dotenv::dotenv;
5+
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
6+
7+
#[tokio::main]
8+
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::print_stdout)]
9+
async fn main() {
10+
dotenv().ok();
11+
12+
tracing_subscriber::registry()
13+
.with(
14+
tracing_subscriber::EnvFilter::try_from_default_env()
15+
.unwrap_or_else(|_| "icehut=debug,nexus=debug,tower_http=debug".into()),
16+
)
17+
.with(tracing_subscriber::fmt::layer())
18+
.init();
19+
20+
let opts = cli::IceHutOpts::parse();
21+
let slatedb_prefix = opts.slatedb_prefix.clone();
22+
let host = opts.host.clone().unwrap();
23+
let port = opts.port.unwrap();
24+
let object_store = opts.object_store_backend();
25+
26+
match object_store {
27+
Err(e) => {
28+
tracing::error!("Failed to create object store: {:?}", e);
29+
return;
30+
}
31+
Ok(object_store) => {
32+
tracing::info!("Starting ❄️🏠 IceHut...");
33+
34+
if let Err(e) = nexus::run_icehut(object_store, slatedb_prefix, host, port).await {
35+
tracing::error!("Failed to start IceHut: {:?}", e);
36+
}
37+
}
38+
}
39+
}

crates/control_plane/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ arrow = { version = "53" }
3030
arrow-json = { version = "53" }
3131
datafusion-functions-json = { version = "0.43.0" }
3232
object_store = { workspace = true }
33-
dotenv = { workspace = true }
3433
rusoto_core = { version = "0.48.0" }
3534
rusoto_s3 = { version = "0.48.0" }
3635
rusoto_credential = { version = "0.48.0" }

crates/control_plane/src/models/mod.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use arrow::array::RecordBatch;
22
use arrow::datatypes::{DataType, Field};
33
use chrono::{NaiveDateTime, Utc};
4-
use dotenv::dotenv;
54
use iceberg_rust::object_store::ObjectStoreBuilder;
65
use object_store::aws::AmazonS3Builder;
76
use object_store::local::LocalFileSystem;
@@ -175,8 +174,6 @@ impl StorageProfile {
175174
///
176175
/// This function will return an error if the cloud platform isn't supported.
177176
pub fn get_base_url(&self) -> ControlPlaneModelResult<String> {
178-
// Doing this for every call is not efficient
179-
dotenv().ok();
180177
let use_file_system_instead_of_cloud = env::var("USE_FILE_SYSTEM_INSTEAD_OF_CLOUD")
181178
.unwrap_or_else(|_| "true".to_string())
182179
.parse::<bool>()
@@ -221,8 +218,6 @@ impl StorageProfile {
221218

222219
// This is needed to initialize the catalog used in JanKaul code
223220
pub fn get_object_store_builder(&self) -> ControlPlaneModelResult<ObjectStoreBuilder> {
224-
// TODO remove duplicated code
225-
dotenv().ok();
226221
let use_file_system_instead_of_cloud = env::var("USE_FILE_SYSTEM_INSTEAD_OF_CLOUD")
227222
.context(error::MissingEnvironmentVariableSnafu {
228223
var: "USE_FILE_SYSTEM_INSTEAD_OF_CLOUD".to_string(),
@@ -274,8 +269,6 @@ impl StorageProfile {
274269
}
275270

276271
pub fn get_object_store(&self) -> ControlPlaneModelResult<Box<dyn ObjectStore>> {
277-
// TODO remove duplicated code
278-
dotenv().ok();
279272
let use_file_system_instead_of_cloud = env::var("USE_FILE_SYSTEM_INSTEAD_OF_CLOUD")
280273
.context(error::MissingEnvironmentVariableSnafu {
281274
var: "USE_FILE_SYSTEM_INSTEAD_OF_CLOUD".to_string(),

crates/nexus/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ utils = { path = "../utils" }
2626
utoipa = { workspace = true }
2727
utoipa-axum = { workspace = true }
2828
utoipa-swagger-ui = { workspace = true }
29-
dotenv = { workspace = true }
3029
swagger = { version = "6.1", features = ["serdejson", "server", "client", "tls", "tcp"] }
3130
validator = { version = "0.18.1", features = ["derive"] }
3231
tracing = { version = "0.1" }

0 commit comments

Comments
 (0)