+
+
+
+ <.labeled_text label="Application ID">
+ <%= @hub.application_id %>
+
+ <.labeled_text label="Type">
+ Fly
+
-
+
-
-
-
- <%= text_input(f, :hub_color,
- class: "input",
- spellcheck: "false",
- maxlength: 7
- ) %>
-
+
+
+
+ Environment Variables
+
+
+
+ <%= for env_var <- @env_vars do %>
+ <.environment_variable_card myself={@myself} env_var={env_var} />
+ <% end %>
- <%= submit("Update Hub",
- class: "button-base button-blue",
- phx_disable_with: "Updating...",
- disabled: not @changeset.valid?
- ) %>
-
+
+ Add environment variable
+
+
+
+
+ <.environment_variable_modal
+ id="environment-variable-modal"
+ on_save={hide_modal("environment-variable-modal")}
+ data={@env_var_data}
+ valid?={@valid_env_var?}
+ myself={@myself}
+ />
+
+ """
+ end
+
+ defp environment_variable_card(assigns) do
+ ~H"""
+
@env_var["id"]}
+ class="flex items-center justify-between border border-gray-200 rounded-lg p-4"
+ >
+
+
+ <.labeled_text label="Name">
+ <%= @env_var["name"] %>
+
+
+
+
+ <.labeled_text label="Created at">
+ <%= @env_var["createdAt"] %>
+
+
+
+
+ <.menu id={"env-var-#{@env_var["id"]}-menu"}>
+ <:toggle>
+
+ <.remix_icon icon="more-2-fill" class="text-xl" />
+
+
+ <:content>
+ @env_var["id"] <> "-edit"}
+ type="button"
+ phx-click={
+ show_modal("environment-variable-modal")
+ |> JS.push("edit", value: %{env_var: @env_var})
+ }
+ phx-target={@myself}
+ role="menuitem"
+ class="menu-item text-gray-600"
+ >
+ <.remix_icon icon="file-edit-line" />
+ Edit
+
+ @env_var["id"] <> "-delete"}
+ type="button"
+ phx-click={
+ with_confirm(
+ JS.push("delete", value: %{env_var: @env_var}),
+ title: "Delete #{@env_var["name"]}",
+ description: "Are you sure you want to delete environment variable?",
+ confirm_text: "Delete",
+ confirm_icon: "delete-bin-6-line"
+ )
+ }
+ phx-target={@myself}
+ role="menuitem"
+ class="menu-item text-red-600"
+ >
+ <.remix_icon icon="delete-bin-line" />
+ Delete
+
+
+
+
"""
end
+ defp environment_variable_modal(assigns) do
+ ~H"""
+ <.modal id={@id} class="w-full max-w-lg">
+
+
+ Add environment variable
+
+
+
+ Enter the environment variable name and its value.
+
+ <.form
+ id="env-var-form"
+ let={f}
+ for={:env_var}
+ phx-submit={@on_save |> JS.push("save")}
+ phx-change="validate"
+ autocomplete="off"
+ phx-target={@myself}
+ >
+
+
+
+ Key (alphanumeric and underscore)
+
+ <%= text_input(f, :key,
+ value: @data["key"],
+ class: "input",
+ placeholder: "environment variable key",
+ autofocus: true,
+ aria_labelledby: "env-var-key",
+ spellcheck: "false"
+ ) %>
+
+
+
Value
+ <%= text_input(f, :value,
+ value: @data["value"],
+ class: "input",
+ placeholder: "environment variable value",
+ aria_labelledby: "env-var-value",
+ spellcheck: "false"
+ ) %>
+
+
+ <%= submit("Add environment variable",
+ class: "mt-5 button-base button-blue",
+ phx_disable_with: "Adding...",
+ disabled: not @valid?
+ ) %>
+
+
+
+
+ """
+ end
+
@impl true
def handle_event("randomize_color", _, socket) do
handle_event("validate", %{"fly" => %{"hub_color" => HexColor.random()}}, socket)
end
+ def handle_event("edit", %{"env_var" => %{"name" => name}}, socket) do
+ {:noreply, assign(socket, operation: :edit, env_var_data: %{"key" => name})}
+ end
+
+ def handle_event("delete", %{"env_var" => %{"name" => key}}, socket) do
+ case FlyClient.delete_secrets(socket.assigns.hub, [key]) do
+ {:ok, _} ->
+ {:noreply,
+ socket
+ |> put_flash(:success, "Environment variable deleted")
+ |> push_redirect(to: Routes.hub_path(socket, :edit, socket.assigns.hub.id))}
+
+ {:error, _} ->
+ {:noreply,
+ socket
+ |> put_flash(:error, "Failed to delete environment variable")
+ |> push_redirect(to: Routes.hub_path(socket, :edit, socket.assigns.hub.id))}
+ end
+ end
+
def handle_event("save", %{"fly" => params}, socket) do
case Fly.update_hub(socket.assigns.hub, params) do
{:ok, hub} ->
@@ -125,10 +306,49 @@ defmodule LivebookWeb.Hub.Edit.FlyComponent do
end
end
+ def handle_event("save", %{"env_var" => params}, socket) do
+ if socket.assigns.valid_env_var? do
+ case FlyClient.put_secrets(socket.assigns.hub, [params]) do
+ {:ok, _} ->
+ message =
+ if socket.assigns.operation == :new do
+ "Environment variable added"
+ else
+ "Environment variable updated"
+ end
+
+ {:noreply,
+ socket
+ |> put_flash(:success, message)
+ |> push_redirect(to: Routes.hub_path(socket, :edit, socket.assigns.hub.id))}
+
+ {:error, _} ->
+ message =
+ if socket.assigns.operation == :new do
+ "Failed to add environment variable"
+ else
+ "Failed to update environment variable"
+ end
+
+ {:noreply,
+ socket
+ |> put_flash(:error, message)
+ |> push_redirect(to: Routes.hub_path(socket, :edit, socket.assigns.hub.id))}
+ end
+ else
+ {:noreply, socket}
+ end
+ end
+
def handle_event("validate", %{"fly" => attrs}, socket) do
changeset = Fly.change_hub(socket.assigns.hub, attrs)
{:noreply, assign(socket, changeset: changeset)}
end
+ def handle_event("validate", %{"env_var" => attrs}, socket) do
+ valid? = String.match?(attrs["key"], ~r/^\w+$/) and attrs["value"] not in ["", nil]
+ {:noreply, assign(socket, valid_env_var?: valid?, env_var_data: attrs)}
+ end
+
defp hub_color(changeset), do: get_field(changeset, :hub_color)
end
diff --git a/lib/livebook_web/live/live_helpers.ex b/lib/livebook_web/live/live_helpers.ex
index 7cdf3e392..f6ba0288f 100644
--- a/lib/livebook_web/live/live_helpers.ex
+++ b/lib/livebook_web/live/live_helpers.ex
@@ -50,6 +50,7 @@ defmodule LivebookWeb.LiveHelpers do
<%= live_redirect("", to: @navigate, class: "hidden", id: "#{@id}-return") %>
<% end %>
"foo-app.fly.dev",
"platformVersion" => "nomad",
"deployed" => true,
- "status" => "running"
+ "status" => "running",
+ "secrets" => [
+ %{
+ "createdAt" => to_string(DateTime.utc_now()),
+ "digest" => to_string(Livebook.Utils.random_cookie()),
+ "id" => Livebook.Utils.random_short_id(),
+ "name" => "FOO"
+ }
+ ]
}
response = %{"data" => %{"app" => app}}
@@ -106,4 +114,112 @@ defmodule Livebook.Hubs.FlyClientTest do
assert {:error, "request failed with code: UNAUTHORIZED"} = FlyClient.fetch_app(hub)
end
end
+
+ describe "put_secrets/2" do
+ test "puts a list of secrets inside application", %{bypass: bypass} do
+ secrets = [
+ %{
+ "createdAt" => to_string(DateTime.utc_now()),
+ "digest" => to_string(Livebook.Utils.random_cookie()),
+ "id" => Livebook.Utils.random_short_id(),
+ "name" => "FOO"
+ }
+ ]
+
+ response = %{"data" => %{"setSecrets" => %{"app" => %{"secrets" => secrets}}}}
+
+ Bypass.expect_once(bypass, "POST", "/", fn conn ->
+ conn
+ |> Plug.Conn.put_resp_content_type("application/json")
+ |> Plug.Conn.resp(200, Jason.encode!(response))
+ end)
+
+ hub = build(:fly)
+ assert {:ok, ^secrets} = FlyClient.put_secrets(hub, [%{key: "FOO", value: "BAR"}])
+ end
+
+ test "returns error when input is invalid", %{bypass: bypass} do
+ message =
+ "Variable $input of type SetSecretsInput! was provided invalid value for secrets.0.Value (Field is not defined on SecretInput), secrets.0.value (Expected value to not be null)"
+
+ error = %{
+ "extensions" => %{
+ "problems" => [
+ %{
+ "explanation" => "Field is not defined on SecretInput",
+ "path" => ["secrets", 0, "Value"]
+ },
+ %{
+ "explanation" => "Expected value to not be null",
+ "path" => ["secrets", 0, "value"]
+ }
+ ],
+ "value" => %{
+ "appId" => "myfoo-test-livebook",
+ "secrets" => [%{"Value" => "BAR", "key" => "FOO"}]
+ }
+ },
+ "locations" => [%{"column" => 10, "line" => 1}],
+ "message" => message
+ }
+
+ response = %{"data" => nil, "errors" => [error]}
+
+ Bypass.expect_once(bypass, "POST", "/", fn conn ->
+ conn
+ |> Plug.Conn.put_resp_content_type("application/json")
+ |> Plug.Conn.resp(200, Jason.encode!(response))
+ end)
+
+ hub = build(:fly)
+ assert {:error, ^message} = FlyClient.put_secrets(hub, [%{key: "FOO", Value: "BAR"}])
+ end
+
+ test "returns unauthorized when token is invalid", %{bypass: bypass} do
+ error = %{"extensions" => %{"code" => "UNAUTHORIZED"}}
+ response = %{"data" => nil, "errors" => [error]}
+
+ Bypass.expect_once(bypass, "POST", "/", fn conn ->
+ conn
+ |> Plug.Conn.put_resp_content_type("application/json")
+ |> Plug.Conn.resp(200, Jason.encode!(response))
+ end)
+
+ hub = build(:fly)
+
+ assert {:error, "request failed with code: UNAUTHORIZED"} =
+ FlyClient.put_secrets(hub, [%{key: "FOO", value: "BAR"}])
+ end
+ end
+
+ describe "delete_secrets/2" do
+ test "deletes a list of secrets inside application", %{bypass: bypass} do
+ response = %{"data" => %{"unsetSecrets" => %{"app" => %{"secrets" => []}}}}
+
+ Bypass.expect_once(bypass, "POST", "/", fn conn ->
+ conn
+ |> Plug.Conn.put_resp_content_type("application/json")
+ |> Plug.Conn.resp(200, Jason.encode!(response))
+ end)
+
+ hub = build(:fly)
+ assert {:ok, []} = FlyClient.delete_secrets(hub, ["FOO"])
+ end
+
+ test "returns unauthorized when token is invalid", %{bypass: bypass} do
+ error = %{"extensions" => %{"code" => "UNAUTHORIZED"}}
+ response = %{"data" => nil, "errors" => [error]}
+
+ Bypass.expect_once(bypass, "POST", "/", fn conn ->
+ conn
+ |> Plug.Conn.put_resp_content_type("application/json")
+ |> Plug.Conn.resp(200, Jason.encode!(response))
+ end)
+
+ hub = build(:fly)
+
+ assert {:error, "request failed with code: UNAUTHORIZED"} =
+ FlyClient.delete_secrets(hub, ["FOO"])
+ end
+ end
end
diff --git a/test/livebook_web/live/hub/edit_live_test.exs b/test/livebook_web/live/hub/edit_live_test.exs
index 1c09968fd..2eb079b89 100644
--- a/test/livebook_web/live/hub/edit_live_test.exs
+++ b/test/livebook_web/live/hub/edit_live_test.exs
@@ -6,20 +6,34 @@ defmodule LivebookWeb.Hub.EditLiveTest do
alias Livebook.Hubs
setup do
- on_exit(&Hubs.clean_hubs/0)
- :ok
+ on_exit(fn ->
+ Hubs.clean_hubs()
+ end)
+
+ bypass = Bypass.open()
+ Application.put_env(:livebook, :fly_graphql_endpoint, "http://localhost:#{bypass.port}")
+
+ {:ok, bypass: bypass}
end
describe "fly" do
- test "updates fly", %{conn: conn} do
- hub = insert_hub(:fly, id: "fly-987654321", application_id: "987654321")
- fly_bypass(hub.application_id)
+ test "updates fly", %{conn: conn, bypass: bypass} do
+ {:ok, pid} = Agent.start(fn -> %{fun: &fetch_app_response/2, type: :mount} end)
+
+ app_id = Livebook.Utils.random_short_id()
+ hub = insert_hub(:fly, id: "fly-#{app_id}", application_id: app_id)
+ fly_bypass(bypass, app_id, pid)
{:ok, view, html} = live(conn, Routes.hub_path(conn, :edit, hub.id))
assert html =~ "See app on Fly"
assert html =~ "https://#{hub.application_id}.fly.dev"
+ assert html =~ "Environment Variables"
+ refute html =~ "FOO_ENV_VAR"
+ assert html =~ "LIVEBOOK_PASSWORD"
+ assert html =~ "LIVEBOOK_SECRET_KEY_BASE"
+
attrs = %{
"hub_name" => "Personal Hub",
"hub_color" => "#FF00FF"
@@ -47,7 +61,7 @@ defmodule LivebookWeb.Hub.EditLiveTest do
assert view
|> element("#hubs")
- |> render() =~ "/hub/fly-987654321"
+ |> render() =~ Routes.hub_path(conn, :edit, hub.id)
assert view
|> element("#hubs")
@@ -55,19 +69,156 @@ defmodule LivebookWeb.Hub.EditLiveTest do
refute Hubs.fetch_hub!(hub.id) == hub
end
+
+ test "add secret", %{conn: conn, bypass: bypass} do
+ {:ok, pid} = Agent.start(fn -> %{fun: &fetch_app_response/2, type: :mount} end)
+
+ app_id = Livebook.Utils.random_short_id()
+ hub = insert_hub(:fly, id: "fly-#{app_id}", application_id: app_id)
+ fly_bypass(bypass, app_id, pid)
+
+ {:ok, view, html} = live(conn, Routes.hub_path(conn, :edit, hub.id))
+
+ assert html =~ "See app on Fly"
+ assert html =~ "https://#{hub.application_id}.fly.dev"
+
+ assert html =~ "Environment Variables"
+ refute html =~ "FOO_ENV_VAR"
+ assert html =~ "LIVEBOOK_PASSWORD"
+ assert html =~ "LIVEBOOK_SECRET_KEY_BASE"
+
+ view
+ |> element("#env-var-form")
+ |> render_change(%{"env_var" => %{"key" => "FOO_ENV_VAR", "value" => "12345"}})
+
+ refute view
+ |> element("#env-var-form button[disabled]")
+ |> has_element?()
+
+ :ok = Agent.update(pid, fn state -> %{state | type: :add} end)
+
+ assert {:ok, _view, html} =
+ view
+ |> element("#env-var-form")
+ |> render_submit(%{"env_var" => %{"key" => "FOO_ENV_VAR", "value" => "12345"}})
+ |> follow_redirect(conn)
+
+ assert html =~ "Environment variable added"
+ assert html =~ "Environment Variables"
+ assert html =~ "FOO_ENV_VAR"
+ assert html =~ "LIVEBOOK_PASSWORD"
+ assert html =~ "LIVEBOOK_SECRET_KEY_BASE"
+ end
+
+ test "update secret", %{conn: conn, bypass: bypass} do
+ {:ok, pid} = Agent.start(fn -> %{fun: &fetch_app_response/2, type: :foo} end)
+
+ old_env_var =
+ :foo
+ |> secrets()
+ |> Enum.find(&(&1["name"] == "FOO_ENV_VAR"))
+
+ new_env_var =
+ :updated_foo
+ |> secrets()
+ |> Enum.find(&(&1["name"] == "FOO_ENV_VAR"))
+
+ app_id = Livebook.Utils.random_short_id()
+ hub = insert_hub(:fly, id: "fly-#{app_id}", application_id: app_id)
+ fly_bypass(bypass, app_id, pid)
+
+ {:ok, view, html} = live(conn, Routes.hub_path(conn, :edit, hub.id))
+
+ assert html =~ "See app on Fly"
+ assert html =~ "https://#{hub.application_id}.fly.dev"
+
+ assert html =~ "Environment Variables"
+ assert html =~ "FOO_ENV_VAR"
+ assert html =~ old_env_var["createdAt"]
+
+ view
+ |> element("#env-var-#{old_env_var["id"]}-edit")
+ |> render_click(%{"env_var" => old_env_var})
+
+ view
+ |> element("#env-var-form")
+ |> render_change(%{"env_var" => %{"key" => "FOO_ENV_VAR", "value" => "12345"}})
+
+ refute view
+ |> element("#env-var-form button[disabled]")
+ |> has_element?()
+
+ :ok = Agent.update(pid, fn state -> %{state | type: :updated_foo} end)
+
+ assert {:ok, _view, html} =
+ view
+ |> element("#env-var-form")
+ |> render_submit(%{"env_var" => %{"key" => "FOO_ENV_VAR", "value" => "12345"}})
+ |> follow_redirect(conn)
+
+ assert html =~ "Environment variable updated"
+ assert html =~ "Environment Variables"
+ assert html =~ "FOO_ENV_VAR"
+ refute html =~ old_env_var["createdAt"]
+ assert html =~ new_env_var["createdAt"]
+ end
+
+ test "delete secret", %{conn: conn, bypass: bypass} do
+ {:ok, pid} = Agent.start(fn -> %{fun: &fetch_app_response/2, type: :add} end)
+
+ env_var =
+ :add
+ |> secrets()
+ |> Enum.find(&(&1["name"] == "FOO_ENV_VAR"))
+
+ app_id = Livebook.Utils.random_short_id()
+ hub = insert_hub(:fly, id: "fly-#{app_id}", application_id: app_id)
+ fly_bypass(bypass, app_id, pid)
+
+ {:ok, view, html} = live(conn, Routes.hub_path(conn, :edit, hub.id))
+
+ assert html =~ "See app on Fly"
+ assert html =~ "https://#{hub.application_id}.fly.dev"
+
+ assert html =~ "Environment Variables"
+ assert html =~ "FOO_ENV_VAR"
+ assert html =~ "LIVEBOOK_PASSWORD"
+ assert html =~ "LIVEBOOK_SECRET_KEY_BASE"
+
+ :ok = Agent.update(pid, fn state -> %{state | type: :mount} end)
+
+ assert {:ok, _view, html} =
+ view
+ |> with_target("#fly-form-component")
+ |> render_click("delete", %{"env_var" => env_var})
+ |> follow_redirect(conn)
+
+ assert html =~ "Environment variable deleted"
+ assert html =~ "Environment Variables"
+ refute html =~ "FOO_ENV_VAR"
+ assert html =~ "LIVEBOOK_PASSWORD"
+ assert html =~ "LIVEBOOK_SECRET_KEY_BASE"
+ end
end
- defp fly_bypass(app_id) do
- bypass = Bypass.open()
- Application.put_env(:livebook, :fly_graphql_endpoint, "http://localhost:#{bypass.port}")
-
+ defp fly_bypass(bypass, app_id, agent_pid) do
Bypass.expect(bypass, "POST", "/", fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
+ body = Jason.decode!(body)
response =
- case Jason.decode!(body) do
- %{"variables" => %{"appId" => ^app_id}} -> fetch_app_response(app_id)
- %{"variables" => %{}} -> fetch_apps_response(app_id)
+ cond do
+ body["query"] =~ "setSecrets" ->
+ put_secrets_response()
+
+ body["query"] =~ "unsetSecrets" ->
+ delete_secrets_response()
+
+ true ->
+ Agent.get(agent_pid, fn
+ %{fun: fun, type: type} -> fun.(app_id, type)
+ %{fun: fun} -> fun.()
+ end)
end
conn
@@ -76,29 +227,87 @@ defmodule LivebookWeb.Hub.EditLiveTest do
end)
end
- defp fetch_apps_response(app_id) do
- app = %{
- "id" => app_id,
- "organization" => %{
- "id" => "l3soyvjmvtmwtl6l2drnbfuvltipprge",
- "name" => "Foo Bar",
- "type" => "PERSONAL"
- }
- }
-
- %{"data" => %{"apps" => %{"nodes" => [app]}}}
- end
-
- defp fetch_app_response(app_id) do
+ defp fetch_app_response(app_id, type) do
app = %{
"id" => app_id,
"name" => app_id,
"hostname" => app_id <> ".fly.dev",
"platformVersion" => "nomad",
"deployed" => true,
- "status" => "running"
+ "status" => "running",
+ "secrets" => secrets(type)
}
%{"data" => %{"app" => app}}
end
+
+ defp secrets(:mount) do
+ [
+ %{
+ "createdAt" => to_string(DateTime.utc_now()),
+ "digest" => to_string(Livebook.Utils.random_cookie()),
+ "id" => "123",
+ "name" => "LIVEBOOK_PASSWORD"
+ },
+ %{
+ "createdAt" => to_string(DateTime.utc_now()),
+ "digest" => to_string(Livebook.Utils.random_cookie()),
+ "id" => "456",
+ "name" => "LIVEBOOK_SECRET_KEY_BASE"
+ }
+ ]
+ end
+
+ defp secrets(:add) do
+ [
+ %{
+ "createdAt" => to_string(DateTime.utc_now()),
+ "digest" => to_string(Livebook.Utils.random_cookie()),
+ "id" => "789",
+ "name" => "FOO_ENV_VAR"
+ },
+ %{
+ "createdAt" => to_string(DateTime.utc_now()),
+ "digest" => to_string(Livebook.Utils.random_cookie()),
+ "id" => "123",
+ "name" => "LIVEBOOK_PASSWORD"
+ },
+ %{
+ "createdAt" => to_string(DateTime.utc_now()),
+ "digest" => to_string(Livebook.Utils.random_cookie()),
+ "id" => "456",
+ "name" => "LIVEBOOK_SECRET_KEY_BASE"
+ }
+ ]
+ end
+
+ defp secrets(:foo) do
+ [
+ %{
+ "createdAt" => "2022-08-31 14:47:39.904338Z",
+ "digest" => to_string(Livebook.Utils.random_cookie()),
+ "id" => "123456789",
+ "name" => "FOO_ENV_VAR"
+ }
+ ]
+ end
+
+ defp secrets(:updated_foo) do
+ [
+ %{
+ "createdAt" => "2022-08-31 14:47:41.632669Z",
+ "digest" => to_string(Livebook.Utils.random_cookie()),
+ "id" => "123456789",
+ "name" => "FOO_ENV_VAR"
+ }
+ ]
+ end
+
+ defp put_secrets_response do
+ %{"data" => %{"setSecrets" => %{"app" => %{"secrets" => secrets(:add)}}}}
+ end
+
+ defp delete_secrets_response do
+ %{"data" => %{"unsetSecrets" => %{"app" => %{"secrets" => secrets(:mount)}}}}
+ end
end
diff --git a/test/livebook_web/live/hub/new_live_test.exs b/test/livebook_web/live/hub/new_live_test.exs
index 27beb9e9b..294b1839c 100644
--- a/test/livebook_web/live/hub/new_live_test.exs
+++ b/test/livebook_web/live/hub/new_live_test.exs
@@ -124,11 +124,12 @@ defmodule LivebookWeb.Hub.NewLiveTest do
Bypass.expect(bypass, "POST", "/", fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
+ body = Jason.decode!(body)
response =
- case Jason.decode!(body) do
- %{"variables" => %{"appId" => ^app_id}} -> fetch_app_response(app_id)
- %{"variables" => %{}} -> fetch_apps_response(app_id)
+ cond do
+ body["query"] =~ "apps" -> fetch_apps_response(app_id)
+ body["query"] =~ "app" -> fetch_app_response(app_id)
end
conn
@@ -157,7 +158,21 @@ defmodule LivebookWeb.Hub.NewLiveTest do
"hostname" => app_id <> ".fly.dev",
"platformVersion" => "nomad",
"deployed" => true,
- "status" => "running"
+ "status" => "running",
+ "secrets" => [
+ %{
+ "createdAt" => to_string(DateTime.utc_now()),
+ "digest" => to_string(Livebook.Utils.random_cookie()),
+ "id" => Livebook.Utils.random_short_id(),
+ "name" => "LIVEBOOK_PASSWORD"
+ },
+ %{
+ "createdAt" => to_string(DateTime.utc_now()),
+ "digest" => to_string(Livebook.Utils.random_cookie()),
+ "id" => Livebook.Utils.random_short_id(),
+ "name" => "LIVEBOOK_SECRET_KEY_BASE"
+ }
+ ]
}
%{"data" => %{"app" => app}}