diff --git a/Cargo.lock b/Cargo.lock index e29380b77..c6f9ec4f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1427,7 +1427,7 @@ dependencies = [ [[package]] name = "edgezero-adapter" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" +source = "git+https://github.com/stackpop/edgezero?rev=5f3d648c3c6c38fc6e6b22b5c65c66177363aad8#5f3d648c3c6c38fc6e6b22b5c65c66177363aad8" dependencies = [ "toml", ] @@ -1435,7 +1435,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-axum" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" +source = "git+https://github.com/stackpop/edgezero?rev=5f3d648c3c6c38fc6e6b22b5c65c66177363aad8#5f3d648c3c6c38fc6e6b22b5c65c66177363aad8" dependencies = [ "anyhow", "async-trait", @@ -1463,7 +1463,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-cloudflare" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" +source = "git+https://github.com/stackpop/edgezero?rev=5f3d648c3c6c38fc6e6b22b5c65c66177363aad8#5f3d648c3c6c38fc6e6b22b5c65c66177363aad8" dependencies = [ "anyhow", "async-trait", @@ -1478,7 +1478,7 @@ dependencies = [ "log", "serde_json", "tempfile", - "toml_edit", + "toml_edit 0.25.12+spec-1.1.0", "walkdir", "worker", ] @@ -1486,7 +1486,7 @@ dependencies = [ [[package]] name = "edgezero-adapter-fastly" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" +source = "git+https://github.com/stackpop/edgezero?rev=5f3d648c3c6c38fc6e6b22b5c65c66177363aad8#5f3d648c3c6c38fc6e6b22b5c65c66177363aad8" dependencies = [ "anyhow", "async-stream", @@ -1508,14 +1508,14 @@ dependencies = [ "serde_json", "sha2 0.10.9", "thiserror 2.0.18", - "toml_edit", + "toml_edit 0.25.12+spec-1.1.0", "walkdir", ] [[package]] name = "edgezero-adapter-spin" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" +source = "git+https://github.com/stackpop/edgezero?rev=5f3d648c3c6c38fc6e6b22b5c65c66177363aad8#5f3d648c3c6c38fc6e6b22b5c65c66177363aad8" dependencies = [ "anyhow", "async-trait", @@ -1535,14 +1535,14 @@ dependencies = [ "subtle", "thiserror 2.0.18", "toml", - "toml_edit", + "toml_edit 0.25.12+spec-1.1.0", "walkdir", ] [[package]] name = "edgezero-cli" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" +source = "git+https://github.com/stackpop/edgezero?rev=5f3d648c3c6c38fc6e6b22b5c65c66177363aad8#5f3d648c3c6c38fc6e6b22b5c65c66177363aad8" dependencies = [ "chrono", "clap", @@ -1567,7 +1567,7 @@ dependencies = [ [[package]] name = "edgezero-core" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" +source = "git+https://github.com/stackpop/edgezero?rev=5f3d648c3c6c38fc6e6b22b5c65c66177363aad8#5f3d648c3c6c38fc6e6b22b5c65c66177363aad8" dependencies = [ "anyhow", "async-compression", @@ -1598,14 +1598,14 @@ dependencies = [ [[package]] name = "edgezero-macros" version = "0.1.0" -source = "git+https://github.com/stackpop/edgezero?tag=v0.0.4#9e661ae520a8130660f18fd10f42703d7f3e050b" +source = "git+https://github.com/stackpop/edgezero?rev=5f3d648c3c6c38fc6e6b22b5c65c66177363aad8#5f3d648c3c6c38fc6e6b22b5c65c66177363aad8" dependencies = [ "log", "proc-macro2", "quote", "serde", "serde_json", - "syn 2.0.118", + "syn 3.0.3", "toml", "validator", ] @@ -4858,6 +4858,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -5163,6 +5174,19 @@ dependencies = [ "winnow 0.7.15", ] +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.3", +] + [[package]] name = "toml_parser" version = "1.1.2+spec-1.1.0" @@ -5431,7 +5455,7 @@ dependencies = [ "tokio", "tokio-rustls", "toml", - "toml_edit", + "toml_edit 0.23.10+spec-1.0.0", "trusted-server-core", "url", "webpki-roots", diff --git a/Cargo.toml b/Cargo.toml index 25c367181..59db5cc1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,12 +54,12 @@ criterion = { version = "0.5", default-features = false, features = ["cargo_benc derive_more = { version = "2.0", features = ["display", "error"] } directories = "5" ed25519-dalek = { version = "2.2", features = ["rand_core"] } -edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.4", default-features = false } -edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.4", default-features = false } -edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.4", default-features = false } -edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.4", default-features = false } -edgezero-cli = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.4" } -edgezero-core = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.4", default-features = false } +edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero", rev = "5f3d648c3c6c38fc6e6b22b5c65c66177363aad8", default-features = false } +edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero", rev = "5f3d648c3c6c38fc6e6b22b5c65c66177363aad8", default-features = false } +edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero", rev = "5f3d648c3c6c38fc6e6b22b5c65c66177363aad8", default-features = false } +edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero", rev = "5f3d648c3c6c38fc6e6b22b5c65c66177363aad8", default-features = false } +edgezero-cli = { git = "https://github.com/stackpop/edgezero", rev = "5f3d648c3c6c38fc6e6b22b5c65c66177363aad8" } +edgezero-core = { git = "https://github.com/stackpop/edgezero", rev = "5f3d648c3c6c38fc6e6b22b5c65c66177363aad8", default-features = false } env_logger = "0.11" error-stack = "0.6" esi = "0.7.2" diff --git a/crates/trusted-server-cli/src/run.rs b/crates/trusted-server-cli/src/run.rs index 1b0bdfa29..ae10ae014 100644 --- a/crates/trusted-server-cli/src/run.rs +++ b/crates/trusted-server-cli/src/run.rs @@ -2,8 +2,8 @@ use std::process; use clap::{Parser, Subcommand}; use edgezero_cli::args::{ - AuthArgs, BuildArgs, ConfigDiffArgs, ConfigPushArgs, ConfigValidateArgs, DeployArgs, - ProvisionArgs, ServeArgs, + ActiveVersionArgs, AuthArgs, BuildArgs, ConfigDiffArgs, ConfigGcArgs, ConfigPushArgs, + ConfigValidateArgs, DeployArgs, HealthcheckArgs, ProvisionArgs, RollbackArgs, ServeArgs, }; use trusted_server_core::config::TrustedServerAppConfig; @@ -13,7 +13,7 @@ use crate::commands::config::init::{ConfigInitArgs, run_config_init}; use crate::prebid_bundle::{NpmPrebidBundleGenerator, PrebidBundleArgs, run_bundle}; #[derive(Debug, Parser)] -#[command(name = "ts", about = "Trusted Server CLI")] +#[command(name = "ts", version, about = "Trusted Server CLI")] struct Args { #[command(subcommand)] command: Command, @@ -21,6 +21,8 @@ struct Args { #[derive(Debug, Subcommand)] enum Command { + /// Print the currently active deployment version for a target adapter. + ActiveVersion(ActiveVersionArgs), /// Audit a public page and write draft Trusted Server artifacts. Audit(AuditArgs), /// Sign in / out / status against an `EdgeZero` adapter. @@ -32,10 +34,14 @@ enum Command { Config(ConfigCommand), /// Deploy the project through a target adapter. Deploy(DeployArgs), + /// Probe a deployed version until it reports healthy. + Healthcheck(HealthcheckArgs), /// Trusted Server Prebid commands. Prebid(PrebidArgs), /// Provision platform resources through a target adapter. Provision(ProvisionArgs), + /// Roll a service back to a previously active deployment version. + Rollback(RollbackArgs), /// Serve the project locally through a target adapter. Serve(ServeArgs), /// Local developer tools (e.g. the macOS-only production-hostname proxy). @@ -49,6 +55,8 @@ enum ConfigCommand { Init(ConfigInitArgs), /// Diff `trusted-server.toml` against the live `EdgeZero` config. Diff(ConfigDiffArgs), + /// Reclaim orphaned chunk entries leaked from prior oversized pushes. + Gc(ConfigGcArgs), /// Push `trusted-server.toml` as a blob envelope through `EdgeZero`. Push(ConfigPushArgs), /// Validate `edgezero.toml` and the typed Trusted Server config. @@ -79,6 +87,7 @@ pub fn run_from_env() -> Result<(), String> { fn dispatch(args: Args) -> Result<(), String> { match args.command { + Command::ActiveVersion(args) => edgezero_cli::run_active_version(&args), Command::Audit(args) => { let stdout = std::io::stdout(); let mut out = stdout.lock(); @@ -95,6 +104,7 @@ fn dispatch(args: Args) -> Result<(), String> { Err(err) => Err(err), } } + Command::Config(ConfigCommand::Gc(args)) => edgezero_cli::run_config_gc(&args), Command::Config(ConfigCommand::Push(args)) => { edgezero_cli::run_config_push_typed::(&args) } @@ -102,6 +112,7 @@ fn dispatch(args: Args) -> Result<(), String> { edgezero_cli::run_config_validate_typed::(&args) } Command::Deploy(args) => edgezero_cli::run_deploy(&args), + Command::Healthcheck(args) => edgezero_cli::run_healthcheck(&args), Command::Prebid(prebid) => { let mut generator = NpmPrebidBundleGenerator; let mut stdout = std::io::stdout(); @@ -113,6 +124,7 @@ fn dispatch(args: Args) -> Result<(), String> { } } Command::Provision(args) => edgezero_cli::run_provision(&args), + Command::Rollback(args) => edgezero_cli::run_rollback(&args), Command::Serve(args) => edgezero_cli::run_serve(&args), Command::Dev(command) => crate::commands::dev::run(command), } @@ -131,6 +143,213 @@ mod tests { Args::try_parse_from(args).expect("should parse args") } + #[test] + fn top_level_version_flag_is_available() { + let err = Args::try_parse_from(["ts", "--version"]) + .expect_err("should short-circuit parsing on --version"); + assert_eq!( + err.kind(), + clap::error::ErrorKind::DisplayVersion, + "should print the version rather than fail to parse" + ); + } + + #[test] + fn parses_active_version() { + let args = parse(&[ + "ts", + "active-version", + "--adapter", + "fastly", + "--service-id", + "service-123", + ]); + let Command::ActiveVersion(active_version) = args.command else { + panic!("expected active-version command"); + }; + assert_eq!(active_version.adapter, "fastly"); + assert_eq!(active_version.service_id, "service-123"); + } + + #[test] + fn parses_healthcheck_with_retry_defaults() { + let args = parse(&[ + "ts", + "healthcheck", + "--adapter", + "fastly", + "--service-id", + "service-123", + "--version", + "7", + "--domain", + "edge.example", + ]); + let Command::Healthcheck(healthcheck) = args.command else { + panic!("expected healthcheck command"); + }; + assert_eq!(healthcheck.domain, "edge.example"); + assert_eq!(healthcheck.version, "7"); + assert_eq!(healthcheck.path, "/", "should default to probing `/`"); + assert_eq!( + healthcheck.retry, 3, + "should default to 3 total attempts, not 3 retries after a first try" + ); + assert_eq!( + healthcheck.retry_delay, 5, + "should default to a 5s retry delay" + ); + assert_eq!(healthcheck.timeout, 10, "should default to a 10s timeout"); + assert!(!healthcheck.staging, "should probe production by default"); + } + + #[test] + fn parses_healthcheck_with_staging_overrides() { + let args = parse(&[ + "ts", + "healthcheck", + "--adapter", + "fastly", + "--service-id", + "service-123", + "--version", + "7", + "--domain", + "edge.example", + "--staging", + "--retry", + "9", + "--retry-delay", + "2", + "--timeout", + "30", + ]); + let Command::Healthcheck(healthcheck) = args.command else { + panic!("expected healthcheck command"); + }; + assert!(healthcheck.staging); + assert_eq!(healthcheck.retry, 9); + assert_eq!(healthcheck.retry_delay, 2); + assert_eq!(healthcheck.timeout, 30); + } + + #[test] + fn healthcheck_requires_domain() { + Args::try_parse_from([ + "ts", + "healthcheck", + "--adapter", + "fastly", + "--service-id", + "service-123", + "--version", + "7", + ]) + .expect_err("should reject healthcheck without a domain"); + } + + #[test] + fn parses_rollback_with_explicit_target() { + let args = parse(&[ + "ts", + "rollback", + "--adapter", + "fastly", + "--service-id", + "service-123", + "--version", + "8", + "--rollback-to", + "7", + ]); + let Command::Rollback(rollback) = args.command else { + panic!("expected rollback command"); + }; + assert_eq!(rollback.version, "8"); + assert_eq!(rollback.rollback_to, Some("7".to_owned())); + assert!(!rollback.staging); + } + + #[test] + fn parses_staging_rollback_without_target() { + let args = parse(&[ + "ts", + "rollback", + "--adapter", + "fastly", + "--service-id", + "service-123", + "--version", + "8", + "--staging", + ]); + let Command::Rollback(rollback) = args.command else { + panic!("expected rollback command"); + }; + assert!(rollback.staging); + assert_eq!( + rollback.rollback_to, None, + "staging rollback should not need an explicit target" + ); + } + + #[test] + fn rollback_requires_service_id() { + Args::try_parse_from(["ts", "rollback", "--adapter", "fastly", "--version", "8"]) + .expect_err("should reject rollback without a service id"); + } + + #[test] + fn parses_deploy_with_staging_flags() { + let args = parse(&[ + "ts", + "deploy", + "--adapter", + "fastly", + "--service-id", + "service-123", + "--staging", + ]); + let Command::Deploy(deploy) = args.command else { + panic!("expected deploy command"); + }; + assert_eq!(deploy.service_id, Some("service-123".to_owned())); + assert!(deploy.staging); + } + + #[test] + fn deploy_rejects_renamed_stage_flag_before_separator() { + // `--stage` was renamed to `--staging`, and adapter passthrough is + // `last = true` (only captured after `--`). A stray `--stage` before the + // separator must fail closed at parse time rather than being swallowed as + // passthrough, which would leave `staging` false and route a + // staging-intended deploy to production. + Args::try_parse_from(["ts", "deploy", "--adapter", "fastly", "--stage"]) + .expect_err("should reject the renamed-away --stage flag, not route it to production"); + } + + #[test] + fn deploy_captures_adapter_passthrough_after_separator() { + let args = parse(&[ + "ts", + "deploy", + "--adapter", + "fastly", + "--", + "--comment", + "ci", + ]); + let Command::Deploy(deploy) = args.command else { + panic!("expected deploy command"); + }; + assert!(!deploy.staging, "should default to a production deploy"); + assert_eq!( + deploy.adapter_args, + vec!["--comment", "ci"], + "should capture args after -- as adapter passthrough" + ); + } + #[test] fn parses_audit_with_default_outputs() { let args = parse(&["ts", "audit", "https://publisher.example"]); @@ -270,6 +489,103 @@ mod tests { assert!(!diff.no_env); } + #[test] + fn config_push_parses_staging_and_rejects_explicit_key() { + let args = parse(&["ts", "config", "push", "--adapter", "fastly", "--staging"]); + let Command::Config(ConfigCommand::Push(push)) = args.command else { + panic!("expected config push command"); + }; + assert!(push.staging, "should target the derived staging key"); + + Args::try_parse_from([ + "ts", + "config", + "push", + "--adapter", + "fastly", + "--staging", + "--key", + "custom", + ]) + .expect_err("should reject --key with --staging; the staging key is derived"); + } + + #[test] + fn config_diff_parses_staging_and_rejects_explicit_key() { + let args = parse(&["ts", "config", "diff", "--adapter", "fastly", "--staging"]); + let Command::Config(ConfigCommand::Diff(diff)) = args.command else { + panic!("expected config diff command"); + }; + assert!( + diff.staging, + "should compare against the derived staging key" + ); + + Args::try_parse_from([ + "ts", + "config", + "diff", + "--adapter", + "fastly", + "--staging", + "--key", + "custom", + ]) + .expect_err("should reject --key with --staging; the staging key is derived"); + } + + #[test] + fn config_gc_previews_by_default() { + let args = parse(&["ts", "config", "gc", "--adapter", "fastly"]); + let Command::Config(ConfigCommand::Gc(gc)) = args.command else { + panic!("expected config gc command"); + }; + assert_eq!(gc.adapter, "fastly"); + assert_eq!( + gc.older_than, None, + "should not require an older-than window to preview" + ); + assert!(!gc.dry_run); + assert!(!gc.no_env); + assert!( + !gc.yes, + "should not delete without an explicit --yes; --yes is the only destructive gate" + ); + } + + #[test] + fn config_gc_parses_destructive_sweep() { + let args = parse(&[ + "ts", + "config", + "gc", + "--adapter", + "fastly", + "--yes", + "--older-than", + "7d", + ]); + let Command::Config(ConfigCommand::Gc(gc)) = args.command else { + panic!("expected config gc command"); + }; + assert!(gc.yes); + assert_eq!(gc.older_than, Some("7d".to_owned())); + } + + #[test] + fn config_gc_rejects_dry_run_with_yes() { + Args::try_parse_from([ + "ts", + "config", + "gc", + "--adapter", + "fastly", + "--dry-run", + "--yes", + ]) + .expect_err("should reject conflicting --dry-run and --yes"); + } + #[test] fn config_validate_uses_edgezero_app_config_flag() { let args = parse(&[ diff --git a/crates/trusted-server-cli/tests/config_env_overlay.rs b/crates/trusted-server-cli/tests/config_env_overlay.rs index 35263c0eb..851110ca7 100644 --- a/crates/trusted-server-cli/tests/config_env_overlay.rs +++ b/crates/trusted-server-cli/tests/config_env_overlay.rs @@ -44,7 +44,7 @@ fn migrated_legacy_project() -> MigratedProject { let mut document = LEGACY_CONFIG .parse::() .expect("should parse legacy integration config"); - // EdgeZero v0.0.4 environment overlays cannot create missing TOML leaves, + // EdgeZero environment overlays cannot create missing TOML leaves, // so a migrated config must carry both creative-processing leaves for the // corresponding environment variables to take effect. document["auction"]["rewrite_creatives"] = value(true); diff --git a/crates/trusted-server-core/src/config.rs b/crates/trusted-server-core/src/config.rs index e74ef4150..331f7ab33 100644 --- a/crates/trusted-server-core/src/config.rs +++ b/crates/trusted-server-core/src/config.rs @@ -110,9 +110,12 @@ impl edgezero_core::app_config::AppConfigMeta for TrustedServerAppConfig { // Phase 1 intentionally preserves the existing inline-settings model: // `ts config push` publishes the validated Trusted Server config as one // app-config blob. Migrating app-level secrets to `EdgeZero` secret-store - // references needs nested/array extraction support and operator migration - // work tracked separately. - const SECRET_FIELDS: &'static [edgezero_core::app_config::SecretField] = &[]; + // references needs the secret-field paths spelled out here (this + // hand-written impl does not inherit the derive's nested/array paths) + // plus operator migration work tracked separately. + fn secret_fields() -> Vec { + Vec::new() + } } /// Runs Trusted Server deploy-time validation for pushed app config. diff --git a/docs/guide/auction-orchestration.md b/docs/guide/auction-orchestration.md index a47dbf155..26a0d5e3b 100644 --- a/docs/guide/auction-orchestration.md +++ b/docs/guide/auction-orchestration.md @@ -757,8 +757,8 @@ timeout_ms = 500 # Mediator budget (called after providers) ### Environment Variable Overrides The typed `ts config validate`, `ts config diff`, and `ts config push` flows can -override auction values that already exist in the TOML. EdgeZero v0.0.4 does -not create missing leaves, so existing configs must add **both** +override auction values that already exist in the TOML. EdgeZero's env overlay +does not create missing leaves, so existing configs must add **both** `rewrite_creatives = true` and `sanitize_creatives = false` under `[auction]` before relying on the corresponding environment overrides — an override for a missing leaf is silently ignored. diff --git a/docs/guide/cli.md b/docs/guide/cli.md index b6829895e..cf28803f9 100644 --- a/docs/guide/cli.md +++ b/docs/guide/cli.md @@ -65,8 +65,8 @@ ts config push --adapter fastly `config validate`, `config diff`, and `config push` use EdgeZero's typed app-config loader. By default that loader applies `TRUSTED_SERVER__...` -environment overlays before validation, comparison, and blob creation. EdgeZero -v0.0.4 only overrides leaves already present in the TOML; add newly introduced +environment overlays before validation, comparison, and blob creation. The +overlay only overrides leaves already present in the TOML; add newly introduced fields to existing configs before relying on their overrides. Pass `--no-env` for file-only operation. See [Configuration](/guide/configuration#environment-variable-overrides-typed-cli) for migration and rollback guidance. @@ -76,6 +76,29 @@ Trusted Server settings JSON. This blob model is intentional because full Trusted Server configs can exceed Fastly limits when split into one config-store entry per setting. +Reclaim orphaned chunk entries leaked from prior oversized pushes: + +```bash +ts config gc --adapter fastly +``` + +Without `--yes`, `config gc` only previews: it reports what it would delete and +deletes nothing. `--dry-run` states that intent explicitly and conflicts with +`--yes`. To actually delete, pass `--yes` together with `--older-than ` +(`s`/`m`/`h`/`d` suffixes, e.g. `7d`; a bare number means seconds): + +```bash +ts config gc --adapter fastly --yes --older-than 7d +``` + +`config gc` sweeps every root in the selected physical store, so `--older-than` +is a safety assertion about the whole store: nothing in it changed within the +window and no writer is targeting it. Unlike the other `config` subcommands, +`gc` never loads the typed app config; its `--no-env` flag instead ignores +`EDGEZERO__STORES__CONFIG____NAME` when resolving which physical store to +sweep. On a destructive run, check the store id `gc` reports before passing +`--yes`. + ## Lifecycle commands Lifecycle commands delegate to the selected EdgeZero adapter: @@ -88,6 +111,58 @@ ts deploy --adapter fastly ts serve --adapter fastly ``` +`ts deploy` accepts `--staging` (Fastly only) to build and upload a staged +draft version cloned from the active one instead of activating a production +deploy. Adapter passthrough arguments must follow a `--` separator; unknown +flags before `--` (including the renamed-away `--stage`) are rejected at parse +time rather than forwarded: + +```bash +ts deploy --adapter fastly --service-id --staging +ts deploy --adapter fastly -- --comment "release" +``` + +A staged deploy only redirects the staged version's config selector at the +`_staging` key — it does not copy the production config blob +there. Push the staged config before probing the staged version, or it comes up +with no config at all: + +```bash +ts config push --adapter fastly --staging +ts config diff --adapter fastly --staging +``` + +`--staging` on `config push` / `config diff` writes and compares the +`_staging` key in the same store. It is mutually exclusive +with `--key`: the staging key is derived from the store's logical id, so an +explicit key would be written where nothing reads it. + +Inspect and verify deployments with the deploy lifecycle commands: + +```bash +# Print the currently active deployment version +ts active-version --adapter fastly --service-id + +# Probe a deployed version until it reports healthy +ts healthcheck --adapter fastly --service-id \ + --version --domain edge.example + +# Re-activate a previously active version +ts rollback --adapter fastly --service-id \ + --version --rollback-to +``` + +`healthcheck` probes `/` by default (`--path` overrides) and makes 3 total +attempts — not 3 retries after a first try — with a 5 second delay between +attempts and a 10 second per-attempt timeout (`--retry`, `--retry-delay`, +`--timeout`). With `--staging` it resolves the staged version's IP from the +service id and probes that instead of the production endpoint. + +`rollback` cannot infer the production rollback target: Fastly exposes no +metadata to tell a previously live version from a staged one, so pass the +version to re-activate via `--rollback-to`. With `--staging`, it deactivates +the staged `--version` instead and needs no `--rollback-to`. + ## Audit a public page `ts audit` loads a public page in a fresh headless Chrome/Chromium session, diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 1240dce20..af1d0d3f5 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -115,7 +115,7 @@ base TOML configuration by `ts config validate`, `ts config diff`, and stored in the app-config blob. Changing an environment variable requires rerunning validation and pushing the resolved config, not rebuilding the binary. -EdgeZero v0.0.4 only overrides leaves that already exist in the parsed TOML; it +EdgeZero's env overlay only overrides leaves that already exist in the parsed TOML; it does not create missing fields. Add newly introduced defaulted fields to an existing config before relying on their environment overrides. Pass `--no-env` to use file values without the overlay. @@ -1429,7 +1429,7 @@ remove that field's non-default value (and any environment override), run `ts config validate`, push the resulting default-compatible blob, and only then roll back the binary. -**Environment overlays:** EdgeZero v0.0.4 overlays cannot create missing TOML +**Environment overlays:** EdgeZero's env overlays cannot create missing TOML leaves. Existing configs must add **both** leaves under `[auction]` (`rewrite_creatives` and `sanitize_creatives`) before `TRUSTED_SERVER__AUCTION__REWRITE_CREATIVES` / @@ -1903,7 +1903,7 @@ trusted-server.dev.toml # Development overrides **Environment Variables Not Applied**: - Run the override through `ts config validate`, `ts config diff`, or `ts config push` -- Verify the target leaf already exists in `trusted-server.toml`; EdgeZero v0.0.4 does not create missing fields +- Verify the target leaf already exists in `trusted-server.toml`; the env overlay does not create missing fields - Verify prefix: `TRUSTED_SERVER__` - Check separator: `__` (double underscore) - Confirm the variable is exported: `echo $VARIABLE_NAME` diff --git a/trusted-server.example.toml b/trusted-server.example.toml index 5bbdfb857..7794c0970 100644 --- a/trusted-server.example.toml +++ b/trusted-server.example.toml @@ -147,7 +147,7 @@ enabled = false [auction] enabled = false -# Defaults to true. Keep this leaf present when using the EdgeZero v0.0.4 +# Defaults to true. Keep this leaf present when using the EdgeZero # environment override. Set false to return unre-written winning-bid adm, # skipping proxy/click URL conversion and creative TSJS injection. # Sanitization is controlled separately by `sanitize_creatives` below.