From 12a8ea14af0b917df9008d8c5ee584dea7962266 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 6 Nov 2023 14:57:36 +0100 Subject: [PATCH] feat: Read install arg from file (#3431) # Description This PR adds support for providing install arguments from a file. This is motivated by these issues: * It is impossible to pass a large canister init arg as the length of shell arguments is limited by the terminal (which in turn may be limited by the operating system). * For shorter arguments, it is more convenient to write `--argument-file somefile.hex` than `--argument "$(cat somefile.hex)"`. * The `dfx canister call` method supports `--argument-file` for similar reasons. It would be nice to be consistent. --- CHANGELOG.md | 5 +++++ docs/cli-reference/dfx-canister.md | 1 + e2e/tests-dfx/install.bash | 27 +++++++++++++++++++++++- src/dfx/src/commands/canister/install.rs | 23 ++++++++++++++++++-- 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a496863420..55e6e868d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ # UNRELEASED +=== feat: Read dfx canister install argument from a file + +Enables passing large arguments that cannot be passed directly in the command line using the `--argument-file` flag. For example `dfx canister install --argument-file ./my/argument/file.txt my_canister_name`. + + ### feat: change `list_permitted` and `list_authorized` to an update call. This requires the `list_authorized` and `list_permitted` methods to be called as an update and disables the ability to diff --git a/docs/cli-reference/dfx-canister.md b/docs/cli-reference/dfx-canister.md index 555b83df78..ffc06eed56 100644 --- a/docs/cli-reference/dfx-canister.md +++ b/docs/cli-reference/dfx-canister.md @@ -438,6 +438,7 @@ You can use the following optional flags with the `dfx canister install` command | Flag | Description | |-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--argument-file` | Specifies the file from which to read the argument to pass to the init method. Stdin may be referred to as `-`. | | `--async-call` | Enables you to continue without waiting for the result of the installation to be returned by polling the Internet Computer or the local canister execution environment. | | `--upgrade-unchanged` | Upgrade the canister even if the .wasm did not change. | diff --git a/e2e/tests-dfx/install.bash b/e2e/tests-dfx/install.bash index 04fcb84fa9..5511205a07 100644 --- a/e2e/tests-dfx/install.bash +++ b/e2e/tests-dfx/install.bash @@ -182,7 +182,32 @@ teardown() { assert_contains db07e7e24f6f8ddf53c33a610713259a7c1eb71c270b819ebd311e2d223267f0 } +@test "installing one canister with an argument succeeds" { + dfx_start + assert_command dfx canister create e2e_project_backend + assert_command dfx build e2e_project_backend + assert_command dfx canister install e2e_project_backend --argument '()' +} + +@test "installing with an argument in a file succeeds" { + dfx_start + assert_command dfx canister create e2e_project_backend + assert_command dfx build e2e_project_backend + TMPFILE="$(mktemp)" + echo '()' >"$TMPFILE" + assert_command dfx canister install e2e_project_backend --argument-file "$TMPFILE" +} + +@test "installing with an argument on stdin succeeds" { + dfx_start + assert_command dfx canister create e2e_project_backend + assert_command dfx build e2e_project_backend + TMPFILE="$(mktemp)" + echo '()' >"$TMPFILE" + assert_command dfx canister install e2e_project_backend --argument-file - <"$TMPFILE" +} + @test "installing multiple canisters with arguments fails" { - assert_command_fail dfx canister install --all --argument hello + assert_command_fail dfx canister install --all --argument '()' assert_contains "error: the argument '--all' cannot be used with '--argument '" } diff --git a/src/dfx/src/commands/canister/install.rs b/src/dfx/src/commands/canister/install.rs index 2985cbb347..304c5eab69 100644 --- a/src/dfx/src/commands/canister/install.rs +++ b/src/dfx/src/commands/canister/install.rs @@ -3,8 +3,12 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::operations::canister::install_canister::install_canister; use crate::lib::root_key::fetch_root_key_if_needed; +use crate::util::clap::parsers::file_or_stdin_parser; use crate::util::get_candid_init_type; -use crate::{lib::canister_info::CanisterInfo, util::blob_from_arguments}; +use crate::{ + lib::canister_info::CanisterInfo, + util::{arguments_from_file, blob_from_arguments}, +}; use dfx_core::identity::CallSender; use anyhow::{anyhow, bail, Context}; @@ -40,9 +44,17 @@ pub struct CanisterInstallOpts { upgrade_unchanged: bool, /// Specifies the argument to pass to the method. - #[arg(long)] + #[arg(long, conflicts_with("argument_file"))] argument: Option, + /// Specifies the file from which to read the argument to pass to the method. + #[arg( + long, + value_parser = file_or_stdin_parser, + conflicts_with("argument") + )] + argument_file: Option, + /// Specifies the data type for the argument when making the call using an argument. #[arg(long, requires("argument"), value_parser = ["idl", "raw"])] argument_type: Option, @@ -107,7 +119,14 @@ pub async fn exec( let canister_id = Principal::from_text(canister).or_else(|_| canister_id_store.get(canister))?; + + let arguments_from_file = opts + .argument_file + .map(|v| arguments_from_file(&v)) + .transpose()?; let arguments = opts.argument.as_deref(); + let arguments = arguments_from_file.as_deref().or(arguments); + let arg_type = opts.argument_type.as_deref(); let canister_info = config.as_ref() .ok_or_else(|| anyhow!("Cannot find dfx configuration file in the current working directory. Did you forget to create one?"))