mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-09-07 05:24:40 +08:00
Move airgapped to deployment group (#2404)
Co-authored-by: José Valim <jose.valim@gmail.com> Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com>
This commit is contained in:
parent
a7be387efa
commit
3430e9a261
4 changed files with 362 additions and 64 deletions
|
@ -57,8 +57,6 @@ defmodule LivebookWeb.Hub.Edit.TeamComponent do
|
|||
hub_metadata: Provider.to_metadata(assigns.hub),
|
||||
default?: default?
|
||||
)
|
||||
|> assign_new(:config_changeset, fn -> Hubs.Dockerfile.config_changeset() end)
|
||||
|> update_dockerfile()
|
||||
|> assign_form(changeset)}
|
||||
end
|
||||
|
||||
|
@ -231,39 +229,6 @@ defmodule LivebookWeb.Hub.Edit.TeamComponent do
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-y-4">
|
||||
<h2 class="text-xl text-gray-800 font-medium pb-2 border-b border-gray-200">
|
||||
Airgapped deployment
|
||||
</h2>
|
||||
|
||||
<p class="text-gray-700">
|
||||
It is possible to deploy notebooks that belong to this Hub in an airgapped
|
||||
deployment, without connecting back to Livebook Teams server. Configure the
|
||||
deployment below and use the generated Dockerfile in a directory with notebooks
|
||||
that belong to your Organization.
|
||||
</p>
|
||||
|
||||
<.form
|
||||
:let={f}
|
||||
for={@config_changeset}
|
||||
as={:data}
|
||||
phx-change="validate_dockerfile"
|
||||
phx-target={@myself}
|
||||
>
|
||||
<LivebookWeb.AppHelpers.docker_config_form_content
|
||||
hub={@hub}
|
||||
form={f}
|
||||
show_deploy_all={false}
|
||||
/>
|
||||
</.form>
|
||||
|
||||
<LivebookWeb.AppHelpers.docker_instructions
|
||||
hub={@hub}
|
||||
dockerfile={@dockerfile}
|
||||
dockerfile_config={Ecto.Changeset.apply_changes(@config_changeset)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-y-4">
|
||||
<h2 class="text-xl text-gray-800 font-medium pb-2 border-b border-gray-200">
|
||||
Danger zone
|
||||
|
@ -458,18 +423,6 @@ defmodule LivebookWeb.Hub.Edit.TeamComponent do
|
|||
)}
|
||||
end
|
||||
|
||||
def handle_event("validate_dockerfile", %{"data" => data}, socket) do
|
||||
changeset =
|
||||
data
|
||||
|> Hubs.Dockerfile.config_changeset()
|
||||
|> Map.replace!(:action, :validate)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(config_changeset: changeset)
|
||||
|> update_dockerfile()}
|
||||
end
|
||||
|
||||
def handle_event("mark_as_default", _, socket) do
|
||||
Hubs.set_default_hub(socket.assigns.hub.id)
|
||||
{:noreply, assign(socket, default?: true)}
|
||||
|
@ -487,18 +440,4 @@ defmodule LivebookWeb.Hub.Edit.TeamComponent do
|
|||
defp assign_form(socket, %Ecto.Changeset{} = changeset) do
|
||||
assign(socket, form: to_form(changeset))
|
||||
end
|
||||
|
||||
defp update_dockerfile(socket) do
|
||||
config =
|
||||
socket.assigns.config_changeset
|
||||
|> Ecto.Changeset.apply_changes()
|
||||
|> Map.replace!(:deploy_all, true)
|
||||
|
||||
%{hub: hub, secrets: hub_secrets, file_systems: hub_file_systems} = socket.assigns
|
||||
|
||||
dockerfile =
|
||||
Hubs.Dockerfile.build_dockerfile(config, hub, hub_secrets, hub_file_systems, nil, [], %{})
|
||||
|
||||
assign(socket, :dockerfile, dockerfile)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,7 +49,9 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupLive do
|
|||
secret_value: secret_value,
|
||||
default?: default?,
|
||||
secrets: secrets
|
||||
)}
|
||||
)
|
||||
|> assign_new(:config_changeset, fn -> Hubs.Dockerfile.config_changeset() end)
|
||||
|> update_dockerfile()}
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
@ -125,7 +127,7 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupLive do
|
|||
|
||||
<.live_component
|
||||
module={LivebookWeb.Hub.SecretListComponent}
|
||||
id="hub-secrets-list"
|
||||
id="deployment-group-secrets-list"
|
||||
hub={@hub}
|
||||
secrets={@secrets}
|
||||
deployment_group={@deployment_group}
|
||||
|
@ -136,6 +138,32 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupLive do
|
|||
return_to={~p"/hub/#{@hub.id}/deployment-groups/edit/#{@deployment_group.id}"}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col space-y-4">
|
||||
<h2 class="text-xl text-gray-800 font-medium pb-2 border-b border-gray-200">
|
||||
Airgapped deployment
|
||||
</h2>
|
||||
|
||||
<p class="text-gray-700">
|
||||
It is possible to deploy notebooks that belong to this Hub in an airgapped
|
||||
deployment, without connecting back to Livebook Teams server. Configure the
|
||||
deployment below and use the generated Dockerfile in a directory with notebooks
|
||||
that belong to your Organization.
|
||||
</p>
|
||||
|
||||
<.form :let={f} for={@config_changeset} as={:data} phx-change="validate_dockerfile">
|
||||
<LivebookWeb.AppHelpers.docker_config_form_content
|
||||
hub={@hub}
|
||||
form={f}
|
||||
show_deploy_all={false}
|
||||
/>
|
||||
</.form>
|
||||
|
||||
<LivebookWeb.AppHelpers.docker_instructions
|
||||
hub={@hub}
|
||||
dockerfile={@dockerfile}
|
||||
dockerfile_config={Ecto.Changeset.apply_changes(@config_changeset)}
|
||||
/>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -162,7 +190,39 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupLive do
|
|||
"""
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("validate_dockerfile", %{"data" => data}, socket) do
|
||||
changeset =
|
||||
data
|
||||
|> Hubs.Dockerfile.config_changeset()
|
||||
|> Map.replace!(:action, :validate)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(config_changeset: changeset)
|
||||
|> update_dockerfile()}
|
||||
end
|
||||
|
||||
defp default_hub?(hub) do
|
||||
Hubs.get_default_hub().id == hub.id
|
||||
end
|
||||
|
||||
defp update_dockerfile(socket) do
|
||||
config =
|
||||
socket.assigns.config_changeset
|
||||
|> Ecto.Changeset.apply_changes()
|
||||
|> Map.replace!(:deploy_all, true)
|
||||
|
||||
%{hub: hub, secrets: deployment_group_secrets} = socket.assigns
|
||||
|
||||
hub_secrets = Hubs.get_secrets(hub)
|
||||
hub_file_systems = Hubs.get_file_systems(hub, hub_only: true)
|
||||
|
||||
secrets = Enum.uniq_by(deployment_group_secrets ++ hub_secrets, & &1.name)
|
||||
|
||||
dockerfile =
|
||||
Hubs.Dockerfile.build_dockerfile(config, hub, secrets, hub_file_systems, nil, [], %{})
|
||||
|
||||
assign(socket, :dockerfile, dockerfile)
|
||||
end
|
||||
end
|
||||
|
|
299
test/livebook_teams/web/hub/deployment_group_live_test.exs
Normal file
299
test/livebook_teams/web/hub/deployment_group_live_test.exs
Normal file
|
@ -0,0 +1,299 @@
|
|||
defmodule LivebookWeb.Integration.Hub.DeploymentGroupLiveTest do
|
||||
use Livebook.TeamsIntegrationCase, async: true
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
import Livebook.TestHelpers
|
||||
|
||||
alias Livebook.Teams.DeploymentGroup
|
||||
|
||||
setup %{user: user, node: node} do
|
||||
Livebook.Hubs.Broadcasts.subscribe([:crud, :connection, :secrets, :file_systems])
|
||||
Livebook.Teams.Broadcasts.subscribe([:deployment_groups])
|
||||
hub = create_team_hub(user, node)
|
||||
id = hub.id
|
||||
|
||||
assert_receive {:hub_connected, ^id}
|
||||
|
||||
{:ok, hub: hub}
|
||||
end
|
||||
|
||||
test "creates a deployment group", %{conn: conn, hub: hub} do
|
||||
deployment_group =
|
||||
build(:deployment_group,
|
||||
name: "TEAM_ADD_DEPLOYMENT_GROUP",
|
||||
mode: "offline",
|
||||
hub_id: hub.id
|
||||
)
|
||||
|
||||
attrs = %{
|
||||
deployment_group: %{
|
||||
name: deployment_group.name,
|
||||
value: deployment_group.mode,
|
||||
hub_id: deployment_group.hub_id
|
||||
}
|
||||
}
|
||||
|
||||
{:ok, view, html} = live(conn, ~p"/hub/#{hub.id}/deployment-groups/new")
|
||||
assert html =~ "Add a new deployment group to"
|
||||
|
||||
view
|
||||
|> element("#deployment-groups-form")
|
||||
|> render_change(attrs)
|
||||
|
||||
refute view
|
||||
|> element("#deployment-groups-form button[disabled]")
|
||||
|> has_element?()
|
||||
|
||||
view
|
||||
|> element("#deployment-groups-form")
|
||||
|> render_submit(attrs)
|
||||
|
||||
assert_receive {:deployment_group_created,
|
||||
%DeploymentGroup{id: id, name: "TEAM_ADD_DEPLOYMENT_GROUP"} =
|
||||
deployment_group}
|
||||
|
||||
assert_patch(view, "/hub/#{hub.id}/deployment-groups/edit/#{id}")
|
||||
assert render(view) =~ "Deployment group TEAM_ADD_DEPLOYMENT_GROUP added successfully"
|
||||
assert deployment_group in Livebook.Teams.get_deployment_groups(hub)
|
||||
|
||||
# Guarantee it shows the error from API
|
||||
|
||||
{:ok, view, _html} = live(conn, ~p"/hub/#{hub.id}/deployment-groups/new")
|
||||
|
||||
view
|
||||
|> element("#deployment-groups-form")
|
||||
|> render_submit(attrs)
|
||||
|
||||
assert render(view) =~ "has already been taken"
|
||||
end
|
||||
|
||||
test "updates an existing deployment group", %{conn: conn, hub: hub} do
|
||||
insert_deployment_group(
|
||||
name: "TEAM_EDIT_DEPLOYMENT_GROUP",
|
||||
mode: "online",
|
||||
hub_id: hub.id
|
||||
)
|
||||
|
||||
assert_receive {:deployment_group_created,
|
||||
%DeploymentGroup{name: "TEAM_EDIT_DEPLOYMENT_GROUP"} = deployment_group}
|
||||
|
||||
attrs = %{
|
||||
deployment_group: %{
|
||||
id: deployment_group.id,
|
||||
name: deployment_group.name,
|
||||
mode: deployment_group.mode,
|
||||
hub_id: deployment_group.hub_id
|
||||
}
|
||||
}
|
||||
|
||||
new_mode = "offline"
|
||||
|
||||
{:ok, view, html} =
|
||||
live(conn, ~p"/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}")
|
||||
|
||||
assert html =~ "Edit deployment group"
|
||||
assert html =~ "Manage the #{deployment_group.name} deployment group"
|
||||
|
||||
view
|
||||
|> element("#deployment-groups-form")
|
||||
|> render_change(attrs)
|
||||
|
||||
refute view
|
||||
|> element("#deployment-groups-form button[disabled]")
|
||||
|> has_element?()
|
||||
|
||||
view
|
||||
|> element("#deployment-groups-form")
|
||||
|> render_submit(put_in(attrs.deployment_group.mode, new_mode))
|
||||
|
||||
updated_deployment_group = %{deployment_group | mode: new_mode}
|
||||
|
||||
assert_receive {:deployment_group_updated, ^updated_deployment_group}
|
||||
assert_patch(view, "/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}")
|
||||
assert render(view) =~ "Deployment group TEAM_EDIT_DEPLOYMENT_GROUP updated successfully"
|
||||
assert updated_deployment_group in Livebook.Teams.get_deployment_groups(hub)
|
||||
end
|
||||
|
||||
test "creates a secret", %{conn: conn, hub: hub} do
|
||||
insert_deployment_group(
|
||||
name: "TEAM_EDIT_DEPLOYMENT_GROUP",
|
||||
mode: "online",
|
||||
hub_id: hub.id
|
||||
)
|
||||
|
||||
assert_receive {:deployment_group_created,
|
||||
%DeploymentGroup{name: "TEAM_EDIT_DEPLOYMENT_GROUP"} = deployment_group}
|
||||
|
||||
{:ok, view, _html} =
|
||||
live(conn, ~p"/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}")
|
||||
|
||||
secret =
|
||||
build(:secret,
|
||||
name: "DEPLOYMENT_GROUP_SECRET",
|
||||
hub_id: hub.id,
|
||||
deployment_group_id: deployment_group.id
|
||||
)
|
||||
|
||||
attrs = %{
|
||||
secret: %{
|
||||
name: secret.name,
|
||||
value: secret.value,
|
||||
hub_id: secret.hub_id,
|
||||
deployment_group_id: secret.deployment_group_id
|
||||
}
|
||||
}
|
||||
|
||||
refute render(view) =~ secret.name
|
||||
|
||||
view
|
||||
|> element("#add-secret")
|
||||
|> render_click(%{})
|
||||
|
||||
assert_patch(
|
||||
view,
|
||||
~p"/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}/secrets/new"
|
||||
)
|
||||
|
||||
assert render(view) =~ "Add secret"
|
||||
|
||||
view
|
||||
|> element("#secrets-form")
|
||||
|> render_change(attrs)
|
||||
|
||||
refute view
|
||||
|> element("#secrets-form button[disabled]")
|
||||
|> has_element?()
|
||||
|
||||
view
|
||||
|> element("#secrets-form")
|
||||
|> render_submit(attrs)
|
||||
|
||||
assert_receive {:deployment_group_updated,
|
||||
%Livebook.Teams.DeploymentGroup{secrets: [^secret]} = deployment_group}
|
||||
|
||||
assert_patch(view, "/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}")
|
||||
assert render(view) =~ "Secret DEPLOYMENT_GROUP_SECRET added successfully"
|
||||
assert render(element(view, "#deployment-group-secrets-list")) =~ secret.name
|
||||
assert secret in deployment_group.secrets
|
||||
|
||||
# Guarantee it shows the error from API
|
||||
|
||||
{:ok, view, _html} =
|
||||
live(conn, ~p"/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}/secrets/new")
|
||||
|
||||
view
|
||||
|> element("#secrets-form")
|
||||
|> render_submit(attrs)
|
||||
|
||||
assert render(view) =~ "has already been taken"
|
||||
end
|
||||
|
||||
test "updates an existing secret", %{conn: conn, hub: hub} do
|
||||
insert_deployment_group(
|
||||
name: "TEAM_EDIT_DEPLOYMENT_GROUP",
|
||||
mode: "online",
|
||||
hub_id: hub.id
|
||||
)
|
||||
|
||||
assert_receive {:deployment_group_created,
|
||||
%DeploymentGroup{name: "TEAM_EDIT_DEPLOYMENT_GROUP"} = deployment_group}
|
||||
|
||||
secret =
|
||||
insert_secret(
|
||||
name: "DEPLOYMENT_GROUP_EDIT_SECRET",
|
||||
hub_id: hub.id,
|
||||
deployment_group_id: deployment_group.id
|
||||
)
|
||||
|
||||
assert_receive {:deployment_group_updated,
|
||||
%Livebook.Teams.DeploymentGroup{secrets: [^secret]} = deployment_group}
|
||||
|
||||
{:ok, view, _html} =
|
||||
live(conn, ~p"/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}")
|
||||
|
||||
attrs = %{
|
||||
secret: %{
|
||||
name: secret.name,
|
||||
value: secret.value,
|
||||
hub_id: secret.hub_id
|
||||
}
|
||||
}
|
||||
|
||||
new_value = "new_value"
|
||||
|
||||
view
|
||||
|> element("#hub-secret-#{secret.name}-edit")
|
||||
|> render_click(%{"secret_name" => secret.name})
|
||||
|
||||
assert_patch(
|
||||
view,
|
||||
~p"/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}/secrets/edit/#{secret.name}"
|
||||
)
|
||||
|
||||
assert render(view) =~ "Edit secret"
|
||||
|
||||
view
|
||||
|> element("#secrets-form")
|
||||
|> render_change(attrs)
|
||||
|
||||
refute view
|
||||
|> element("#secrets-form button[disabled]")
|
||||
|> has_element?()
|
||||
|
||||
view
|
||||
|> element("#secrets-form")
|
||||
|> render_submit(put_in(attrs.secret.value, new_value))
|
||||
|
||||
updated_secret = %{secret | value: new_value}
|
||||
|
||||
assert_receive {:deployment_group_updated,
|
||||
%Livebook.Teams.DeploymentGroup{secrets: [^updated_secret]} = deployment_group}
|
||||
|
||||
assert_patch(view, "/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}")
|
||||
assert render(view) =~ "Secret DEPLOYMENT_GROUP_EDIT_SECRET updated successfully"
|
||||
assert render(element(view, "#deployment-group-secrets-list")) =~ secret.name
|
||||
assert updated_secret in deployment_group.secrets
|
||||
end
|
||||
|
||||
test "deletes an existing secret", %{conn: conn, hub: hub} do
|
||||
insert_deployment_group(
|
||||
name: "TEAM_EDIT_DEPLOYMENT_GROUP",
|
||||
mode: "online",
|
||||
hub_id: hub.id
|
||||
)
|
||||
|
||||
assert_receive {:deployment_group_created,
|
||||
%DeploymentGroup{name: "TEAM_EDIT_DEPLOYMENT_GROUP"} = deployment_group}
|
||||
|
||||
secret =
|
||||
insert_secret(
|
||||
name: "DEPLOYMENT_GROUP_DELETE_SECRET",
|
||||
hub_id: hub.id,
|
||||
deployment_group_id: deployment_group.id
|
||||
)
|
||||
|
||||
assert_receive {:deployment_group_updated,
|
||||
%Livebook.Teams.DeploymentGroup{secrets: [^secret]} = deployment_group}
|
||||
|
||||
{:ok, view, _html} =
|
||||
live(conn, ~p"/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}")
|
||||
|
||||
refute view
|
||||
|> element("#secrets-form button[disabled]")
|
||||
|> has_element?()
|
||||
|
||||
view
|
||||
|> element("#hub-secret-#{secret.name}-delete", "Delete")
|
||||
|> render_click()
|
||||
|
||||
render_confirm(view)
|
||||
|
||||
assert_receive {:deployment_group_updated,
|
||||
%Livebook.Teams.DeploymentGroup{secrets: []} = deployment_group}
|
||||
|
||||
assert_patch(view, "/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}")
|
||||
assert render(view) =~ "Secret DEPLOYMENT_GROUP_DELETE_SECRET deleted successfully"
|
||||
refute render(element(view, "#deployment-group-secrets-list")) =~ secret.name
|
||||
refute secret in deployment_group.secrets
|
||||
end
|
||||
end
|
|
@ -354,7 +354,7 @@ defmodule LivebookWeb.Integration.Hub.EditLiveTest do
|
|||
assert render(view) =~ "has already been taken"
|
||||
end
|
||||
|
||||
test "updates existing deployment group", %{conn: conn, hub: hub} do
|
||||
test "updates an existing deployment group", %{conn: conn, hub: hub} do
|
||||
insert_deployment_group(
|
||||
name: "TEAM_EDIT_DEPLOYMENT_GROUP",
|
||||
mode: "online",
|
||||
|
|
Loading…
Add table
Reference in a new issue