Packages
livebook
0.17.2
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook_cli/deploy.ex
defmodule LivebookCLI.Deploy do
import LivebookCLI.Utils
alias Livebook.Teams
@behaviour LivebookCLI.Task
@org_token_prefix Teams.Constants.org_token_prefix()
@teams_key_prefix Teams.Constants.teams_key_prefix()
@impl true
def usage() do
"""
Usage: livebook deploy [options] filename [filename...]
## Available options
--org-token Token from your Livebook Teams organization
--teams-key Teams key from your Teams workspace
--deployment-group-id The ID of the deployment group you want to deploy to
The --help option can be given to print this notice.
## Examples
Deploys a single notebook:
livebook deploy --org-token="lb_ot_..." --teams-key="lb_tk_..." --deployment-group-id=123 path/to/app1.livemd
Deploys multiple notebooks:
livebook deploy --org-token="lb_ot_..." --teams-key="lb_tk_..." --deployment-group-id=123 path/to/*.livemd\
"""
end
@switches [
org_token: :string,
teams_key: :string,
deployment_group_id: :integer
]
@impl true
def call(args) do
Application.put_env(:livebook, :persist_storage, false)
{:ok, _} = Application.ensure_all_started(:livebook)
config = config_from_args(args)
ensure_config!(config)
team = authenticate_cli!(config)
deploy_to_teams(team, config)
end
defp config_from_args(args) do
{opts, paths} = OptionParser.parse!(args, strict: @switches)
%{
paths: paths,
session_token: opts[:org_token],
teams_key: opts[:teams_key],
deployment_group_id: opts[:deployment_group_id]
}
end
defp ensure_config!(config) do
log_debug("Validating config from options...")
errors =
Enum.reduce(config, %{}, fn
{key, value}, acc when value in ["", nil] ->
add_error(acc, normalize_key(key), "can't be blank")
{:session_token, value}, acc ->
if not String.starts_with?(value, @org_token_prefix) do
add_error(
acc,
normalize_key(:session_token),
"must be a Livebook Teams organization token"
)
else
acc
end
{:teams_key, value}, acc ->
if not String.starts_with?(value, @teams_key_prefix) do
add_error(acc, normalize_key(:teams_key), "must be a Livebook Teams key")
else
acc
end
{:paths, values}, acc ->
Enum.reduce_while(values, acc, &validate_path/2)
_otherwise, acc ->
acc
end)
if Map.keys(errors) == [] do
:ok
else
raise LivebookCLI.Error, """
You configuration is invalid, make sure you are using the correct options for this task.
#{format_errors(errors, " * ")}\
"""
end
end
defp validate_path(value, acc) do
cond do
not File.exists?(value) ->
{:halt, add_error(acc, normalize_key(:paths), "must be a valid path")}
File.dir?(value) ->
{:halt, add_error(acc, normalize_key(:paths), "must be a file path")}
true ->
{:cont, acc}
end
end
defp authenticate_cli!(config) do
log_debug("Authenticating CLI...")
case Teams.fetch_cli_session(config) do
{:ok, team} ->
team
{:error, error} ->
raise LivebookCLI.Error, error
{:transport_error, error} ->
raise LivebookCLI.Error, error
end
end
defp deploy_to_teams(team, config) do
if length(config.paths) == 1 do
log_debug("Found 1 notebook")
else
log_debug("Found #{length(config.paths)} notebooks")
end
log_info("Deploying notebooks:")
deploy_results =
for path <- config.paths do
log_info(" * Preparing to deploy notebook #{Path.basename(path)}")
absolute_path = Path.expand(path)
files_dir = Livebook.FileSystem.File.local(absolute_path)
with {:ok, content} <- File.read(path),
{:ok, app_deployment} <- prepare_app_deployment(path, content, files_dir) do
case Livebook.Teams.deploy_app_from_cli(
team,
app_deployment,
config.deployment_group_id
) do
{:ok, url} ->
log_info([:green, " * #{app_deployment.title} deployed successfully. (#{url})"])
{:error, errors} ->
log_error(" * #{app_deployment.title} failed to deploy.")
error_message =
errors
|> normalize_errors
|> format_errors(" * ")
log_error(error_message)
:error
{:transport_error, reason} ->
log_error(
" * #{app_deployment.title} failed to deploy. Transport error: #{reason}"
)
:error
end
end
end
if Enum.any?(deploy_results, fn result -> result != :ok end) do
raise LivebookCLI.Error, "Some app deployments failed."
else
:ok
end
end
defp prepare_app_deployment(path, content, files_dir) do
case Livebook.Teams.AppDeployment.new(content, files_dir) do
{:ok, app_deployment} ->
{:ok, app_deployment}
{:warning, warnings} ->
error_message = """
* Deployment for notebook #{Path.basename(path)} failed because the notebook has some warnings:
#{format_list(warnings, " * ")}
"""
log_error(error_message)
:error
{:error, reason} ->
log_error(" * Failed to handle I/O operations: #{reason}")
:error
end
end
defp add_error(errors, key, message) do
Map.update(errors, key, [message], &[message | &1])
end
def normalize_errors(%{} = errors) do
for {key, values} <- errors, into: %{} do
{normalize_key(key), values}
end
end
defp normalize_key(key) when is_atom(key), do: to_string(key) |> normalize_key()
defp normalize_key("session_token"), do: "Token"
defp normalize_key("teams_key"), do: "Teams key"
defp normalize_key("deployment_group_id"), do: "Deployment group id"
defp normalize_key("paths"), do: "File paths"
defp format_errors(errors, prefix) do
errors
|> Enum.map(fn {key, values} ->
values |> Enum.map(&"#{prefix}#{key} #{&1}") |> Enum.join("\n")
end)
|> Enum.join("\n")
end
defp format_list(errors, prefix) do
errors |> Enum.map(&"#{prefix}#{&1}") |> Enum.join("\n")
end
end