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:
Cristine Guadelupe 2023-12-20 11:47:46 -03:00 committed by GitHub
parent a7be387efa
commit 3430e9a261
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 362 additions and 64 deletions

View file

@ -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

View file

@ -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

View 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

View file

@ -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",