From ac71d087712f2a82bd559b8bb190f7e0d72dde0f Mon Sep 17 00:00:00 2001 From: Alexandre de Souza Date: Fri, 2 Sep 2022 12:54:28 -0300 Subject: [PATCH] Add and Fetch secrets from Fly applications (#1361) --- lib/livebook/hubs/fly_client.ex | 60 ++- .../live/hub/edit/fly_component.ex | 368 ++++++++++++++---- lib/livebook_web/live/live_helpers.ex | 1 + test/livebook/hubs/fly_client_test.exs | 118 +++++- test/livebook_web/live/hub/edit_live_test.exs | 265 +++++++++++-- test/livebook_web/live/hub/new_live_test.exs | 23 +- 6 files changed, 724 insertions(+), 111 deletions(-) diff --git a/lib/livebook/hubs/fly_client.ex b/lib/livebook/hubs/fly_client.ex index f555d1d30..f28261b3c 100644 --- a/lib/livebook/hubs/fly_client.ex +++ b/lib/livebook/hubs/fly_client.ex @@ -20,9 +20,9 @@ defmodule Livebook.Hubs.FlyClient do } """ - with {:ok, body} <- graphql(access_token, query) do + with {:ok, %{"apps" => %{"nodes" => nodes}}} <- graphql(access_token, query) do apps = - for node <- body["apps"]["nodes"] do + for node <- nodes do %Fly{ id: "fly-" <> node["id"], access_token: access_token, @@ -47,12 +47,64 @@ defmodule Livebook.Hubs.FlyClient do platformVersion deployed status + secrets { + id + name + digest + createdAt + } } } """ - with {:ok, body} <- graphql(access_token, query, %{appId: app_id}) do - {:ok, body["app"]} + with {:ok, %{"app" => app}} <- graphql(access_token, query, %{appId: app_id}) do + {:ok, app} + end + end + + def put_secrets(%Fly{access_token: access_token, application_id: application_id}, secrets) do + mutation = """ + mutation($input: SetSecretsInput!) { + setSecrets(input: $input) { + app { + secrets { + id + name + digest + createdAt + } + } + } + } + """ + + input = %{input: %{appId: application_id, secrets: secrets}} + + with {:ok, %{"setSecrets" => %{"app" => app}}} <- graphql(access_token, mutation, input) do + {:ok, app["secrets"]} + end + end + + def delete_secrets(%Fly{access_token: access_token, application_id: application_id}, keys) do + mutation = """ + mutation($input: UnsetSecretsInput!) { + unsetSecrets(input: $input) { + app { + secrets { + id + name + digest + createdAt + } + } + } + } + """ + + input = %{input: %{appId: application_id, keys: keys}} + + with {:ok, %{"unsetSecrets" => %{"app" => app}}} <- graphql(access_token, mutation, input) do + {:ok, app["secrets"]} end end diff --git a/lib/livebook_web/live/hub/edit/fly_component.ex b/lib/livebook_web/live/hub/edit/fly_component.ex index 760965d53..8a5593f07 100644 --- a/lib/livebook_web/live/hub/edit/fly_component.ex +++ b/lib/livebook_web/live/hub/edit/fly_component.ex @@ -14,104 +14,285 @@ defmodule LivebookWeb.Hub.Edit.FlyComponent do {:ok, socket |> assign(assigns) - |> assign(app_url: "https://#{app["hostname"]}", changeset: changeset)} + |> assign( + app_url: "https://#{app["hostname"]}", + changeset: changeset, + env_vars: app["secrets"], + env_var_data: %{}, + operation: :new, + valid_env_var?: false + )} end @impl true def render(assigns) do ~H""" -
- -
-
-
- <.labeled_text label="Application ID"> - <%= @hub.application_id %> - - <.labeled_text label="Type"> - Fly - -
- - - <.remix_icon icon="dashboard-2-line" class="align-middle mr-1" /> - See app on Fly - -
-
- -
-

- General -

- - <.form - id={@id} - class="flex flex-col mt-4 space-y-4" - let={f} - for={@changeset} - phx-submit="save" - phx-change="validate" - phx-target={@myself} - phx-debounce="blur" - > -
-
-

- Name -

- <%= text_input(f, :hub_name, class: "input") %> - <%= error_tag(f, :hub_name) %> +
"-component"}> +
+
+
+
+ <.labeled_text label="Application ID"> + <%= @hub.application_id %> + + <.labeled_text label="Type"> + Fly +
- +
-
-
-
-
-
- <%= text_input(f, :hub_color, - class: "input", - spellcheck: "false", - maxlength: 7 - ) %> - - <%= error_tag(f, :hub_color) %> +
+
+
+ <%= text_input(f, :hub_color, + class: "input", + spellcheck: "false", + maxlength: 7 + ) %> + + <%= error_tag(f, :hub_color) %> +
+ + <%= submit("Update Hub", + class: "button-base button-blue", + phx_disable_with: "Updating...", + disabled: not @changeset.valid? + ) %> + +
+ +
+

+ 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? - ) %> - + +
+
+ + <.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> + + + <:content> + + + + +
""" 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 %>