Skip to content

Commit e9d4c36

Browse files
committed
fix(cli): validate transform expressions without environment
1 parent 3d6b49f commit e9d4c36

17 files changed

Lines changed: 300 additions & 72 deletions

File tree

lib/vector-vrl/enrichment/src/tables.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,31 @@ impl TableRegistry {
139139
}
140140
}
141141

142+
/// Load compile-only table placeholders into the registry.
143+
///
144+
/// These placeholders are useful when compiling VRL in contexts that must
145+
/// not load external enrichment table data. Compilation only needs table
146+
/// names and index registration; runtime lookups still fail against these
147+
/// placeholders.
148+
pub fn load_compile_time_table_ids<I, S>(&self, table_ids: I)
149+
where
150+
I: IntoIterator<Item = S>,
151+
S: Into<String>,
152+
{
153+
let tables = table_ids
154+
.into_iter()
155+
.map(|table_id| {
156+
let table_id = table_id.into();
157+
(
158+
table_id.clone(),
159+
Box::new(CompileTimeTable::new(table_id)) as Box<dyn Table + Send + Sync>,
160+
)
161+
})
162+
.collect();
163+
164+
self.load(tables);
165+
}
166+
142167
/// Adds an index to the given Enrichment Table.
143168
///
144169
/// If we are in the reading stage, this function will error.
@@ -204,6 +229,68 @@ impl std::fmt::Debug for TableRegistry {
204229
}
205230
}
206231

232+
#[derive(Clone, Debug)]
233+
struct CompileTimeTable {
234+
name: String,
235+
indexes: Vec<(Case, Vec<String>)>,
236+
}
237+
238+
impl CompileTimeTable {
239+
fn new(name: String) -> Self {
240+
Self {
241+
name,
242+
indexes: Vec::new(),
243+
}
244+
}
245+
246+
fn lookup_error(&self) -> Error {
247+
Error::TableNotLoaded {
248+
table: self.name.clone(),
249+
}
250+
}
251+
}
252+
253+
impl Table for CompileTimeTable {
254+
fn find_table_row<'a>(
255+
&self,
256+
_case: Case,
257+
_condition: &'a [Condition<'a>],
258+
_select: Option<&[String]>,
259+
_wildcard: Option<&Value>,
260+
_index: Option<IndexHandle>,
261+
) -> Result<ObjectMap, Error> {
262+
Err(self.lookup_error())
263+
}
264+
265+
fn find_table_rows<'a>(
266+
&self,
267+
_case: Case,
268+
_condition: &'a [Condition<'a>],
269+
_select: Option<&[String]>,
270+
_wildcard: Option<&Value>,
271+
_index: Option<IndexHandle>,
272+
) -> Result<Vec<ObjectMap>, Error> {
273+
Err(self.lookup_error())
274+
}
275+
276+
fn add_index(&mut self, case: Case, fields: &[&str]) -> Result<IndexHandle, Error> {
277+
let handle = IndexHandle(self.indexes.len());
278+
self.indexes.push((
279+
case,
280+
fields.iter().map(|field| (*field).to_owned()).collect(),
281+
));
282+
Ok(handle)
283+
}
284+
285+
fn index_fields(&self) -> Vec<(Case, Vec<String>)> {
286+
self.indexes.clone()
287+
}
288+
289+
fn needs_reload(&self) -> bool {
290+
false
291+
}
292+
}
293+
207294
/// Provides read only access to the enrichment tables via the
208295
/// `vrl::EnrichmentTableSearch` trait. Cloning this object is designed to be
209296
/// cheap. The underlying data will be shared by all clones.

src/conditions/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ impl AnyCondition {
210210
AnyCondition::Map(m) => m.build(enrichment_tables, metrics_storage),
211211
}
212212
}
213+
214+
pub fn validate(
215+
&self,
216+
enrichment_tables: &vector_lib::enrichment::TableRegistry,
217+
metrics_storage: &MetricsStorage,
218+
) -> crate::Result<()> {
219+
self.build(enrichment_tables, metrics_storage).map(|_| ())
220+
}
213221
}
214222

215223
impl From<ConditionConfig> for AnyCondition {

src/config/sink.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,15 @@ pub trait SinkConfig: DynClone + NamedComponent + core::fmt::Debug + Send + Sync
247247
/// returned.
248248
async fn build(&self, cx: SinkContext) -> crate::Result<(VectorSink, Healthcheck)>;
249249

250+
/// Validates sink configuration without requiring access to the running environment.
251+
///
252+
/// This is used by `vector validate --no-environment` for component-local checks that do not
253+
/// need to build the sink runtime, read files, resolve credentials, contact external services,
254+
/// or otherwise depend on host state.
255+
fn validate_no_environment(&self) -> crate::Result<()> {
256+
Ok(())
257+
}
258+
250259
/// Gets the input configuration for this sink.
251260
fn input(&self) -> Input;
252261

src/config/source.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ pub trait SourceConfig: DynClone + NamedComponent + core::fmt::Debug + Send + Sy
9494
/// returned.
9595
async fn build(&self, cx: SourceContext) -> crate::Result<Source>;
9696

97+
/// Validates source configuration without requiring access to the running environment.
98+
///
99+
/// This is used by `vector validate --no-environment` for component-local checks that do not
100+
/// need to build the source runtime, bind sockets, read files, contact external services, or
101+
/// otherwise depend on host state.
102+
fn validate_no_environment(&self) -> crate::Result<()> {
103+
Ok(())
104+
}
105+
97106
/// Gets the list of outputs exposed by this source.
98107
fn outputs(&self, global_log_namespace: LogNamespace) -> Vec<SourceOutput>;
99108

src/config/transform.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,14 @@ pub trait TransformConfig: DynClone + NamedComponent + core::fmt::Debug + Send +
228228
/// returned.
229229
async fn build(&self, globals: &TransformContext) -> crate::Result<Transform>;
230230

231-
/// Whether `build` requires environment-dependent setup.
231+
/// Validates transform configuration without requiring access to the running environment.
232232
///
233-
/// This is used by `vector validate --no-environment` to skip transforms that intentionally do
234-
/// network or runtime initialization as part of `build`.
235-
fn build_requires_environment(&self) -> bool {
236-
false
233+
/// This is used by `vector validate --no-environment` for component-local checks that do not
234+
/// need to build the transform runtime, spawn tasks, contact external services, execute user
235+
/// code, or otherwise depend on host state. Transforms that compile VRL programs or conditions
236+
/// in `build` should compile them here as well.
237+
fn validate_no_environment(&self, _context: &TransformContext) -> crate::Result<()> {
238+
Ok(())
237239
}
238240

239241
/// Gets the input configuration for this transform.

src/transforms/aws_ec2_metadata.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,6 @@ impl TransformConfig for Ec2Metadata {
254254
Ok(Transform::event_task(Ec2MetadataTransform { state }))
255255
}
256256

257-
fn build_requires_environment(&self) -> bool {
258-
// `build` performs network initialization and starts the refresh task, so
259-
// `validate --no-environment` must skip this transform until #25162 is resolved:
260-
// https://github.com/vectordotdev/vector/issues/25162
261-
true
262-
}
263-
264257
fn input(&self) -> Input {
265258
Input::new(DataType::Metric | DataType::Log)
266259
}

src/transforms/delay.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ impl TransformConfig for DelayConfig {
9191
Ok(Transform::event_task(Delay::new(self, context)?))
9292
}
9393

94+
fn validate_no_environment(&self, context: &TransformContext) -> crate::Result<()> {
95+
if let Some(condition) = &self.condition {
96+
condition.validate(&context.enrichment_tables, &context.metrics_storage)?;
97+
}
98+
Ok(())
99+
}
100+
94101
fn input(&self) -> Input {
95102
Input::all()
96103
}

src/transforms/exclusive_route/config.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,15 @@ impl TransformConfig for ExclusiveRouteConfig {
9797
Ok(Transform::synchronous(route))
9898
}
9999

100+
fn validate_no_environment(&self, context: &TransformContext) -> crate::Result<()> {
101+
for route in &self.routes {
102+
route
103+
.condition
104+
.validate(&context.enrichment_tables, &context.metrics_storage)?;
105+
}
106+
Ok(())
107+
}
108+
100109
fn input(&self) -> Input {
101110
Input::all()
102111
}

src/transforms/filter.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ impl TransformConfig for FilterConfig {
5050
)?)))
5151
}
5252

53+
fn validate_no_environment(&self, context: &TransformContext) -> crate::Result<()> {
54+
self.condition
55+
.validate(&context.enrichment_tables, &context.metrics_storage)
56+
}
57+
5358
fn input(&self) -> Input {
5459
Input::all()
5560
}

src/transforms/reduce/config.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ impl TransformConfig for ReduceConfig {
123123
.map(Transform::event_task)
124124
}
125125

126+
fn validate_no_environment(&self, context: &TransformContext) -> crate::Result<()> {
127+
for condition in self.ends_when.iter().chain(self.starts_when.iter()) {
128+
condition.validate(&context.enrichment_tables, &context.metrics_storage)?;
129+
}
130+
Ok(())
131+
}
132+
126133
fn input(&self) -> Input {
127134
Input::log()
128135
}

0 commit comments

Comments
 (0)