Skip to content

Commit

Permalink
Pipe node eval result to IO.inspect
Browse files Browse the repository at this point in the history
  • Loading branch information
Robert Adams committed Nov 1, 2018
1 parent 0f162e3 commit 7a6ae1e
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 5 deletions.
123 changes: 123 additions & 0 deletions docs/extensibility/release_tasks.md
Original file line number Diff line number Diff line change
@@ -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 <file> | Start myapp as a daemon, but supply a custom .boot file |
| foreground | Start myapp in the foreground<br>This is similar to running `mix run --no-halt` |
| console | Start myapp with a console attached<br>This is similar to running `iex -S mix` |
| console_clean | Start a console with code paths set but no apps loaded/started |
| console_boot <file> | 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 <version> | Upgrade myapp to <version> |
| downgrade <version> | Downgrade myapp to <version> |
| 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 <peer> | 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"
```
3 changes: 1 addition & 2 deletions docs/tooling/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
12 changes: 9 additions & 3 deletions lib/mix/lib/releases/runtime/control.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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("""
Expand Down Expand Up @@ -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("""
Expand All @@ -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} ->
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
18 changes: 18 additions & 0 deletions test/cases/cli_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand All @@ -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 [email protected]\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)"])
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/files/eval_file_example_noout.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule FooTuple do
def do_something, do: {:ok, "done"}
end

FooTuple.do_something
4 changes: 4 additions & 0 deletions test/support/tasks.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 7a6ae1e

Please sign in to comment.