From 0fc601089acd6e9dbb0b6719acd6c8a6c64b4912 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Wed, 15 Jun 2022 14:04:01 -0700 Subject: [PATCH 01/14] Add post-install tasks --- e2e/tests-dfx/install.bash | 14 ++++++++ src/dfx/src/commands/canister/install.rs | 2 ++ src/dfx/src/config/dfinity.rs | 5 +++ src/dfx/src/lib/builders/mod.rs | 2 +- src/dfx/src/lib/canister_info.rs | 10 ++++++ .../operations/canister/deploy_canisters.rs | 10 ++++-- .../operations/canister/install_canister.rs | 34 +++++++++++++++++++ src/dfx/src/util/mod.rs | 23 +++++++++++++ 8 files changed, 96 insertions(+), 4 deletions(-) diff --git a/e2e/tests-dfx/install.bash b/e2e/tests-dfx/install.bash index 1144cde09f..dea170d94e 100644 --- a/e2e/tests-dfx/install.bash +++ b/e2e/tests-dfx/install.bash @@ -81,3 +81,17 @@ teardown() { dfx canister create --all assert_command_fail dfx canister install --all --wasm "${archive:?}/wallet/0.10.0/wallet.wasm" } + +@test "install runs post-install tasks" { + dfx_start + cat <<<"$(jq '.canisters.e2e_project."post-install"="sh -c \"echo hello\""' dfx.json)" >dfx.json + + assert_command dfx canister create --all + assert_command dfx build + + assert_command dfx canister install --all + assert_match hello + + cat <<<"$(jq '.canisters.e2e_project."post-install"="sh -c \"return 1\""' dfx.json)" >dfx.json + assert_command_fail dfx canister install --all +} diff --git a/src/dfx/src/commands/canister/install.rs b/src/dfx/src/commands/canister/install.rs index 8fe2a98404..a94e6d45c9 100644 --- a/src/dfx/src/commands/canister/install.rs +++ b/src/dfx/src/commands/canister/install.rs @@ -126,6 +126,7 @@ pub async fn exec( call_sender, installed_module_hash, opts.upgrade_unchanged, + None, ) .await } @@ -165,6 +166,7 @@ pub async fn exec( call_sender, installed_module_hash, opts.upgrade_unchanged, + None, ) .await?; } diff --git a/src/dfx/src/config/dfinity.rs b/src/dfx/src/config/dfinity.rs index 47e4537c74..58b8aa4597 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -1,5 +1,6 @@ #![allow(dead_code)] use crate::lib::error::{BuildError, DfxError, DfxResult}; +use crate::util::SerdeVec; use crate::{error_invalid_argument, error_invalid_config, error_invalid_data}; use anyhow::{anyhow, Context}; @@ -62,6 +63,7 @@ pub const DEFAULT_IC_GATEWAY_TRAILING_SLASH: &str = "https://ic0.app/"; /// It only contains a type; everything else should be infered using the /// CanisterInfo type. #[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] pub struct ConfigCanistersCanister { pub r#type: Option, @@ -71,6 +73,9 @@ pub struct ConfigCanistersCanister { #[serde(default)] pub remote: Option, + #[serde(default)] + pub post_install: SerdeVec, + #[serde(flatten)] pub extras: BTreeMap, } diff --git a/src/dfx/src/lib/builders/mod.rs b/src/dfx/src/lib/builders/mod.rs index fc05811591..7624e2b882 100644 --- a/src/dfx/src/lib/builders/mod.rs +++ b/src/dfx/src/lib/builders/mod.rs @@ -250,7 +250,7 @@ fn ensure_trailing_newline(s: String) -> String { type Env<'a> = (Cow<'static, str>, Cow<'a, OsStr>); -fn environment_variables<'a>( +pub fn environment_variables<'a>( info: &CanisterInfo, network_name: &'a str, pool: &'a CanisterPool, diff --git a/src/dfx/src/lib/canister_info.rs b/src/dfx/src/lib/canister_info.rs index 290f990bd2..37171e90f8 100644 --- a/src/dfx/src/lib/canister_info.rs +++ b/src/dfx/src/lib/canister_info.rs @@ -50,6 +50,8 @@ pub struct CanisterInfo { packtool: Option, args: Option, + post_install: Vec, + extras: BTreeMap, } @@ -112,6 +114,8 @@ impl CanisterInfo { .cloned() .unwrap_or_else(|| "motoko".to_owned()); + let post_install = canister_config.post_install.clone().into_vec(); + let canister_info = CanisterInfo { name: name.to_string(), canister_type, @@ -130,6 +134,8 @@ impl CanisterInfo { packtool: build_defaults.get_packtool(), args: build_defaults.get_args(), extras, + + post_install, }; let canister_args: Option = canister_info.get_extra_optional("args")?; @@ -233,6 +239,10 @@ impl CanisterInfo { &self.packtool } + pub fn get_post_install(&self) -> &[String] { + &self.post_install + } + pub fn get_args(&self) -> &Option { &self.args } diff --git a/src/dfx/src/lib/operations/canister/deploy_canisters.rs b/src/dfx/src/lib/operations/canister/deploy_canisters.rs index 8ed39fb52e..0821f4f853 100644 --- a/src/dfx/src/lib/operations/canister/deploy_canisters.rs +++ b/src/dfx/src/lib/operations/canister/deploy_canisters.rs @@ -91,7 +91,7 @@ pub async fn deploy_canisters( ) .await?; - build_canisters(env, &canisters_to_build, &config)?; + let pool = build_canisters(env, &canisters_to_build, &config)?; install_canisters( env, @@ -104,6 +104,7 @@ pub async fn deploy_canisters( upgrade_unchanged, timeout, call_sender, + pool, ) .await?; @@ -193,12 +194,13 @@ async fn register_canisters( } #[context("Failed to build call canisters.")] -fn build_canisters(env: &dyn Environment, canister_names: &[String], config: &Config) -> DfxResult { +fn build_canisters(env: &dyn Environment, canister_names: &[String], config: &Config) -> DfxResult { info!(env.get_logger(), "Building canisters..."); let build_mode_check = false; let canister_pool = CanisterPool::load(env, build_mode_check, canister_names)?; - canister_pool.build_or_fail(&BuildConfig::from_config(config)?) + canister_pool.build_or_fail(&BuildConfig::from_config(config)?)?; + Ok(canister_pool) } #[allow(clippy::too_many_arguments)] @@ -214,6 +216,7 @@ async fn install_canisters( upgrade_unchanged: bool, timeout: Duration, call_sender: &CallSender, + pool: CanisterPool, ) -> DfxResult { info!(env.get_logger(), "Installing canisters..."); @@ -265,6 +268,7 @@ async fn install_canisters( call_sender, installed_module_hash, upgrade_unchanged, + Some(&pool), ) .await?; } diff --git a/src/dfx/src/lib/operations/canister/install_canister.rs b/src/dfx/src/lib/operations/canister/install_canister.rs index b0e99fc30a..87f56133bd 100644 --- a/src/dfx/src/lib/operations/canister/install_canister.rs +++ b/src/dfx/src/lib/operations/canister/install_canister.rs @@ -1,9 +1,11 @@ +use crate::lib::builders::environment_variables; use crate::lib::canister_info::CanisterInfo; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::identity::identity_utils::CallSender; use crate::lib::identity::Identity; use crate::lib::installers::assets::post_install_store_assets; +use crate::lib::models::canister::CanisterPool; use crate::lib::named_canister; use crate::lib::waiter::waiter_with_timeout; use crate::util::assets::wallet_wasm; @@ -17,10 +19,12 @@ use ic_utils::call::AsyncCall; use ic_utils::interfaces::management_canister::builders::{CanisterInstall, InstallMode}; use ic_utils::interfaces::ManagementCanister; use ic_utils::Argument; +use itertools::Itertools; use openssl::sha::Sha256; use slog::info; use std::collections::HashSet; use std::io::stdin; +use std::process::{Command, Stdio}; use std::time::Duration; #[allow(clippy::too_many_arguments)] @@ -35,6 +39,7 @@ pub async fn install_canister( call_sender: &CallSender, installed_module_hash: Option>, upgrade_unchanged: bool, + pool: Option<&CanisterPool>, ) -> DfxResult { let log = env.get_logger(); let network = env.get_network_descriptor(); @@ -159,6 +164,35 @@ pub async fn install_canister( post_install_store_assets(canister_info, agent, timeout).await?; } + if !canister_info.get_post_install().is_empty() { + let tmp; + let pool = match pool { + Some(pool) => pool, + None => { + tmp = env.get_config_or_anyhow()?.get_config().get_canister_names_with_dependencies(Some(canister_info.get_name())).and_then(|deps| CanisterPool::load(env, false, &deps)).context("Error collecting canisters for post-install task")?; + &tmp + } + }; + let dependencies = pool.get_canister_list().iter().map(|can| can.canister_id()).collect_vec(); + for task in canister_info.get_post_install() { + let words = shell_words::split(task).with_context(|| format!("Error interpreting post-install task `{task}`"))?; + let mut command = Command::new(&words[0]); + command.args(&words[1..]); + let vars = environment_variables(canister_info, &network.name, &pool, &dependencies); + for (key, val) in vars { + command.env(&*key, val); + } + command.stdin(Stdio::piped()).stdout(Stdio::inherit()).stderr(Stdio::inherit()); + let status = command.status().with_context(|| format!("Error running post-install task `{task}`"))?; + if !status.success() { + match status.code() { + Some(code) => bail!("The post-install task `{task}` failed with exit code {code}"), + None => bail!("The post-install task `{task}` was terminated by a signal"), + } + } + } + } + Ok(()) } diff --git a/src/dfx/src/util/mod.rs b/src/dfx/src/util/mod.rs index e08da25516..0c7ad5365d 100644 --- a/src/dfx/src/util/mod.rs +++ b/src/dfx/src/util/mod.rs @@ -8,6 +8,7 @@ use candid::{parser::value::IDLValue, IDLArgs}; use fn_error_context::context; use net2::TcpListenerExt; use net2::{unix::UnixTcpBuilderExt, TcpBuilder}; +use serde::{Serialize, Deserialize}; use std::net::{IpAddr, SocketAddr}; use std::time::Duration; @@ -220,3 +221,25 @@ pub fn blob_from_arguments( v => Err(error_unknown!("Invalid type: {}", v)), } } + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum SerdeVec { + One(T), + Many(Vec), +} + +impl SerdeVec { + pub fn into_vec(self) -> Vec { + match self { + Self::One(t) => vec![t], + Self::Many(ts) => ts, + } + } +} + +impl Default for SerdeVec { + fn default() -> Self { + Self::Many(vec![]) + } +} From a9a34217ab277d9ce99b9e14df9d290286c209d9 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Wed, 15 Jun 2022 14:12:48 -0700 Subject: [PATCH 02/14] Update changelog --- CHANGELOG.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index eac29d25dd..d4254365de 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -5,6 +5,10 @@ == DFX +=== feat: Post-installation tasks + +You can now add your own custom post-installation/post-deployment tasks to any canister type. The new `+post-install+` key for canister objects in `+dfx.json+` can be a command or list of commands, similar to the `+build+` key of `+custom+` canisters, and receives all the same environment variables. For example, to replicate the upload task performed with `+assets+` canisters, you might set `+"post-install": "icx-asset sync $CANISTER_ID dist"+`. + === feat: Support for new ledger notify function Ledger 7424ea8 deprecates the existing `+notify+` function with a switch parameter between creating and topping up a canister, and introduces two From bee3124d557ae6eeae89c0970dec1c816e0ac8b2 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Wed, 15 Jun 2022 14:15:42 -0700 Subject: [PATCH 03/14] fmt/lint --- src/dfx/src/lib/canister_info.rs | 2 +- .../operations/canister/deploy_canisters.rs | 6 +++- .../operations/canister/install_canister.rs | 31 ++++++++++++++----- src/dfx/src/util/mod.rs | 2 +- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/dfx/src/lib/canister_info.rs b/src/dfx/src/lib/canister_info.rs index 37171e90f8..b8d126b827 100644 --- a/src/dfx/src/lib/canister_info.rs +++ b/src/dfx/src/lib/canister_info.rs @@ -134,7 +134,7 @@ impl CanisterInfo { packtool: build_defaults.get_packtool(), args: build_defaults.get_args(), extras, - + post_install, }; diff --git a/src/dfx/src/lib/operations/canister/deploy_canisters.rs b/src/dfx/src/lib/operations/canister/deploy_canisters.rs index 0821f4f853..8171bf3bad 100644 --- a/src/dfx/src/lib/operations/canister/deploy_canisters.rs +++ b/src/dfx/src/lib/operations/canister/deploy_canisters.rs @@ -194,7 +194,11 @@ async fn register_canisters( } #[context("Failed to build call canisters.")] -fn build_canisters(env: &dyn Environment, canister_names: &[String], config: &Config) -> DfxResult { +fn build_canisters( + env: &dyn Environment, + canister_names: &[String], + config: &Config, +) -> DfxResult { info!(env.get_logger(), "Building canisters..."); let build_mode_check = false; let canister_pool = CanisterPool::load(env, build_mode_check, canister_names)?; diff --git a/src/dfx/src/lib/operations/canister/install_canister.rs b/src/dfx/src/lib/operations/canister/install_canister.rs index 87f56133bd..b718053a07 100644 --- a/src/dfx/src/lib/operations/canister/install_canister.rs +++ b/src/dfx/src/lib/operations/canister/install_canister.rs @@ -169,24 +169,41 @@ pub async fn install_canister( let pool = match pool { Some(pool) => pool, None => { - tmp = env.get_config_or_anyhow()?.get_config().get_canister_names_with_dependencies(Some(canister_info.get_name())).and_then(|deps| CanisterPool::load(env, false, &deps)).context("Error collecting canisters for post-install task")?; + tmp = env + .get_config_or_anyhow()? + .get_config() + .get_canister_names_with_dependencies(Some(canister_info.get_name())) + .and_then(|deps| CanisterPool::load(env, false, &deps)) + .context("Error collecting canisters for post-install task")?; &tmp } }; - let dependencies = pool.get_canister_list().iter().map(|can| can.canister_id()).collect_vec(); + let dependencies = pool + .get_canister_list() + .iter() + .map(|can| can.canister_id()) + .collect_vec(); for task in canister_info.get_post_install() { - let words = shell_words::split(task).with_context(|| format!("Error interpreting post-install task `{task}`"))?; + let words = shell_words::split(task) + .with_context(|| format!("Error interpreting post-install task `{task}`"))?; let mut command = Command::new(&words[0]); command.args(&words[1..]); - let vars = environment_variables(canister_info, &network.name, &pool, &dependencies); + let vars = environment_variables(canister_info, &network.name, pool, &dependencies); for (key, val) in vars { command.env(&*key, val); } - command.stdin(Stdio::piped()).stdout(Stdio::inherit()).stderr(Stdio::inherit()); - let status = command.status().with_context(|| format!("Error running post-install task `{task}`"))?; + command + .stdin(Stdio::piped()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + let status = command + .status() + .with_context(|| format!("Error running post-install task `{task}`"))?; if !status.success() { match status.code() { - Some(code) => bail!("The post-install task `{task}` failed with exit code {code}"), + Some(code) => { + bail!("The post-install task `{task}` failed with exit code {code}") + } None => bail!("The post-install task `{task}` was terminated by a signal"), } } diff --git a/src/dfx/src/util/mod.rs b/src/dfx/src/util/mod.rs index 0c7ad5365d..62f571c397 100644 --- a/src/dfx/src/util/mod.rs +++ b/src/dfx/src/util/mod.rs @@ -8,7 +8,7 @@ use candid::{parser::value::IDLValue, IDLArgs}; use fn_error_context::context; use net2::TcpListenerExt; use net2::{unix::UnixTcpBuilderExt, TcpBuilder}; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use std::net::{IpAddr, SocketAddr}; use std::time::Duration; From 13040e61a9062a5df759a10e11e35fe1229c27a3 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Wed, 15 Jun 2022 14:18:32 -0700 Subject: [PATCH 04/14] shellcheck --- e2e/tests-dfx/install.bash | 2 ++ 1 file changed, 2 insertions(+) diff --git a/e2e/tests-dfx/install.bash b/e2e/tests-dfx/install.bash index dea170d94e..a2928ccd2e 100644 --- a/e2e/tests-dfx/install.bash +++ b/e2e/tests-dfx/install.bash @@ -84,6 +84,7 @@ teardown() { @test "install runs post-install tasks" { dfx_start + # shellcheck disable=SC2094 cat <<<"$(jq '.canisters.e2e_project."post-install"="sh -c \"echo hello\""' dfx.json)" >dfx.json assert_command dfx canister create --all @@ -92,6 +93,7 @@ teardown() { assert_command dfx canister install --all assert_match hello + # shellcheck disable=SC2094 cat <<<"$(jq '.canisters.e2e_project."post-install"="sh -c \"return 1\""' dfx.json)" >dfx.json assert_command_fail dfx canister install --all } From 23af40cb563d65fc7160f0637e68174a0356ccad Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Thu, 16 Jun 2022 07:38:02 -0700 Subject: [PATCH 05/14] Switch from kebab-case to snake_case --- src/dfx/src/config/dfinity.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dfx/src/config/dfinity.rs b/src/dfx/src/config/dfinity.rs index 58b8aa4597..4ca73217c2 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -63,7 +63,6 @@ pub const DEFAULT_IC_GATEWAY_TRAILING_SLASH: &str = "https://ic0.app/"; /// It only contains a type; everything else should be infered using the /// CanisterInfo type. #[derive(Clone, Debug, Default, Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] pub struct ConfigCanistersCanister { pub r#type: Option, From 3a262b3a23705bf09ff1583b822d1c26ad15704d Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Thu, 16 Jun 2022 07:49:44 -0700 Subject: [PATCH 06/14] Refactor command-processing logic into own function --- .../operations/canister/install_canister.rs | 107 +++++++++++------- 1 file changed, 65 insertions(+), 42 deletions(-) diff --git a/src/dfx/src/lib/operations/canister/install_canister.rs b/src/dfx/src/lib/operations/canister/install_canister.rs index b718053a07..3c17d13804 100644 --- a/src/dfx/src/lib/operations/canister/install_canister.rs +++ b/src/dfx/src/lib/operations/canister/install_canister.rs @@ -7,6 +7,7 @@ use crate::lib::identity::Identity; use crate::lib::installers::assets::post_install_store_assets; use crate::lib::models::canister::CanisterPool; use crate::lib::named_canister; +use crate::lib::network::network_descriptor::NetworkDescriptor; use crate::lib::waiter::waiter_with_timeout; use crate::util::assets::wallet_wasm; use crate::util::{expiry_duration, read_module_metadata}; @@ -165,51 +166,73 @@ pub async fn install_canister( } if !canister_info.get_post_install().is_empty() { - let tmp; - let pool = match pool { - Some(pool) => pool, - None => { - tmp = env - .get_config_or_anyhow()? - .get_config() - .get_canister_names_with_dependencies(Some(canister_info.get_name())) - .and_then(|deps| CanisterPool::load(env, false, &deps)) - .context("Error collecting canisters for post-install task")?; - &tmp - } - }; - let dependencies = pool - .get_canister_list() - .iter() - .map(|can| can.canister_id()) - .collect_vec(); - for task in canister_info.get_post_install() { - let words = shell_words::split(task) - .with_context(|| format!("Error interpreting post-install task `{task}`"))?; - let mut command = Command::new(&words[0]); - command.args(&words[1..]); - let vars = environment_variables(canister_info, &network.name, pool, &dependencies); - for (key, val) in vars { - command.env(&*key, val); - } - command - .stdin(Stdio::piped()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()); - let status = command - .status() - .with_context(|| format!("Error running post-install task `{task}`"))?; - if !status.success() { - match status.code() { - Some(code) => { - bail!("The post-install task `{task}` failed with exit code {code}") - } - None => bail!("The post-install task `{task}` was terminated by a signal"), - } - } + run_post_install_tasks(env, canister_info, network, pool)?; + } + + Ok(()) +} + +#[context("Failed to run post-install tasks")] +fn run_post_install_tasks( + env: &dyn Environment, + canister: &CanisterInfo, + network: &NetworkDescriptor, + pool: Option<&CanisterPool>, +) -> DfxResult { + let tmp; + let pool = match pool { + Some(pool) => pool, + None => { + tmp = env + .get_config_or_anyhow()? + .get_config() + .get_canister_names_with_dependencies(Some(canister.get_name())) + .and_then(|deps| CanisterPool::load(env, false, &deps)) + .context("Error collecting canisters for post-install task")?; + &tmp } + }; + let dependencies = pool + .get_canister_list() + .iter() + .map(|can| can.canister_id()) + .collect_vec(); + for task in canister.get_post_install() { + run_post_install_task(env, canister, task, network, pool, &dependencies)?; } + Ok(()) +} +#[context("Failed to run post-install task {task}")] +fn run_post_install_task( + _: &dyn Environment, + canister: &CanisterInfo, + task: &str, + network: &NetworkDescriptor, + pool: &CanisterPool, + dependencies: &[Principal], +) -> DfxResult { + let words = shell_words::split(task) + .with_context(|| format!("Error interpreting post-install task `{task}`"))?; + let mut command = Command::new(&words[0]); + command.args(&words[1..]); + let vars = environment_variables(canister, &network.name, pool, dependencies); + for (key, val) in vars { + command.env(&*key, val); + } + command + .stdin(Stdio::piped()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + let status = command.status()?; + if !status.success() { + match status.code() { + Some(code) => { + bail!("The post-install task `{task}` failed with exit code {code}") + } + None => bail!("The post-install task `{task}` was terminated by a signal"), + } + } Ok(()) } From a88984064c0126faea86c0ea804f350164223221 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Thu, 16 Jun 2022 08:22:21 -0700 Subject: [PATCH 07/14] Update e2e test script --- e2e/tests-dfx/install.bash | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/e2e/tests-dfx/install.bash b/e2e/tests-dfx/install.bash index a2928ccd2e..7098e4b345 100644 --- a/e2e/tests-dfx/install.bash +++ b/e2e/tests-dfx/install.bash @@ -85,7 +85,7 @@ teardown() { @test "install runs post-install tasks" { dfx_start # shellcheck disable=SC2094 - cat <<<"$(jq '.canisters.e2e_project."post-install"="sh -c \"echo hello\""' dfx.json)" >dfx.json + cat <<<"$(jq '.canisters.e2e_project.post_install="sh -c \"echo hello\""' dfx.json)" >dfx.json assert_command dfx canister create --all assert_command dfx build @@ -94,6 +94,27 @@ teardown() { assert_match hello # shellcheck disable=SC2094 - cat <<<"$(jq '.canisters.e2e_project."post-install"="sh -c \"return 1\""' dfx.json)" >dfx.json - assert_command_fail dfx canister install --all + cat <<<"$(jq '.canisters.e2e_project.post_install=["sh -c \"echo hello\"", "sh -c \"return 1\""]' dfx.json)" >dfx.json + assert_command_fail dfx canister install --all --mode upgrade + assert_match hello } + +@test "post-install tasks receive environment variables" { + dfx_start + # shellcheck disable=SC2094 + cat <<<"$(jq '.canisters.e2e_project.post_install="sh -c \"echo hello $CANISTER_ID\""' dfx.json)" >dfx.json + + assert_command dfx canister create --all + assert_command dfx build + id=$(dfx canister id e2e_project) + + assert_command dfx canister install --all + assert_match "hello $id" + assert_command dfx canister install e2e_project --mode upgrade + assert_match "hello $id" + + assert_command dfx deploy + assert_match "hello $id" + assert_command dfx deploy e2e_project + assert_match "hello $id" +} \ No newline at end of file From 14c4290fb493ce56a6fbfde5617c91fbcf4eda60 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Thu, 16 Jun 2022 13:13:44 -0700 Subject: [PATCH 08/14] Add test for dependency variable --- e2e/tests-dfx/install.bash | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/e2e/tests-dfx/install.bash b/e2e/tests-dfx/install.bash index 7098e4b345..a6dc83a27c 100644 --- a/e2e/tests-dfx/install.bash +++ b/e2e/tests-dfx/install.bash @@ -117,4 +117,17 @@ teardown() { assert_match "hello $id" assert_command dfx deploy e2e_project assert_match "hello $id" +} + +@test "post-install tasks discover dependencies" { + dfx_start + # shellcheck disable=SC2094 + cat <<<"$(jq '.canisters.e2e_project_assets.post_install="sh -c \"echo hello $CANISTER_ID_e2e_project\""' dfx.json)" >dfx.json + + assert_command dfx canister create --all + assert_command dfx build + id=$(dfx canister id e2e_project) + + assert_command dfx canister install --all + assert_match "hello $id" } \ No newline at end of file From d7c68e188a341db0c23cd4bf998fe38223c7fd88 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 21 Jun 2022 15:17:46 -0700 Subject: [PATCH 09/14] switch to post-#2267 format & reorganize tests --- e2e/assets/post_install/dfx.json | 24 +++++++++++++++ e2e/assets/post_install/main.mo | 3 ++ e2e/assets/post_install/patch.bash | 2 ++ e2e/assets/post_install/postinstall.sh | 2 ++ e2e/tests-dfx/install.bash | 29 ++++++++++--------- .../operations/canister/install_canister.rs | 9 ++++-- 6 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 e2e/assets/post_install/dfx.json create mode 100644 e2e/assets/post_install/main.mo create mode 100644 e2e/assets/post_install/patch.bash create mode 100755 e2e/assets/post_install/postinstall.sh diff --git a/e2e/assets/post_install/dfx.json b/e2e/assets/post_install/dfx.json new file mode 100644 index 0000000000..f9d6ec5361 --- /dev/null +++ b/e2e/assets/post_install/dfx.json @@ -0,0 +1,24 @@ +{ + "version": 1, + "canisters": { + "postinstall": { + "main": "main.mo", + "post_install": "echo hello" + }, + "postinstall_script": { + "main": "main.mo", + "post_install": "postinstall.sh", + "dependencies": ["postinstall"] + } + }, + "defaults": { + "build": { + "output": "canisters/" + } + }, + "networks": { + "local": { + "bind": "127.0.0.1:8000" + } + } +} diff --git a/e2e/assets/post_install/main.mo b/e2e/assets/post_install/main.mo new file mode 100644 index 0000000000..ac7f33bbd5 --- /dev/null +++ b/e2e/assets/post_install/main.mo @@ -0,0 +1,3 @@ +actor { + +} diff --git a/e2e/assets/post_install/patch.bash b/e2e/assets/post_install/patch.bash new file mode 100644 index 0000000000..a8f7e08f60 --- /dev/null +++ b/e2e/assets/post_install/patch.bash @@ -0,0 +1,2 @@ +# Do nothing + diff --git a/e2e/assets/post_install/postinstall.sh b/e2e/assets/post_install/postinstall.sh new file mode 100755 index 0000000000..e1a69b7af5 --- /dev/null +++ b/e2e/assets/post_install/postinstall.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +echo hello diff --git a/e2e/tests-dfx/install.bash b/e2e/tests-dfx/install.bash index a6dc83a27c..e240e5c49d 100644 --- a/e2e/tests-dfx/install.bash +++ b/e2e/tests-dfx/install.bash @@ -83,50 +83,51 @@ teardown() { } @test "install runs post-install tasks" { + install_asset post_install dfx_start - # shellcheck disable=SC2094 - cat <<<"$(jq '.canisters.e2e_project.post_install="sh -c \"echo hello\""' dfx.json)" >dfx.json assert_command dfx canister create --all assert_command dfx build - assert_command dfx canister install --all + assert_command dfx canister install postinstall + assert_match hello + + assert_command dfx canister install postinstall_script assert_match hello - # shellcheck disable=SC2094 - cat <<<"$(jq '.canisters.e2e_project.post_install=["sh -c \"echo hello\"", "sh -c \"return 1\""]' dfx.json)" >dfx.json - assert_command_fail dfx canister install --all --mode upgrade + echo 'return 1' >> postinstall.sh + assert_command_fail dfx canister install postinstall_script --mode upgrade assert_match hello } @test "post-install tasks receive environment variables" { + install_asset post_install dfx_start - # shellcheck disable=SC2094 - cat <<<"$(jq '.canisters.e2e_project.post_install="sh -c \"echo hello $CANISTER_ID\""' dfx.json)" >dfx.json + echo 'echo $CANISTER_ID' >> postinstall.sh assert_command dfx canister create --all assert_command dfx build - id=$(dfx canister id e2e_project) + id=$(dfx canister id postinstall_script) assert_command dfx canister install --all assert_match "hello $id" - assert_command dfx canister install e2e_project --mode upgrade + assert_command dfx canister install postinstall_script --mode upgrade assert_match "hello $id" assert_command dfx deploy assert_match "hello $id" - assert_command dfx deploy e2e_project + assert_command dfx deploy postinstall_script assert_match "hello $id" } @test "post-install tasks discover dependencies" { + install_asset post_install dfx_start - # shellcheck disable=SC2094 - cat <<<"$(jq '.canisters.e2e_project_assets.post_install="sh -c \"echo hello $CANISTER_ID_e2e_project\""' dfx.json)" >dfx.json + echo 'echo hello $CANISTER_ID_postinstall' >> postinstall.sh assert_command dfx canister create --all assert_command dfx build - id=$(dfx canister id e2e_project) + id=$(dfx canister id postinstall) assert_command dfx canister install --all assert_match "hello $id" diff --git a/src/dfx/src/lib/operations/canister/install_canister.rs b/src/dfx/src/lib/operations/canister/install_canister.rs index 3c17d13804..e59be04743 100644 --- a/src/dfx/src/lib/operations/canister/install_canister.rs +++ b/src/dfx/src/lib/operations/canister/install_canister.rs @@ -212,16 +212,21 @@ fn run_post_install_task( pool: &CanisterPool, dependencies: &[Principal], ) -> DfxResult { + let cwd = canister.get_workspace_root(); let words = shell_words::split(task) .with_context(|| format!("Error interpreting post-install task `{task}`"))?; - let mut command = Command::new(&words[0]); + let canonicalized = cwd.join(&words[0]) + .canonicalize() + .or_else(|_| which::which(&words[0])) + .map_err(|_| anyhow!("Cannot find command or file {}", &words[0]))?; + let mut command = Command::new(&canonicalized); command.args(&words[1..]); let vars = environment_variables(canister, &network.name, pool, dependencies); for (key, val) in vars { command.env(&*key, val); } command - .stdin(Stdio::piped()) + .current_dir(cwd) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()); let status = command.status()?; From 16e9d8fafdb1342f7d64646eed3c1f9297438348 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Tue, 21 Jun 2022 15:48:28 -0700 Subject: [PATCH 10/14] shellcheck --- e2e/tests-dfx/install.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/tests-dfx/install.bash b/e2e/tests-dfx/install.bash index e240e5c49d..d799ac485f 100644 --- a/e2e/tests-dfx/install.bash +++ b/e2e/tests-dfx/install.bash @@ -103,7 +103,7 @@ teardown() { @test "post-install tasks receive environment variables" { install_asset post_install dfx_start - echo 'echo $CANISTER_ID' >> postinstall.sh + echo "echo \$CANISTER_ID" >> postinstall.sh assert_command dfx canister create --all assert_command dfx build @@ -123,7 +123,7 @@ teardown() { @test "post-install tasks discover dependencies" { install_asset post_install dfx_start - echo 'echo hello $CANISTER_ID_postinstall' >> postinstall.sh + echo "echo hello \$CANISTER_ID_postinstall" >> postinstall.sh assert_command dfx canister create --all assert_command dfx build From f33c864a087dfefae7251b38a02c79c867829fe4 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Wed, 22 Jun 2022 08:09:06 -0700 Subject: [PATCH 11/14] fmt --- src/dfx/src/lib/operations/canister/install_canister.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dfx/src/lib/operations/canister/install_canister.rs b/src/dfx/src/lib/operations/canister/install_canister.rs index e59be04743..a97dd8ff3b 100644 --- a/src/dfx/src/lib/operations/canister/install_canister.rs +++ b/src/dfx/src/lib/operations/canister/install_canister.rs @@ -215,7 +215,8 @@ fn run_post_install_task( let cwd = canister.get_workspace_root(); let words = shell_words::split(task) .with_context(|| format!("Error interpreting post-install task `{task}`"))?; - let canonicalized = cwd.join(&words[0]) + let canonicalized = cwd + .join(&words[0]) .canonicalize() .or_else(|_| which::which(&words[0])) .map_err(|_| anyhow!("Cannot find command or file {}", &words[0]))?; From 47991a03fc6ea004eb887fa2abe920989981fb78 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Mon, 27 Jun 2022 13:42:19 -0700 Subject: [PATCH 12/14] unused param --- src/dfx/src/lib/operations/canister/install_canister.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dfx/src/lib/operations/canister/install_canister.rs b/src/dfx/src/lib/operations/canister/install_canister.rs index a97dd8ff3b..1239947962 100644 --- a/src/dfx/src/lib/operations/canister/install_canister.rs +++ b/src/dfx/src/lib/operations/canister/install_canister.rs @@ -198,14 +198,13 @@ fn run_post_install_tasks( .map(|can| can.canister_id()) .collect_vec(); for task in canister.get_post_install() { - run_post_install_task(env, canister, task, network, pool, &dependencies)?; + run_post_install_task(canister, task, network, pool, &dependencies)?; } Ok(()) } #[context("Failed to run post-install task {task}")] fn run_post_install_task( - _: &dyn Environment, canister: &CanisterInfo, task: &str, network: &NetworkDescriptor, From 339f8dd02348745c908add940afdbf3dde5d123c Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Mon, 27 Jun 2022 13:53:36 -0700 Subject: [PATCH 13/14] Make two canisters distinguishable --- e2e/assets/post_install/dfx.json | 2 +- e2e/assets/post_install/postinstall.sh | 2 +- e2e/tests-dfx/install.bash | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/e2e/assets/post_install/dfx.json b/e2e/assets/post_install/dfx.json index f9d6ec5361..13f72c635f 100644 --- a/e2e/assets/post_install/dfx.json +++ b/e2e/assets/post_install/dfx.json @@ -3,7 +3,7 @@ "canisters": { "postinstall": { "main": "main.mo", - "post_install": "echo hello" + "post_install": "echo hello-file" }, "postinstall_script": { "main": "main.mo", diff --git a/e2e/assets/post_install/postinstall.sh b/e2e/assets/post_install/postinstall.sh index e1a69b7af5..b92917fe6e 100755 --- a/e2e/assets/post_install/postinstall.sh +++ b/e2e/assets/post_install/postinstall.sh @@ -1,2 +1,2 @@ #!/usr/bin/env bash -echo hello +echo hello-script diff --git a/e2e/tests-dfx/install.bash b/e2e/tests-dfx/install.bash index d799ac485f..c6cf07cfa3 100644 --- a/e2e/tests-dfx/install.bash +++ b/e2e/tests-dfx/install.bash @@ -90,20 +90,20 @@ teardown() { assert_command dfx build assert_command dfx canister install postinstall - assert_match hello + assert_match 'hello-file' assert_command dfx canister install postinstall_script - assert_match hello + assert_match 'hello-script' echo 'return 1' >> postinstall.sh assert_command_fail dfx canister install postinstall_script --mode upgrade - assert_match hello + assert_match 'hello-script' } @test "post-install tasks receive environment variables" { install_asset post_install dfx_start - echo "echo \$CANISTER_ID" >> postinstall.sh + echo "echo hello \$CANISTER_ID" >> postinstall.sh assert_command dfx canister create --all assert_command dfx build From 62b63b3d05bdb921a7b2a1eb02782e1b93f74410 Mon Sep 17 00:00:00 2001 From: Adam Spofford Date: Mon, 27 Jun 2022 15:01:08 -0700 Subject: [PATCH 14/14] clarify --- e2e/tests-dfx/install.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests-dfx/install.bash b/e2e/tests-dfx/install.bash index c6cf07cfa3..e3d0388269 100644 --- a/e2e/tests-dfx/install.bash +++ b/e2e/tests-dfx/install.bash @@ -129,6 +129,6 @@ teardown() { assert_command dfx build id=$(dfx canister id postinstall) - assert_command dfx canister install --all + assert_command dfx canister install postinstall_script assert_match "hello $id" } \ No newline at end of file