mirror of
https://github.com/livebook-dev/livebook.git
synced 2024-09-20 10:05:57 +08:00
Deployment group for app deployment (#2410)
Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com> Co-authored-by: José Valim <jose.valim@gmail.com>
This commit is contained in:
parent
8b2add1e6c
commit
37c7444328
|
@ -277,4 +277,6 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Personal do
|
|||
:ok = Personal.remove_file_system(file_system.id)
|
||||
:ok = Broadcasts.file_system_deleted(file_system)
|
||||
end
|
||||
|
||||
def deployment_groups(_personal), do: nil
|
||||
end
|
||||
|
|
|
@ -138,4 +138,13 @@ defprotocol Livebook.Hubs.Provider do
|
|||
"""
|
||||
@spec delete_file_system(t(), FileSystem.t()) :: :ok | {:transport_error, String.t()}
|
||||
def delete_file_system(hub, file_system)
|
||||
|
||||
@doc """
|
||||
Get the deployment groups for a given hub.
|
||||
|
||||
Returns `nil` if deployment groups are not applicable to this hub.
|
||||
"""
|
||||
@spec deployment_groups(t()) ::
|
||||
list(%{id: String.t(), name: String.t(), secrets: list(Secret.t())}) | nil
|
||||
def deployment_groups(hub)
|
||||
end
|
||||
|
|
|
@ -280,6 +280,8 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Team do
|
|||
end
|
||||
end
|
||||
|
||||
def deployment_groups(team), do: TeamClient.get_deployment_groups(team.id)
|
||||
|
||||
defp add_secret_errors(%Secret{} = secret, errors_map) do
|
||||
Requests.add_errors(secret, errors_map)
|
||||
end
|
||||
|
|
|
@ -102,7 +102,14 @@ defmodule Livebook.LiveMarkdown.Export do
|
|||
end
|
||||
|
||||
defp notebook_metadata(notebook) do
|
||||
keys = [:persist_outputs, :autosave_interval_s, :default_language, :hub_id]
|
||||
keys = [
|
||||
:persist_outputs,
|
||||
:autosave_interval_s,
|
||||
:default_language,
|
||||
:hub_id,
|
||||
:deployment_group_id
|
||||
]
|
||||
|
||||
metadata = put_unless_default(%{}, Map.take(notebook, keys), Map.take(Notebook.new(), keys))
|
||||
|
||||
app_settings_metadata = app_settings_metadata(notebook.app_settings)
|
||||
|
|
|
@ -420,6 +420,9 @@ defmodule Livebook.LiveMarkdown.Import do
|
|||
{"hub_id", hub_id}, {attrs, messages} ->
|
||||
{Map.put(attrs, :hub_id, hub_id), messages}
|
||||
|
||||
{"deployment_group_id", deployment_group_id}, {attrs, messages} ->
|
||||
{Map.put(attrs, :deployment_group_id, deployment_group_id), messages}
|
||||
|
||||
{"app_settings", app_settings_metadata}, {attrs, messages} ->
|
||||
app_settings =
|
||||
Map.merge(
|
||||
|
|
|
@ -25,7 +25,8 @@ defmodule Livebook.Notebook do
|
|||
:hub_secret_names,
|
||||
:file_entries,
|
||||
:quarantine_file_entry_names,
|
||||
:teams_enabled
|
||||
:teams_enabled,
|
||||
:deployment_group_id
|
||||
]
|
||||
|
||||
alias Livebook.Notebook.{Section, Cell, AppSettings}
|
||||
|
@ -46,7 +47,8 @@ defmodule Livebook.Notebook do
|
|||
hub_secret_names: list(String.t()),
|
||||
file_entries: list(file_entry()),
|
||||
quarantine_file_entry_names: MapSet.new(String.t()),
|
||||
teams_enabled: boolean()
|
||||
teams_enabled: boolean(),
|
||||
deployment_group_id: String.t() | nil
|
||||
}
|
||||
|
||||
@typedoc """
|
||||
|
@ -110,7 +112,8 @@ defmodule Livebook.Notebook do
|
|||
hub_secret_names: [],
|
||||
file_entries: [],
|
||||
quarantine_file_entry_names: MapSet.new(),
|
||||
teams_enabled: false
|
||||
teams_enabled: false,
|
||||
deployment_group_id: nil
|
||||
}
|
||||
|> put_setup_cell(Cell.new(:code))
|
||||
end
|
||||
|
|
|
@ -606,6 +606,14 @@ defmodule Livebook.Session do
|
|||
GenServer.cast(pid, {:set_notebook_hub, self(), id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends a deployment group selection request to the server.
|
||||
"""
|
||||
@spec set_notebook_deployment_group(pid(), String.t()) :: :ok
|
||||
def set_notebook_deployment_group(pid, id) do
|
||||
GenServer.cast(pid, {:set_notebook_deployment_group, self(), id})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends a file entries addition request to the server.
|
||||
|
||||
|
@ -1393,6 +1401,12 @@ defmodule Livebook.Session do
|
|||
{:noreply, handle_operation(state, operation)}
|
||||
end
|
||||
|
||||
def handle_cast({:set_notebook_deployment_group, client_pid, id}, state) do
|
||||
client_id = client_id(state, client_pid)
|
||||
operation = {:set_notebook_deployment_group, client_id, id}
|
||||
{:noreply, handle_operation(state, operation)}
|
||||
end
|
||||
|
||||
def handle_cast({:add_file_entries, client_pid, file_entries}, state) do
|
||||
client_id = client_id(state, client_pid)
|
||||
operation = {:add_file_entries, client_id, file_entries}
|
||||
|
|
|
@ -227,6 +227,7 @@ defmodule Livebook.Session.Data do
|
|||
| {:set_deployed_app_slug, client_id(), String.t()}
|
||||
| {:app_deactivate, client_id()}
|
||||
| {:app_shutdown, client_id()}
|
||||
| {:set_notebook_deployment_group, String.t()}
|
||||
|
||||
@type action ::
|
||||
:connect_runtime
|
||||
|
@ -905,11 +906,20 @@ defmodule Livebook.Session.Data do
|
|||
|> with_actions()
|
||||
|> set_notebook_hub(hub)
|
||||
|> update_notebook_hub_secret_names()
|
||||
|> set_notebook_deployment_group(nil)
|
||||
|> set_dirty()
|
||||
|> wrap_ok()
|
||||
end
|
||||
end
|
||||
|
||||
def apply_operation(data, {:set_notebook_deployment_group, _client_id, id}) do
|
||||
data
|
||||
|> with_actions()
|
||||
|> set_notebook_deployment_group(id)
|
||||
|> set_dirty()
|
||||
|> wrap_ok()
|
||||
end
|
||||
|
||||
def apply_operation(data, {:sync_hub_secrets, _client_id}) do
|
||||
data
|
||||
|> with_actions()
|
||||
|
@ -1714,6 +1724,10 @@ defmodule Livebook.Session.Data do
|
|||
)
|
||||
end
|
||||
|
||||
defp set_notebook_deployment_group({data, _} = data_actions, id) do
|
||||
set!(data_actions, notebook: %{data.notebook | deployment_group_id: id})
|
||||
end
|
||||
|
||||
defp sync_hub_secrets({data, _} = data_actions) do
|
||||
hub = Livebook.Hubs.fetch_hub!(data.notebook.hub_id)
|
||||
secrets = Livebook.Hubs.get_secrets(hub)
|
||||
|
|
|
@ -502,6 +502,7 @@ defmodule LivebookWeb.SessionLive do
|
|||
secrets={@data_view.secrets}
|
||||
file_entries={@data_view.file_entries}
|
||||
settings={@data_view.app_settings}
|
||||
deployment_group_id={@data_view.deployment_group_id}
|
||||
/>
|
||||
</.modal>
|
||||
|
||||
|
@ -2671,7 +2672,8 @@ defmodule LivebookWeb.SessionLive do
|
|||
file_entries: Enum.sort_by(data.notebook.file_entries, & &1.name),
|
||||
quarantine_file_entry_names: data.notebook.quarantine_file_entry_names,
|
||||
app_settings: data.notebook.app_settings,
|
||||
deployed_app_slug: data.deployed_app_slug
|
||||
deployed_app_slug: data.deployed_app_slug,
|
||||
deployment_group_id: data.notebook.deployment_group_id
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -6,17 +6,21 @@ defmodule LivebookWeb.SessionLive.AppDockerComponent do
|
|||
alias Livebook.Hubs
|
||||
alias Livebook.FileSystem
|
||||
alias LivebookWeb.AppHelpers
|
||||
alias Livebook.Hubs.Provider
|
||||
|
||||
@impl true
|
||||
def update(assigns, socket) do
|
||||
socket = assign(socket, assigns)
|
||||
deployment_groups = Provider.deployment_groups(assigns.hub)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(settings_valid?: Livebook.Notebook.AppSettings.valid?(socket.assigns.settings))
|
||||
|> assign(
|
||||
hub_secrets: Hubs.get_secrets(assigns.hub),
|
||||
hub_file_systems: Hubs.get_file_systems(assigns.hub, hub_only: true)
|
||||
hub_file_systems: Hubs.get_file_systems(assigns.hub, hub_only: true),
|
||||
deployment_groups: deployment_groups,
|
||||
deployment_group_form: %{"id" => assigns.deployment_group_id}
|
||||
)
|
||||
|> assign_new(:changeset, fn -> Hubs.Dockerfile.config_changeset() end)
|
||||
|> assign_new(:save_result, fn -> nil end)
|
||||
|
@ -34,6 +38,8 @@ defmodule LivebookWeb.SessionLive.AppDockerComponent do
|
|||
file={@file}
|
||||
settings_valid?={@settings_valid?}
|
||||
hub={@hub}
|
||||
deployment_groups={@deployment_groups}
|
||||
deployment_group_form={@deployment_group_form}
|
||||
changeset={@changeset}
|
||||
session={@session}
|
||||
dockerfile={@dockerfile}
|
||||
|
@ -80,13 +86,42 @@ defmodule LivebookWeb.SessionLive.AppDockerComponent do
|
|||
You can deploy this app in the cloud using Docker. To do that, configure
|
||||
the deployment and then use the generated Dockerfile.
|
||||
</p>
|
||||
<p class="text-gray-700">
|
||||
<.label>Hub</.label>
|
||||
<span>
|
||||
<span class="text-lg"><%= @hub.hub_emoji %></span>
|
||||
<span><%= @hub.hub_name %></span>
|
||||
</span>
|
||||
</p>
|
||||
<div class="flex gap-12">
|
||||
<p class="text-gray-700">
|
||||
<.label>Hub</.label>
|
||||
<span>
|
||||
<span class="text-lg"><%= @hub.hub_emoji %></span>
|
||||
<span><%= @hub.hub_name %></span>
|
||||
</span>
|
||||
</p>
|
||||
<%= if @deployment_groups do %>
|
||||
<%= if @deployment_groups != [] do %>
|
||||
<.form
|
||||
:let={f}
|
||||
for={@deployment_group_form}
|
||||
phx-change="select_deployment_group"
|
||||
phx-target={@myself}
|
||||
id="select_deployment_group_form"
|
||||
>
|
||||
<.select_field
|
||||
help={
|
||||
~S'''
|
||||
Share deployment credentials, secrets, and configuration with deployment groups.
|
||||
'''
|
||||
}
|
||||
field={f[:id]}
|
||||
options={deployment_group_options(@deployment_groups)}
|
||||
label="Deployment Group"
|
||||
/>
|
||||
</.form>
|
||||
<% else %>
|
||||
<p class="text-gray-700">
|
||||
<.label>Deployment Group</.label>
|
||||
<span>No deployment groups available</span>
|
||||
</p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<.message_box :for={warning <- @warnings} kind={:warning}>
|
||||
<%= raw(warning) %>
|
||||
|
@ -156,6 +191,12 @@ defmodule LivebookWeb.SessionLive.AppDockerComponent do
|
|||
{:noreply, assign(socket, save_result: save_result)}
|
||||
end
|
||||
|
||||
def handle_event("select_deployment_group", %{"id" => id}, socket) do
|
||||
Livebook.Session.set_notebook_deployment_group(socket.assigns.session.pid, id)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
defp update_dockerfile(socket) when socket.assigns.file == nil do
|
||||
assign(socket, dockerfile: nil, warnings: [])
|
||||
end
|
||||
|
@ -170,9 +211,21 @@ defmodule LivebookWeb.SessionLive.AppDockerComponent do
|
|||
file: file,
|
||||
file_entries: file_entries,
|
||||
secrets: secrets,
|
||||
app_settings: app_settings
|
||||
app_settings: app_settings,
|
||||
deployment_groups: deployment_groups,
|
||||
deployment_group_id: deployment_group_id
|
||||
} = socket.assigns
|
||||
|
||||
deployment_group =
|
||||
if deployment_group_id, do: Enum.find(deployment_groups, &(&1.id == deployment_group_id))
|
||||
|
||||
hub_secrets =
|
||||
if deployment_group do
|
||||
Enum.uniq_by(deployment_group.secrets ++ hub_secrets, & &1.name)
|
||||
else
|
||||
hub_secrets
|
||||
end
|
||||
|
||||
dockerfile =
|
||||
Hubs.Dockerfile.build_dockerfile(
|
||||
config,
|
||||
|
@ -197,4 +250,9 @@ defmodule LivebookWeb.SessionLive.AppDockerComponent do
|
|||
|
||||
assign(socket, dockerfile: dockerfile, warnings: warnings)
|
||||
end
|
||||
|
||||
defp deployment_group_options(deployment_groups) do
|
||||
for deployment_group <- [%{name: "none", id: nil}] ++ deployment_groups,
|
||||
do: {deployment_group.name, deployment_group.id}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -359,4 +359,103 @@ defmodule LivebookWeb.Integration.SessionLiveTest do
|
|||
remove_offline_hub_file_system(file_system)
|
||||
end
|
||||
end
|
||||
|
||||
describe "deployment group for app deployment" do
|
||||
@tag :tmp_dir
|
||||
test "show deployment group on app deployment",
|
||||
%{conn: conn, user: user, node: node, session: session, tmp_dir: tmp_dir} do
|
||||
team = create_team_hub(user, node)
|
||||
team_id = team.id
|
||||
|
||||
insert_deployment_group(
|
||||
name: "DEPLOYMENT_GROUP_SUSIE",
|
||||
mode: "online",
|
||||
hub_id: team_id
|
||||
)
|
||||
|
||||
Session.subscribe(session.id)
|
||||
Session.set_notebook_hub(session.pid, team_id)
|
||||
assert_receive {:operation, {:set_notebook_hub, _client, ^team_id}}
|
||||
|
||||
notebook_path = Path.join(tmp_dir, "notebook.livemd")
|
||||
file = Livebook.FileSystem.File.local(notebook_path)
|
||||
Session.set_file(session.pid, file)
|
||||
|
||||
slug = Livebook.Utils.random_short_id()
|
||||
app_settings = %{Livebook.Notebook.AppSettings.new() | slug: slug}
|
||||
Session.set_app_settings(session.pid, app_settings)
|
||||
|
||||
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}/app-docker")
|
||||
|
||||
assert render(view) =~ "Deployment Group"
|
||||
assert has_element?(view, "#select_deployment_group_form")
|
||||
end
|
||||
|
||||
@tag :tmp_dir
|
||||
test "set deployment group on app deployment",
|
||||
%{conn: conn, user: user, node: node, session: session, tmp_dir: tmp_dir} do
|
||||
team = create_team_hub(user, node)
|
||||
team_id = team.id
|
||||
|
||||
insert_deployment_group(
|
||||
name: "DEPLOYMENT_GROUP_SUSIE",
|
||||
mode: "online",
|
||||
hub_id: team_id
|
||||
)
|
||||
|
||||
insert_deployment_group(
|
||||
name: "DEPLOYMENT_GROUP_TOBIAS",
|
||||
mode: "online",
|
||||
hub_id: team_id
|
||||
)
|
||||
|
||||
Session.subscribe(session.id)
|
||||
Session.set_notebook_hub(session.pid, team_id)
|
||||
assert_receive {:operation, {:set_notebook_hub, _client, ^team_id}}
|
||||
|
||||
notebook_path = Path.join(tmp_dir, "notebook.livemd")
|
||||
file = Livebook.FileSystem.File.local(notebook_path)
|
||||
Session.set_file(session.pid, file)
|
||||
|
||||
slug = Livebook.Utils.random_short_id()
|
||||
app_settings = %{Livebook.Notebook.AppSettings.new() | slug: slug}
|
||||
Session.set_app_settings(session.pid, app_settings)
|
||||
|
||||
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}/app-docker")
|
||||
|
||||
assert render(view) =~ "Deployment Group"
|
||||
assert has_element?(view, "#select_deployment_group_form")
|
||||
|
||||
view
|
||||
|> form("#select_deployment_group_form", %{id: "2"})
|
||||
|> render_change()
|
||||
|
||||
assert_receive {:operation, {:set_notebook_deployment_group, _client, "2"}}
|
||||
end
|
||||
|
||||
@tag :tmp_dir
|
||||
test "show no deployments groups available",
|
||||
%{conn: conn, user: user, node: node, session: session, tmp_dir: tmp_dir} do
|
||||
team = create_team_hub(user, node)
|
||||
team_id = team.id
|
||||
|
||||
Session.subscribe(session.id)
|
||||
Session.set_notebook_hub(session.pid, team_id)
|
||||
assert_receive {:operation, {:set_notebook_hub, _client, ^team_id}}
|
||||
|
||||
notebook_path = Path.join(tmp_dir, "notebook.livemd")
|
||||
file = Livebook.FileSystem.File.local(notebook_path)
|
||||
Session.set_file(session.pid, file)
|
||||
|
||||
slug = Livebook.Utils.random_short_id()
|
||||
app_settings = %{Livebook.Notebook.AppSettings.new() | slug: slug}
|
||||
Session.set_app_settings(session.pid, app_settings)
|
||||
|
||||
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}/app-docker")
|
||||
|
||||
assert render(view) =~ "Deployment Group"
|
||||
assert render(view) =~ "No deployment groups available"
|
||||
refute has_element?(view, "#select_deployment_group_form")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue