mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-10-09 13:07:37 +08:00
Pass notebook files when deploying apps (#2045)
This commit is contained in:
parent
f4365e44d2
commit
d3f7cfa665
5 changed files with 93 additions and 13 deletions
|
@ -58,13 +58,17 @@ defmodule Livebook.App do
|
||||||
|
|
||||||
* `:warnings` - a list of warnings to show for the initial deployment
|
* `:warnings` - a list of warnings to show for the initial deployment
|
||||||
|
|
||||||
|
* `:files_source` - a location to fetch notebook files from, see
|
||||||
|
`Livebook.Session.start_link/1` for more details
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec start_link(keyword()) :: {:ok, pid} | {:error, any()}
|
@spec start_link(keyword()) :: {:ok, pid} | {:error, any()}
|
||||||
def start_link(opts) do
|
def start_link(opts) do
|
||||||
notebook = Keyword.fetch!(opts, :notebook)
|
notebook = Keyword.fetch!(opts, :notebook)
|
||||||
warnings = Keyword.get(opts, :warnings, [])
|
warnings = Keyword.get(opts, :warnings, [])
|
||||||
|
files_source = Keyword.get(opts, :files_source)
|
||||||
|
|
||||||
GenServer.start_link(__MODULE__, {notebook, warnings})
|
GenServer.start_link(__MODULE__, {notebook, warnings, files_source})
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -116,7 +120,8 @@ defmodule Livebook.App do
|
||||||
@spec deploy(pid(), Livebook.Notebook.t(), keyword()) :: :ok
|
@spec deploy(pid(), Livebook.Notebook.t(), keyword()) :: :ok
|
||||||
def deploy(pid, notebook, opts \\ []) do
|
def deploy(pid, notebook, opts \\ []) do
|
||||||
warnings = Keyword.get(opts, :warnings, [])
|
warnings = Keyword.get(opts, :warnings, [])
|
||||||
GenServer.cast(pid, {:deploy, notebook, warnings})
|
files_source = Keyword.get(opts, :files_source)
|
||||||
|
GenServer.cast(pid, {:deploy, notebook, warnings, files_source})
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -150,11 +155,12 @@ defmodule Livebook.App do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init({notebook, warnings}) do
|
def init({notebook, warnings, files_source}) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
version: 1,
|
version: 1,
|
||||||
notebook: notebook,
|
notebook: notebook,
|
||||||
|
files_source: files_source,
|
||||||
warnings: warnings,
|
warnings: warnings,
|
||||||
sessions: [],
|
sessions: [],
|
||||||
users: %{}
|
users: %{}
|
||||||
|
@ -191,11 +197,17 @@ defmodule Livebook.App do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_cast({:deploy, notebook, warnings}, state) do
|
def handle_cast({:deploy, notebook, warnings, files_source}, state) do
|
||||||
true = notebook.app_settings.slug == state.notebook.app_settings.slug
|
true = notebook.app_settings.slug == state.notebook.app_settings.slug
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
%{state | notebook: notebook, version: state.version + 1, warnings: warnings}
|
%{
|
||||||
|
state
|
||||||
|
| notebook: notebook,
|
||||||
|
version: state.version + 1,
|
||||||
|
warnings: warnings,
|
||||||
|
files_source: files_source
|
||||||
|
}
|
||||||
|> start_eagerly()
|
|> start_eagerly()
|
||||||
|> shutdown_old_versions()
|
|> shutdown_old_versions()
|
||||||
|> notify_update()}
|
|> notify_update()}
|
||||||
|
@ -265,6 +277,7 @@ defmodule Livebook.App do
|
||||||
|
|
||||||
opts = [
|
opts = [
|
||||||
notebook: state.notebook,
|
notebook: state.notebook,
|
||||||
|
files_source: state.files_source,
|
||||||
mode: :app,
|
mode: :app,
|
||||||
app_pid: self(),
|
app_pid: self(),
|
||||||
auto_shutdown_ms: state.notebook.app_settings.auto_shutdown_ms,
|
auto_shutdown_ms: state.notebook.app_settings.auto_shutdown_ms,
|
||||||
|
|
|
@ -21,10 +21,13 @@ defmodule Livebook.Apps do
|
||||||
|
|
||||||
* `:warnings` - a list of warnings to show for the new deployment
|
* `:warnings` - a list of warnings to show for the new deployment
|
||||||
|
|
||||||
|
* `:files_source` - a location to fetch notebook files from, see
|
||||||
|
`Livebook.Session.start_link/1` for more details
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec deploy(Livebook.Notebook.t(), keyword()) :: {:ok, pid()} | {:error, term()}
|
@spec deploy(Livebook.Notebook.t(), keyword()) :: {:ok, pid()} | {:error, term()}
|
||||||
def deploy(notebook, opts \\ []) do
|
def deploy(notebook, opts \\ []) do
|
||||||
opts = Keyword.validate!(opts, warnings: [])
|
opts = Keyword.validate!(opts, warnings: [], files_source: nil)
|
||||||
|
|
||||||
slug = notebook.app_settings.slug
|
slug = notebook.app_settings.slug
|
||||||
name = name(slug)
|
name = name(slug)
|
||||||
|
@ -34,25 +37,29 @@ defmodule Livebook.Apps do
|
||||||
:global.trans({{:app_registration, name}, node()}, fn ->
|
:global.trans({{:app_registration, name}, node()}, fn ->
|
||||||
case :global.whereis_name(name) do
|
case :global.whereis_name(name) do
|
||||||
:undefined ->
|
:undefined ->
|
||||||
with {:ok, pid} <- start_app(notebook, opts[:warnings]) do
|
with {:ok, pid} <- start_app(notebook, opts[:warnings], opts[:files_source]) do
|
||||||
:yes = :global.register_name(name, pid)
|
:yes = :global.register_name(name, pid)
|
||||||
{:ok, pid}
|
{:ok, pid}
|
||||||
end
|
end
|
||||||
|
|
||||||
pid ->
|
pid ->
|
||||||
App.deploy(pid, notebook, warnings: opts[:warnings])
|
App.deploy(pid, notebook,
|
||||||
|
warnings: opts[:warnings],
|
||||||
|
files_source: opts[:files_source]
|
||||||
|
)
|
||||||
|
|
||||||
{:ok, pid}
|
{:ok, pid}
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
pid ->
|
pid ->
|
||||||
App.deploy(pid, notebook, warnings: opts[:warnings])
|
App.deploy(pid, notebook, warnings: opts[:warnings], files_source: opts[:files_source])
|
||||||
{:ok, pid}
|
{:ok, pid}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp start_app(notebook, warnings) do
|
defp start_app(notebook, warnings, files_source) do
|
||||||
opts = [notebook: notebook, warnings: warnings]
|
opts = [notebook: notebook, warnings: warnings, files_source: files_source]
|
||||||
|
|
||||||
case DynamicSupervisor.start_child(Livebook.AppSupervisor, {App, opts}) do
|
case DynamicSupervisor.start_child(Livebook.AppSupervisor, {App, opts}) do
|
||||||
{:ok, pid} ->
|
{:ok, pid} ->
|
||||||
|
@ -191,7 +198,9 @@ defmodule Livebook.Apps do
|
||||||
apps_path_hub_id = Livebook.Config.apps_path_hub_id()
|
apps_path_hub_id = Livebook.Config.apps_path_hub_id()
|
||||||
|
|
||||||
if apps_path_hub_id == nil or apps_path_hub_id == verified_hub_id do
|
if apps_path_hub_id == nil or apps_path_hub_id == verified_hub_id do
|
||||||
deploy(notebook, warnings: warnings)
|
notebook_file = Livebook.FileSystem.File.local(path)
|
||||||
|
files_dir = Livebook.Session.files_dir_for_notebook(notebook_file)
|
||||||
|
deploy(notebook, warnings: warnings, files_source: {:dir, files_dir})
|
||||||
else
|
else
|
||||||
Logger.warning(
|
Logger.warning(
|
||||||
"Skipping app deployment at #{path}. The notebook is not verified to come from hub #{apps_path_hub_id}"
|
"Skipping app deployment at #{path}. The notebook is not verified to come from hub #{apps_path_hub_id}"
|
||||||
|
|
|
@ -1288,7 +1288,8 @@ defmodule Livebook.Session do
|
||||||
# In the initial state app settings are empty, hence not valid,
|
# In the initial state app settings are empty, hence not valid,
|
||||||
# so we double-check that we can actually deploy
|
# so we double-check that we can actually deploy
|
||||||
if Notebook.AppSettings.valid?(state.data.notebook.app_settings) do
|
if Notebook.AppSettings.valid?(state.data.notebook.app_settings) do
|
||||||
{:ok, pid} = Livebook.Apps.deploy(state.data.notebook)
|
files_dir = files_dir_from_state(state)
|
||||||
|
{:ok, pid} = Livebook.Apps.deploy(state.data.notebook, files_source: {:dir, files_dir})
|
||||||
|
|
||||||
if ref = state.deployed_app_monitor_ref do
|
if ref = state.deployed_app_monitor_ref do
|
||||||
Process.demonitor(ref, [:flush])
|
Process.demonitor(ref, [:flush])
|
||||||
|
|
|
@ -160,5 +160,34 @@ defmodule Livebook.AppsTest do
|
||||||
|
|
||||||
Livebook.App.close(app.pid)
|
Livebook.App.close(app.pid)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :tmp_dir
|
||||||
|
test "deploys notebook with attachment files", %{tmp_dir: tmp_dir} do
|
||||||
|
app_path = Path.join(tmp_dir, "app.livemd")
|
||||||
|
files_path = Path.join(tmp_dir, "files")
|
||||||
|
File.mkdir_p!(files_path)
|
||||||
|
image_path = Path.join(files_path, "image.jpg")
|
||||||
|
File.write!(image_path, "content")
|
||||||
|
|
||||||
|
File.write!(app_path, """
|
||||||
|
<!-- livebook:{"app_settings":{"slug":"app"},"file_entries":[{"name":"image.jpg","type":"attachment"}]} -->
|
||||||
|
|
||||||
|
# App
|
||||||
|
""")
|
||||||
|
|
||||||
|
Livebook.Apps.subscribe()
|
||||||
|
|
||||||
|
Livebook.Apps.deploy_apps_in_dir(tmp_dir)
|
||||||
|
|
||||||
|
assert_receive {:app_created, %{slug: "app"} = app}
|
||||||
|
|
||||||
|
session_id = Livebook.App.get_session_id(app.pid)
|
||||||
|
{:ok, session} = Livebook.Sessions.fetch_session(session_id)
|
||||||
|
|
||||||
|
assert Livebook.FileSystem.File.resolve(session.files_dir, "image.jpg")
|
||||||
|
|> Livebook.FileSystem.File.read() == {:ok, "content"}
|
||||||
|
|
||||||
|
Livebook.App.close(app.pid)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1277,6 +1277,34 @@ defmodule Livebook.SessionTest do
|
||||||
|
|
||||||
assert_receive {:operation, {:set_deployed_app_slug, _client_id, nil}}
|
assert_receive {:operation, {:set_deployed_app_slug, _client_id, nil}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "deploys notebook with attachment files" do
|
||||||
|
session = start_session()
|
||||||
|
|
||||||
|
%{files_dir: files_dir} = session
|
||||||
|
image_file = FileSystem.File.resolve(files_dir, "image.jpg")
|
||||||
|
:ok = FileSystem.File.write(image_file, "content")
|
||||||
|
Session.add_file_entries(session.pid, [%{type: :attachment, name: "image.jpg"}])
|
||||||
|
|
||||||
|
Session.subscribe(session.id)
|
||||||
|
|
||||||
|
slug = Utils.random_short_id()
|
||||||
|
app_settings = %{Notebook.AppSettings.new() | slug: slug}
|
||||||
|
Session.set_app_settings(session.pid, app_settings)
|
||||||
|
|
||||||
|
Apps.subscribe()
|
||||||
|
|
||||||
|
Session.deploy_app(session.pid)
|
||||||
|
assert_receive {:app_created, %{slug: ^slug, pid: app_pid}}
|
||||||
|
|
||||||
|
session_id = App.get_session_id(app_pid)
|
||||||
|
{:ok, session} = Livebook.Sessions.fetch_session(session_id)
|
||||||
|
|
||||||
|
assert FileSystem.File.resolve(session.files_dir, "image.jpg")
|
||||||
|
|> FileSystem.File.read() == {:ok, "content"}
|
||||||
|
|
||||||
|
App.close(app_pid)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "apps" do
|
describe "apps" do
|
||||||
|
|
Loading…
Add table
Reference in a new issue