Deployment group secrets (#2374)

Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com>
This commit is contained in:
Cristine Guadelupe 2023-12-06 18:53:49 -03:00 committed by GitHub
parent 839c326ab0
commit 8923e700d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 303 additions and 52 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

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

View file

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

View file

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

View 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

View file

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

View file

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

View file

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

View file

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