Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions changelog.d/api_playground_graphql_soft_deprecation.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Configurations carrying the removed `api.playground` or `api.graphql` fields no longer fail to load. Both options were dropped in 0.55.0 along with the GraphQL observability API; setting them now is accepted at deserialize time but logs a deprecation warning at startup, and the values are otherwise ignored. This makes upgrades from 0.54.0 and earlier non-breaking when those fields are still present in pinned configs.

The deprecation follows a graduated policy: the field is accepted with a `warn!` at startup for `WARN_WINDOW_MINORS = 12` minor releases after the version in which it was removed (about a year at Vector's monthly cadence), and the warning text names the exact future version in which the field becomes a hard configuration error. Once that future version ships, an attempt to load a config that still sets the field fails fast with a clear "remove this field" message — no silent breaking changes. Operators have a deterministic deadline for cleanup.

The policy lives in a new reusable module, `src/config/deprecation`, so future field removals can opt into the same behavior with a single call site rather than reimplementing per-field warnings.

Remove `api.playground` / `api.graphql` from your configuration to silence the warning — they have no runtime effect.

authors: joshcoughlan
10 changes: 9 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,15 @@ impl ApplicationConfig {
extra_context: ExtraContext,
) -> Result<Self, ExitCode> {
#[cfg(feature = "api")]
let api = config.api;
let api = {
if let Err(errors) = config.api.check_deprecated_fields() {
for e in &errors {
error!(message = %e, internal_log_rate_limit = false);
}
return Err(exitcode::CONFIG);
}
Comment on lines +104 to +110
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply deprecated-field checks to all config load paths

This change enforces api.check_deprecated_fields() only in ApplicationConfig::from_config, so the new hard-error stage is bypassed anywhere a Config is loaded without going through that constructor (notably vector validate and runtime reloads via --watch-config/SIGHUP). In those paths, configs that should be rejected after the warn window can still be accepted, which breaks the promised “deterministic deadline” and causes inconsistent behavior between validation/reload and cold start.

Useful? React with 👍 / 👎.

config.api
};

let (topology, graceful_crash_receiver) =
RunningTopology::start_init_validated(config, extra_context.clone())
Expand Down
117 changes: 117 additions & 0 deletions src/config/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ pub struct Options {
#[configurable(metadata(docs::examples = "127.0.0.1:1234"))]
#[configurable(metadata(docs::common = true, docs::required = false))]
pub address: Option<SocketAddr>,

/// Removed in 0.55.0. Accepted but ignored for backwards compatibility.
///
/// The GraphQL Playground UI was removed when the observability API migrated
/// from GraphQL to gRPC. Setting this option has no effect; remove it from
/// your configuration.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[configurable(deprecated)]
#[configurable(metadata(docs::hidden))]
pub playground: Option<bool>,

/// Removed in 0.55.0. Accepted but ignored for backwards compatibility.
///
/// The GraphQL endpoint was removed when the observability API migrated to
/// gRPC. Setting this option has no effect; remove it from your
/// configuration.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[configurable(deprecated)]
#[configurable(metadata(docs::hidden))]
pub graphql: Option<bool>,
}

impl_generate_config_from_default!(Options);
Expand All @@ -33,6 +53,8 @@ impl Default for Options {
Self {
enabled: default_enabled(),
address: default_address(),
playground: None,
graphql: None,
}
}
}
Expand Down Expand Up @@ -77,18 +99,64 @@ impl Options {
let options = Options {
address,
enabled: self.enabled | other.enabled,
playground: self.playground.or(other.playground),
graphql: self.graphql.or(other.graphql),
};

*self = options;
Ok(())
}

/// Check deprecated, ignored fields the user has set.
///
/// `playground` and `graphql` were removed in 0.55.0 along with the GraphQL
/// observability API. They are still accepted at deserialize time so configs
/// that carry them don't break on upgrade, but the values have no effect.
/// While in the warn window (see [`crate::config::deprecation`]) a `warn!`
/// fires; once past the window each set field becomes a config-load error.
///
/// Returns `Err` with one message per still-set field that has reached the
/// error stage, so callers can surface every failure in a single load
/// attempt.
pub fn check_deprecated_fields(&self) -> Result<(), Vec<String>> {
use crate::config::deprecation::{VectorMinor, check_deprecated_field};

const REMOVED_IN: VectorMinor = VectorMinor::new(0, 55);
let mut errors = Vec::new();

if self.playground.is_some()
&& let Err(e) = check_deprecated_field(
"api.playground",
REMOVED_IN,
"The GraphQL Playground was removed when the observability API migrated to gRPC.",
)
{
errors.push(e);
}
if self.graphql.is_some()
&& let Err(e) = check_deprecated_field(
"api.graphql",
REMOVED_IN,
"The GraphQL endpoint was removed when the observability API migrated to gRPC.",
)
{
errors.push(e);
}

if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}

#[test]
fn bool_merge() {
let mut a = Options {
enabled: true,
address: None,
..Options::default()
};

a.merge(Options::default()).unwrap();
Expand All @@ -98,6 +166,7 @@ fn bool_merge() {
Options {
enabled: true,
address: default_address(),
..Options::default()
}
);
}
Expand All @@ -108,6 +177,7 @@ fn bind_merge() {
let mut a = Options {
enabled: true,
address: Some(address),
..Options::default()
};

a.merge(Options::default()).unwrap();
Expand All @@ -117,10 +187,57 @@ fn bind_merge() {
Options {
enabled: true,
address: Some(address),
..Options::default()
}
);
}

#[test]
fn deprecated_fields_default_to_none() {
let opts = Options::default();
assert!(opts.playground.is_none());
assert!(opts.graphql.is_none());
}

#[test]
fn deprecated_fields_round_trip_through_toml() {
// Setting either deprecated field must not be a config error.
let opts: Options = toml::from_str(
r#"
enabled = true
playground = false
graphql = false
"#,
)
.expect("config with deprecated api.playground/api.graphql must still parse");
assert_eq!(opts.playground, Some(false));
assert_eq!(opts.graphql, Some(false));
assert!(opts.enabled);
}

#[test]
fn deprecated_fields_carry_through_merge() {
let mut a = Options {
playground: Some(false),
..Options::default()
};
let b = Options {
graphql: Some(true),
..Options::default()
};
a.merge(b).unwrap();
assert_eq!(a.playground, Some(false));
assert_eq!(a.graphql, Some(true));
}

#[test]
fn check_deprecated_fields_ok_when_nothing_set() {
// Default config has neither field set; must return Ok without producing
// any error messages (and without printing a warning, though that's a
// tracing-side observation we don't try to assert here).
assert!(Options::default().check_deprecated_fields().is_ok());
}

#[test]
fn bind_conflict() {
let mut a = Options {
Expand Down
Loading
Loading