|
1 | 1 | use super::Config;
|
2 | 2 | use crate::config::{LogFormat, LogLevel, SenderConfig};
|
| 3 | +use std::collections::HashMap; |
| 4 | +use svix_bridge_plugin_queue::config::{QueueConsumerConfig, RabbitMqInputOpts, SenderInputOpts}; |
| 5 | +use svix_bridge_types::{SenderOutputOpts, SvixSenderOutputOpts}; |
3 | 6 |
|
4 | 7 | /// This is meant to be a kitchen sink config, hitting as many possible
|
5 | 8 | /// configuration options as possible to ensure they parse correctly.
|
@@ -331,3 +334,154 @@ fn test_senders_example() {
|
331 | 334 | assert!(!conf.senders.is_empty());
|
332 | 335 | assert!(conf.receivers.is_empty());
|
333 | 336 | }
|
| 337 | + |
| 338 | +#[test] |
| 339 | +fn test_variable_substitution_missing_vars() { |
| 340 | + let src = r#" |
| 341 | + opentelemetry: |
| 342 | + address: "${OTEL_ADDR}" |
| 343 | + "#; |
| 344 | + let vars = HashMap::new(); |
| 345 | + let cfg = Config::from_src(src, Some(&vars)).unwrap(); |
| 346 | + let otel = cfg.opentelemetry.unwrap(); |
| 347 | + // when lookups in the vars map fail, the original token text is preserved. |
| 348 | + assert_eq!(&otel.address, "${OTEL_ADDR}"); |
| 349 | +} |
| 350 | + |
| 351 | +#[test] |
| 352 | +fn test_variable_substitution_available_vars() { |
| 353 | + let src = r#" |
| 354 | + opentelemetry: |
| 355 | + address: "${OTEL_ADDR}" |
| 356 | + sample_ratio: ${OTEL_SAMPLE_RATIO} |
| 357 | + "#; |
| 358 | + let mut vars = HashMap::new(); |
| 359 | + vars.insert( |
| 360 | + String::from("OTEL_ADDR"), |
| 361 | + String::from("http://127.0.0.1:8080"), |
| 362 | + ); |
| 363 | + vars.insert(String::from("OTEL_SAMPLE_RATIO"), String::from("0.25")); |
| 364 | + let cfg = Config::from_src(src, Some(&vars)).unwrap(); |
| 365 | + // when lookups succeed, the token should be replaced. |
| 366 | + let otel = cfg.opentelemetry.unwrap(); |
| 367 | + assert_eq!(&otel.address, "http://127.0.0.1:8080"); |
| 368 | + assert_eq!(otel.sample_ratio, Some(0.25)); |
| 369 | +} |
| 370 | + |
| 371 | +#[test] |
| 372 | +fn test_variable_substitution_requires_braces() { |
| 373 | + let src = r#" |
| 374 | + opentelemetry: |
| 375 | + # Neglecting to use ${} notation means the port number will not be substituted. |
| 376 | + address: "${OTEL_SCHEME}://${OTEL_HOST}:$OTEL_PORT" |
| 377 | + "#; |
| 378 | + let mut vars = HashMap::new(); |
| 379 | + vars.insert(String::from("OTEL_SCHEME"), String::from("https")); |
| 380 | + vars.insert(String::from("OTEL_HOST"), String::from("127.0.0.1")); |
| 381 | + vars.insert(String::from("OTEL_PORT"), String::from("9999")); |
| 382 | + let cfg = Config::from_src(src, Some(&vars)).unwrap(); |
| 383 | + // when lookups succeed, the token should be replaced. |
| 384 | + let otel = cfg.opentelemetry.unwrap(); |
| 385 | + // Not the user-intended outcome, but it simplifies the parsing requirements. |
| 386 | + assert_eq!(&otel.address, "https://127.0.0.1:$OTEL_PORT"); |
| 387 | +} |
| 388 | + |
| 389 | +#[test] |
| 390 | +fn test_variable_substitution_missing_numeric_var_is_err() { |
| 391 | + // Unfortunate side-effect of templating yaml. |
| 392 | + // |
| 393 | + // If the variable is missing, usually you've got three options: |
| 394 | + // - retain the token text that failed the lookup (envsubst-rs does this) |
| 395 | + // - replace the token with an empty string (the CLI `envsubst` does this) |
| 396 | + // - mark it an error (neither do this, but we can if we roll our own impl) |
| 397 | + // |
| 398 | + // For yaml, the field typings are heavily/poorly inferred so for an optional float like |
| 399 | + // `sample_ratio` an empty string would parse as a `None`, which could be a bad fallback since |
| 400 | + // otel considers this a 1.0 ratio (send everything). |
| 401 | + // |
| 402 | + // For this specific case, retaining the token text produces an error, which happens to be useful. |
| 403 | + // For fields that happen to be strings anyway, errors may show up later (after the config parsing). |
| 404 | + // Ex: using `${QUEUE_NAME}` in a rabbit sender input will surface in logs as an error when we |
| 405 | + // try to connect: "no such queue '${QUEUE_NAME}'". |
| 406 | + |
| 407 | + let src = r#" |
| 408 | + opentelemetry: |
| 409 | + address: "${OTEL_ADDR}" |
| 410 | + # This var will be missing, causing the template token to |
| 411 | + # be retained causing a parse failure :( |
| 412 | + sample_ratio: ${OTEL_SAMPLE_RATIO} |
| 413 | + "#; |
| 414 | + let vars = HashMap::new(); |
| 415 | + let err = Config::from_src(src, Some(&vars)).err().unwrap(); |
| 416 | + let want = "Failed to parse config: opentelemetry.sample_ratio: invalid type: \ |
| 417 | + string \"${OTEL_SAMPLE_RATIO}\", expected f64 at line 6 column 23"; |
| 418 | + assert_eq!(want, err.to_string()); |
| 419 | +} |
| 420 | + |
| 421 | +#[test] |
| 422 | +fn test_variable_substitution_repeated_lookups() { |
| 423 | + // This is probably a given, but we should expect a single variable can be referenced multiple |
| 424 | + // times within the config. |
| 425 | + // The concrete use case: auth tokens. |
| 426 | + |
| 427 | + let src = r#" |
| 428 | + senders: |
| 429 | + - name: "rabbitmq-1" |
| 430 | + input: |
| 431 | + type: "rabbitmq" |
| 432 | + uri: "${RABBIT_URI}" |
| 433 | + queue_name: "${QUEUE_NAME_1}" |
| 434 | + output: |
| 435 | + type: "svix" |
| 436 | + token: "${SVIX_TOKEN}" |
| 437 | + - name: "rabbitmq-2" |
| 438 | + input: |
| 439 | + type: "rabbitmq" |
| 440 | + uri: "${RABBIT_URI}" |
| 441 | + queue_name: "${QUEUE_NAME_2}" |
| 442 | + output: |
| 443 | + type: "svix" |
| 444 | + token: "${SVIX_TOKEN}" |
| 445 | + "#; |
| 446 | + let mut vars = HashMap::new(); |
| 447 | + vars.insert( |
| 448 | + String::from("RABBIT_URI"), |
| 449 | + String::from("amqp://guest:guest@localhost:5672/%2f"), |
| 450 | + ); |
| 451 | + vars.insert(String::from("QUEUE_NAME_1"), String::from("one")); |
| 452 | + vars.insert(String::from("QUEUE_NAME_2"), String::from("two")); |
| 453 | + vars.insert(String::from("SVIX_TOKEN"), String::from("x")); |
| 454 | + let cfg = Config::from_src(src, Some(&vars)).unwrap(); |
| 455 | + |
| 456 | + if let SenderConfig::QueueConsumer(QueueConsumerConfig { |
| 457 | + input: |
| 458 | + SenderInputOpts::RabbitMQ(RabbitMqInputOpts { |
| 459 | + uri, queue_name, .. |
| 460 | + }), |
| 461 | + output: SenderOutputOpts::Svix(SvixSenderOutputOpts { token, .. }), |
| 462 | + .. |
| 463 | + }) = &cfg.senders[0] |
| 464 | + { |
| 465 | + assert_eq!(uri, "amqp://guest:guest@localhost:5672/%2f"); |
| 466 | + assert_eq!(queue_name, "one"); |
| 467 | + assert_eq!(token, "x"); |
| 468 | + } else { |
| 469 | + panic!("sender did not match expected pattern"); |
| 470 | + } |
| 471 | + |
| 472 | + if let SenderConfig::QueueConsumer(QueueConsumerConfig { |
| 473 | + input: |
| 474 | + SenderInputOpts::RabbitMQ(RabbitMqInputOpts { |
| 475 | + uri, queue_name, .. |
| 476 | + }), |
| 477 | + output: SenderOutputOpts::Svix(SvixSenderOutputOpts { token, .. }), |
| 478 | + .. |
| 479 | + }) = &cfg.senders[1] |
| 480 | + { |
| 481 | + assert_eq!(uri, "amqp://guest:guest@localhost:5672/%2f"); |
| 482 | + assert_eq!(queue_name, "two"); |
| 483 | + assert_eq!(token, "x"); |
| 484 | + } else { |
| 485 | + panic!("sender did not match expected pattern"); |
| 486 | + } |
| 487 | +} |
0 commit comments