diff --git a/CHANGELOG.md b/CHANGELOG.md index fae22bc965..59c38087b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,18 @@ Previously, `dfx ledger top-up` only accepted canister principals. Now it accept A change to `curl --help` output made it so the install script did not detect that the `--proto` and `--tlsv1.2` options are available. +### feat: Added support for icx-proxy `--domain` parameter + +In order to access a local replica through a domain name or domain names, +it's necessary to pass the `--domain` parameter to icx-proxy. dfx now supports +this in configuration and as a parameter to dfx start. You can specify a single +domain or a list of domains in any of the following ways: + +- in networks.json, in `..proxy.domain` +- in dfx.json, in `.networks..proxy.domain` +- in dfx.json, in `.defaults.proxy.domain` +- to dfx start, as `dfx start --domain --domain ...` + # 0.15.2 ### fix: `dfx canister delete ` removes the related entry from the canister id store diff --git a/docs/cli-reference/dfx-start.md b/docs/cli-reference/dfx-start.md index 83a399d66c..3621c67bc2 100644 --- a/docs/cli-reference/dfx-start.md +++ b/docs/cli-reference/dfx-start.md @@ -32,6 +32,7 @@ You can use the following option with the `dfx start` command. | `--host host` | Specifies the host interface IP address and port number to bind the frontend to. The default for the local shared network is `127.0.0.1:4943`, while the default for a project-specific network is '127.0.0.1:8000'. | | `--bitcoin-node host:port` | Specifies the address of a bitcoind node. Implies `--enable-bitcoin`. | | `--artificial-delay milliseconds` | Specifies the delay that an update call should incur. Default: 600ms | +| `--domain domain` | A domain that can be served. Can be specified more than once. These are used for canister resolution [default: localhost] | ## Examples diff --git a/docs/dfx-json-schema.json b/docs/dfx-json-schema.json index 186ca4c6f7..40f4e890ac 100644 --- a/docs/dfx-json-schema.json +++ b/docs/dfx-json-schema.json @@ -527,6 +527,16 @@ } ] }, + "proxy": { + "anyOf": [ + { + "$ref": "#/definitions/ConfigDefaultsProxy" + }, + { + "type": "null" + } + ] + }, "replica": { "anyOf": [ { @@ -646,6 +656,23 @@ } } }, + "ConfigDefaultsProxy": { + "description": "Configuration for icx-proxy.", + "type": "object", + "required": [ + "domain" + ], + "properties": { + "domain": { + "description": "A list of domains that can be served. These are used for canister resolution [default: localhost]", + "allOf": [ + { + "$ref": "#/definitions/SerdeVec_for_String" + } + ] + } + } + }, "ConfigDefaultsReplica": { "title": "Local Replica Configuration", "type": "object", @@ -735,6 +762,16 @@ } ] }, + "proxy": { + "anyOf": [ + { + "$ref": "#/definitions/ConfigDefaultsProxy" + }, + { + "type": "null" + } + ] + }, "replica": { "anyOf": [ { diff --git a/docs/networks-json-schema.json b/docs/networks-json-schema.json index c5d7ef47c7..dd7a239ef3 100644 --- a/docs/networks-json-schema.json +++ b/docs/networks-json-schema.json @@ -105,6 +105,23 @@ } } }, + "ConfigDefaultsProxy": { + "description": "Configuration for icx-proxy.", + "type": "object", + "required": [ + "domain" + ], + "properties": { + "domain": { + "description": "A list of domains that can be served. These are used for canister resolution [default: localhost]", + "allOf": [ + { + "$ref": "#/definitions/SerdeVec_for_String" + } + ] + } + } + }, "ConfigDefaultsReplica": { "title": "Local Replica Configuration", "type": "object", @@ -194,6 +211,16 @@ } ] }, + "proxy": { + "anyOf": [ + { + "$ref": "#/definitions/ConfigDefaultsProxy" + }, + { + "type": "null" + } + ] + }, "replica": { "anyOf": [ { @@ -319,6 +346,19 @@ "application", "verifiedapplication" ] + }, + "SerdeVec_for_String": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] } } } \ No newline at end of file diff --git a/e2e/tests-dfx/start.bash b/e2e/tests-dfx/start.bash index 2ba61ab7b7..52a735bda6 100644 --- a/e2e/tests-dfx/start.bash +++ b/e2e/tests-dfx/start.bash @@ -12,6 +12,48 @@ teardown() { standard_teardown } +@test "icx-proxy domain configuration in string form" { + create_networks_json + jq '.local.proxy.domain="xyz.domain"' "$E2E_NETWORKS_JSON" | sponge "$E2E_NETWORKS_JSON" + + dfx_start + + assert_command ps aux + assert_match "icx-proxy.*--domain xyz.domain" +} + +@test "icx-proxy domain configuration in vector form" { + create_networks_json + jq '.local.proxy.domain=["xyz.domain", "abc.something"]' "$E2E_NETWORKS_JSON" | sponge "$E2E_NETWORKS_JSON" + + dfx_start + + assert_command ps aux + assert_match "icx-proxy.*--domain xyz.domain" + assert_match "icx-proxy.*--domain abc.something" +} + +@test "icx-proxy domain configuration from project defaults" { + dfx_new + define_project_network + + jq '.defaults.proxy.domain=["xyz.domain", "abc.something"]' dfx.json | sponge dfx.json + + dfx_start + + assert_command ps aux + assert_match "icx-proxy.*--domain xyz.domain" + assert_match "icx-proxy.*--domain abc.something" +} + +@test "icx-proxy domain configuration from command-line" { + dfx_start --domain xyz.domain --domain def.somewhere + + assert_command ps aux + assert_match "icx-proxy.*--domain xyz.domain" + assert_match "icx-proxy.*--domain def.somewhere" +} + @test "dfx restarts the replica" { dfx_new hello dfx_start diff --git a/src/dfx-core/src/config/model/dfinity.rs b/src/dfx-core/src/config/model/dfinity.rs index 17c8b1d8a7..f4ffd78cc6 100644 --- a/src/dfx-core/src/config/model/dfinity.rs +++ b/src/dfx-core/src/config/model/dfinity.rs @@ -57,6 +57,7 @@ const EMPTY_CONFIG_DEFAULTS: ConfigDefaults = ConfigDefaults { bootstrap: None, build: None, canister_http: None, + proxy: None, replica: None, }; @@ -555,6 +556,13 @@ pub struct ConfigDefaultsReplica { pub log_level: Option, } +/// Configuration for icx-proxy. +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub struct ConfigDefaultsProxy { + /// A list of domains that can be served. These are used for canister resolution [default: localhost] + pub domain: SerdeVec, +} + // Schemars doesn't add the enum value's docstrings. Therefore the explanations have to be up here. /// # Network Type /// Type 'ephemeral' is used for networks that are regularly reset. @@ -643,6 +651,7 @@ pub struct ConfigLocalProvider { pub canister_http: Option, pub replica: Option, pub playground: Option, + pub proxy: Option, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] @@ -667,6 +676,7 @@ pub struct ConfigDefaults { pub bootstrap: Option, pub build: Option, pub canister_http: Option, + pub proxy: Option, pub replica: Option, } diff --git a/src/dfx-core/src/config/model/local_server_descriptor.rs b/src/dfx-core/src/config/model/local_server_descriptor.rs index ff4403247c..6d59829cfa 100644 --- a/src/dfx-core/src/config/model/local_server_descriptor.rs +++ b/src/dfx-core/src/config/model/local_server_descriptor.rs @@ -1,12 +1,14 @@ use crate::config::model::bitcoin_adapter; use crate::config::model::canister_http_adapter::HttpAdapterLogLevel; use crate::config::model::dfinity::{ - to_socket_addr, ConfigDefaultsBitcoin, ConfigDefaultsCanisterHttp, ConfigDefaultsReplica, - ReplicaLogLevel, ReplicaSubnetType, DEFAULT_PROJECT_LOCAL_BIND, DEFAULT_SHARED_LOCAL_BIND, + to_socket_addr, ConfigDefaultsBitcoin, ConfigDefaultsCanisterHttp, ConfigDefaultsProxy, + ConfigDefaultsReplica, ReplicaLogLevel, ReplicaSubnetType, DEFAULT_PROJECT_LOCAL_BIND, + DEFAULT_SHARED_LOCAL_BIND, }; use crate::error::network_config::{ NetworkConfigError, NetworkConfigError::ParseBindAddressFailed, }; +use crate::json::structure::SerdeVec; use slog::{debug, info, Logger}; use std::net::SocketAddr; use std::path::{Path, PathBuf}; @@ -30,6 +32,7 @@ pub struct LocalServerDescriptor { pub bitcoin: ConfigDefaultsBitcoin, pub canister_http: ConfigDefaultsCanisterHttp, + pub proxy: ConfigDefaultsProxy, pub replica: ConfigDefaultsReplica, pub scope: LocalNetworkScopeDescriptor, @@ -51,6 +54,7 @@ impl LocalServerDescriptor { bind: String, bitcoin: ConfigDefaultsBitcoin, canister_http: ConfigDefaultsCanisterHttp, + proxy: ConfigDefaultsProxy, replica: ConfigDefaultsReplica, scope: LocalNetworkScopeDescriptor, legacy_pid_path: Option, @@ -61,6 +65,7 @@ impl LocalServerDescriptor { bind_address, bitcoin, canister_http, + proxy, replica, scope, legacy_pid_path, @@ -199,6 +204,13 @@ impl LocalServerDescriptor { }; Self { bitcoin, ..self } } + + pub fn with_proxy_domains(self, domains: Vec) -> LocalServerDescriptor { + let proxy = ConfigDefaultsProxy { + domain: SerdeVec::Many(domains), + }; + Self { proxy, ..self } + } } impl LocalServerDescriptor { diff --git a/src/dfx-core/src/json/structure.rs b/src/dfx-core/src/json/structure.rs index 8bfbd71e62..c838f0eacc 100644 --- a/src/dfx-core/src/json/structure.rs +++ b/src/dfx-core/src/json/structure.rs @@ -4,7 +4,7 @@ use serde::Serialize; use std::fmt::Display; use std::str::FromStr; -#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, PartialEq, Eq)] #[serde(untagged)] pub enum SerdeVec { One(T), diff --git a/src/dfx-core/src/network/provider.rs b/src/dfx-core/src/network/provider.rs index 4d39284ab0..6c12ec4efd 100644 --- a/src/dfx-core/src/network/provider.rs +++ b/src/dfx-core/src/network/provider.rs @@ -96,6 +96,11 @@ fn config_network_to_network_descriptor( .clone() .or_else(|| project_defaults.and_then(|x| x.canister_http.clone())) .unwrap_or_default(); + let proxy = local_provider + .proxy + .clone() + .or_else(|| project_defaults.and_then(|x| x.proxy.clone())) + .unwrap_or_default(); let replica = local_provider .replica .clone() @@ -121,6 +126,7 @@ fn config_network_to_network_descriptor( bind_address, bitcoin, canister_http, + proxy, replica, local_scope, legacy_pid_path, @@ -237,6 +243,7 @@ fn create_shared_network_descriptor( canister_http: None, replica: None, playground: None, + proxy: None, })) } (network_name, None) => { diff --git a/src/dfx/src/actors/icx_proxy.rs b/src/dfx/src/actors/icx_proxy.rs index 74e6aa2ba7..916577767d 100644 --- a/src/dfx/src/actors/icx_proxy.rs +++ b/src/dfx/src/actors/icx_proxy.rs @@ -42,6 +42,9 @@ pub struct IcxProxyConfig { /// run icx-proxy in non-quiet mode pub verbose: bool, + + /// list of domains that can be served (localhost if none specified) + pub domains: Vec, } /// The configuration for the icx_proxy actor. @@ -96,6 +99,7 @@ impl IcxProxy { receiver, fetch_root_key, config.verbose, + config.domains.clone(), ), "Failed to start ICX proxy thread.", )?; @@ -189,6 +193,7 @@ fn icx_proxy_start_thread( receiver: Receiver<()>, fetch_root_key: bool, verbose: bool, + domains: Vec, ) -> DfxResult> { let thread_handler = move || { // Start the process, then wait for the file. @@ -205,6 +210,9 @@ fn icx_proxy_start_thread( let s = format!("{}", url); cmd.args(["--replica", &s]); } + for domain in domains { + cmd.args(["--domain", &domain]); + } if !verbose { cmd.arg("-q"); } diff --git a/src/dfx/src/commands/start.rs b/src/dfx/src/commands/start.rs index 03515f5740..1fdcd36230 100644 --- a/src/dfx/src/commands/start.rs +++ b/src/dfx/src/commands/start.rs @@ -80,6 +80,10 @@ pub struct StartOpts { /// Use old metering. #[arg(long)] use_old_metering: bool, + + /// A list of domains that can be served. These are used for canister resolution [default: localhost] + #[arg(long)] + domain: Vec, } // The frontend webserver is brought up by the bg process; thus, the fg process @@ -146,6 +150,7 @@ pub fn exec( enable_canister_http, artificial_delay, use_old_metering, + domain, }: StartOpts, ) -> DfxResult { if !background { @@ -179,6 +184,7 @@ pub fn exec( bitcoin_node, enable_canister_http, emulator, + domain, )?; let local_server_descriptor = network_descriptor.local_server_descriptor()?; @@ -292,6 +298,8 @@ pub fn exec( .log_level .unwrap_or_default(); + let proxy_domains = local_server_descriptor.proxy.domain.clone().into_vec(); + let replica_config = { let replica_config = ReplicaConfig::new( &state_root, @@ -388,6 +396,7 @@ pub fn exec( bind: address_and_port, replica_urls: vec![], // will be determined after replica starts fetch_root_key: !network_descriptor.is_ic, + domains: proxy_domains, verbose: env.get_verbose_level() > 0, }; @@ -453,6 +462,7 @@ pub fn apply_command_line_parameters( bitcoin_nodes: Vec, enable_canister_http: bool, emulator: bool, + domain: Vec, ) -> DfxResult { if enable_canister_http { warn!( @@ -492,6 +502,10 @@ pub fn apply_command_line_parameters( local_server_descriptor = local_server_descriptor.with_bitcoin_nodes(bitcoin_nodes) } + if !domain.is_empty() { + local_server_descriptor = local_server_descriptor.with_proxy_domains(domain) + } + Ok(NetworkDescriptor { local_server_descriptor: Some(local_server_descriptor), ..network_descriptor