defmodule LivebookWeb.SessionLive.AppDockerComponent do use LivebookWeb, :live_component import Ecto.Changeset alias Livebook.Hubs alias Livebook.FileSystem alias LivebookWeb.AppComponents 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), deployment_groups: deployment_groups, deployment_group_form: %{"deployment_group_id" => assigns.deployment_group_id}, deployment_group_id: assigns.deployment_group_id ) |> assign_new(:changeset, fn -> Hubs.Dockerfile.config_changeset() end) |> assign_new(:save_result, fn -> nil end) |> update_dockerfile()} end @impl true def render(assigns) do ~H"""

App deployment

<.content file={@file} settings_valid?={@settings_valid?} hub={@hub} deployment_groups={@deployment_groups} deployment_group_form={@deployment_group_form} deployment_group_id={@deployment_group_id} changeset={@changeset} session={@session} dockerfile={@dockerfile} warnings={@warnings} save_result={@save_result} myself={@myself} />
""" end defp content(%{file: nil} = assigns) do ~H"""

To deploy this app, make sure to save the notebook first.

<.link class="text-blue-600 font-medium" patch={~p"/sessions/#{@session.id}/settings/file"}> Save <.remix_icon icon="arrow-right-line" />
""" end defp content(%{settings_valid?: false} = assigns) do ~H"""

To deploy this app, make sure to specify valid settings.

<.link class="text-blue-600 font-medium" patch={~p"/sessions/#{@session.id}/settings/app"}> Configure <.remix_icon icon="arrow-right-line" />
""" end defp content(assigns) do ~H"""

You can deploy this app in the cloud using Docker. To do that, configure the deployment and then use the generated Dockerfile.

<.label>Hub <%= @hub.hub_emoji %> <%= @hub.hub_name %>

<%= if @deployment_groups do %> <%= if @deployment_groups != [] do %> <.form for={@deployment_group_form} phx-change="select_deployment_group" phx-target={@myself} id="select_deployment_group_form" > <.select_field help={deployment_group_help()} field={@deployment_group_form[:deployment_group_id]} options={deployment_group_options(@deployment_groups)} label="Deployment Group" name="deployment_group_id" value={@deployment_group_id} /> <% else %>

<.label help={deployment_group_help()}> Deployment Group No deployment groups available

<% end %> <% end %>
<.message_box :for={warning <- @warnings} kind={:warning}> <%= raw(warning) %>
<.form :let={f} for={deployment_group_form_content(assigns)} as={:data} phx-change="validate" phx-target={@myself} >
<.form :let={f} for={@changeset} as={:data} phx-change="validate" phx-target={@myself}> <.save_result :if={@save_result} save_result={@save_result} /> <:dockerfile_actions>
""" end defp save_result(%{save_result: {:ok, file}}) do assigns = %{path: file.path} ~H""" <.message_box kind={:info} message={"File saved at #{@path}"} /> """ end defp save_result(%{save_result: {:error, message}}) do assigns = %{message: message} ~H""" <.message_box kind={:error} message={@message} /> """ end @impl true def handle_event("validate", %{"data" => data}, socket) do changeset = data |> Hubs.Dockerfile.config_changeset() |> Map.replace!(:action, :validate) {:noreply, assign(socket, changeset: changeset) |> update_dockerfile()} end def handle_event("save_dockerfile", %{}, socket) do dockerfile_file = FileSystem.File.resolve(socket.assigns.file, "./Dockerfile") save_result = case FileSystem.File.write(dockerfile_file, socket.assigns.dockerfile) do :ok -> {:ok, dockerfile_file} {:error, message} -> {:error, message} end {:noreply, assign(socket, save_result: save_result)} end def handle_event("select_deployment_group", %{"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 defp update_dockerfile(socket) do config = apply_changes(socket.assigns.changeset) %{ hub: hub, hub_secrets: hub_secrets, hub_file_systems: hub_file_systems, file: file, file_entries: file_entries, secrets: secrets, 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, hub, hub_secrets, hub_file_systems, file, file_entries, secrets ) warnings = Hubs.Dockerfile.warnings( config, hub, hub_secrets, hub_file_systems, app_settings, file_entries, secrets ) 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 defp deployment_group_form_content(%{deployment_group_id: id} = assigns) do deployment_group = if id, do: Enum.find(assigns.deployment_groups, &(&1.id == id)) if deployment_group do %{ "deployment_group_id" => deployment_group.id, "clustering" => deployment_group.clustering, "zta_provider" => deployment_group.zta_provider, "zta_key" => deployment_group.zta_key, "ready_only" => true } else assigns.changeset end end defp deployment_group_help() do "Share deployment credentials, secrets, and configuration with deployment groups." end end