feat(sinks): add validate_structure() for early template confinement checks - #25959
feat(sinks): add validate_structure() for early template confinement checks#25959thomasqueirozb wants to merge 21 commits into
Conversation
🎉 All green!🧪 All tests passed 🔗 Commit SHA: d9fa031 | Docs | Datadog PR Page | Give us feedback! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d172a7cfa6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 45539ea4cc
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…y, axiom, humio) This adds validate_structure() implementations for wrapper sinks that delegate to HTTP or HEC sinks: - opentelemetry: Delegates to HttpSinkConfig::validate_structure() - axiom: Builds HttpSinkConfig and delegates validation - humio_logs: Delegates to HecLogsSinkConfig::validate_structure() - humio_metrics: Builds HumioLogsConfig and delegates to HEC validation These wrapper sinks were using the default validate_structure() which returns Ok(()), allowing unconfined templates to pass validation even though they would fail at boot. Now they properly delegate to their inner sink's validation. Also adds unit tests for each wrapper sink's confinement validation.
45539ea to
5dd8b9b
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5dd8b9b0f6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- Parse static URIs during validation to catch invalid URI syntax - Validate HTTP headers before splitting templates (malformed headers, auth conflicts) - Reject raw endpoint with auto_extract_timestamp in HEC logs during validation These validations catch errors at time that would otherwise fail at boot.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 35bb233832
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Add check for empty endpoints during structural validation so that `vector validate --no-environment` catches the same error that would fail at startup.
Validate content_type, content_encoding, cache_control, and metadata header values during structural validation to catch malformed headers that would otherwise fail at startup in RequestSettings::new().
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e6077b6057
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- Check for empty labels (mirrors build() check) - Validate label names using valid_label_name() (mirrors build() check) - Add tests for both validation cases
- azure_blob: validate credential shape (connection_string/account_name/blob_endpoint combinations) - clickhouse: validate batch_encoding compatibility and arrow schema requirements - doris: check for empty endpoints - gcp_cloud_storage: validate header values (content_type, content_encoding, cache_control, metadata) - http: validate batch settings (max_events, max_bytes, timeout) - kafka: check batch option conflicts with librdkafka_options - loki: validate empty labels and label name format - mqtt: check empty client_id and user/password pairing - nats: parse server URLs during validation - prometheus_remote_write: validate endpoint URI and headers - redis: reject empty keys All validations mirror checks in build() so `vector validate --no-environment` catches errors that would otherwise fail at boot.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2530d6247
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- amqp: reject zero max_channels - aws_cloudwatch_logs: validate batch settings - elasticsearch: validate endpoint/endpoints mutual exclusivity - gcp_cloud_storage: fix default endpoint in tests - greptimedb_logs: parse endpoint URL - http: validate payload wrapper for JSON with delimited framing - loki: reject duplicate credentials (auth + endpoint auth) - pulsar: validate auth shape (name/token, oauth2 combinations) - redis: validate endpoints (non-empty, parseable URLs)
The url crate interprets 'localhost:4000' as a valid URL with scheme 'localhost'. Use 'http://' which actually fails to parse.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a52b3a6d19
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: da858354ef
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8b598a84f8
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 886abf3b0b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cf6357d672
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ecc5dc472e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // Parse the authority part only (between :// and /) | ||
| // Use split to avoid UTF-8 slicing issues | ||
| if let Some(after_scheme) = uri_str.split("://").nth(1) { | ||
| let authority = after_scheme.split('/').next().unwrap_or(""); |
There was a problem hiding this comment.
Stop dynamic URI auth scan at query delimiters
Fresh evidence after the path fix: this still only stops the authority scan at /, so a dynamic URI like https://api.example?email=alice@example.com/{{ id }} with top-level auth treats the query as part of the authority and rejects a valid config. Runtime URI parsing only extracts credentials from the authority, so stop at ?/# too or parse the literal prefix as a URI authority.
Useful? React with 👍 / 👎.
|
|
||
| // Validate encoding configuration (e.g., CSV with empty fields list) | ||
| // Mirrors build() in FileSink::new() | ||
| if let Err(e) = self.encoding.build(SinkType::StreamBased) { |
There was a problem hiding this comment.
Avoid reading protobuf descriptors during no-env validation
When a file sink uses encoding.codec = "protobuf" and encoding.protobuf.desc_file is only present on the deployment host, this new structural pass calls encoding.build(...); the Protobuf serializer build reads desc_file, so vector validate --no-environment now fails on the validation host even though startup would succeed where the file exists. Keep this check to serializers that do not need external files, or use a non-I/O structural validation path.
Useful? React with 👍 / 👎.
| if uri.uri.host().is_none() { | ||
| errors.push(format!("endpoint '{endpoint}': must include hostname")); |
There was a problem hiding this comment.
Reject non-HTTP Elasticsearch endpoints
For an endpoint such as ftp://example.com, this structural pass parses the URI and only verifies that a host exists, so no-environment validation succeeds. The same endpoint is later converted into Elasticsearch healthcheck and _bulk requests and sent through HttpClient, so startup or all sends fail for non-HTTP schemes; require the parsed scheme to be http or https here.
Useful? React with 👍 / 👎.
| } else { | ||
| // Check each endpoint has a host and no auth conflict | ||
| for endpoint in &self.endpoints { | ||
| if endpoint.uri.host().is_none() { |
There was a problem hiding this comment.
Reject non-HTTP Doris endpoints
When endpoints contains a value like ftp://fe:8030, this validator only checks that the parsed URI has a host, so the config passes validation. DorisSinkClient later rebuilds stream-load and healthcheck URIs from the endpoint's scheme and sends them via HttpClient, which leaves the sink unable to connect for non-HTTP schemes; check that each endpoint scheme is http or https before accepting it.
Useful? React with 👍 / 👎.
| if let Some(connstr) = &self.connection_string { | ||
| if let Err(e) = ParsedConnectionString::parse(connstr.inner()) { | ||
| errors.push(format!("connection_string: invalid format: {e}")); |
There was a problem hiding this comment.
Validate Azure connection-string URLs
For connection_string values that parse as key/value pairs but cannot produce a container URL, such as BlobEndpoint=not-a-url or a string without AccountName and without BlobEndpoint, this branch returns success because ParsedConnectionString::parse accepts the shape. build_client() immediately calls parsed.container_url(&container_name) and then Url::parse, both before any environment access, so no-environment validation can still accept configs startup rejects; mirror that container URL construction here.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| // Validate endpoint has a host (check before with_default_parts fills in defaults) | ||
| if self.endpoint.uri.host().is_none() { |
There was a problem hiding this comment.
Reject non-HTTP ClickHouse endpoints
For a ClickHouse endpoint such as ftp://localhost:8123, this validator accepts the config because it only checks that a host is present. Build then uses the same URI to construct healthcheck and insert requests and sends them through HttpClient, so the sink fails to connect for non-HTTP schemes; require the scheme to be http or https here as well.
Useful? React with 👍 / 👎.
|
|
||
| // Parse endpoint URL to catch malformed endpoints early | ||
| // (mirrors url::Url::parse in prepare_log_ingester_url) | ||
| if let Err(e) = url::Url::parse(&self.endpoint) { |
There was a problem hiding this comment.
Reject non-HTTP Greptime endpoints
An endpoint like ftp://localhost:4000 passes this check because Url::parse accepts the URL, but the sink later turns it into /health and /v1/events/logs requests sent by HttpClient, which only supports HTTP(S) destinations. Validate that the parsed endpoint scheme is http or https before returning success.
Useful? React with 👍 / 👎.
| &self.acknowledgements.inner | ||
| } | ||
|
|
||
| fn validate_structure(&self) -> std::result::Result<(), Vec<String>> { |
There was a problem hiding this comment.
Validate Splunk HEC endpoint schemes
This structural pass never validates endpoint, so a logs config with endpoint = "ftp://localhost:8088" passes even though request construction later appends /services/collector/... and sends the resulting URI via HttpClient, causing healthchecks and every batch to fail for non-HTTP schemes. Parse the endpoint here and require http or https before accepting the config.
Useful? React with 👍 / 👎.
pront
left a comment
There was a problem hiding this comment.
Preliminary feedback - will keep reviewing
| /// `vector validate --no-environment` and normal startup/reload. | ||
| pub fn validate_structure( | ||
| &self, | ||
| ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { |
There was a problem hiding this comment.
Use the existing vector_common::Error type wherever possible:
| ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { | |
| ) -> Result<(), vector_common::Error> { |
| /// `validate` runs twice at startup, which is intentional and cheap: it is pure by contract, so | ||
| /// re-running it costs microseconds and avoids threading state from config compilation through to | ||
| /// the topology builder. |
There was a problem hiding this comment.
| /// `validate` runs twice at startup, which is intentional and cheap: it is pure by contract, so | |
| /// re-running it costs microseconds and avoids threading state from config compilation through to | |
| /// the topology builder. | |
| /// Currently `validate` runs twice at startup, and this is accepted trade-off. | |
| /// It is pure by contract, so, re-running it is very cheap and avoids threading | |
| /// state from config compilation through to the topology builder. |
Summary
Implements RFC 2026-07-15 migration for sinks (parallel to PR #25953 which did transforms). Adds
validate_structure()toSinkConfigtrait and wires it into validation so thatvector validate --no-environmentcatches structural errors like template confinement violations that would otherwise silently pass and only fail at boot.Vector configuration
NA
How did you test this PR?
make check-clippy- passesmake fmt- passescargo nextest run -p vector --lib "confinement"- all 69 tests passbuild()confinement logicIs this a breaking change?
Does this PR include user facing changes?
no-changeloglabel to this PR.References
vector validate --no-environmentsilently passing unconfined templates