Pass notebook files when deploying apps (#2045)

This commit is contained in:
Jonatan Kłosko 2023-07-07 20:31:54 +02:00 committed by GitHub
parent f4365e44d2
commit d3f7cfa665
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 13 deletions

View file

@ -58,13 +58,17 @@ defmodule Livebook.App do
* `: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()}
def start_link(opts) do
notebook = Keyword.fetch!(opts, :notebook)
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
@doc """
@ -116,7 +120,8 @@ defmodule Livebook.App do
@spec deploy(pid(), Livebook.Notebook.t(), keyword()) :: :ok
def deploy(pid, notebook, opts \\ []) do
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
@doc """
@ -150,11 +155,12 @@ defmodule Livebook.App do
end
@impl true
def init({notebook, warnings}) do
def init({notebook, warnings, files_source}) do
{:ok,
%{
version: 1,
notebook: notebook,
files_source: files_source,
warnings: warnings,
sessions: [],
users: %{}
@ -191,11 +197,17 @@ defmodule Livebook.App do
end
@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
{: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()
|> shutdown_old_versions()
|> notify_update()}
@ -265,6 +277,7 @@ defmodule Livebook.App do
opts = [
notebook: state.notebook,
files_source: state.files_source,
mode: :app,
app_pid: self(),
auto_shutdown_ms: state.notebook.app_settings.auto_shutdown_ms,

View file

@ -21,10 +21,13 @@ defmodule Livebook.Apps do
* `: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()}
def deploy(notebook, opts \\ []) do
opts = Keyword.validate!(opts, warnings: [])
opts = Keyword.validate!(opts, warnings: [], files_source: nil)
slug = notebook.app_settings.slug
name = name(slug)
@ -34,25 +37,29 @@ defmodule Livebook.Apps do
:global.trans({{:app_registration, name}, node()}, fn ->
case :global.whereis_name(name) do
: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)
{:ok, pid}
end
pid ->
App.deploy(pid, notebook, warnings: opts[:warnings])
App.deploy(pid, notebook,
warnings: opts[:warnings],
files_source: opts[:files_source]
)
{:ok, pid}
end
end)
pid ->
App.deploy(pid, notebook, warnings: opts[:warnings])
App.deploy(pid, notebook, warnings: opts[:warnings], files_source: opts[:files_source])
{:ok, pid}
end
end
defp start_app(notebook, warnings) do
opts = [notebook: notebook, warnings: warnings]
defp start_app(notebook, warnings, files_source) do
opts = [notebook: notebook, warnings: warnings, files_source: files_source]
case DynamicSupervisor.start_child(Livebook.AppSupervisor, {App, opts}) do
{:ok, pid} ->
@ -191,7 +198,9 @@ defmodule Livebook.Apps do
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
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
Logger.warning(
"Skipping app deployment at #{path}. The notebook is not verified to come from hub #{apps_path_hub_id}"

View file

@ -1288,7 +1288,8 @@ defmodule Livebook.Session do
# In the initial state app settings are empty, hence not valid,
# so we double-check that we can actually deploy
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
Process.demonitor(ref, [:flush])

View file

@ -160,5 +160,34 @@ defmodule Livebook.AppsTest do
Livebook.App.close(app.pid)
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

View file

@ -1277,6 +1277,34 @@ defmodule Livebook.SessionTest do
assert_receive {:operation, {:set_deployed_app_slug, _client_id, nil}}
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
describe "apps" do