Skip to content

Commit d0c3d8d

Browse files
authored
Merge pull request #707 from FrankYang0529/read-dotenv
feat(config): support dotenv
2 parents 232567f + ca71dd1 commit d0c3d8d

File tree

8 files changed

+105
-95
lines changed

8 files changed

+105
-95
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/config/src/provider/env.rs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1+
use std::collections::HashMap;
2+
13
use anyhow::Context;
24

35
use crate::{Key, Provider};
46

5-
const DEFAULT_PREFIX: &str = "SPIN_APP";
7+
pub const DEFAULT_PREFIX: &str = "SPIN_APP";
68

79
/// A config Provider that uses environment variables.
810
#[derive(Debug)]
911
pub struct EnvProvider {
1012
prefix: String,
13+
envs: HashMap<String, String>,
1114
}
1215

1316
impl EnvProvider {
1417
/// Creates a new EnvProvider.
15-
pub fn new(prefix: impl Into<String>) -> Self {
18+
pub fn new(prefix: impl Into<String>, envs: HashMap<String, String>) -> Self {
1619
Self {
1720
prefix: prefix.into(),
21+
envs,
1822
}
1923
}
2024
}
@@ -23,6 +27,7 @@ impl Default for EnvProvider {
2327
fn default() -> Self {
2428
Self {
2529
prefix: DEFAULT_PREFIX.to_string(),
30+
envs: HashMap::new(),
2631
}
2732
}
2833
}
@@ -31,7 +36,13 @@ impl Provider for EnvProvider {
3136
fn get(&self, key: &Key) -> anyhow::Result<Option<String>> {
3237
let env_key = format!("{}_{}", &self.prefix, key.as_ref().to_ascii_uppercase());
3338
match std::env::var(&env_key) {
34-
Err(std::env::VarError::NotPresent) => Ok(None),
39+
Err(std::env::VarError::NotPresent) => {
40+
if let Some(value) = self.envs.get(&env_key) {
41+
return Ok(Some(value.to_string()));
42+
}
43+
44+
Ok(None)
45+
}
3546
other => other
3647
.map(Some)
3748
.with_context(|| format!("failed to resolve env var {}", &env_key)),
@@ -46,11 +57,30 @@ mod test {
4657
#[test]
4758
fn provider_get() {
4859
std::env::set_var("TESTING_SPIN_ENV_KEY1", "val");
49-
let key = Key::new("env_key1").unwrap();
60+
let key1 = Key::new("env_key1").unwrap();
61+
let mut envs = HashMap::new();
62+
envs.insert(
63+
"TESTING_SPIN_ENV_KEY1".to_string(),
64+
"dotenv_val".to_string(),
65+
);
5066
assert_eq!(
51-
EnvProvider::new("TESTING_SPIN").get(&key).unwrap(),
67+
EnvProvider::new("TESTING_SPIN", envs.clone())
68+
.get(&key1)
69+
.unwrap(),
5270
Some("val".to_string())
5371
);
72+
73+
let key2 = Key::new("env_key2").unwrap();
74+
envs.insert(
75+
"TESTING_SPIN_ENV_KEY2".to_string(),
76+
"dotenv_val".to_string(),
77+
);
78+
assert_eq!(
79+
EnvProvider::new("TESTING_SPIN", envs.clone())
80+
.get(&key2)
81+
.unwrap(),
82+
Some("dotenv_val".to_string())
83+
);
5484
}
5585

5686
#[test]

crates/trigger/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ spin-manifest = { path = "../manifest" }
2121
tracing = { version = "0.1", features = [ "log" ] }
2222
wasi-outbound-http = { path = "../outbound-http" }
2323
wasmtime = "0.35.3"
24+
dotenvy = "0.15.1"

crates/trigger/src/cli.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use std::{error::Error, path::PathBuf, sync::Arc};
1+
use std::{
2+
collections::HashMap,
3+
error::Error,
4+
path::{Path, PathBuf},
5+
sync::Arc,
6+
};
27

38
use anyhow::{bail, Context, Result};
49
use clap::{Args, IntoApp, Parser};
@@ -141,7 +146,9 @@ where
141146
.context("SPIN_ALLOW_TRANSIENT_WRITE")?;
142147

143148
// TODO(lann): Find a better home for this; spin_loader?
149+
let dotenv_path;
144150
let mut app = if let Some(manifest_file) = manifest_url.strip_prefix("file://") {
151+
dotenv_path = Path::new(manifest_file).parent().unwrap().join(".env");
145152
let bindle_connection = std::env::var("BINDLE_URL")
146153
.ok()
147154
.map(|url| BindleConnectionInfo::new(url, false, None, None));
@@ -153,6 +160,7 @@ where
153160
)
154161
.await?
155162
} else if let Some(bindle_url) = manifest_url.strip_prefix("bindle+") {
163+
dotenv_path = Path::new("./.env").to_path_buf();
156164
let (bindle_server, bindle_id) = bindle_url
157165
.rsplit_once("?id=")
158166
.context("invalid bindle URL")?;
@@ -169,12 +177,17 @@ where
169177
}
170178
}
171179

180+
let envs = read_dotenv(dotenv_path)?;
181+
172182
// Add default config provider(s).
173183
// TODO(lann): Make config provider(s) configurable.
174184
if let Some(ref mut resolver) = app.config_resolver {
175185
let resolver = Arc::get_mut(resolver)
176186
.context("Internal error: app.config_resolver unexpectedly shared")?;
177-
resolver.add_provider(spin_config::provider::env::EnvProvider::default());
187+
resolver.add_provider(spin_config::provider::env::EnvProvider::new(
188+
spin_config::provider::env::DEFAULT_PREFIX,
189+
envs,
190+
));
178191
}
179192

180193
Ok(app)
@@ -211,3 +224,18 @@ fn parse_env_var(s: &str) -> Result<(String, String)> {
211224
}
212225
Ok((parts[0].to_owned(), parts[1].to_owned()))
213226
}
227+
228+
// Return environment key value mapping in ".env" file.
229+
fn read_dotenv(dotenv_path: impl AsRef<Path>) -> Result<HashMap<String, String>> {
230+
let mut envs = HashMap::new();
231+
if !dotenv_path.as_ref().exists() {
232+
return Ok(envs);
233+
}
234+
235+
let env_iter = dotenvy::from_path_iter(dotenv_path)?;
236+
for env_item in env_iter {
237+
let (key, value) = env_item?;
238+
envs.insert(key, value);
239+
}
240+
Ok(envs)
241+
}

examples/config-rust/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SPIN_APP_DOTENV = "dotenv"

examples/config-rust/spin.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ version = "0.1.0"
77

88
[variables]
99
object = { default = "teapot" }
10+
dotenv = { default = "should-be-replaced" }
1011

1112
[[component]]
1213
id = "spin_config_rust"
@@ -15,5 +16,6 @@ source = "target/wasm32-wasi/release/spin_config_example.wasm"
1516
route = "/..."
1617
[component.config]
1718
message = "I'm a {{object}}"
19+
dotenv = "{{dotenv}}"
1820
[component.build]
1921
command = "cargo build --target wasm32-wasi --release"

examples/config-rust/src/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@ use spin_sdk::{
77

88
/// This endpoint returns the config value specified by key.
99
#[http_component]
10-
fn get(_req: Request) -> Result<Response> {
10+
fn get(req: Request) -> Result<Response> {
11+
let path = req.uri().path();
12+
13+
if path.contains("dotenv") {
14+
let val = config::get("dotenv").expect("Failed to acquire dotenv from spin.toml");
15+
return Ok(http::Response::builder()
16+
.status(200)
17+
.body(Some(val.into()))?);
18+
}
1119
let val = format!("message: {}", config::get("message")?);
1220
Ok(http::Response::builder()
1321
.status(200)

tests/http/simple-spin-rust/Cargo.lock

Lines changed: 17 additions & 87 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)