Skip to content

Commit 8bc0d6b

Browse files
committed
feat(turbo): add platform env support
1 parent c84638a commit 8bc0d6b

File tree

5 files changed

+107
-1
lines changed

5 files changed

+107
-1
lines changed

Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/turborepo-env/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ regex = { workspace = true }
1515
serde = { workspace = true }
1616
sha2 = { workspace = true }
1717
thiserror = { workspace = true }
18+
turborepo-ci = { workspace = true }
19+
turborepo-ui = { workspace = true }
1820

1921
[dev-dependencies]
2022
test-case = { workspace = true }

crates/turborepo-env/src/ci.rs

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use turborepo_ci::Vendor;
2+
use turborepo_ui::{cprint, cprintln, ColorConfig, GREY, UNDERLINE, YELLOW};
3+
4+
use crate::EnvironmentVariableMap;
5+
6+
pub struct CIEnv {
7+
ci_keys: Vec<String>,
8+
}
9+
10+
const SYSTEM_ENV_KEY: &str = "TURBO_SYSTEM_ENV";
11+
const VALIDATE_PLATFORM_ENV: &str = "TURBO_VALIDATE_PLATFORM_ENV";
12+
13+
impl CIEnv {
14+
pub fn new() -> Self {
15+
let ci_keys = std::env::var(SYSTEM_ENV_KEY)
16+
.unwrap_or_default()
17+
.split(',')
18+
.map(|s| s.to_string())
19+
.collect();
20+
21+
Self { ci_keys }
22+
}
23+
24+
pub fn enabled() -> bool {
25+
let validate_platform_env = std::env::var(VALIDATE_PLATFORM_ENV).unwrap_or_default();
26+
validate_platform_env == "1" || validate_platform_env == "true"
27+
}
28+
29+
pub fn validate(
30+
&self,
31+
execution_env: &EnvironmentVariableMap,
32+
color_config: ColorConfig,
33+
task_id_for_display: &str,
34+
) {
35+
if !Self::enabled() {
36+
return;
37+
}
38+
39+
let missing_env = self.diff(execution_env);
40+
if !missing_env.is_empty() {
41+
self.output(missing_env, task_id_for_display, color_config);
42+
}
43+
}
44+
45+
pub fn diff(&self, execution_env: &EnvironmentVariableMap) -> Vec<String> {
46+
self.ci_keys
47+
.iter()
48+
.filter(|key| !execution_env.contains_key(*key))
49+
.map(|s| s.to_string())
50+
.collect()
51+
}
52+
53+
pub fn output(
54+
&self,
55+
missing: Vec<String>,
56+
task_id_for_display: &str,
57+
color_config: ColorConfig,
58+
) {
59+
let ci = Vendor::get_constant().unwrap_or("unknown");
60+
match ci {
61+
"VERCEL" => {
62+
cprintln!(color_config, YELLOW, "\n{} WARNINGS:", task_id_for_display);
63+
cprintln!(
64+
color_config,
65+
GREY,
66+
"1. The following environment variables are set on your Vercel project, but \
67+
missing from \"turbo.json\""
68+
);
69+
for key in missing {
70+
cprint!(color_config, GREY, " - ");
71+
cprint!(color_config, UNDERLINE, "{}\n", key);
72+
}
73+
println!();
74+
}
75+
_ => {
76+
cprintln!(color_config, YELLOW, "\n{} WARNINGS:", task_id_for_display);
77+
cprintln!(
78+
color_config,
79+
GREY,
80+
"1. The following environment variables are missing from \"turbo.json\""
81+
);
82+
for key in missing {
83+
cprint!(color_config, GREY, " - ");
84+
cprint!(color_config, UNDERLINE, "{}\n", key);
85+
}
86+
println!();
87+
}
88+
}
89+
}
90+
}

crates/turborepo-env/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use serde::Serialize;
1111
use sha2::{Digest, Sha256};
1212
use thiserror::Error;
1313

14+
pub mod ci;
15+
1416
const DEFAULT_ENV_VARS: [&str; 1] = ["VERCEL_ANALYTICS_ID"];
1517

1618
#[derive(Clone, Debug, Error)]

crates/turborepo-lib/src/task_graph/visitor.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use tokio::sync::{mpsc, oneshot};
1616
use tracing::{debug, error, Instrument, Span};
1717
use turbopath::{AbsoluteSystemPath, AbsoluteSystemPathBuf, AnchoredSystemPath};
1818
use turborepo_ci::{Vendor, VendorBehavior};
19-
use turborepo_env::EnvironmentVariableMap;
19+
use turborepo_env::{ci::CIEnv, EnvironmentVariableMap};
2020
use turborepo_repository::{
2121
package_graph::{PackageGraph, PackageName, ROOT_PKG_NAME},
2222
package_manager::PackageManager,
@@ -294,6 +294,7 @@ impl<'a> Visitor<'a> {
294294
} else {
295295
TaskOutput::Direct(self.output_client(&info, vendor_behavior))
296296
};
297+
297298
let tracker = self.run_tracker.track_task(info.clone().into_owned());
298299
let spaces_client = self.run_tracker.spaces_task_client();
299300
let parent_span = Span::current();
@@ -694,6 +695,7 @@ impl<'a> ExecContextFactory<'a> {
694695
errors: self.errors.clone(),
695696
takes_input,
696697
task_access,
698+
ci_env: CIEnv::new(),
697699
}
698700
}
699701

@@ -730,6 +732,7 @@ struct ExecContext {
730732
errors: Arc<Mutex<Vec<TaskError>>>,
731733
takes_input: bool,
732734
task_access: TaskAccess,
735+
ci_env: CIEnv,
733736
}
734737

735738
enum ExecOutcome {
@@ -882,6 +885,13 @@ impl ExecContext {
882885
}
883886
}
884887

888+
// validate the provided environment variables against those in CI
889+
self.ci_env.validate(
890+
&self.execution_env,
891+
self.color_config,
892+
self.task_id_for_display.as_str(),
893+
);
894+
885895
match self
886896
.task_cache
887897
.restore_outputs(&mut prefixed_ui, telemetry)

0 commit comments

Comments
 (0)