diff --git a/CHANGELOG.md b/CHANGELOG.md index e1cf4174de..e79b949ff0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Do not combine a URL and non-embedded auth token from different configuration files. When configuration sources provide only one of these values, an existing value from another source may be ignored with a warning. Configure the URL and token in the same file or through CLI arguments and environment variables, which are treated as one runtime source. This change does not alter parent-config discovery or how other configuration keys are selected ([#3378](https://github.com/getsentry/sentry-cli/pull/3378/)). + ## 3.6.1 ### Fixes diff --git a/lib/index.ts b/lib/index.ts index 2be8e3149b..3c232eb135 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -43,6 +43,7 @@ export class SentryCli { * * @param configFile Path to Sentry CLI config properties, as described in https://docs.sentry.io/learn/cli/configuration/#properties-files. * By default, the config file is looked for upwards from the current path and defaults from ~/.sentryclirc are always loaded. + * A URL and a non-embedded auth token must be configured in the same file or through runtime options and environment variables. * This value will update `SENTRY_PROPERTIES` env variable. * @param options More options to pass to the CLI */ diff --git a/src/commands/login.rs b/src/commands/login.rs index eed0e7738f..c7c8957d7d 100644 --- a/src/commands/login.rs +++ b/src/commands/login.rs @@ -19,9 +19,9 @@ pub fn make_command(command: Command) -> Command { ) } -fn update_config(config: &Config, token: AuthToken) -> Result<()> { +fn update_config(config: &Config, token: AuthToken, url: &str) -> Result<()> { let mut new_cfg = config.clone(); - new_cfg.set_auth(Auth::Token(token)); + new_cfg.set_auth_and_url(Auth::Token(token), url); new_cfg.save()?; Ok(()) } @@ -61,7 +61,7 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { } let mut token; - loop { + let validated_url = loop { token = if let Some(token) = predefined_token { token.to_owned() } else { @@ -72,6 +72,7 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { cfg.set_auth(Auth::Token(token.clone())); Ok(()) })?; + let tested_url = test_cfg.get_base_url()?.to_owned(); match Api::with_config(test_cfg).authenticated()?.get_auth_info() { Ok(info) => { @@ -85,7 +86,7 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { println!("Valid org token"); } } - break; + break tested_url; } Err(err) => { // Convert to anyhow error to take advantage of anyhow's Debug impl @@ -98,20 +99,21 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { } } } - } + }; let config_to_update = if matches.get_flag("global") { Config::global()? } else { - Config::from_cli_config()? + Config::from_cli_config(None, None)? }; - if should_warn_about_overwrite(config_to_update.get_auth(), &token) { + let persisted_auth = config_to_update.get_persisted_auth(); + if should_warn_about_overwrite(persisted_auth.as_ref(), &token) { println!(); println!("Warning: You are about to overwrite an existing token!"); // Show organization information - if let Some(existing_auth) = config_to_update.get_auth() { + if let Some(existing_auth) = persisted_auth.as_ref() { let existing_org = get_org_from_auth(existing_auth); let new_org = get_org_from_token(&token); @@ -126,7 +128,7 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { } } - update_config(&config_to_update, token)?; + update_config(&config_to_update, token, &validated_url)?; println!(); println!( "Stored token in {}", diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 6e58f85712..b9135502bd 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -10,7 +10,7 @@ use std::process; use std::{env, iter}; use crate::api::Api; -use crate::config::{Auth, Config}; +use crate::config::Config; use crate::constants::{ARCH, PLATFORM, VERSION}; use crate::utils::auth_token::{redact_token_from_string, AuthToken}; use crate::utils::logging::set_quiet_mode; @@ -139,14 +139,6 @@ fn preexecute_hooks() -> Result { } fn configure_args(config: &mut Config, matches: &ArgMatches) { - if let Some(auth_token) = matches.get_one::("auth_token") { - config.set_auth(Auth::Token(auth_token.to_owned())); - } - - if let Some(url) = matches.get_one::("url") { - config.set_base_url(url); - } - if let Some(headers) = matches.get_many::("headers") { let headers = headers.map(|h| h.to_owned()).collect(); config.set_headers(headers); @@ -263,7 +255,10 @@ pub fn execute() -> Result<()> { if let Some(&log_level) = log_level { set_max_level(log_level); } - let mut config = Config::from_cli_config()?; + let mut config = Config::from_cli_config( + matches.get_one::("url").map(String::as_str), + matches.get_one::("auth_token"), + )?; configure_args(&mut config, &matches); set_quiet_mode(matches.get_flag("quiet")); diff --git a/src/config.rs b/src/config.rs index 30296962dd..a12b6cf57b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ //! This module implements config access. use std::env; use std::env::VarError; +use std::fmt; use std::fs; use std::fs::OpenOptions; use std::io::{self, Write as _}; @@ -58,44 +59,117 @@ pub struct Config { } impl Config { - /// Loads the CLI config from the default location and returns it. - pub fn from_cli_config() -> Result { - let (filename, ini) = load_cli_config()?; - Ok(Config::from_file(filename, ini)) + /// Loads config files and applies URL and token values from the environment and CLI. + pub fn from_cli_config(cli_url: Option<&str>, cli_token: Option<&AuthToken>) -> Result { + let (global_filename, mut rv) = load_global_config_file()?; + let mut warning = None; + + let (path, mut rv) = if let Some(project_config_path) = find_project_config_file() { + let file_desc = format!( + "{CONFIG_RC_FILE_NAME} file from project path ({})", + project_config_path.display() + ); + let mut f = fs::File::open(&project_config_path) + .context(failed_local_config_load_message(&file_desc))?; + let ini = Ini::read_from(&mut f).context(format!("Failed to parse {file_desc}"))?; + warning = merge_config_source(&mut rv, &ini); + (project_config_path, rv) + } else { + (global_filename, rv) + }; + + if let Ok(prop_path) = env::var("SENTRY_PROPERTIES") { + match fs::File::open(&prop_path) { + Ok(f) => { + let props = match java_properties::read(f) { + Ok(props) => props, + Err(err) => { + bail!("Could not load java style properties file: {err}"); + } + }; + info!( + "Loaded file referenced by SENTRY_PROPERTIES ({})", + &prop_path + ); + let mut properties_ini = Ini::new(); + for (key, value) in props { + let mut iter = key.rsplitn(2, '.'); + if let Some(key) = iter.next() { + let section = iter.next(); + properties_ini.set_to(section, key.to_owned(), value); + } else { + debug!("Incorrect properties file key: {key}"); + } + } + warning = merge_config_source(&mut rv, &properties_ini).or(warning); + } + Err(err) => { + if err.kind() != io::ErrorKind::NotFound { + return Err(Error::from(err).context(format!( + "Failed to load file referenced by SENTRY_PROPERTIES ({})", + &prop_path + ))); + } else { + warn!( + "Failed to find file referenced by SENTRY_PROPERTIES ({})", + &prop_path + ); + } + } + } + } + + let mut auth_and_url = AuthAndUrl::from_ini(&rv); + let runtime_warning = auth_and_url.merge_runtime( + env::var("SENTRY_URL").ok(), + env::var("SENTRY_AUTH_TOKEN").ok().map(AuthToken::from), + cli_url, + cli_token, + ); + if let Some(warning) = runtime_warning.or(warning) { + warn!("{warning}"); + } + + Ok(Config::from_file_and_auth(path, rv, auth_and_url)) } - /// Creates Config based on provided config file. + /// Creates a config without applying runtime URL or auth token overrides. + /// + /// Other environment-backed settings retain their existing behavior. pub fn from_file(filename: PathBuf, ini: Ini) -> Self { - let auth = get_default_auth(&ini); - let token_embedded_data = match auth { + let auth_and_url = AuthAndUrl::from_ini(&ini); + Self::from_file_and_auth(filename, ini, auth_and_url) + } + + /// Constructs a config that persists `ini` while using the separately selected auth and URL. + fn from_file_and_auth(filename: PathBuf, ini: Ini, auth_and_url: AuthAndUrl) -> Self { + let auth = auth_and_url.token.map(Auth::Token); + let token_data = match auth { Some(Auth::Token(ref token)) => token.payload().cloned(), - _ => None, // get_default_auth never returns Auth::Token variant + _ => None, }; - - let manually_configured_url = configured_url(&ini); - let token_url = token_embedded_data + let token_url = token_data .as_ref() - .map(|td| td.url.as_str()) + .map(|data| data.url.as_str()) .unwrap_or_default(); - - let url = if token_url.is_empty() { - manually_configured_url.unwrap_or_else(|| DEFAULT_URL.to_owned()) + let base_url = if token_url.is_empty() { + auth_and_url.url.unwrap_or_else(|| DEFAULT_URL.to_owned()) } else { - warn_about_conflicting_urls(token_url, manually_configured_url.as_deref()); - token_url.into() + warn_about_conflicting_urls(token_url, auth_and_url.url.as_deref()); + token_url.to_owned() }; Config { filename, process_bound: false, cached_auth: auth, - cached_base_url: url, + cached_base_url: base_url, cached_headers: get_default_headers(&ini), cached_log_level: get_default_log_level(&ini), cached_vcs_remote: get_default_vcs_remote(&ini), max_retries: get_max_retries(&ini), ini, - cached_token_data: token_embedded_data, + cached_token_data: token_data, } } @@ -187,11 +261,18 @@ impl Config { .map_err(Into::into) } - /// Returns the auth info + /// Returns the auth info selected for the current process. pub fn get_auth(&self) -> Option<&Auth> { self.cached_auth.as_ref() } + /// Returns the auth info that would be persisted when this config is saved. + pub fn get_persisted_auth(&self) -> Option { + self.ini + .get_from(Some("auth"), "token") + .map(|token| Auth::Token(token.into())) + } + /// Updates the auth info pub fn set_auth(&mut self, auth: Auth) { self.cached_auth = Some(auth); @@ -215,6 +296,14 @@ impl Config { } } + /// Updates the auth token and URL as a pair. + pub fn set_auth_and_url(&mut self, auth: Auth, url: &str) { + self.set_auth(auth); + url.clone_into(&mut self.cached_base_url); + self.ini + .set_to(Some("defaults"), "url".into(), url.to_owned()); + } + /// Returns the base url (without trailing slashes) pub fn get_base_url(&self) -> Result<&str> { let base = self.cached_base_url.trim_end_matches('/'); @@ -227,26 +316,6 @@ impl Config { Ok(base) } - /// Sets the URL - pub fn set_base_url(&mut self, url: &str) { - let token_url = self - .cached_token_data - .as_ref() - .map(|td| td.url.as_str()) - .unwrap_or_default(); - - if !token_url.is_empty() && url != token_url { - log::warn!( - "Using {token_url} (embedded in token) rather than manually-configured URL {url}. \ - To use {url}, please provide an auth token for this URL." - ); - } else { - url.clone_into(&mut self.cached_base_url); - self.ini - .set_to(Some("defaults"), "url".into(), self.cached_base_url.clone()); - } - } - /// Sets headers that should be attached to all requests pub fn set_headers(&mut self, headers: Vec) { self.cached_headers = Some(headers); @@ -572,12 +641,140 @@ fn max_retries_from_ini(ini: &Ini) -> Result, ParseIntError> { .transpose() } +#[derive(Clone, Copy, Debug, PartialEq)] +enum DiscardedAuthUrlValue { + AuthToken, + Url, +} + +impl fmt::Display for DiscardedAuthUrlValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DiscardedAuthUrlValue::AuthToken => write!( + f, + "Ignoring an auth token because the selected URL comes from a different \ + configuration source. Configure the URL and auth token together in the same file \ + or through CLI arguments and environment variables." + ), + DiscardedAuthUrlValue::Url => write!( + f, + "Ignoring a configured URL because the selected auth token comes from a different \ + configuration source. Configure the URL and auth token together in the same file \ + or through CLI arguments and environment variables." + ), + } + } +} + +/// URL and token selected after all file sources have been reconciled. +/// +/// Runtime values are applied to this state without modifying the file-backed INI. +struct AuthAndUrl { + url: Option, + token: Option, +} + +impl AuthAndUrl { + fn from_ini(ini: &Ini) -> Self { + Self { + url: url_from_ini(ini), + token: ini.get_from(Some("auth"), "token").map(AuthToken::from), + } + } + + /// Applies CLI-over-environment runtime values as one source without converting the token back + /// into a string or storing runtime values in the file-backed INI. + fn merge_runtime( + &mut self, + environment_url: Option, + environment_token: Option, + cli_url: Option<&str>, + cli_token: Option<&AuthToken>, + ) -> Option { + self.merge(AuthAndUrl { + url: cli_url.map(str::to_owned).or(environment_url), + token: cli_token.cloned().or(environment_token), + }) + } + + /// Applies a higher-priority URL/token source using the shared source-separation rule. + fn merge(&mut self, overlay: AuthAndUrl) -> Option { + let should_discard = reconcile_auth_url(self, &overlay); + match should_discard { + Some(DiscardedAuthUrlValue::AuthToken) => self.token = None, + Some(DiscardedAuthUrlValue::Url) => self.url = None, + None => {} + } + + if let Some(url) = overlay.url { + self.url = Some(url); + } + if let Some(token) = overlay.token { + self.token = Some(token); + } + + should_discard + } +} + +/// Returns whether a token is self-contained because it has a nonempty embedded URL. +fn token_has_embedded_url(token: &AuthToken) -> bool { + token.payload().is_some_and(|data| !data.url.is_empty()) +} + +/// Determines which inherited value must be removed before applying another source. +/// +/// Tokens with embedded URLs are exempt because the manual URL cannot redirect them; final config +/// construction always selects the URL embedded in the token. +fn reconcile_auth_url(base: &AuthAndUrl, overlay: &AuthAndUrl) -> Option { + match (&overlay.url, &overlay.token) { + (Some(_), None) + if base + .token + .as_ref() + .is_some_and(|token| !token_has_embedded_url(token)) => + { + Some(DiscardedAuthUrlValue::AuthToken) + } + (None, Some(token)) if !token_has_embedded_url(token) && base.url.is_some() => { + Some(DiscardedAuthUrlValue::Url) + } + _ => None, + } +} + +/// Merges a higher-priority file source while keeping URL and token selection source-consistent. +/// +/// URL/token reconciliation uses the same typed rule as runtime merging. All other values +/// retain the existing key-by-key INI merge behavior. +fn merge_config_source(base: &mut Ini, overlay: &Ini) -> Option { + let should_discard = + reconcile_auth_url(&AuthAndUrl::from_ini(base), &AuthAndUrl::from_ini(overlay)); + match should_discard { + Some(DiscardedAuthUrlValue::AuthToken) => { + base.delete_from(Some("auth"), "token"); + } + Some(DiscardedAuthUrlValue::Url) => { + base.delete_from(Some("defaults"), "url"); + } + None => {} + } + + for (section, props) in overlay.iter() { + for (key, value) in props.iter() { + base.set_to(section, key.to_owned(), value.to_owned()); + } + } + + should_discard +} + fn warn_about_conflicting_urls(token_url: &str, manually_configured_url: Option<&str>) { if let Some(manually_configured_url) = manually_configured_url { if manually_configured_url != token_url { warn!( "Using {token_url} (embedded in token) rather than manually-configured URL \ - {manually_configured_url}. To use {manually_configured_url}, please provide an \ + {manually_configured_url}. To use {manually_configured_url}, please provide an \ auth token for {manually_configured_url}." ); } @@ -648,69 +845,6 @@ fn failed_local_config_load_message(file_desc: &str) -> String { msg } -fn load_cli_config() -> Result<(PathBuf, Ini)> { - let (global_filename, mut rv) = load_global_config_file()?; - - let (path, mut rv) = if let Some(project_config_path) = find_project_config_file() { - let file_desc = format!( - "{CONFIG_RC_FILE_NAME} file from project path ({})", - project_config_path.display() - ); - let mut f = fs::File::open(&project_config_path) - .context(failed_local_config_load_message(&file_desc))?; - let ini = Ini::read_from(&mut f).context(format!("Failed to parse {file_desc}"))?; - for (section, props) in ini.iter() { - for (key, value) in props.iter() { - rv.set_to(section, key.to_owned(), value.to_owned()); - } - } - (project_config_path, rv) - } else { - (global_filename, rv) - }; - - if let Ok(prop_path) = env::var("SENTRY_PROPERTIES") { - match fs::File::open(&prop_path) { - Ok(f) => { - let props = match java_properties::read(f) { - Ok(props) => props, - Err(err) => { - bail!("Could not load java style properties file: {err}"); - } - }; - info!( - "Loaded file referenced by SENTRY_PROPERTIES ({})", - &prop_path - ); - for (key, value) in props { - let mut iter = key.rsplitn(2, '.'); - if let Some(key) = iter.next() { - let section = iter.next(); - rv.set_to(section, key.to_owned(), value); - } else { - debug!("Incorrect properties file key: {key}"); - } - } - } - Err(err) => { - if err.kind() != io::ErrorKind::NotFound { - return Err(Error::from(err).context(format!( - "Failed to load file referenced by SENTRY_PROPERTIES ({})", - &prop_path - ))); - } else { - warn!( - "Failed to find file referenced by SENTRY_PROPERTIES ({})", - &prop_path - ); - } - } - } - } - - Ok((path, rv)) -} - impl Clone for Config { fn clone(&self) -> Config { Config { @@ -728,22 +862,9 @@ impl Clone for Config { } } -fn get_default_auth(ini: &Ini) -> Option { - if let Ok(val) = env::var("SENTRY_AUTH_TOKEN") { - Some(Auth::Token(val.into())) - } else { - ini.get_from(Some("auth"), "token") - .map(|val| Auth::Token(val.into())) - } -} - -/// Returns the URL configured in the SENTRY_URL environment variable or provided ini (in that -/// order of precedence), or returns None if neither is set. -fn configured_url(ini: &Ini) -> Option { - env::var("SENTRY_URL").ok().or_else(|| { - ini.get_from(Some("defaults"), "url") - .map(|url| url.to_owned()) - }) +fn url_from_ini(ini: &Ini) -> Option { + ini.get_from(Some("defaults"), "url") + .map(|url| url.to_owned()) } fn get_default_headers(ini: &Ini) -> Option> { @@ -791,6 +912,120 @@ mod tests { use super::*; + const USER_TOKEN: &str = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + const EMBEDDED_URL_TOKEN: &str = "sntrys_\ + eyJpYXQiOjE3MDQyMDU4MDIuMTk5NzQzLCJ1cmwiOiJodHRwOi8vbG9jYWxob3N0OjgwMDAiLCJyZ\ + Wdpb25fdXJsIjoiaHR0cDovL2xvY2FsaG9zdDo4MDAwIiwib3JnIjoic2VudHJ5In0=_\ + lQ5ETt61cHhvJa35fxvxARsDXeVrd0pu4/smF4sRieA"; + + fn ini(url: Option<&str>, token: Option<&str>) -> Ini { + let mut ini = Ini::new(); + if let Some(url) = url { + ini.set_to(Some("defaults"), "url".into(), url.to_owned()); + } + if let Some(token) = token { + ini.set_to(Some("auth"), "token".into(), token.to_owned()); + } + ini + } + + #[test] + fn reconcile_keeps_url_and_token_from_same_source() { + let base = AuthAndUrl::from_ini(&ini(Some("https://global.invalid"), Some(USER_TOKEN))); + let overlay = + AuthAndUrl::from_ini(&ini(Some("https://project.invalid"), Some("project-token"))); + + assert_eq!(reconcile_auth_url(&base, &overlay), None); + } + + #[test] + fn reconcile_url_discards_inherited_token() { + let base = AuthAndUrl::from_ini(&ini(Some("https://global.invalid"), Some(USER_TOKEN))); + let overlay = AuthAndUrl::from_ini(&ini(Some("https://project.invalid"), None)); + + assert_eq!( + reconcile_auth_url(&base, &overlay), + Some(DiscardedAuthUrlValue::AuthToken) + ); + } + + #[test] + fn reconcile_token_discards_inherited_url() { + let base = AuthAndUrl::from_ini(&ini(Some("https://global.invalid"), None)); + let overlay = AuthAndUrl::from_ini(&ini(None, Some(USER_TOKEN))); + + assert_eq!( + reconcile_auth_url(&base, &overlay), + Some(DiscardedAuthUrlValue::Url) + ); + } + + #[test] + fn reconcile_url_keeps_inherited_embedded_url_token() { + let base = AuthAndUrl::from_ini(&ini(None, Some(EMBEDDED_URL_TOKEN))); + let overlay = AuthAndUrl::from_ini(&ini(Some("https://project.invalid"), None)); + + assert_eq!(reconcile_auth_url(&base, &overlay), None); + } + + #[test] + fn merge_config_source_without_url_or_token_keeps_inherited_pair() { + let mut base = ini(Some("https://global.invalid"), Some(USER_TOKEN)); + let mut overlay = Ini::new(); + overlay.set_to(Some("http"), "verify_ssl".into(), "false".into()); + + assert_eq!(merge_config_source(&mut base, &overlay), None); + assert_eq!( + base.get_from(Some("defaults"), "url"), + Some("https://global.invalid") + ); + assert_eq!(base.get_from(Some("auth"), "token"), Some(USER_TOKEN)); + assert_eq!(base.get_from(Some("http"), "verify_ssl"), Some("false")); + } + + #[test] + fn cli_and_environment_form_one_runtime_source() { + let cli_token = AuthToken::from("cli-token"); + let mut auth_and_url = AuthAndUrl { + url: Some("https://file.invalid".to_owned()), + token: None, + }; + + assert_eq!( + auth_and_url.merge_runtime( + Some("https://environment.invalid".to_owned()), + Some(AuthToken::from(USER_TOKEN)), + Some("https://cli.invalid"), + Some(&cli_token), + ), + None + ); + assert_eq!(auth_and_url.url.as_deref(), Some("https://cli.invalid")); + assert_eq!( + auth_and_url + .token + .as_ref() + .map(|token| token.raw().expose_secret().as_str()), + Some("cli-token") + ); + } + + #[test] + fn persisted_auth_is_available_when_runtime_url_discards_it() { + let ini = ini(Some("https://file.invalid"), Some(USER_TOKEN)); + let config = Config::from_file_and_auth( + PathBuf::from(".sentryclirc"), + ini, + AuthAndUrl { + url: Some("https://environment.invalid".to_owned()), + token: None, + }, + ); + + assert!(config.get_auth().is_none()); + assert!(config.get_persisted_auth().is_some()); + } + #[cfg(not(windows))] #[test] fn save_restricts_existing_file_permissions() { diff --git a/tests/integration/_cases/org_tokens/org-mismatch.trycmd b/tests/integration/_cases/org_tokens/org-mismatch.trycmd index d68bb6a9e0..3e08539f26 100644 --- a/tests/integration/_cases/org_tokens/org-mismatch.trycmd +++ b/tests/integration/_cases/org_tokens/org-mismatch.trycmd @@ -1,6 +1,7 @@ ``` $ sentry-cli --auth-token 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 sourcemaps upload --org otherorg test_path ? failed +[..]WARN[..]Using http://localhost:8000 (embedded in token) rather than manually-configured URL [..]. To use [..], please provide an auth token for [..]. [..]WARN[..]Using organization `sentry` (embedded in token) rather than manually-configured organization `otherorg`. To use `otherorg`, please provide an auth token for this organization. ... diff --git a/tests/integration/_cases/org_tokens/url-mismatch.trycmd b/tests/integration/_cases/org_tokens/url-mismatch.trycmd index f77e7788a1..fc5d98fa1a 100644 --- a/tests/integration/_cases/org_tokens/url-mismatch.trycmd +++ b/tests/integration/_cases/org_tokens/url-mismatch.trycmd @@ -1,7 +1,7 @@ ``` $ sentry-cli --auth-token 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 --url http://example.com sourcemaps upload test_path ? failed -[..]WARN[..] Using http://localhost:8000 (embedded in token) rather than manually-configured URL http://example.com. To use http://example.com, please provide an auth token for this URL. +[..]WARN[..] Using http://localhost:8000 (embedded in token) rather than manually-configured URL http://example.com. To use http://example.com, please provide an auth token for http://example.com. ... ``` diff --git a/tests/integration/_cases/org_tokens/url-works.trycmd b/tests/integration/_cases/org_tokens/url-works.trycmd index ea0c907af4..c0d0992a03 100644 --- a/tests/integration/_cases/org_tokens/url-works.trycmd +++ b/tests/integration/_cases/org_tokens/url-works.trycmd @@ -3,6 +3,7 @@ Stop the devserver and try again. ``` $ sentry-cli --auth-token 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 info ? failed +[..]WARN[..]Using http://localhost:8000 (embedded in token) rather than manually-configured URL [..]. To use [..], please provide an auth token for [..]. Sentry Server: http://localhost:8000 Default Organization: wat-org Default Project: wat-project