From 1c7d66704ffccb9e87796060219dc018dff69573 Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Wed, 31 Oct 2018 15:15:56 -0700 Subject: [PATCH] Pipe node eval result to IO.inspect --- docs/extensibility/release_tasks.md | 123 ++++++++++++++++++ docs/tooling/cli.md | 3 +- lib/mix/lib/releases/runtime/control.ex | 12 +- mkdocs.yml | 1 + test/cases/cli_test.exs | 18 +++ .../files/eval_file_example_noout.exs | 5 + test/support/tasks.ex | 4 + 7 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 docs/extensibility/release_tasks.md create mode 100644 test/fixtures/files/eval_file_example_noout.exs diff --git a/docs/extensibility/release_tasks.md b/docs/extensibility/release_tasks.md new file mode 100644 index 00000000..9f36c672 --- /dev/null +++ b/docs/extensibility/release_tasks.md @@ -0,0 +1,123 @@ +Once your release is built, you can execute a number of helpful commands from the entry script. + +If you've built a release for `#!elixir :myapp`, you can run `bin/myapp` to see the full list of commands. + +## Release tasks + +| Task | Description | +|:--------------------|:--------------------------------------------------------------------------------| +| start | Start myapp as a daemon | +| start_boot | Start myapp as a daemon, but supply a custom .boot file | +| foreground | Start myapp in the foreground
This is similar to running `mix run --no-halt` | +| console | Start myapp with a console attached
This is similar to running `iex -S mix` | +| console_clean | Start a console with code paths set but no apps loaded/started | +| console_boot | Start myapp with a console attached, but supply a custom .boot file | +| stop | Stop the myapp daemon | +| restart | Restart the myapp daemon without shutting down the VM | +| reboot | Restart the myapp daemon | +| upgrade | Upgrade myapp to | +| downgrade | Downgrade myapp to | +| attach | Attach the current TTY to myapp's console | +| remote_console | Remote shell to myapp's console | +| reload_config | Reload the current system's configuration from disk | +| pid | Get the pid of the running myapp instance | +| ping | Checks if myapp is running, pong is returned if successful | +| pingpeer | Check if a peer node is running, pong is returned if successful | +| escript | Execute an escript | +| rpc | Execute Elixir code on the running node | +| eval | Execute Elixir code locally | +| describe | Print useful information about the myapp release | + +## Running code in releases + +Distillery comes with two very useful tasks for working with your release. + +* Both will take some Elixir code and run it for you and show you the result. +* Both tasks use the exact same syntax, the only difference is the context your code is run in. + +### Running code with `rpc` + +This task executes code on the running node, and is what you'd want to use to interact with your application when _it's already running_. + +!!! example "Using Module.fun/arity" + ```bash tab="Command" + $ ./bin/myapp rpc --mfa "Application.loaded_applications/0" + ``` + + ```elixir tab="Output" + [ + {:lz_string, + 'Elixir implementation of pieroxy\'s lz-string compression algorithm.', + '0.0.7'}, + {:phoenix_html, + ...snip + ``` + +??? example "Using Module.fun/arity with arguments" + ```bash tab="Commands" + $ ./bin/myapp rpc --mfa "Application.put_env/3" -- myapp token supersecret + $ ./bin/myapp rpc --mfa "Application.get_env/2" -- myapp token + ``` + + ```bash tab="Output" + $ ./bin/myapp rpc --mfa "Application.put_env/3" -- myapp token supersecret + :ok + $ ./bin/myapp rpc --mfa "Application.get_env/2" -- myapp token + "supersecret" + ``` + +??? example "Using Module.fun/1 with arguments as a single list" + Here, the `--argv` option can be used to construct a list before passing your arguments to the function specified. + + ```bash tab="Command" + $ ./bin/myapp rpc --mfa "Enum.join/1" --argv -- foo bar baz + ``` + + ```elixir tab="Output" + "foobarbaz" + ``` + +??? example "Using an expression, getting application version" + You can also use an expression, but you'll need to be mindful of shell quoting. + + ```bash tab="Command" + $ ./bin/myapp rpc 'Application.spec(:myapp, :vsn)' + ``` + + ```elixir tab="Output" + '0.0.1' + ``` + +??? example "Using an expression, broadcasting to a Phoenix channel" + ```bash + $ ./bin/myapp rpc 'MyappWeb.Endpoint.broadcast!("channel:lobby", "status", %{current_status: "oopsy"})' + ``` + +### Running code with `eval` + +This task executes code locally in a clean instance. Although execution will be within the context of your release, no applications will have been started. This is very useful for things like migrations, where you'll want to start only some applications (e.g. Ecto) manually before doing some work. + +!!! example "Using Module.fun/arity" + Assuming that you've created a `Myapp.ReleaseTasks` module, you can call it into like so: + ``` + $ ./bin/myapp eval --mfa 'Myapp.ReleaseTasks.migrate/0' + ``` + +??? example "Using Module.fun/arity with arguments" + Like with `rpc`, arguments can be specified (but are generally treated as strings). + + ```bash + $ ./bin/myapp eval --mfa 'File.touch/1' -- /tmp/foo + :ok + ``` + +??? example "Using an expression" + Like with `rpc`, an expression can be used. + + ```bash tab="Command" + $ ./bin/myapp eval 'Timex.Duration.now |> Timex.format_duration(:humanized)' + ``` + + ```elixir tab="Output" + "48 years, 10 months, 2 weeks, 2 days, 4 hours, 8 minutes, 52 seconds, 883.16 milliseconds" + ``` diff --git a/docs/tooling/cli.md b/docs/tooling/cli.md index becb7226..f837d8ec 100644 --- a/docs/tooling/cli.md +++ b/docs/tooling/cli.md @@ -30,7 +30,6 @@ entry point, i.e. the `bin/myapp` script. There are numerous tasks available, you have already seen a few of them: - * `foreground` - run the release in the foreground, like `mix run --no-halt` * `console` - run the release with a shell attached, like `iex -S mix` * `start` - run the release in the background @@ -40,4 +39,4 @@ There are a few other important tasks: * `remote_console` - attach a shell to a running release * `describe` - print metadata about the release -To see a full listing of tasks available, run `bin/myapp` with no arguments. +To see a full listing of tasks available, run `bin/myapp` with no arguments, or refer to the [Release Tasks](/extensibility/release_tasks.md) reference page. diff --git a/lib/mix/lib/releases/runtime/control.ex b/lib/mix/lib/releases/runtime/control.ex index bc9412bd..95f32842 100644 --- a/lib/mix/lib/releases/runtime/control.ex +++ b/lib/mix/lib/releases/runtime/control.ex @@ -675,7 +675,9 @@ defmodule Mix.Releases.Runtime.Control do Executes an expression or a file locally (i.e. not on the running node) """ def eval(_argv, %{file: file}) do - Code.eval_file(file) + file + |> Code.eval_file() + |> IO.inspect() rescue err in [Code.LoadError] -> Console.error(""" @@ -714,7 +716,9 @@ defmodule Mix.Releases.Runtime.Control do [argv] end - apply(module, fun, args) + module + |> apply(fun, args) + |> IO.inspect() {:ok, [_module, _fun, _arity]} when use_argv? -> Console.error(""" @@ -736,7 +740,9 @@ defmodule Mix.Releases.Runtime.Control do but the function has a different arity! """) else - apply(module, fun, args) + module + |> apply(fun, args) + |> IO.inspect() end {:ok, _parts} -> diff --git a/mkdocs.yml b/mkdocs.yml index 762c5ba4..5aab7533 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -60,6 +60,7 @@ nav: - 'Release Plugins': 'extensibility/release_plugins.md' - 'Config Providers': 'extensibility/config_providers.md' - 'Custom Commands': 'extensibility/custom_commands.md' + - 'Release Tasks': 'extensibility/release_tasks.md' - 'Overlays': 'extensibility/overlays.md' - 'Boot Hooks': 'extensibility/boot_hooks.md' - 'Shell Scripts': 'extensibility/shell_scripts.md' diff --git a/test/cases/cli_test.exs b/test/cases/cli_test.exs index aaec1568..06a2f388 100644 --- a/test/cases/cli_test.exs +++ b/test/cases/cli_test.exs @@ -63,6 +63,12 @@ defmodule Distillery.Test.CliTest do end) =~ "[\"foo\", \"bar\"]" end + test "eval --mfa --argv outputs result" do + assert is_success(fn -> + Control.main(["eval", "--mfa", "Enum.join/1", "--argv", "--", "foo", "bar"]) + end) =~ "foobar" + end + test "eval --mfa correct args" do assert is_success(fn -> Control.main(["eval", "--mfa", "Distillery.Test.Tasks.run/2", "--", "foo", "bar"]) @@ -75,12 +81,24 @@ defmodule Distillery.Test.CliTest do end) =~ "function has a different arity!" end + test "eval --mfa outputs result" do + assert is_success(fn -> + Control.main(["eval", "--mfa", "Distillery.Test.Tasks.eval/2", "--", "foo", "bar"]) + end) =~ "{\"foo\", \"bar\"}" + end + test "eval --file" do assert is_success(fn -> Control.main(["eval", "--file", Path.join([@fixtures_path, "files", "eval_file_example.exs"]) |> Path.expand]) end) =~ "ok from primary@127.0.0.1\n" end + test "eval --file outputs result" do + assert is_success(fn -> + Control.main(["eval", "--file", Path.join([@fixtures_path, "files", "eval_file_example_noout.exs"]) |> Path.expand]) + end) =~ "{{:ok, \"done\"}, []}\n" + end + test "eval syntax error produces friendly error" do assert is_failure(fn -> Control.main(["eval", "div(2, 0)"]) diff --git a/test/fixtures/files/eval_file_example_noout.exs b/test/fixtures/files/eval_file_example_noout.exs new file mode 100644 index 00000000..3a790ac6 --- /dev/null +++ b/test/fixtures/files/eval_file_example_noout.exs @@ -0,0 +1,5 @@ +defmodule FooTuple do + def do_something, do: {:ok, "done"} +end + +FooTuple.do_something diff --git a/test/support/tasks.ex b/test/support/tasks.ex index 785192ae..ee9fd457 100644 --- a/test/support/tasks.ex +++ b/test/support/tasks.ex @@ -8,4 +8,8 @@ defmodule Distillery.Test.Tasks do def run(arg1, arg2) do IO.inspect(arg1: arg1, arg2: arg2) end + + def eval(arg1, arg2) do + {arg1, arg2} + end end