mirror of
https://github.com/livebook-dev/livebook.git
synced 2024-09-20 10:05:57 +08:00
Deployment group secrets (#2374)
Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com>
This commit is contained in:
parent
839c326ab0
commit
8923e700d8
|
@ -196,13 +196,14 @@ defmodule Livebook.Hubs.TeamClient do
|
||||||
%{state | secrets: Enum.reject(state.secrets, &(&1.name == secret.name))}
|
%{state | secrets: Enum.reject(state.secrets, &(&1.name == secret.name))}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_secret(state, %{name: name, value: value}) do
|
defp build_secret(state, %{name: name, value: value} = attrs) do
|
||||||
{:ok, decrypted_value} = Teams.decrypt(value, state.derived_key)
|
{:ok, decrypted_value} = Teams.decrypt(value, state.derived_key)
|
||||||
|
|
||||||
%Secrets.Secret{
|
%Secrets.Secret{
|
||||||
name: name,
|
name: name,
|
||||||
value: decrypted_value,
|
value: decrypted_value,
|
||||||
hub_id: state.hub.id
|
hub_id: state.hub.id,
|
||||||
|
deployment_group_id: Map.get(attrs, :deployment_group_id)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -243,8 +244,9 @@ defmodule Livebook.Hubs.TeamClient do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_deployment_group(state, %{id: id, name: name, mode: mode}) do
|
defp build_deployment_group(state, %{id: id, name: name, mode: mode, secrets: secrets}) do
|
||||||
%DeploymentGroup{id: id, name: name, mode: mode, hub_id: state.hub.id}
|
secrets = Enum.map(secrets, &build_secret(state, &1))
|
||||||
|
%DeploymentGroup{id: id, name: name, mode: mode, hub_id: state.hub.id, secrets: secrets}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp handle_event(:secret_created, %Secrets.Secret{} = secret, state) do
|
defp handle_event(:secret_created, %Secrets.Secret{} = secret, state) do
|
||||||
|
|
|
@ -5,18 +5,20 @@ defmodule Livebook.Secrets.Secret do
|
||||||
@type t :: %__MODULE__{
|
@type t :: %__MODULE__{
|
||||||
name: String.t(),
|
name: String.t(),
|
||||||
value: String.t(),
|
value: String.t(),
|
||||||
hub_id: String.t() | nil
|
hub_id: String.t() | nil,
|
||||||
|
deployment_group_id: String.t() | nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@primary_key {:name, :string, autogenerate: false}
|
@primary_key {:name, :string, autogenerate: false}
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field :value, :string
|
field :value, :string
|
||||||
field :hub_id, :string
|
field :hub_id, :string
|
||||||
|
field :deployment_group_id, :string
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(secret, attrs \\ %{}) do
|
def changeset(secret, attrs \\ %{}) do
|
||||||
secret
|
secret
|
||||||
|> cast(attrs, [:name, :value, :hub_id])
|
|> cast(attrs, [:name, :value, :hub_id, :deployment_group_id])
|
||||||
|> update_change(:name, &String.upcase/1)
|
|> update_change(:name, &String.upcase/1)
|
||||||
|> validate_format(:name, ~r/^\w+$/,
|
|> validate_format(:name, ~r/^\w+$/,
|
||||||
message: "should contain only alphanumeric characters and underscore"
|
message: "should contain only alphanumeric characters and underscore"
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
defmodule Livebook.Teams.DeploymentGroup do
|
defmodule Livebook.Teams.DeploymentGroup do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
alias Livebook.Secrets.Secret
|
||||||
|
|
||||||
@type t :: %__MODULE__{
|
@type t :: %__MODULE__{
|
||||||
id: pos_integer() | nil,
|
id: String.t() | nil,
|
||||||
name: String.t() | nil,
|
name: String.t() | nil,
|
||||||
mode: :online | :offline,
|
mode: :online | :offline,
|
||||||
hub_id: String.t() | nil
|
hub_id: String.t() | nil,
|
||||||
|
secrets: [Secret.t()]
|
||||||
}
|
}
|
||||||
|
|
||||||
@primary_key {:id, :id, autogenerate: false}
|
@primary_key {:id, :string, autogenerate: false}
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field :name, :string
|
field :name, :string
|
||||||
field :mode, Ecto.Enum, values: [:online, :offline]
|
field :mode, Ecto.Enum, values: [:online, :offline]
|
||||||
field :hub_id, :string
|
field :hub_id, :string
|
||||||
|
has_many :secrets, Secret
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(deployment_group, attrs \\ %{}) do
|
def changeset(deployment_group, attrs \\ %{}) do
|
||||||
|
|
|
@ -49,34 +49,66 @@ defmodule Livebook.Teams.Requests do
|
||||||
"""
|
"""
|
||||||
@spec create_secret(Team.t(), Secret.t()) ::
|
@spec create_secret(Team.t(), Secret.t()) ::
|
||||||
{:ok, map()} | {:error, map() | String.t()} | {:transport_error, String.t()}
|
{:ok, map()} | {:error, map() | String.t()} | {:transport_error, String.t()}
|
||||||
def create_secret(team, secret) do
|
def create_secret(team, %{deployment_group_id: nil} = secret) do
|
||||||
secret_key = Teams.derive_key(team.teams_key)
|
secret_key = Teams.derive_key(team.teams_key)
|
||||||
secret_value = Teams.encrypt(secret.value, secret_key)
|
secret_value = Teams.encrypt(secret.value, secret_key)
|
||||||
|
|
||||||
post("/api/v1/org/secrets", %{name: secret.name, value: secret_value}, team)
|
post("/api/v1/org/secrets", %{name: secret.name, value: secret_value}, team)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_secret(team, secret) do
|
||||||
|
secret_key = Teams.derive_key(team.teams_key)
|
||||||
|
secret_value = Teams.encrypt(secret.value, secret_key)
|
||||||
|
|
||||||
|
params = %{
|
||||||
|
name: secret.name,
|
||||||
|
value: secret_value,
|
||||||
|
deployment_group_id: secret.deployment_group_id
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/api/v1/org/deployment-groups/secrets", params, team)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Send a request to Livebook Team API to update a secret.
|
Send a request to Livebook Team API to update a secret.
|
||||||
"""
|
"""
|
||||||
@spec update_secret(Team.t(), Secret.t()) ::
|
@spec update_secret(Team.t(), Secret.t()) ::
|
||||||
{:ok, map()} | {:error, map() | String.t()} | {:transport_error, String.t()}
|
{:ok, map()} | {:error, map() | String.t()} | {:transport_error, String.t()}
|
||||||
def update_secret(team, secret) do
|
def update_secret(team, %{deployment_group_id: nil} = secret) do
|
||||||
secret_key = Teams.derive_key(team.teams_key)
|
secret_key = Teams.derive_key(team.teams_key)
|
||||||
secret_value = Teams.encrypt(secret.value, secret_key)
|
secret_value = Teams.encrypt(secret.value, secret_key)
|
||||||
|
|
||||||
put("/api/v1/org/secrets", %{name: secret.name, value: secret_value}, team)
|
put("/api/v1/org/secrets", %{name: secret.name, value: secret_value}, team)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_secret(team, secret) do
|
||||||
|
secret_key = Teams.derive_key(team.teams_key)
|
||||||
|
secret_value = Teams.encrypt(secret.value, secret_key)
|
||||||
|
|
||||||
|
params = %{
|
||||||
|
name: secret.name,
|
||||||
|
value: secret_value,
|
||||||
|
deployment_group_id: secret.deployment_group_id
|
||||||
|
}
|
||||||
|
|
||||||
|
put("/api/v1/org/deployment-groups/secrets", params, team)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Send a request to Livebook Team API to delete a secret.
|
Send a request to Livebook Team API to delete a secret.
|
||||||
"""
|
"""
|
||||||
@spec delete_secret(Team.t(), Secret.t()) ::
|
@spec delete_secret(Team.t(), Secret.t()) ::
|
||||||
{:ok, String.t()} | {:error, map() | String.t()} | {:transport_error, String.t()}
|
{:ok, String.t()} | {:error, map() | String.t()} | {:transport_error, String.t()}
|
||||||
def delete_secret(team, secret) do
|
def delete_secret(team, %{deployment_group_id: nil} = secret) do
|
||||||
delete("/api/v1/org/secrets", %{name: secret.name}, team)
|
delete("/api/v1/org/secrets", %{name: secret.name}, team)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def delete_secret(team, secret) do
|
||||||
|
params = %{name: secret.name, deployment_group_id: secret.deployment_group_id}
|
||||||
|
|
||||||
|
delete("/api/v1/org/deployment-groups/secrets", params, team)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Send a request to Livebook Team API to create a file system.
|
Send a request to Livebook Team API to create a file system.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -101,6 +101,9 @@ defmodule LivebookWeb.Hub.Edit.PersonalComponent do
|
||||||
id="hub-secrets-list"
|
id="hub-secrets-list"
|
||||||
hub={@hub}
|
hub={@hub}
|
||||||
secrets={@secrets}
|
secrets={@secrets}
|
||||||
|
add_path={~p"/hub/#{@hub.id}/secrets/new"}
|
||||||
|
edit_path={"hub/#{@hub.id}/secrets/edit"}
|
||||||
|
return_to={~p"/hub/#{@hub.id}"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -185,6 +185,9 @@ defmodule LivebookWeb.Hub.Edit.TeamComponent do
|
||||||
id="hub-secrets-list"
|
id="hub-secrets-list"
|
||||||
hub={@hub}
|
hub={@hub}
|
||||||
secrets={@secrets}
|
secrets={@secrets}
|
||||||
|
add_path={~p"/hub/#{@hub.id}/secrets/new"}
|
||||||
|
edit_path={"hub/#{@hub.id}/secrets/edit"}
|
||||||
|
return_to={~p"/hub/#{@hub.id}"}
|
||||||
target={@myself}
|
target={@myself}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -324,23 +327,6 @@ defmodule LivebookWeb.Hub.Edit.TeamComponent do
|
||||||
return_to={~p"/hub/#{@hub.id}"}
|
return_to={~p"/hub/#{@hub.id}"}
|
||||||
/>
|
/>
|
||||||
</.modal>
|
</.modal>
|
||||||
|
|
||||||
<.modal
|
|
||||||
:if={@live_action in [:new_deployment_group, :edit_deployment_group]}
|
|
||||||
id="deployment-groups-modal"
|
|
||||||
show
|
|
||||||
width={:medium}
|
|
||||||
patch={~p"/hub/#{@hub.id}"}
|
|
||||||
>
|
|
||||||
<.live_component
|
|
||||||
module={LivebookWeb.Hub.Teams.DeploymentGroupFormComponent}
|
|
||||||
id="deployment-groups"
|
|
||||||
hub={@hub}
|
|
||||||
deployment_group_id={@deployment_group_id}
|
|
||||||
deployment_group={@deployment_group}
|
|
||||||
return_to={~p"/hub/#{@hub.id}"}
|
|
||||||
/>
|
|
||||||
</.modal>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,6 +20,7 @@ defmodule LivebookWeb.Hub.SecretFormComponent do
|
||||||
title: title(socket),
|
title: title(socket),
|
||||||
button: button(socket),
|
button: button(socket),
|
||||||
changeset: changeset,
|
changeset: changeset,
|
||||||
|
deployment_group_id: assigns[:deployment_group_id],
|
||||||
error_message: nil
|
error_message: nil
|
||||||
)}
|
)}
|
||||||
end
|
end
|
||||||
|
@ -66,6 +67,7 @@ defmodule LivebookWeb.Hub.SecretFormComponent do
|
||||||
phx-debounce
|
phx-debounce
|
||||||
/>
|
/>
|
||||||
<.hidden_field field={f[:hub_id]} value={@hub.id} />
|
<.hidden_field field={f[:hub_id]} value={@hub.id} />
|
||||||
|
<.hidden_field field={f[:deployment_group_id]} value={@deployment_group_id} />
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<button class="button-base button-blue" type="submit" disabled={not @changeset.valid?}>
|
<button class="button-base button-blue" type="submit" disabled={not @changeset.valid?}>
|
||||||
<.remix_icon icon={@button.icon} class="align-middle mr-1" />
|
<.remix_icon icon={@button.icon} class="align-middle mr-1" />
|
||||||
|
|
|
@ -16,7 +16,7 @@ defmodule LivebookWeb.Hub.SecretListComponent do
|
||||||
<div id={@id} class="flex flex-col space-y-4">
|
<div id={@id} class="flex flex-col space-y-4">
|
||||||
<div class="flex flex-col space-y-4">
|
<div class="flex flex-col space-y-4">
|
||||||
<.no_entries :if={@secrets == []}>
|
<.no_entries :if={@secrets == []}>
|
||||||
No secrets in this Hub yet.
|
No secrets here... yet!
|
||||||
</.no_entries>
|
</.no_entries>
|
||||||
<div
|
<div
|
||||||
:for={secret <- @secrets}
|
:for={secret <- @secrets}
|
||||||
|
@ -43,7 +43,7 @@ defmodule LivebookWeb.Hub.SecretListComponent do
|
||||||
<.menu_item>
|
<.menu_item>
|
||||||
<.link
|
<.link
|
||||||
id={"hub-secret-#{secret.name}-edit"}
|
id={"hub-secret-#{secret.name}-edit"}
|
||||||
patch={~p"/hub/#{secret.hub_id}/secrets/edit/#{secret.name}"}
|
patch={"/#{@edit_path}/#{secret.name}"}
|
||||||
type="button"
|
type="button"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
|
@ -60,7 +60,9 @@ defmodule LivebookWeb.Hub.SecretListComponent do
|
||||||
value: %{
|
value: %{
|
||||||
name: secret.name,
|
name: secret.name,
|
||||||
value: secret.value,
|
value: secret.value,
|
||||||
hub_id: secret.hub_id
|
hub_id: secret.hub_id,
|
||||||
|
deployment_group_id: secret.deployment_group_id,
|
||||||
|
return_to: @return_to
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -77,7 +79,7 @@ defmodule LivebookWeb.Hub.SecretListComponent do
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<.link patch={~p"/hub/#{@hub.id}/secrets/new"} class="button-base button-blue" id="add-secret">
|
<.link patch={@add_path} class="button-base button-blue" id="add-secret">
|
||||||
Add secret
|
Add secret
|
||||||
</.link>
|
</.link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,7 +97,7 @@ defmodule LivebookWeb.Hub.SecretListComponent do
|
||||||
:ok ->
|
:ok ->
|
||||||
socket
|
socket
|
||||||
|> put_flash(:success, "Secret #{secret.name} deleted successfully")
|
|> put_flash(:success, "Secret #{secret.name} deleted successfully")
|
||||||
|> push_navigate(to: ~p"/hub/#{hub.id}")
|
|> push_navigate(to: attrs["return_to"])
|
||||||
|
|
||||||
{:transport_error, reason} ->
|
{:transport_error, reason} ->
|
||||||
put_flash(socket, :error, reason)
|
put_flash(socket, :error, reason)
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupFormComponent do
|
||||||
@impl true
|
@impl true
|
||||||
def update(assigns, socket) do
|
def update(assigns, socket) do
|
||||||
deployment_group = assigns.deployment_group
|
deployment_group = assigns.deployment_group
|
||||||
|
hub = assigns.hub
|
||||||
|
|
||||||
deployment_group = deployment_group || %DeploymentGroup{hub_id: assigns.hub.id}
|
deployment_group = deployment_group || %DeploymentGroup{hub_id: assigns.hub.id}
|
||||||
changeset = Teams.change_deployment_group(deployment_group)
|
changeset = Teams.change_deployment_group(deployment_group)
|
||||||
|
@ -20,6 +21,7 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupFormComponent do
|
||||||
mode: mode(deployment_group),
|
mode: mode(deployment_group),
|
||||||
title: title(deployment_group),
|
title: title(deployment_group),
|
||||||
button: button(deployment_group),
|
button: button(deployment_group),
|
||||||
|
subtitle: subtitle(deployment_group, hub.hub_name),
|
||||||
error_message: nil
|
error_message: nil
|
||||||
)}
|
)}
|
||||||
end
|
end
|
||||||
|
@ -27,10 +29,14 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupFormComponent do
|
||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="p-6 max-w-4xl flex flex-col space-y-5">
|
<div class="max-w-4xl flex flex-col space-y-5">
|
||||||
<h3 class="text-2xl font-semibold text-gray-800">
|
<h2 class="text-xl text-gray-800 font-medium pb-2 border-b border-gray-200">
|
||||||
<%= @title %>
|
<%= @title %>
|
||||||
</h3>
|
</h2>
|
||||||
|
|
||||||
|
<p class="text-gray-700">
|
||||||
|
<%= @subtitle %>
|
||||||
|
</p>
|
||||||
<div class="flex flex-columns gap-4">
|
<div class="flex flex-columns gap-4">
|
||||||
<.form
|
<.form
|
||||||
:let={f}
|
:let={f}
|
||||||
|
@ -69,9 +75,11 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupFormComponent do
|
||||||
<.remix_icon icon={@button.icon} class="align-middle mr-1" />
|
<.remix_icon icon={@button.icon} class="align-middle mr-1" />
|
||||||
<span class="font-normal"><%= @button.label %></span>
|
<span class="font-normal"><%= @button.label %></span>
|
||||||
</button>
|
</button>
|
||||||
<.link patch={@return_to} class="button-base button-outlined-gray">
|
<%= if @mode == :new do %>
|
||||||
Cancel
|
<.link patch={@return_to} class="button-base button-outlined-gray">
|
||||||
</.link>
|
Cancel
|
||||||
|
</.link>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</.form>
|
</.form>
|
||||||
|
@ -85,7 +93,7 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupFormComponent do
|
||||||
changeset = Teams.change_deployment_group(socket.assigns.deployment_group, attrs)
|
changeset = Teams.change_deployment_group(socket.assigns.deployment_group, attrs)
|
||||||
|
|
||||||
with {:ok, deployment_group} <- Ecto.Changeset.apply_action(changeset, :update),
|
with {:ok, deployment_group} <- Ecto.Changeset.apply_action(changeset, :update),
|
||||||
{:ok, _id} <- save_deployment_group(deployment_group, socket) do
|
{:ok, id} <- save_deployment_group(deployment_group, socket) do
|
||||||
message =
|
message =
|
||||||
case socket.assigns.mode do
|
case socket.assigns.mode do
|
||||||
:new -> "Deployment group #{deployment_group.name} added successfully"
|
:new -> "Deployment group #{deployment_group.name} added successfully"
|
||||||
|
@ -95,7 +103,7 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupFormComponent do
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> put_flash(:success, message)
|
|> put_flash(:success, message)
|
||||||
|> push_redirect(to: socket.assigns.return_to)}
|
|> push_redirect(to: ~p"/hub/#{socket.assigns.hub.id}/deployment-groups/edit/#{id}")}
|
||||||
else
|
else
|
||||||
{:error, %Ecto.Changeset{} = changeset} ->
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
{:noreply, assign(socket, changeset: changeset)}
|
{:noreply, assign(socket, changeset: changeset)}
|
||||||
|
@ -130,6 +138,12 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupFormComponent do
|
||||||
defp title(%DeploymentGroup{name: nil}), do: "Add deployment group"
|
defp title(%DeploymentGroup{name: nil}), do: "Add deployment group"
|
||||||
defp title(_), do: "Edit deployment group"
|
defp title(_), do: "Edit deployment group"
|
||||||
|
|
||||||
|
defp subtitle(%DeploymentGroup{name: nil}, hub_name),
|
||||||
|
do: "Add a new deployment group to #{hub_name}"
|
||||||
|
|
||||||
|
defp subtitle(%DeploymentGroup{name: deployment_group}, _),
|
||||||
|
do: "Manage the #{deployment_group} deployment group"
|
||||||
|
|
||||||
defp button(%DeploymentGroup{name: nil}), do: %{icon: "add-line", label: "Add"}
|
defp button(%DeploymentGroup{name: nil}), do: %{icon: "add-line", label: "Add"}
|
||||||
defp button(_), do: %{icon: "save-line", label: "Save"}
|
defp button(_), do: %{icon: "save-line", label: "Save"}
|
||||||
end
|
end
|
||||||
|
|
168
lib/livebook_web/live/hub/teams/deployment_group_live.ex
Normal file
168
lib/livebook_web/live/hub/teams/deployment_group_live.ex
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
defmodule LivebookWeb.Hub.Teams.DeploymentGroupLive do
|
||||||
|
use LivebookWeb, :live_view
|
||||||
|
|
||||||
|
alias LivebookWeb.LayoutHelpers
|
||||||
|
alias Livebook.Hubs
|
||||||
|
alias Livebook.Teams
|
||||||
|
alias Livebook.Hubs.Provider
|
||||||
|
alias LivebookWeb.NotFoundError
|
||||||
|
|
||||||
|
on_mount LivebookWeb.SidebarHook
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_params(%{"id" => id} = params, _url, socket) do
|
||||||
|
hub = Hubs.fetch_hub!(id)
|
||||||
|
deployment_group_id = params["deployment_group_id"]
|
||||||
|
secret_name = params["secret_name"]
|
||||||
|
deployment_groups = Teams.get_deployment_groups(hub)
|
||||||
|
default? = default_hub?(hub)
|
||||||
|
|
||||||
|
deployment_group =
|
||||||
|
if socket.assigns.live_action != :new_deployment_group do
|
||||||
|
Enum.find_value(deployment_groups, &(&1.id == deployment_group_id && &1)) ||
|
||||||
|
raise(
|
||||||
|
NotFoundError,
|
||||||
|
"could not find deployment group matching #{inspect(deployment_group_id)}"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
secrets =
|
||||||
|
if socket.assigns.live_action != :new_deployment_group,
|
||||||
|
do: deployment_group.secrets,
|
||||||
|
else: []
|
||||||
|
|
||||||
|
secret_value =
|
||||||
|
if socket.assigns.live_action == :edit_secret do
|
||||||
|
Enum.find_value(secrets, &(&1.name == secret_name and &1.value)) ||
|
||||||
|
raise(NotFoundError, "could not find secret matching #{inspect(secret_name)}")
|
||||||
|
end
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(
|
||||||
|
hub: hub,
|
||||||
|
deployment_groups: deployment_groups,
|
||||||
|
deployment_group_id: deployment_group_id,
|
||||||
|
deployment_group: deployment_group,
|
||||||
|
hub_metadata: Provider.to_metadata(hub),
|
||||||
|
secret_name: secret_name,
|
||||||
|
secret_value: secret_value,
|
||||||
|
default?: default?,
|
||||||
|
secrets: secrets
|
||||||
|
)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def render(assigns) do
|
||||||
|
~H"""
|
||||||
|
<LayoutHelpers.layout
|
||||||
|
current_page={~p"/hub/#{@hub.id}"}
|
||||||
|
current_user={@current_user}
|
||||||
|
saved_hubs={@saved_hubs}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<LayoutHelpers.topbar
|
||||||
|
:if={not @hub_metadata.connected? && Provider.connection_error(@hub)}
|
||||||
|
variant={:warning}
|
||||||
|
>
|
||||||
|
<%= Provider.connection_error(@hub) %>
|
||||||
|
</LayoutHelpers.topbar>
|
||||||
|
|
||||||
|
<div class="p-4 md:px-12 md:py-7 max-w-screen-md mx-auto">
|
||||||
|
<div id={"#{@hub.id}-component"}>
|
||||||
|
<div class="mb-8 flex flex-col space-y-10">
|
||||||
|
<div class="flex flex-col space-y-2">
|
||||||
|
<LayoutHelpers.title>
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<span class="relative">
|
||||||
|
<%= @hub.hub_emoji %>
|
||||||
|
|
||||||
|
<div class={[
|
||||||
|
"absolute w-[10px] h-[10px] border-white border-2 rounded-full right-0 bottom-1",
|
||||||
|
if(@hub_metadata.connected?, do: "bg-green-400", else: "bg-red-400")
|
||||||
|
]} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<%= @hub.hub_name %>
|
||||||
|
<span class="bg-green-100 text-green-800 text-xs px-2.5 py-0.5 rounded cursor-default">
|
||||||
|
Livebook Teams
|
||||||
|
</span>
|
||||||
|
<%= if @default? do %>
|
||||||
|
<span class="bg-blue-100 text-blue-800 text-xs px-2.5 py-0.5 rounded cursor-default">
|
||||||
|
Default
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</LayoutHelpers.title>
|
||||||
|
<p class="text-sm flex flex-row space-x-6 text-gray-700">
|
||||||
|
<.link patch={~p"/hub/#{@hub.id}"} class="hover:text-blue-600 cursor-pointer">
|
||||||
|
<.remix_icon icon="arrow-left-line" /> Back to Hub
|
||||||
|
</.link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col space-y-4">
|
||||||
|
<.live_component
|
||||||
|
module={LivebookWeb.Hub.Teams.DeploymentGroupFormComponent}
|
||||||
|
id="deployment-groups"
|
||||||
|
hub={@hub}
|
||||||
|
deployment_group_id={@deployment_group_id}
|
||||||
|
deployment_group={@deployment_group}
|
||||||
|
return_to={~p"/hub/#{@hub.id}"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= if @deployment_group_id do %>
|
||||||
|
<div class="flex flex-col space-y-4">
|
||||||
|
<h2 class="text-xl text-gray-800 font-medium pb-2 border-b border-gray-200">
|
||||||
|
Secrets
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p class="text-gray-700">
|
||||||
|
Deployment group secrets overrides Hub secrets
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<.live_component
|
||||||
|
module={LivebookWeb.Hub.SecretListComponent}
|
||||||
|
id="hub-secrets-list"
|
||||||
|
hub={@hub}
|
||||||
|
secrets={@secrets}
|
||||||
|
deployment_group={@deployment_group}
|
||||||
|
add_path={
|
||||||
|
~p"/hub/#{@hub.id}/deployment-groups/edit/#{@deployment_group.id}/secrets/new"
|
||||||
|
}
|
||||||
|
edit_path={"hub/#{@hub.id}/deployment-groups/edit/#{@deployment_group.id}/secrets/edit"}
|
||||||
|
return_to={~p"/hub/#{@hub.id}/deployment-groups/edit/#{@deployment_group.id}"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<.modal
|
||||||
|
:if={@live_action in [:new_secret, :edit_secret]}
|
||||||
|
id="secrets-modal"
|
||||||
|
show
|
||||||
|
width={:medium}
|
||||||
|
patch={~p"/hub/#{@hub.id}/deployment-groups/edit/#{@deployment_group.id}"}
|
||||||
|
>
|
||||||
|
<.live_component
|
||||||
|
module={LivebookWeb.Hub.SecretFormComponent}
|
||||||
|
id="secrets"
|
||||||
|
hub={@hub}
|
||||||
|
deployment_group_id={@deployment_group.id}
|
||||||
|
secret_name={@secret_name}
|
||||||
|
secret_value={@secret_value}
|
||||||
|
return_to={~p"/hub/#{@hub.id}/deployment-groups/edit/#{@deployment_group.id}"}
|
||||||
|
/>
|
||||||
|
</.modal>
|
||||||
|
</LayoutHelpers.layout>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
defp default_hub?(hub) do
|
||||||
|
Hubs.get_default_hub().id == hub.id
|
||||||
|
end
|
||||||
|
end
|
|
@ -83,13 +83,25 @@ defmodule LivebookWeb.Router do
|
||||||
live "/hub/:id/secrets/edit/:secret_name", Hub.EditLive, :edit_secret, as: :hub
|
live "/hub/:id/secrets/edit/:secret_name", Hub.EditLive, :edit_secret, as: :hub
|
||||||
live "/hub/:id/file-systems/new", Hub.EditLive, :new_file_system, as: :hub
|
live "/hub/:id/file-systems/new", Hub.EditLive, :new_file_system, as: :hub
|
||||||
live "/hub/:id/file-systems/edit/:file_system_id", Hub.EditLive, :edit_file_system, as: :hub
|
live "/hub/:id/file-systems/edit/:file_system_id", Hub.EditLive, :edit_file_system, as: :hub
|
||||||
live "/hub/:id/deployment-groups/new", Hub.EditLive, :new_deployment_group, as: :hub
|
|
||||||
|
live "/hub/:id/deployment-groups/new", Hub.Teams.DeploymentGroupLive, :new_deployment_group,
|
||||||
|
as: :hub
|
||||||
|
|
||||||
live "/hub/:id/deployment-groups/edit/:deployment_group_id",
|
live "/hub/:id/deployment-groups/edit/:deployment_group_id",
|
||||||
Hub.EditLive,
|
Hub.Teams.DeploymentGroupLive,
|
||||||
:edit_deployment_group,
|
:edit_deployment_group,
|
||||||
as: :hub
|
as: :hub
|
||||||
|
|
||||||
|
live "/hub/:id/deployment-groups/edit/:deployment_group_id/secrets/new",
|
||||||
|
Hub.Teams.DeploymentGroupLive,
|
||||||
|
:new_secret,
|
||||||
|
as: :hub
|
||||||
|
|
||||||
|
live "/hub/:id/deployment-groups/edit/:deployment_group_id/secrets/edit/:secret_name",
|
||||||
|
Hub.Teams.DeploymentGroupLive,
|
||||||
|
:edit_secret,
|
||||||
|
as: :hub
|
||||||
|
|
||||||
live "/sessions/:id", SessionLive, :page
|
live "/sessions/:id", SessionLive, :page
|
||||||
live "/sessions/:id/shortcuts", SessionLive, :shortcuts
|
live "/sessions/:id/shortcuts", SessionLive, :shortcuts
|
||||||
live "/sessions/:id/secrets", SessionLive, :secrets
|
live "/sessions/:id/secrets", SessionLive, :secrets
|
||||||
|
|
|
@ -4,4 +4,5 @@ defmodule LivebookProto.DeploymentGroup do
|
||||||
field :id, 1, type: :string
|
field :id, 1, type: :string
|
||||||
field :name, 2, type: :string
|
field :name, 2, type: :string
|
||||||
field :mode, 3, type: :string
|
field :mode, 3, type: :string
|
||||||
|
field :secrets, 4, repeated: true, type: LivebookProto.DeploymentGroupSecret
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,4 +4,5 @@ defmodule LivebookProto.DeploymentGroupCreated do
|
||||||
field :id, 1, type: :string
|
field :id, 1, type: :string
|
||||||
field :name, 2, type: :string
|
field :name, 2, type: :string
|
||||||
field :mode, 3, type: :string
|
field :mode, 3, type: :string
|
||||||
|
field :secrets, 4, repeated: true, type: LivebookProto.DeploymentGroupSecret
|
||||||
end
|
end
|
||||||
|
|
7
proto/lib/livebook_proto/deployment_group_secret.pb.ex
Normal file
7
proto/lib/livebook_proto/deployment_group_secret.pb.ex
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
defmodule LivebookProto.DeploymentGroupSecret do
|
||||||
|
use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0"
|
||||||
|
|
||||||
|
field :name, 1, type: :string
|
||||||
|
field :value, 2, type: :string
|
||||||
|
field :deployment_group_id, 3, type: :string
|
||||||
|
end
|
|
@ -4,4 +4,5 @@ defmodule LivebookProto.DeploymentGroupUpdated do
|
||||||
field :id, 1, type: :string
|
field :id, 1, type: :string
|
||||||
field :name, 2, type: :string
|
field :name, 2, type: :string
|
||||||
field :mode, 3, type: :string
|
field :mode, 3, type: :string
|
||||||
|
field :secrets, 4, repeated: true, type: LivebookProto.DeploymentGroupSecret
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,22 +48,31 @@ message FileSystemDeleted {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message DeploymentGroupSecret {
|
||||||
|
string name = 1;
|
||||||
|
string value = 2;
|
||||||
|
string deployment_group_id = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message DeploymentGroup {
|
message DeploymentGroup {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
string name = 2;
|
string name = 2;
|
||||||
string mode = 3;
|
string mode = 3;
|
||||||
|
repeated DeploymentGroupSecret secrets = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DeploymentGroupCreated {
|
message DeploymentGroupCreated {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
string name = 2;
|
string name = 2;
|
||||||
string mode = 3;
|
string mode = 3;
|
||||||
|
repeated DeploymentGroupSecret secrets = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DeploymentGroupUpdated {
|
message DeploymentGroupUpdated {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
string name = 2;
|
string name = 2;
|
||||||
string mode = 3;
|
string mode = 3;
|
||||||
|
repeated DeploymentGroupSecret secrets = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DeploymentGroupDeleted {
|
message DeploymentGroupDeleted {
|
||||||
|
|
|
@ -325,14 +325,14 @@ defmodule LivebookWeb.Integration.Hub.EditLiveTest do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refute render(view) =~ deployment_group.name
|
|
||||||
|
|
||||||
view
|
view
|
||||||
|> element("#add-deployment-group")
|
|> element("#add-deployment-group")
|
||||||
|> render_click(%{})
|
|> render_click()
|
||||||
|
|
||||||
assert_patch(view, ~p"/hub/#{hub.id}/deployment-groups/new")
|
assert_patch(view, ~p"/hub/#{hub.id}/deployment-groups/new")
|
||||||
assert render(view) =~ "Add deployment group"
|
|
||||||
|
{:ok, view, html} = live(conn, ~p"/hub/#{hub.id}/deployment-groups/new")
|
||||||
|
assert html =~ "Add a new deployment group to"
|
||||||
|
|
||||||
view
|
view
|
||||||
|> element("#deployment-groups-form")
|
|> element("#deployment-groups-form")
|
||||||
|
@ -350,7 +350,7 @@ defmodule LivebookWeb.Integration.Hub.EditLiveTest do
|
||||||
%DeploymentGroup{name: "TEAM_ADD_DEPLOYMENT_GROUP"} = deployment_group}
|
%DeploymentGroup{name: "TEAM_ADD_DEPLOYMENT_GROUP"} = deployment_group}
|
||||||
|
|
||||||
%{"success" => "Deployment group TEAM_ADD_DEPLOYMENT_GROUP added successfully"} =
|
%{"success" => "Deployment group TEAM_ADD_DEPLOYMENT_GROUP added successfully"} =
|
||||||
assert_redirect(view, "/hub/#{hub.id}")
|
assert_redirect(view, "/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}")
|
||||||
|
|
||||||
{:ok, view, _html} = live(conn, ~p"/hub/#{hub.id}")
|
{:ok, view, _html} = live(conn, ~p"/hub/#{hub.id}")
|
||||||
|
|
||||||
|
@ -386,7 +386,12 @@ defmodule LivebookWeb.Integration.Hub.EditLiveTest do
|
||||||
|> render_click(%{"deployment_group_name" => deployment_group.id})
|
|> render_click(%{"deployment_group_name" => deployment_group.id})
|
||||||
|
|
||||||
assert_patch(view, ~p"/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}")
|
assert_patch(view, ~p"/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}")
|
||||||
assert render(view) =~ "Edit deployment group"
|
|
||||||
|
{: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
|
view
|
||||||
|> element("#deployment-groups-form")
|
|> element("#deployment-groups-form")
|
||||||
|
@ -405,7 +410,7 @@ defmodule LivebookWeb.Integration.Hub.EditLiveTest do
|
||||||
assert_receive {:deployment_group_updated, ^updated_deployment_group}
|
assert_receive {:deployment_group_updated, ^updated_deployment_group}
|
||||||
|
|
||||||
%{"success" => "Deployment group TEAM_EDIT_DEPLOYMENT_GROUP updated successfully"} =
|
%{"success" => "Deployment group TEAM_EDIT_DEPLOYMENT_GROUP updated successfully"} =
|
||||||
assert_redirect(view, "/hub/#{hub.id}")
|
assert_redirect(view, "/hub/#{hub.id}/deployment-groups/edit/#{deployment_group.id}")
|
||||||
|
|
||||||
{:ok, view, _html} = live(conn, ~p"/hub/#{hub.id}")
|
{:ok, view, _html} = live(conn, ~p"/hub/#{hub.id}")
|
||||||
assert render(element(view, "#hub-deployment-groups-list")) =~ deployment_group.name
|
assert render(element(view, "#hub-deployment-groups-list")) =~ deployment_group.name
|
||||||
|
|
|
@ -51,7 +51,8 @@ defmodule Livebook.Factory do
|
||||||
%Livebook.Secrets.Secret{
|
%Livebook.Secrets.Secret{
|
||||||
name: "FOO",
|
name: "FOO",
|
||||||
value: "123",
|
value: "123",
|
||||||
hub_id: Livebook.Hubs.Personal.id()
|
hub_id: Livebook.Hubs.Personal.id(),
|
||||||
|
deployment_group_id: nil
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue