mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-09-11 15:34:37 +08:00
Final touchups to secrets (#1464)
* Use font-mono on secret names * Unify error handling with changesets * Unify put_env/delete_env as set_env/unset_env * Review text and descriptions
This commit is contained in:
parent
195bc502fd
commit
950982304d
15 changed files with 193 additions and 205 deletions
|
@ -124,7 +124,7 @@
|
|||
}
|
||||
|
||||
.switch-button {
|
||||
@apply relative inline-block w-14 h-7 mr-2 select-none;
|
||||
@apply relative inline-block w-14 h-7 select-none;
|
||||
}
|
||||
|
||||
.switch-button--disabled {
|
||||
|
|
|
@ -53,11 +53,9 @@ defmodule Livebook.Hubs do
|
|||
@spec save_hub(Provider.t()) :: Provider.t()
|
||||
def save_hub(struct) do
|
||||
attributes = struct |> Map.from_struct() |> Map.to_list()
|
||||
|
||||
with :ok <- Storage.insert(@namespace, struct.id, attributes),
|
||||
:ok <- broadcast_hubs_change() do
|
||||
struct
|
||||
end
|
||||
:ok = Storage.insert(@namespace, struct.id, attributes)
|
||||
:ok = broadcast_hubs_change()
|
||||
struct
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
|
|
@ -62,7 +62,7 @@ defmodule Livebook.Hubs.FlyClient do
|
|||
end
|
||||
end
|
||||
|
||||
def put_secrets(%Fly{access_token: access_token, application_id: application_id}, secrets) do
|
||||
def set_secrets(%Fly{access_token: access_token, application_id: application_id}, secrets) do
|
||||
mutation = """
|
||||
mutation($input: SetSecretsInput!) {
|
||||
setSecrets(input: $input) {
|
||||
|
@ -85,7 +85,7 @@ defmodule Livebook.Hubs.FlyClient do
|
|||
end
|
||||
end
|
||||
|
||||
def delete_secrets(%Fly{access_token: access_token, application_id: application_id}, keys) do
|
||||
def unset_secrets(%Fly{access_token: access_token, application_id: application_id}, keys) do
|
||||
mutation = """
|
||||
mutation($input: UnsetSecretsInput!) {
|
||||
unsetSecrets(input: $input) {
|
||||
|
|
|
@ -36,25 +36,23 @@ defmodule Livebook.Secrets do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Sets the given secret.
|
||||
Validates a secret map and either returns a struct struct or changeset.
|
||||
"""
|
||||
@spec set_secret(Secret.t() | %Secret{}, map()) ::
|
||||
{:ok, Secret.t()} | {:error, Ecto.Changeset.t()}
|
||||
def set_secret(%Secret{} = secret \\ %Secret{}, attrs) do
|
||||
changeset = Secret.changeset(secret, attrs)
|
||||
|
||||
with {:ok, secret} <- apply_action(changeset, :insert) do
|
||||
save_secret(secret)
|
||||
end
|
||||
@spec validate_secret(map()) :: {:ok, Secret.t()} | {:error, Ecto.Changeset.t()}
|
||||
def validate_secret(attrs) do
|
||||
changeset = Secret.changeset(%Secret{}, attrs)
|
||||
apply_action(changeset, :validate)
|
||||
end
|
||||
|
||||
defp save_secret(secret) do
|
||||
@doc """
|
||||
Stores the given secret as is, without validation.
|
||||
"""
|
||||
@spec set_secret(Secret.t()) :: Secret.t()
|
||||
def set_secret(secret) do
|
||||
attributes = secret |> Map.from_struct() |> Map.to_list()
|
||||
|
||||
with :ok <- Storage.insert(:secrets, secret.name, attributes),
|
||||
:ok <- broadcast_secrets_change({:set_secret, secret}) do
|
||||
{:ok, secret}
|
||||
end
|
||||
:ok = Storage.insert(:secrets, secret.name, attributes)
|
||||
:ok = broadcast_secrets_change({:set_secret, secret})
|
||||
secret
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -18,7 +18,7 @@ defmodule Livebook.Secrets.Secret do
|
|||
|> cast(attrs, [:name, :value])
|
||||
|> update_change(:name, &String.upcase/1)
|
||||
|> validate_format(:name, ~r/^\w+$/,
|
||||
message: "should contain only alphanumeric and underscore"
|
||||
message: "should contain only alphanumeric characters and underscore"
|
||||
)
|
||||
|> validate_required([:name, :value])
|
||||
end
|
||||
|
|
|
@ -505,17 +505,17 @@ defmodule Livebook.Session do
|
|||
@doc """
|
||||
Sends a secret addition request to the server.
|
||||
"""
|
||||
@spec put_secret(pid(), map()) :: :ok
|
||||
def put_secret(pid, secret) do
|
||||
GenServer.cast(pid, {:put_secret, self(), secret})
|
||||
@spec set_secret(pid(), map()) :: :ok
|
||||
def set_secret(pid, secret) do
|
||||
GenServer.cast(pid, {:set_secret, self(), secret})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends a secret deletion request to the server.
|
||||
"""
|
||||
@spec delete_secret(pid(), map()) :: :ok
|
||||
def delete_secret(pid, secret_name) do
|
||||
GenServer.cast(pid, {:delete_secret, self(), secret_name})
|
||||
@spec unset_secret(pid(), map()) :: :ok
|
||||
def unset_secret(pid, secret_name) do
|
||||
GenServer.cast(pid, {:unset_secret, self(), secret_name})
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -980,15 +980,15 @@ defmodule Livebook.Session do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_cast({:put_secret, client_pid, secret}, state) do
|
||||
def handle_cast({:set_secret, client_pid, secret}, state) do
|
||||
client_id = client_id(state, client_pid)
|
||||
operation = {:put_secret, client_id, secret}
|
||||
operation = {:set_secret, client_id, secret}
|
||||
{:noreply, handle_operation(state, operation)}
|
||||
end
|
||||
|
||||
def handle_cast({:delete_secret, client_pid, secret_name}, state) do
|
||||
def handle_cast({:unset_secret, client_pid, secret_name}, state) do
|
||||
client_id = client_id(state, client_pid)
|
||||
operation = {:delete_secret, client_id, secret_name}
|
||||
operation = {:unset_secret, client_id, secret_name}
|
||||
{:noreply, handle_operation(state, operation)}
|
||||
end
|
||||
|
||||
|
@ -1451,12 +1451,12 @@ defmodule Livebook.Session do
|
|||
state
|
||||
end
|
||||
|
||||
defp after_operation(state, _prev_state, {:put_secret, _client_id, secret}) do
|
||||
defp after_operation(state, _prev_state, {:set_secret, _client_id, secret}) do
|
||||
if Runtime.connected?(state.data.runtime), do: set_runtime_secret(state, secret)
|
||||
state
|
||||
end
|
||||
|
||||
defp after_operation(state, _prev_state, {:delete_secret, _client_id, secret_name}) do
|
||||
defp after_operation(state, _prev_state, {:unset_secret, _client_id, secret_name}) do
|
||||
if Runtime.connected?(state.data.runtime), do: delete_runtime_secrets(state, [secret_name])
|
||||
state
|
||||
end
|
||||
|
|
|
@ -193,8 +193,8 @@ defmodule Livebook.Session.Data do
|
|||
| {:set_file, client_id(), FileSystem.File.t() | nil}
|
||||
| {:set_autosave_interval, client_id(), non_neg_integer() | nil}
|
||||
| {:mark_as_not_dirty, client_id()}
|
||||
| {:put_secret, client_id(), secret()}
|
||||
| {:delete_secret, client_id(), String.t()}
|
||||
| {:set_secret, client_id(), secret()}
|
||||
| {:unset_secret, client_id(), String.t()}
|
||||
|
||||
@type action ::
|
||||
:connect_runtime
|
||||
|
@ -764,17 +764,17 @@ defmodule Livebook.Session.Data do
|
|||
|> wrap_ok()
|
||||
end
|
||||
|
||||
def apply_operation(data, {:put_secret, _client_id, secret}) do
|
||||
def apply_operation(data, {:set_secret, _client_id, secret}) do
|
||||
data
|
||||
|> with_actions()
|
||||
|> put_secret(secret)
|
||||
|> set_secret(secret)
|
||||
|> wrap_ok()
|
||||
end
|
||||
|
||||
def apply_operation(data, {:delete_secret, _client_id, secret_name}) do
|
||||
def apply_operation(data, {:unset_secret, _client_id, secret_name}) do
|
||||
data
|
||||
|> with_actions()
|
||||
|> delete_secret(secret_name)
|
||||
|> unset_secret(secret_name)
|
||||
|> wrap_ok()
|
||||
end
|
||||
|
||||
|
@ -1515,12 +1515,12 @@ defmodule Livebook.Session.Data do
|
|||
end
|
||||
end
|
||||
|
||||
defp put_secret({data, _} = data_actions, secret) do
|
||||
defp set_secret({data, _} = data_actions, secret) do
|
||||
secrets = Map.put(data.secrets, secret.name, secret.value)
|
||||
set!(data_actions, secrets: secrets)
|
||||
end
|
||||
|
||||
defp delete_secret({data, _} = data_actions, secret_name) do
|
||||
defp unset_secret({data, _} = data_actions, secret_name) do
|
||||
secrets = Map.delete(data.secrets, secret_name)
|
||||
set!(data_actions, secrets: secrets)
|
||||
end
|
||||
|
|
|
@ -159,17 +159,15 @@ defmodule Livebook.Settings do
|
|||
changeset = EnvVar.changeset(env_var, attrs)
|
||||
|
||||
with {:ok, env_var} <- apply_action(changeset, :insert) do
|
||||
save_env_var(env_var)
|
||||
{:ok, save_env_var(env_var)}
|
||||
end
|
||||
end
|
||||
|
||||
defp save_env_var(env_var) do
|
||||
attributes = env_var |> Map.from_struct() |> Map.to_list()
|
||||
|
||||
with :ok <- Storage.insert(:env_vars, env_var.name, attributes),
|
||||
:ok <- broadcast_env_vars_change({:env_var_set, env_var}) do
|
||||
{:ok, env_var}
|
||||
end
|
||||
:ok = Storage.insert(:env_vars, env_var.name, attributes)
|
||||
:ok = broadcast_env_vars_change({:env_var_set, env_var})
|
||||
env_var
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -161,7 +161,7 @@ defmodule LivebookWeb.Hub.Edit.FlyComponent do
|
|||
env_operation = attrs["operation"]
|
||||
attrs = %{"key" => attrs["name"], "value" => attrs["value"]}
|
||||
|
||||
case FlyClient.put_secrets(socket.assigns.hub, [attrs]) do
|
||||
case FlyClient.set_secrets(socket.assigns.hub, [attrs]) do
|
||||
{:ok, _} ->
|
||||
message =
|
||||
if env_operation == "new",
|
||||
|
@ -192,7 +192,7 @@ defmodule LivebookWeb.Hub.Edit.FlyComponent do
|
|||
end
|
||||
|
||||
def handle_event("delete_env_var", %{"env_var" => key}, socket) do
|
||||
case FlyClient.delete_secrets(socket.assigns.hub, [key]) do
|
||||
case FlyClient.unset_secrets(socket.assigns.hub, [key]) do
|
||||
{:ok, _} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|
|
|
@ -57,7 +57,9 @@ defmodule LivebookWeb.SessionLive do
|
|||
data_view: data_to_view(data),
|
||||
autofocus_cell_id: autofocus_cell_id(data.notebook),
|
||||
page_title: get_page_title(data.notebook.name),
|
||||
livebook_secrets: Secrets.fetch_secrets() |> Map.new(&{&1.name, &1.value})
|
||||
livebook_secrets: Secrets.fetch_secrets() |> Map.new(&{&1.name, &1.value}),
|
||||
select_secret_ref: nil,
|
||||
select_secret_options: nil
|
||||
)
|
||||
|> assign_private(data: data)
|
||||
|> prune_outputs()
|
||||
|
@ -557,48 +559,56 @@ defmodule LivebookWeb.SessionLive do
|
|||
defp secrets_list(assigns) do
|
||||
~H"""
|
||||
<div class="flex flex-col grow">
|
||||
<h3 class="uppercase text-sm font-semibold text-gray-500">
|
||||
Secrets
|
||||
</h3>
|
||||
<span class="mt-4 text-sm font-semibold text-gray-500">Available to this notebook</span>
|
||||
<div class="flex flex-col mt-4 space-y-4">
|
||||
<%= for {secret_name, _} <- session_only_secrets(@data_view.secrets, @livebook_secrets) do %>
|
||||
<div class="flex justify-between items-center text-gray-500">
|
||||
<span class="text-sm break-all">
|
||||
<%= secret_name %>
|
||||
</span>
|
||||
<span class="rounded-full bg-gray-200 px-2 text-xs text-gray-600">
|
||||
Session
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="w-full border-t border-gray-300 py-1"></div>
|
||||
<div class="flex justify-between mt-4">
|
||||
<span class="text-sm font-semibold text-gray-500">Stored in your Livebook</span>
|
||||
<span class="text-sm font-light text-gray-500">On session</span>
|
||||
</div>
|
||||
<%= for {secret_name, secret_value} = secret <- Enum.sort(@livebook_secrets) do %>
|
||||
<div class="flex justify-between items-center text-gray-500">
|
||||
<span class="text-sm break-all">
|
||||
<%= secret_name %>
|
||||
</span>
|
||||
<.switch_checkbox
|
||||
name="toggle_secret"
|
||||
checked={is_secret_on_session?(secret, @data_view.secrets)}
|
||||
phx-click="toggle_secret"
|
||||
phx-value-secret_name={secret_name}
|
||||
phx-value-secret_value={secret_value}
|
||||
/>
|
||||
</div>
|
||||
<% end %>
|
||||
<div>
|
||||
<h3 class="uppercase text-sm font-semibold text-gray-500">
|
||||
Secrets
|
||||
</h3>
|
||||
<span class="text-sm text-gray-500">Available only to this session</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col space-y-4 mt-6">
|
||||
<%= for {secret_name, _} <- session_only_secrets(@data_view.secrets, @livebook_secrets) do %>
|
||||
<div class="flex justify-between items-center text-gray-500">
|
||||
<span class="text-sm font-mono break-all">
|
||||
<%= secret_name %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= live_patch to: Routes.session_path(@socket, :secrets, @session.id),
|
||||
class: "inline-flex items-center justify-center p-8 py-1 mt-6 space-x-2 text-sm font-medium text-gray-500 border border-gray-400 border-dashed rounded-xl hover:bg-gray-100",
|
||||
aria_label: "add secret",
|
||||
role: "button" do %>
|
||||
<.remix_icon icon="add-line" class="text-lg align-center" />
|
||||
<span>New secret</span>
|
||||
<% end %>
|
||||
|
||||
<div class="mt-16">
|
||||
<h3 class="uppercase text-sm font-semibold text-gray-500">
|
||||
App secrets
|
||||
</h3>
|
||||
<span class="text-sm text-gray-500">Toggle to share with this session</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-y-4 mt-6">
|
||||
<%= for {secret_name, secret_value} = secret <- Enum.sort(@livebook_secrets) do %>
|
||||
<div class="flex justify-between items-center text-gray-500">
|
||||
<span class="text-sm font-mono break-all">
|
||||
<%= secret_name %>
|
||||
</span>
|
||||
<.switch_checkbox
|
||||
name="toggle_secret"
|
||||
checked={is_secret_on_session?(secret, @data_view.secrets)}
|
||||
phx-click="toggle_secret"
|
||||
phx-value-secret_name={secret_name}
|
||||
phx-value-secret_value={secret_value}
|
||||
/>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<%= live_patch to: Routes.session_path(@socket, :secrets, @session.id),
|
||||
class: "inline-flex items-center justify-center p-8 py-1 mt-8 space-x-2 text-sm font-medium text-gray-500 border border-gray-400 border-dashed rounded-xl hover:bg-gray-100",
|
||||
aria_label: "add secret",
|
||||
role: "button" do %>
|
||||
<.remix_icon icon="add-line" class="text-lg align-center" />
|
||||
<span>New secret</span>
|
||||
<% end %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
@ -783,13 +793,19 @@ defmodule LivebookWeb.SessionLive do
|
|||
|
||||
def handle_params(params, _url, socket)
|
||||
when socket.assigns.live_action == :secrets do
|
||||
{:noreply,
|
||||
assign(socket,
|
||||
prefill_secret_name: params["secret_name"] || params["preselect_name"],
|
||||
select_secret_ref: if(params["preselect_name"], do: socket.assigns.select_secret_ref),
|
||||
select_secret_options:
|
||||
if(params["preselect_name"], do: socket.assigns.select_secret_options)
|
||||
)}
|
||||
socket =
|
||||
if params["preselect_name"] do
|
||||
assign(socket, prefill_secret_name: params["preselect_name"])
|
||||
else
|
||||
# Erase any previously stored reference
|
||||
assign(socket,
|
||||
prefill_secret_name: params["secret_name"],
|
||||
select_secret_ref: nil,
|
||||
select_secret_options: nil
|
||||
)
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_params(_params, _url, socket) do
|
||||
|
@ -1179,12 +1195,12 @@ defmodule LivebookWeb.SessionLive do
|
|||
socket
|
||||
) do
|
||||
secret = %{name: secret_name, value: secret_value}
|
||||
Livebook.Session.put_secret(socket.assigns.session.pid, secret)
|
||||
Livebook.Session.set_secret(socket.assigns.session.pid, secret)
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("toggle_secret", %{"secret-name" => secret_name}, socket) do
|
||||
Livebook.Session.delete_secret(socket.assigns.session.pid, secret_name)
|
||||
Livebook.Session.unset_secret(socket.assigns.session.pid, secret_name)
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
else
|
||||
assign(socket,
|
||||
data: %{"name" => prefill_form, "value" => "", "store" => "session"},
|
||||
errors: [{"value", {"can't be blank", []}}],
|
||||
title: title(socket),
|
||||
grant_access: must_grant_access(socket),
|
||||
has_prefill: prefill_form != ""
|
||||
|
@ -51,7 +52,7 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
<%= for {secret_name, _} <- livebook_only_secrets(@secrets, @livebook_secrets) do %>
|
||||
<.secret_with_badge
|
||||
secret_name={secret_name}
|
||||
stored="livebook"
|
||||
stored="Livebook"
|
||||
action="select_livebook_secret"
|
||||
active={false}
|
||||
target={@myself}
|
||||
|
@ -76,7 +77,7 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
phx-change="validate"
|
||||
autocomplete="off"
|
||||
phx-target={@myself}
|
||||
errors={data_errors(@data)}
|
||||
errors={@errors}
|
||||
class="basis-1/2 grow"
|
||||
>
|
||||
<div class="flex flex-col space-y-4">
|
||||
|
@ -106,13 +107,13 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
) %>
|
||||
</.input_wrapper>
|
||||
<div>
|
||||
<span class="text-base font-medium text-gray-900">Store</span>
|
||||
<div class="mt-2 space-y-1">
|
||||
<div class="input-label">Storage</div>
|
||||
<div class="my-2 space-y-1 text-sm">
|
||||
<%= label class: "flex items-center gap-2 text-gray-600" do %>
|
||||
<%= radio_button(f, :store, "session", checked: @data["store"] == "session") %> Session
|
||||
<%= radio_button(f, :store, "session", checked: @data["store"] == "session") %> only this session
|
||||
<% end %>
|
||||
<%= label class: "flex items-center gap-2 text-gray-600" do %>
|
||||
<%= radio_button(f, :store, "livebook", checked: @data["store"] == "livebook") %> Notebook
|
||||
<%= radio_button(f, :store, "livebook", checked: @data["store"] == "livebook") %> in the Livebook app
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -134,25 +135,27 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
~H"""
|
||||
<div
|
||||
role="button"
|
||||
class={
|
||||
class={[
|
||||
"flex justify-between w-full font-mono text-sm p-2 border-b cursor-pointer",
|
||||
if @active do
|
||||
"flex justify-between w-full bg-blue-100 text-sm text-blue-700 p-2 border-b cursor-pointer"
|
||||
"bg-blue-100 text-blue-700"
|
||||
else
|
||||
"flex justify-between w-full text-sm text-gray-700 p-2 border-b cursor-pointer hover:bg-gray-100"
|
||||
"text-gray-700 hover:bg-gray-100"
|
||||
end
|
||||
}
|
||||
]}
|
||||
phx-value-secret_name={@secret_name}
|
||||
phx-target={@target}
|
||||
phx-click={@action}
|
||||
>
|
||||
<%= @secret_name %>
|
||||
<span class={
|
||||
<span class={[
|
||||
"inline-flex items-center font-sans rounded-full px-2.5 py-0.5 text-xs font-medium bg-gray-100",
|
||||
if @active do
|
||||
"inline-flex items-center rounded-full bg-indigo-100 px-2.5 py-0.5 text-xs font-medium text-blue-800"
|
||||
"bg-indigo-100 text-blue-800"
|
||||
else
|
||||
"inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-800"
|
||||
"bg-gray-100 text-gray-800"
|
||||
end
|
||||
}>
|
||||
]}>
|
||||
<%= if @active do %>
|
||||
<svg class="-ml-0.5 mr-1.5 h-2 w-2 text-blue-400" fill="currentColor" viewBox="0 0 8 8">
|
||||
<circle cx="4" cy="4" r="3" />
|
||||
|
@ -168,7 +171,7 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
~H"""
|
||||
<div>
|
||||
<div class="mx-auto">
|
||||
<div class="rounded-lg bg-blue-600 p-2 shadow-sm">
|
||||
<div class="rounded-lg bg-blue-600 py-1 px-4 shadow-sm">
|
||||
<div class="flex flex-wrap items-center justify-between">
|
||||
<div class="flex w-0 flex-1 items-center">
|
||||
<.remix_icon
|
||||
|
@ -176,8 +179,9 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
class="align-middle text-2xl flex text-gray-100 rounded-lg py-2"
|
||||
/>
|
||||
<span class="ml-2 text-sm font-normal text-gray-100">
|
||||
The secret <span class="font-semibold text-white"><%= @grant_access %></span>
|
||||
needs to be made available to the session
|
||||
There is a secret named
|
||||
<span class="font-semibold text-white"><%= @grant_access %></span>
|
||||
in your Livebook app. Allow this session to access it?
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
|
@ -197,23 +201,25 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
|
||||
@impl true
|
||||
def handle_event("save", %{"data" => data}, socket) do
|
||||
if data_errors(data) == [] do
|
||||
secret_name = String.upcase(data["name"])
|
||||
secret = %{name: secret_name, value: data["value"]}
|
||||
store = data["store"]
|
||||
assigns = socket.assigns
|
||||
|
||||
put_secret(socket.assigns.session.pid, secret, store)
|
||||
case Livebook.Secrets.validate_secret(data) do
|
||||
{:ok, secret} ->
|
||||
store = data["store"]
|
||||
|
||||
if store == "livebook" &&
|
||||
(socket.assigns.select_secret_ref ||
|
||||
{secret.name, socket.assigns.livebook_secrets[secret.name]} in socket.assigns.secrets) do
|
||||
put_secret(socket.assigns.session.pid, secret, "session")
|
||||
end
|
||||
set_secret(assigns.session.pid, secret, store)
|
||||
|
||||
{:noreply,
|
||||
socket |> push_patch(to: socket.assigns.return_to) |> push_secret_selected(secret_name)}
|
||||
else
|
||||
{:noreply, assign(socket, data: data)}
|
||||
if store == "livebook" &&
|
||||
(assigns.select_secret_ref ||
|
||||
{secret.name, assigns.livebook_secrets[secret.name]} in assigns.secrets) do
|
||||
set_secret(assigns.session.pid, secret, "session")
|
||||
end
|
||||
|
||||
{:noreply,
|
||||
socket |> push_patch(to: assigns.return_to) |> push_secret_selected(secret.name)}
|
||||
|
||||
{:error, changeset} ->
|
||||
{:noreply, assign(socket, errors: changeset.errors)}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -230,7 +236,12 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
end
|
||||
|
||||
def handle_event("validate", %{"data" => data}, socket) do
|
||||
{:noreply, assign(socket, data: data)}
|
||||
socket = assign(socket, data: data)
|
||||
|
||||
case Livebook.Secrets.validate_secret(data) do
|
||||
{:ok, _} -> {:noreply, assign(socket, errors: [])}
|
||||
{:error, changeset} -> {:noreply, assign(socket, errors: changeset.errors)}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("grant_access", %{"secret_name" => secret_name}, socket) do
|
||||
|
@ -240,27 +251,6 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
socket |> push_patch(to: socket.assigns.return_to) |> push_secret_selected(secret_name)}
|
||||
end
|
||||
|
||||
defp data_errors(data) do
|
||||
Enum.flat_map(data, fn {key, value} ->
|
||||
if error = data_error(key, value) do
|
||||
[{String.to_existing_atom(key), {error, []}}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp data_error("name", value) do
|
||||
cond do
|
||||
String.match?(value, ~r/^\w+$/) -> nil
|
||||
value == "" -> "can't be blank"
|
||||
true -> "is invalid"
|
||||
end
|
||||
end
|
||||
|
||||
defp data_error("value", ""), do: "can't be blank"
|
||||
defp data_error(_key, _value), do: nil
|
||||
|
||||
defp push_secret_selected(%{assigns: %{select_secret_ref: nil}} = socket, _), do: socket
|
||||
|
||||
defp push_secret_selected(%{assigns: %{select_secret_ref: ref}} = socket, secret_name) do
|
||||
|
@ -289,13 +279,13 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
defp title(%{assigns: %{select_secret_options: %{"title" => title}}}), do: title
|
||||
defp title(_), do: "Select secret"
|
||||
|
||||
defp put_secret(pid, secret, "session"), do: Livebook.Session.put_secret(pid, secret)
|
||||
defp put_secret(_pid, secret, "livebook"), do: Livebook.Secrets.set_secret(secret)
|
||||
defp set_secret(pid, secret, "session"), do: Livebook.Session.set_secret(pid, secret)
|
||||
defp set_secret(_pid, secret, "livebook"), do: Livebook.Secrets.set_secret(secret)
|
||||
|
||||
defp grant_access(secret_name, socket) do
|
||||
secret_value = socket.assigns.livebook_secrets[secret_name]
|
||||
secret = %{name: secret_name, value: secret_value}
|
||||
put_secret(socket.assigns.session.pid, secret, "session")
|
||||
set_secret(socket.assigns.session.pid, secret, "session")
|
||||
end
|
||||
|
||||
defp livebook_only_secrets(secrets, livebook_secrets) do
|
||||
|
|
|
@ -115,7 +115,7 @@ defmodule Livebook.Hubs.FlyClientTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "put_secrets/2" do
|
||||
describe "set_secrets/2" do
|
||||
test "puts a list of secrets inside application", %{bypass: bypass} do
|
||||
secrets = [
|
||||
%{
|
||||
|
@ -135,7 +135,7 @@ defmodule Livebook.Hubs.FlyClientTest do
|
|||
end)
|
||||
|
||||
hub = build(:fly)
|
||||
assert {:ok, ^secrets} = FlyClient.put_secrets(hub, [%{key: "FOO", value: "BAR"}])
|
||||
assert {:ok, ^secrets} = FlyClient.set_secrets(hub, [%{key: "FOO", value: "BAR"}])
|
||||
end
|
||||
|
||||
test "returns error when input is invalid", %{bypass: bypass} do
|
||||
|
@ -172,7 +172,7 @@ defmodule Livebook.Hubs.FlyClientTest do
|
|||
end)
|
||||
|
||||
hub = build(:fly)
|
||||
assert {:error, ^message} = FlyClient.put_secrets(hub, [%{key: "FOO", Value: "BAR"}])
|
||||
assert {:error, ^message} = FlyClient.set_secrets(hub, [%{key: "FOO", Value: "BAR"}])
|
||||
end
|
||||
|
||||
test "returns unauthorized when token is invalid", %{bypass: bypass} do
|
||||
|
@ -188,11 +188,11 @@ defmodule Livebook.Hubs.FlyClientTest do
|
|||
hub = build(:fly)
|
||||
|
||||
assert {:error, "request failed with code: UNAUTHORIZED"} =
|
||||
FlyClient.put_secrets(hub, [%{key: "FOO", value: "BAR"}])
|
||||
FlyClient.set_secrets(hub, [%{key: "FOO", value: "BAR"}])
|
||||
end
|
||||
end
|
||||
|
||||
describe "delete_secrets/2" do
|
||||
describe "unset_secrets/2" do
|
||||
test "deletes a list of secrets inside application", %{bypass: bypass} do
|
||||
response = %{"data" => %{"unsetSecrets" => %{"app" => %{"secrets" => []}}}}
|
||||
|
||||
|
@ -203,7 +203,7 @@ defmodule Livebook.Hubs.FlyClientTest do
|
|||
end)
|
||||
|
||||
hub = build(:fly)
|
||||
assert {:ok, []} = FlyClient.delete_secrets(hub, ["FOO"])
|
||||
assert {:ok, []} = FlyClient.unset_secrets(hub, ["FOO"])
|
||||
end
|
||||
|
||||
test "returns unauthorized when token is invalid", %{bypass: bypass} do
|
||||
|
@ -219,7 +219,7 @@ defmodule Livebook.Hubs.FlyClientTest do
|
|||
hub = build(:fly)
|
||||
|
||||
assert {:error, "request failed with code: UNAUTHORIZED"} =
|
||||
FlyClient.delete_secrets(hub, ["FOO"])
|
||||
FlyClient.unset_secrets(hub, ["FOO"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule Livebook.SecretsTest do
|
|||
alias Livebook.Secrets.Secret
|
||||
|
||||
test "fetch secrets" do
|
||||
Secrets.set_secret(%{name: "FOO", value: "111"})
|
||||
Secrets.set_secret(%Secret{name: "FOO", value: "111"})
|
||||
assert %Secret{name: "FOO", value: "111"} in Secrets.fetch_secrets()
|
||||
|
||||
Secrets.unset_secret("FOO")
|
||||
|
@ -14,7 +14,7 @@ defmodule Livebook.SecretsTest do
|
|||
end
|
||||
|
||||
test "fetch an specific secret" do
|
||||
secret = %{name: "FOO", value: "111"}
|
||||
secret = %Secret{name: "FOO", value: "111"}
|
||||
Secrets.set_secret(secret)
|
||||
|
||||
assert_raise Livebook.Storage.NotFoundError,
|
||||
|
@ -30,41 +30,28 @@ defmodule Livebook.SecretsTest do
|
|||
test "secret_exists?/1" do
|
||||
Secrets.unset_secret("FOO")
|
||||
refute Secrets.secret_exists?("FOO")
|
||||
Secrets.set_secret(%{name: "FOO", value: "111"})
|
||||
Secrets.set_secret(%Secret{name: "FOO", value: "111"})
|
||||
assert Secrets.secret_exists?("FOO")
|
||||
Secrets.unset_secret("FOO")
|
||||
end
|
||||
|
||||
describe "set_secret/1" do
|
||||
test "creates and stores a secret" do
|
||||
describe "validate_secret/1" do
|
||||
test "returns a valid secret" do
|
||||
attrs = %{name: "FOO", value: "111"}
|
||||
assert {:ok, secret} = Secrets.set_secret(attrs)
|
||||
|
||||
assert {:ok, secret} = Secrets.validate_secret(attrs)
|
||||
assert attrs.name == secret.name
|
||||
assert attrs.value == secret.value
|
||||
|
||||
Secrets.unset_secret(secret.name)
|
||||
end
|
||||
|
||||
test "updates an stored secret" do
|
||||
secret = %Secret{name: "FOO", value: "111"}
|
||||
attrs = %{value: "222"}
|
||||
assert {:ok, updated_secret} = Secrets.set_secret(secret, attrs)
|
||||
|
||||
assert secret.name == updated_secret.name
|
||||
assert updated_secret.value == attrs.value
|
||||
|
||||
Secrets.unset_secret(secret.name)
|
||||
end
|
||||
|
||||
test "returns changeset error" do
|
||||
attrs = %{value: "111"}
|
||||
assert {:error, changeset} = Secrets.set_secret(attrs)
|
||||
assert {:error, changeset} = Secrets.validate_secret(attrs)
|
||||
assert "can't be blank" in errors_on(changeset).name
|
||||
attrs = %{name: "@inavalid", value: "111"}
|
||||
|
||||
assert {:error, changeset} = Secrets.set_secret(attrs)
|
||||
assert "should contain only alphanumeric and underscore" in errors_on(changeset).name
|
||||
assert {:error, changeset} = Secrets.validate_secret(attrs)
|
||||
|
||||
assert "should contain only alphanumeric characters and underscore" in errors_on(changeset).name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -205,10 +205,10 @@ defmodule LivebookWeb.Hub.EditLiveTest do
|
|||
response =
|
||||
cond do
|
||||
body["query"] =~ "setSecrets" ->
|
||||
put_secrets_response()
|
||||
set_secrets_response()
|
||||
|
||||
body["query"] =~ "unsetSecrets" ->
|
||||
delete_secrets_response()
|
||||
unset_secrets_response()
|
||||
|
||||
true ->
|
||||
Agent.get(agent_pid, fn
|
||||
|
@ -299,11 +299,11 @@ defmodule LivebookWeb.Hub.EditLiveTest do
|
|||
]
|
||||
end
|
||||
|
||||
defp put_secrets_response do
|
||||
defp set_secrets_response do
|
||||
%{"data" => %{"setSecrets" => %{"app" => %{"secrets" => secrets(:add)}}}}
|
||||
end
|
||||
|
||||
defp delete_secrets_response do
|
||||
defp unset_secrets_response do
|
||||
%{"data" => %{"unsetSecrets" => %{"app" => %{"secrets" => secrets(:mount)}}}}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|
||||
alias Livebook.{Sessions, Session, Settings, Runtime, Users, FileSystem}
|
||||
alias Livebook.Notebook.Cell
|
||||
alias Livebook.Secrets.Secret
|
||||
|
||||
setup do
|
||||
{:ok, session} = Sessions.create_session(notebook: Livebook.Notebook.new())
|
||||
|
@ -948,13 +949,13 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|> element(~s{form[phx-submit="save"]})
|
||||
|> render_submit(%{data: %{name: "bar", value: "456", store: "livebook"}})
|
||||
|
||||
assert %Livebook.Secrets.Secret{name: "BAR", value: "456"} in Livebook.Secrets.fetch_secrets()
|
||||
assert %Secret{name: "BAR", value: "456"} in Livebook.Secrets.fetch_secrets()
|
||||
end
|
||||
|
||||
test "sync secrets when they're equal", %{conn: conn, session: session} do
|
||||
Livebook.Secrets.set_secret(%{name: "FOO", value: "123"})
|
||||
Livebook.Secrets.set_secret(%Secret{name: "FOO", value: "123"})
|
||||
{:ok, view, _} = live(conn, "/sessions/#{session.id}/secrets")
|
||||
Session.put_secret(session.pid, %{name: "FOO", value: "123"})
|
||||
Session.set_secret(session.pid, %{name: "FOO", value: "123"})
|
||||
|
||||
view
|
||||
|> element(~s{form[phx-submit="save"]})
|
||||
|
@ -962,13 +963,13 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|
||||
assert %{secrets: %{"FOO" => "456"}} = Session.get_data(session.pid)
|
||||
|
||||
assert %Livebook.Secrets.Secret{name: "FOO", value: "456"} in Livebook.Secrets.fetch_secrets()
|
||||
assert %Secret{name: "FOO", value: "456"} in Livebook.Secrets.fetch_secrets()
|
||||
end
|
||||
|
||||
test "doesn't sync secrets when they are not the same", %{conn: conn, session: session} do
|
||||
Livebook.Secrets.set_secret(%{name: "FOO_BAR", value: "456"})
|
||||
Livebook.Secrets.set_secret(%Secret{name: "FOO_BAR", value: "456"})
|
||||
{:ok, view, _} = live(conn, "/sessions/#{session.id}/secrets")
|
||||
Session.put_secret(session.pid, %{name: "FOO_BAR", value: "123"})
|
||||
Session.set_secret(session.pid, %{name: "FOO_BAR", value: "123"})
|
||||
|
||||
view
|
||||
|> element(~s{form[phx-submit="save"]})
|
||||
|
@ -976,15 +977,15 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|
||||
assert %{secrets: %{"FOO_BAR" => "123"}} = Session.get_data(session.pid)
|
||||
|
||||
assert %Livebook.Secrets.Secret{name: "FOO_BAR", value: "999"} in Livebook.Secrets.fetch_secrets()
|
||||
assert %Secret{name: "FOO_BAR", value: "999"} in Livebook.Secrets.fetch_secrets()
|
||||
|
||||
refute %Livebook.Secrets.Secret{name: "FOO_BAR", value: "456"} in Livebook.Secrets.fetch_secrets()
|
||||
refute %Secret{name: "FOO_BAR", value: "456"} in Livebook.Secrets.fetch_secrets()
|
||||
end
|
||||
|
||||
test "never sync secrets when updating from session", %{conn: conn, session: session} do
|
||||
Livebook.Secrets.set_secret(%{name: "FOO", value: "123"})
|
||||
Livebook.Secrets.set_secret(%Secret{name: "FOO", value: "123"})
|
||||
{:ok, view, _} = live(conn, "/sessions/#{session.id}/secrets")
|
||||
Session.put_secret(session.pid, %{name: "FOO", value: "123"})
|
||||
Session.set_secret(session.pid, %{name: "FOO", value: "123"})
|
||||
|
||||
view
|
||||
|> element(~s{form[phx-submit="save"]})
|
||||
|
@ -992,9 +993,9 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|
||||
assert %{secrets: %{"FOO" => "456"}} = Session.get_data(session.pid)
|
||||
|
||||
refute %Livebook.Secrets.Secret{name: "FOO", value: "456"} in Livebook.Secrets.fetch_secrets()
|
||||
refute %Secret{name: "FOO", value: "456"} in Livebook.Secrets.fetch_secrets()
|
||||
|
||||
assert %Livebook.Secrets.Secret{name: "FOO", value: "123"} in Livebook.Secrets.fetch_secrets()
|
||||
assert %Secret{name: "FOO", value: "123"} in Livebook.Secrets.fetch_secrets()
|
||||
end
|
||||
|
||||
test "shows the 'Add secret' button for unavailable secrets", %{conn: conn, session: session} do
|
||||
|
|
Loading…
Add table
Reference in a new issue