diff --git a/lib/console/deployments/events.ex b/lib/console/deployments/events.ex index d9ddb8d3f3..9451af9beb 100644 --- a/lib/console/deployments/events.ex +++ b/lib/console/deployments/events.ex @@ -3,6 +3,7 @@ defmodule Console.PubSub.ServiceUpdated, do: use Piazza.PubSub.Event defmodule Console.PubSub.ServiceDeleted, do: use Piazza.PubSub.Event defmodule Console.PubSub.ServiceComponentsUpdated, do: use Piazza.PubSub.Event defmodule Console.PubSub.ServiceHardDeleted, do: use Piazza.PubSub.Event +defmodule Console.PubSub.ServiceManifestsRequested, do: use Piazza.PubSub.Event defmodule Console.PubSub.ClusterCreated, do: use Piazza.PubSub.Event defmodule Console.PubSub.ClusterUpdated, do: use Piazza.PubSub.Event diff --git a/lib/console/deployments/pubsub/protocols/broadcastable.ex b/lib/console/deployments/pubsub/protocols/broadcastable.ex index 31f3dd6a62..093891c05e 100644 --- a/lib/console/deployments/pubsub/protocols/broadcastable.ex +++ b/lib/console/deployments/pubsub/protocols/broadcastable.ex @@ -23,6 +23,11 @@ defimpl Console.Deployments.PubSub.Broadcastable, for: [ do: {"cluster:#{cluster_id}", "service.event", %{"id" => id}} end +defimpl Console.Deployments.PubSub.Broadcastable, for: Console.PubSub.ServiceManifestsRequested do + def message(%{item: %{id: id, cluster_id: cluster_id}}), + do: {"cluster:#{cluster_id}", "service.manifests", %{"id" => id}} +end + defimpl Console.Deployments.PubSub.Broadcastable, for: [Console.PubSub.ClusterRestoreCreated] do def message(%{item: item}) do %{id: id, backup: %{cluster_id: cluster_id}} = Console.Repo.preload(item, [:backup]) diff --git a/lib/console/deployments/services.ex b/lib/console/deployments/services.ex index f0d01033f4..79e799921b 100644 --- a/lib/console/deployments/services.ex +++ b/lib/console/deployments/services.ex @@ -41,6 +41,40 @@ defmodule Console.Deployments.Services do def tarball(%Service{id: id}), do: api_url("v1/git/tarballs?id=#{id}") + @doc """ + Pushes a request to the relevant agent to gather manifests for a service + """ + @spec request_manifests(binary, User.t) :: service_resp + def request_manifests(id, %User{} = user) do + get_service!(id) + |> allow(user, :write) + |> notify(:manifests, user) + end + + @doc """ + Saves the manifests in cache for 30 minutes to be queried by the ui + """ + @spec save_manifests([binary], binary | Service.t, Cluster.t) :: :ok | Console.error + def save_manifests(manifests, %Service{id: id, cluster_id: cid}, %Cluster{id: cid}), + do: Console.Cache.put({:manifests, id}, manifests, ttl: :timer.minutes(30)) + def save_manifests(_, %Service{}, %Cluster{}), + do: {:error, "service doesn't belong to this cluster"} + def save_manifests(manifests, id, %Cluster{} = cluster) when is_binary(id), + do: save_manifests(manifests, get_service!(id), cluster) + + @doc """ + Fetches the manifests from cache at query time + """ + @spec fetch_manifests(binary, User.t) :: {:ok, [binary]} | Console.error + def fetch_manifests(id, %User{} = user) do + get_service!(id) + |> allow(user, :write) + |> when_ok(fn _ -> + Console.Cache.get({:manifests, id}) + |> ok() + end) + end + @doc """ Constructs a filestream for the tar artifact of a service, and perhaps performs some JIT modifications before sending it upstream to the given client. @@ -730,6 +764,8 @@ defmodule Console.Deployments.Services do do: handle_notify(PubSub.ServiceUpdated, svc, actor: user) defp notify({:ok, %Service{} = svc}, :delete, user), do: handle_notify(PubSub.ServiceDeleted, svc, actor: user) + defp notify({:ok, %Service{} = svc}, :manifests, user), + do: handle_notify(PubSub.ServiceManifestsRequested, svc, actor: user) defp notify(pass, _, _), do: pass defp notify({:ok, %Service{} = svc}, :components), diff --git a/lib/console/graphql/deployments/service.ex b/lib/console/graphql/deployments/service.ex index 779559091f..f3e43e086e 100644 --- a/lib/console/graphql/deployments/service.ex +++ b/lib/console/graphql/deployments/service.ex @@ -377,6 +377,15 @@ defmodule Console.GraphQl.Deployments.Service do safe_resolve &Deployments.update_service_components/2 end + + @desc "save the manifests in cache to be retrieved by the requesting user" + field :save_manifests, :boolean do + middleware ClusterAuthenticated + arg :id, non_null(:id) + arg :manifests, list_of(:string) + + safe_resolve &Deployments.save_manifests/2 + end end object :service_queries do @@ -413,6 +422,22 @@ defmodule Console.GraphQl.Deployments.Service do resolve &Deployments.tree/2 end + + @desc "request manifests from an agent, to be returned by a future call to fetchManifests" + field :request_manifests, :service_deployment do + middleware Authenticated + arg :id, non_null(:id) + + resolve &Deployments.request_manifests/2 + end + + @desc "Fetches the manifests from cache once the agent has given us them, will be null otherwise" + field :fetch_manifests, list_of(:string) do + middleware Authenticated + arg :id, non_null(:id) + + resolve &Deployments.fetch_manifests/2 + end end object :service_mutations do diff --git a/lib/console/graphql/resolvers/deployments/service.ex b/lib/console/graphql/resolvers/deployments/service.ex index 430957ef8a..fa0ceff248 100644 --- a/lib/console/graphql/resolvers/deployments/service.ex +++ b/lib/console/graphql/resolvers/deployments/service.ex @@ -164,6 +164,17 @@ defmodule Console.GraphQl.Resolvers.Deployments.Service do def tarball(svc, _, _), do: {:ok, Services.tarball(svc)} def docs(svc, _, _), do: Services.docs(svc) + def fetch_manifests(%{id: id}, %{context: %{current_user: user}}), + do: Services.fetch_manifests(id, user) + + def request_manifests(%{id: id}, %{context: %{current_user: user}}), + do: Services.request_manifests(id, user) + + def save_manifests(%{id: id, manifests: mans}, %{context: %{cluster: cluster}}) do + with :ok <- Services.save_manifests(mans, id, cluster), + do: {:ok, true} + end + defp service_filters(query, args) do Enum.reduce(args, query, fn {:cluster_id, id}, q -> Service.for_cluster(q, id) diff --git a/test/console/graphql/mutations/deployments/services_mutations_test.exs b/test/console/graphql/mutations/deployments/services_mutations_test.exs index 416221fc83..bf9eadc3a4 100644 --- a/test/console/graphql/mutations/deployments/services_mutations_test.exs +++ b/test/console/graphql/mutations/deployments/services_mutations_test.exs @@ -612,4 +612,28 @@ defmodule Console.GraphQl.Deployments.ServicesMutationsTest do assert svc["id"] end end + + describe "saveManifests" do + test "clusters can save manifests" do + service = insert(:service) + + {:ok, %{data: %{"saveManifests" => true}}} = run_query(""" + mutation Save($manifests: [String], $id: ID!) { + saveManifests(id: $id, manifests: $manifests) + } + """, %{"manifests" => ["testing"], "id" => service.id}, %{cluster: service.cluster}) + + {:ok, ["testing"]} = Console.Deployments.Services.fetch_manifests(service.id, admin_user()) + end + + test "clusters cannot save manifests for services on other clusters" do + service = insert(:service) + + {:ok, %{errors: [_ | _]}} = run_query(""" + mutation Save($manifests: [String], $id: ID!) { + saveManifests(id: $id, manifests: $manifests) + } + """, %{"manifests" => ["testing"], "id" => service.id}, %{cluster: insert(:cluster)}) + end + end end diff --git a/test/console/graphql/queries/deployments/service_queries_test.exs b/test/console/graphql/queries/deployments/service_queries_test.exs index 50f7b0f651..ad76bcf0d1 100644 --- a/test/console/graphql/queries/deployments/service_queries_test.exs +++ b/test/console/graphql/queries/deployments/service_queries_test.exs @@ -380,4 +380,52 @@ defmodule Console.GraphQl.Deployments.ServiceQueriesTest do |> ids_equal(globals) end end + + describe "requestManifests" do + test "admins can request manifests" do + service = insert(:service) + + {:ok, %{data: %{"requestManifests" => found}}} = run_query(""" + query Request($id: ID!) { + requestManifests(id: $id) { id } + } + """, %{"id" => service.id}, %{current_user: admin_user()}) + + + assert found["id"] == service.id + end + + test "non-admins cannot request manifests" do + service = insert(:service) + + {:ok, %{errors: [_ | _]}} = run_query(""" + query Request($id: ID!) { + requestManifests(id: $id) { id } + } + """, %{"id" => service.id}, %{current_user: insert(:user)}) + end + end + + describe "fetchManifests" do + test "admins can fetch manifests" do + service = insert(:service) + Console.Deployments.Services.save_manifests(["testing"], service.id, service.cluster) + + {:ok, %{data: %{"fetchManifests" => ["testing"]}}} = run_query(""" + query Fetch($id: ID!) { + fetchManifests(id: $id) + } + """, %{"id" => service.id}, %{current_user: admin_user()}) + end + + test "non-admins cannot request manifests" do + service = insert(:service) + + {:ok, %{errors: [_ | _]}} = run_query(""" + query Request($id: ID!) { + fetchManifests(id: $id) + } + """, %{"id" => service.id}, %{current_user: insert(:user)}) + end + end end