diff --git a/.tool-versions b/.tool-versions index 265d1a352..72808ec94 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -erlang 24.3.4.14 -elixir 1.12.3 +erlang 24.3.4.17 +elixir 1.13.4 diff --git a/Dockerfile b/Dockerfile index f82fb15e2..40a464b58 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM bitwalker/alpine-elixir:1.12.3 AS builder +FROM bitwalker/alpine-elixir:1.13.4 AS builder # The following are build arguments used to change variable parts of the image. # The name of your application/release (required) diff --git a/Makefile b/Makefile index 149a2b8c8..0a235a9d3 100644 --- a/Makefile +++ b/Makefile @@ -49,10 +49,10 @@ else endif testup: ## sets up dependent services for test - docker-compose up -d + docker compose up -d testdown: ## tear down test dependencies - docker-compose down + docker compose down connectdb: ## proxies the db in kubernetes via kubectl @echo "run psql -U forge -h 127.0.0.1 forge to connect" diff --git a/apps/core/lib/core/application.ex b/apps/core/lib/core/application.ex index 90502c529..b2ff0b951 100644 --- a/apps/core/lib/core/application.ex +++ b/apps/core/lib/core/application.ex @@ -25,7 +25,7 @@ defmodule Core.Application do def broker() do case conf(:start_broker) do - true -> [{Core.Conduit.Broker, []}] + true -> [{Core.Conduit.Broker, []}, Core.Services.Coud.Poller] _ -> [] end end diff --git a/apps/core/lib/core/clients/console.ex b/apps/core/lib/core/clients/console.ex new file mode 100644 index 000000000..85f726e6a --- /dev/null +++ b/apps/core/lib/core/clients/console.ex @@ -0,0 +1,96 @@ +defmodule Core.Clients.Console do + require Logger + + @clusters_q """ + query { + clusters(first: 100) { + edges { node { name id distro metadata } } + } + } + """ + + @create_svc_q """ + mutation Create($clusterId: ID!, $attributes: ServiceDeploymentAttributes!) { + createServiceDeployment(clusterId: $clusterId, attributes: $attributes) { + id + } + } + """ + + @delete_svc_q """ + mutation Delete($id: ID!) { + deleteServiceDeployment(id: $id) { + id + } + } + """ + + @update_svc_q """ + mutation Update($id: ID!, $attributes: ServiceUpdateAttributes!) { + updateServiceDeployment(id: $id) { + id + } + } + """ + + @repo_q """ + query Repo($url: String!) { + gitRepository(url: $url) { + id + } + } + """ + + def new(url, token) do + Req.new(base_url: url, auth: "Token #{token}") + |> AbsintheClient.attach() + end + + def clusters(client) do + Req.post(client, graphql: @clusters_q) + |> case do + {:ok, %Req.Response{body: %{"clusters" => %{"edges" => edges}}}} -> {:ok, Enum.map(edges, & &1["node"])} + res -> + Logger.warn "Failed to fetch clusters: #{inspect(res)}" + {:error, "could not fetch clusters"} + end + end + + def repo(client, url) do + Req.post(client, graphql: {@repo_q, %{url: url}}) + |> case do + {:ok, %Req.Response{body: %{"gitRepository" => %{"id" => id}}}} -> {:ok, id} + res -> + Logger.warn "Failed to fetch clusters: #{inspect(res)}" + {:error, "could not fetch repo"} + end + end + + def create_service(client, cluster_id, attrs) do + Req.post(client, graphql: {@create_svc_q, %{clusterId: cluster_id, attributes: attrs}}) + |> service_resp("createServiceDeployment") + end + + def update_service(client, id, attrs) do + Req.post(client, graphql: {@update_svc_q, %{id: id, attributes: attrs}}) + |> service_resp("updateServiceDeployment") + end + + def delete_service(client, id) do + Req.post(client, graphql: {@delete_svc_q, %{id: id}}) + |> service_resp("deleteServiceDeployment") + end + + defp service_resp({:ok, %Req.Response{status: 200, body: body}}, field) do + case body[field] do + %{"id" => id} -> {:ok, id} + err -> + Logger.warn "invalid console gql response: #{inspect(err)}" + end + end + + defp service_resp(resp, _) do + Logger.error "failed to fetch from console: #{inspect(resp)}" + {:error, "console error"} + end +end diff --git a/apps/core/lib/core/pubsub/events.ex b/apps/core/lib/core/pubsub/events.ex index 89037f6a1..922710c27 100644 --- a/apps/core/lib/core/pubsub/events.ex +++ b/apps/core/lib/core/pubsub/events.ex @@ -90,3 +90,7 @@ defmodule Core.PubSub.ClusterDependencyCreated, do: use Piazza.PubSub.Event defmodule Core.PubSub.DeferredUpdateCreated, do: use Piazza.PubSub.Event defmodule Core.PubSub.UpgradesPromoted, do: use Piazza.PubSub.Event + +defmodule Core.PubSub.ConsoleInstanceCreated, do: use Piazza.PubSub.Event +defmodule Core.PubSub.ConsoleInstanceUpdated, do: use Piazza.PubSub.Event +defmodule Core.PubSub.ConsoleInstanceDeleted, do: use Piazza.PubSub.Event diff --git a/apps/core/lib/core/pubsub/protocols/fanout.ex b/apps/core/lib/core/pubsub/protocols/fanout.ex index 8ee3d6062..2c85865c9 100644 --- a/apps/core/lib/core/pubsub/protocols/fanout.ex +++ b/apps/core/lib/core/pubsub/protocols/fanout.ex @@ -235,3 +235,21 @@ defimpl Core.PubSub.Fanout, for: [Core.PubSub.RoleCreated, Core.PubSub.RoleUpdat |> Enum.count() end end + +defimpl Core.PubSub.Fanout, for: Core.PubSub.ConsoleInstanceCreated do + alias Core.Services.Cloud.Workflow + + def fanout(%{item: instance}), do: Workflow.provision(instance) +end + +defimpl Core.PubSub.Fanout, for: Core.PubSub.ConsoleInstanceUpdated do + alias Core.Services.Cloud.Workflow + + def fanout(%{item: instance}), do: Workflow.sync(instance) +end + +defimpl Core.PubSub.Fanout, for: Core.PubSub.ConsoleInstanceDeleted do + alias Core.Services.Cloud.Workflow + + def fanout(%{item: instance}), do: Workflow.deprovision(instance) +end diff --git a/apps/core/lib/core/schema/cloud_cluster.ex b/apps/core/lib/core/schema/cloud_cluster.ex new file mode 100644 index 000000000..9a179ab18 --- /dev/null +++ b/apps/core/lib/core/schema/cloud_cluster.ex @@ -0,0 +1,42 @@ +defmodule Core.Schema.CloudCluster do + use Piazza.Ecto.Schema + + defenum Cloud, aws: 0 + + @saturation 1000 + + @region_map %{ + aws: ~w(us-east-1) + } + + schema "cloud_clusters" do + field :name, :string + field :external_id, :binary_id + field :cloud, Cloud + field :region, :string + field :count, :integer + + timestamps() + end + + def for_cloud(query \\ __MODULE__, cloud) do + from(c in query, where: c.cloud == ^cloud) + end + + def unsaturated(query \\ __MODULE__) do + from(c in query, where: c.count < @saturation) + end + + def for_region(query \\ __MODULE__, region) do + from(c in query, where: c.region == ^region) + end + + def region_information(), do: @region_map + + def changeset(model, attrs \\ %{}) do + model + |> cast(attrs, ~w(name external_id cloud region)a) + |> unique_constraint(:name) + |> validate_required(~w(name external_id cloud region)a) + end +end diff --git a/apps/core/lib/core/schema/cockroach_cluster.ex b/apps/core/lib/core/schema/cockroach_cluster.ex new file mode 100644 index 000000000..0e92016a5 --- /dev/null +++ b/apps/core/lib/core/schema/cockroach_cluster.ex @@ -0,0 +1,34 @@ +defmodule Core.Schema.CockroachCluster do + use Piazza.Ecto.Schema + alias Piazza.Ecto.EncryptedString + alias Core.Schema.CloudCluster + + @saturation 1000 + + schema "cockroach_clusters" do + field :name, :string + field :cloud, CloudCluster.Cloud + field :region, :string + field :url, EncryptedString + field :certificate, :string + field :endpoints, :map + field :count, :integer, default: 0 + + timestamps() + end + + def for_cloud(query \\ __MODULE__ , cloud) do + from(c in query, where: c.cloud == ^cloud) + end + + def unsaturated(query \\ __MODULE__) do + from(c in query, where: c.count < @saturation) + end + + def changeset(model, attrs \\ %{}) do + model + |> cast(attrs, ~w(name cloud region url certificate endpoints)a) + |> unique_constraint(:name) + |> validate_required(~w(name cloud region url certificate endpoints)a) + end +end diff --git a/apps/core/lib/core/schema/console_instance.ex b/apps/core/lib/core/schema/console_instance.ex new file mode 100644 index 000000000..840d813a0 --- /dev/null +++ b/apps/core/lib/core/schema/console_instance.ex @@ -0,0 +1,117 @@ +defmodule Core.Schema.ConsoleInstance do + use Piazza.Ecto.Schema + alias Piazza.Ecto.EncryptedString + alias Core.Schema.{CockroachCluster, CloudCluster, User} + + defenum Size, small: 0, medium: 1, large: 2 + defenum Status, + pending: 0, + database_created: 1, + deployment_created: 2, + provisioned: 3, + deployment_deleted: 4, + database_deleted: 5 + + @region_map %{ + aws: ~w(us-east-1) + } + + schema "console_instances" do + field :name, :string + field :status, Status + field :subdomain, :string + field :url, :string + field :external_id, :string + field :cloud, CloudCluster.Cloud + field :size, Size + field :region, :string + + field :deleted_at, :utc_datetime_usec + + embeds_one :instance_status, InstanceStatus, on_replace: :update do + field :db, :boolean, default: false + field :svc, :boolean, default: false + end + + embeds_one :configuration, Configuration, on_replace: :update do + field :database, :string + field :dbuser, :string + field :dbpassword, EncryptedString + field :subdomain, :string + field :jwt_secret, EncryptedString + field :owner_name, :string + field :owner_email, :string + field :admin_password, EncryptedString + field :aes_key, EncryptedString + field :encryption_key, EncryptedString + field :client_id, :string + field :client_secret, EncryptedString + field :plural_token, EncryptedString + field :kas_api, EncryptedString + field :kas_private, EncryptedString + field :kas_redis, EncryptedString + end + + belongs_to :cockroach, CockroachCluster + belongs_to :cluster, CloudCluster + belongs_to :owner, User + + timestamps() + end + + def for_account(query \\ __MODULE__, account_id) do + from(c in query, + join: u in assoc(c, :owner), + where: u.account_id == ^account_id + ) + end + + def ordered(query \\ __MODULE__, order \\ [asc: :name]) do + from(c in query, order_by: ^order) + end + + def regions(), do: @region_map + + @valid ~w(name cloud size region status subdomain url external_id cockroach_id cluster_id owner_id)a + + def changeset(model, attrs \\ %{}) do + model + |> cast(attrs, @valid) + |> cast_embed(:configuration, with: &configuration_changeset/2) + |> cast_embed(:instance_status, with: &status_changeset/2) + |> validate_required(@valid -- [:external_id]) + |> unique_constraint(:subdomain) + |> unique_constraint(:name) + |> validate_format(:name, ~r/[a-z][a-z0-9]{5,10}/, message: "must be an alphanumeric string between 5 and 10 characters") + |> validate_region() + end + + defp validate_region(cs) do + cloud = get_field(cs, :cloud) + regions = @region_map[cloud] + validate_change(cs, :region, fn :region, reg -> + case reg in regions do + true -> [] + _ -> [region: "Invalid region #{reg} for cloud #{cloud}"] + end + end) + end + + @conf_valid ~w( + database dbuser dbpassword + subdomain jwt_secret owner_name owner_email admin_password aes_key + encryption_key client_id client_secret plural_token + kas_api kas_private kas_redis + )a + + defp configuration_changeset(model, attrs) do + model + |> cast(attrs, @conf_valid) + |> validate_required(@conf_valid) + end + + defp status_changeset(model, attrs) do + model + |> cast(attrs, ~w(db svc)a) + end +end diff --git a/apps/core/lib/core/services/cloud.ex b/apps/core/lib/core/services/cloud.ex new file mode 100644 index 000000000..01e5f52da --- /dev/null +++ b/apps/core/lib/core/services/cloud.ex @@ -0,0 +1,196 @@ +defmodule Core.Services.Cloud do + use Core.Services.Base + alias Core.Repo + alias Core.PubSub + alias Core.Services.{Accounts, Users, Repositories, Shell} + alias Core.Schema.{CloudCluster, CockroachCluster, ConsoleInstance, User, OIDCProvider} + + @type error :: {:error, term} + @type console_resp :: {:ok, ConsoleInstance.t} | error + @type cluster_resp :: {:ok, CloudCluster.t} | error + @type cockroach_resp :: {:ok, CockroachCluster.t} | error + + def get_instance!(id), do: Repo.get!(ConsoleInstance, id) + + @spec upsert_cluster(map, binary) :: cluster_resp + def upsert_cluster(attrs, name) do + case Repo.get_by(CloudCluster, name: name) do + %CloudCluster{} = cluster -> cluster + nil -> %CloudCluster{name: name} + end + |> CloudCluster.changeset(attrs) + |> Repo.insert_or_update() + end + + @spec upsert_cockroach(map, binary) :: cockroach_resp + def upsert_cockroach(attrs, name) do + case Repo.get_by(CockroachCluster, name: name) do + %CockroachCluster{} = cluster -> cluster + nil -> %CockroachCluster{name: name} + end + |> CockroachCluster.changeset(attrs) + |> Repo.insert_or_update() + end + + @doc """ + Creates a new Cloud instance of the Plural console + """ + @spec create_instance(map, User.t) :: console_resp + def create_instance(%{name: name} = attrs, %User{} = user) do + start_transaction() + |> add_operation(:cluster, fn _ -> select_cluster(attrs[:cloud], attrs[:region]) end) + |> add_operation(:cockroach, fn _ -> select_roach(attrs[:cloud]) end) + |> add_operation(:sa, fn _ -> + Accounts.create_service_account(%{name: "#{name}-cloud-sa", email: "#{name}-cloud-sa@srv.plural.sh"}, user) + end) + |> add_operation(:token, fn %{sa: sa} -> Users.create_persisted_token(sa) end) + |> add_operation(:install, fn %{sa: sa} -> + repo = Repositories.get_repository_by_name!("console") + case Repositories.get_installation(sa.id, repo.id) do + nil -> Repositories.create_installation(%{}, repo, sa) + inst -> {:ok, inst} + end + end) + |> add_operation(:oidc, fn %{install: inst, sa: sa} -> + inst = Core.Repo.preload(inst, [oidc_provider: :bindings]) + Repositories.upsert_oidc_provider(%{ + auth_method: :post, + bindings: Shell.oidc_bindings(inst.oidc_provider, user), + redirect_uris: Shell.merge_uris(["https://console.#{name}.cloud.plural.sh/oauth/callback"], inst.oidc_provider) + }, inst.id, sa) + end) + |> add_operation(:instance, fn %{oidc: oidc, token: token, cluster: cluster, cockroach: roach, sa: sa} -> + %ConsoleInstance{status: :pending, cluster_id: cluster.id, cockroach_id: roach.id, owner_id: sa.id} + |> ConsoleInstance.changeset(add_configuration(attrs, name, token.token, oidc, user)) + |> Repo.insert() + end) + |> execute(extract: :instance) + |> notify(:create, user) + end + + @doc """ + Updates base attributes of a console instance + """ + @spec update_instance(map, binary, User.t) :: console_resp + def update_instance(attrs, id, %User{} = user) do + start_transaction() + |> add_operation(:inst, fn _ -> authorize(id, user) end) + |> add_operation(:updated, fn %{inst: inst} -> + ConsoleInstance.changeset(inst, attrs) + |> Repo.update() + end) + |> execute(extract: :updated) + |> notify(:update, user) + end + + @doc """ + Schedules a console instance to be cleaned up + """ + @spec delete_instance(binary, User.t) :: console_resp + def delete_instance(id, %User{} = user) do + start_transaction() + |> add_operation(:inst, fn _ -> authorize(id, user) end) + |> add_operation(:deleted, fn %{inst: inst} -> + Ecto.Changeset.change(inst, %{deleted_at: Timex.now()}) + |> Repo.update() + end) + |> execute(extract: :deleted) + |> notify(:delete, user) + end + + def authorize(id, %User{} = user) do + inst = get_instance!(id) |> Repo.preload([:owner]) + with {:ok, _} <- Core.Policies.Account.allow(inst.owner, user, :impersonate), + do: {:ok, inst} + end + + def visible(id, %User{account_id: aid}) do + get_instance!(id) + |> Repo.preload([:owner]) + |> case do + %ConsoleInstance{owner: %User{account_id: ^aid}} = instance -> {:ok, instance} + _ -> {:error, :forbidden} + end + end + + defp add_configuration(attrs, name, token, %OIDCProvider{} = oidc, %User{} = user) do + Map.merge(attrs, %{subdomain: "#{name}.cloud.plural.sh", url: "console.#{name}.cloud.plural.sh"}) + |> Map.put(:configuration, %{ + aes_key: aes_key(), + encryption_key: encryption_key(), + database: "#{name}_cloud", + dbuser: "#{name}_user", + dbpassword: Core.random_alphanum(30), + subdomain: "#{name}.cloud.plural.sh", + jwt_secret: Core.random_alphanum(30), + owner_name: user.name, + owner_email: user.email, + admin_password: Core.random_alphanum(30), + client_id: oidc.client_id, + client_secret: oidc.client_secret, + plural_token: token, + kas_api: Core.random_alphanum(30), + kas_private: Core.random_alphanum(30), + kas_redis: Core.random_alphanum(30) + }) + end + + defp select_cluster(cloud, region) do + CloudCluster.for_cloud(cloud) + |> CloudCluster.for_region(region) + |> CloudCluster.unsaturated() + |> Repo.all() + |> random_choice("Could not find cluster for #{cloud} and #{region}") + end + + defp select_roach(cloud) do + CockroachCluster.for_cloud(cloud) + |> CockroachCluster.unsaturated() + |> Repo.all() + |> random_choice("Could not place in #{cloud}") + end + + defp random_choice([], message), do: {:error, message} + defp random_choice(l, _) do + Enum.random(l) + |> inc() + end + + def inc(%schema{id: id}) do + schema.selected() + |> schema.for_id(id) + |> Core.Repo.update_all(inc: [count: 1]) + |> case do + {1, [res]} -> {:ok, res} + _ -> {:error, "could not increment #{schema} [id=#{id}]"} + end + end + + def dec(%schema{id: id}) do + schema.selected() + |> schema.for_id(id) + |> Core.Repo.update_all(inc: [count: -1]) + |> case do + {1, [res]} -> {:ok, res} + _ -> {:error, "could not increment #{schema} [id=#{id}]"} + end + end + + defp aes_key() do + :crypto.strong_rand_bytes(32) + |> Base.url_encode64() + end + + defp encryption_key() do + :crypto.strong_rand_bytes(32) + |> Base.encode64() + end + + defp notify({:ok, %ConsoleInstance{} = inst}, :create, user), + do: handle_notify(PubSub.ConsoleInstanceCreated, inst, actor: user) + defp notify({:ok, %ConsoleInstance{} = inst}, :update, user), + do: handle_notify(PubSub.ConsoleInstanceUpdated, inst, actor: user) + defp notify({:ok, %ConsoleInstance{} = inst}, :delete, user), + do: handle_notify(PubSub.ConsoleInstanceDeleted, inst, actor: user) + defp notify(pass, _, _), do: pass +end diff --git a/apps/core/lib/core/services/cloud/configuration.ex b/apps/core/lib/core/services/cloud/configuration.ex new file mode 100644 index 000000000..d4a60d957 --- /dev/null +++ b/apps/core/lib/core/services/cloud/configuration.ex @@ -0,0 +1,32 @@ +defmodule Core.Services.Cloud.Configuration do + alias Core.Schema.{ConsoleInstance, CockroachCluster} + + def build(%ConsoleInstance{configuration: conf, size: size} = inst) do + Map.take(conf, ~w( + subdomain + jwt_secret + owner_name + owner_email + admin_password + aes_key + encryption_key + client_id + client_secret + plural_token + kas_api + kas_private + kas_redis + )a) + |> Map.put(:postgres_url, build_pg_url(inst)) + |> Map.put(:size, "#{size}") + |> Enum.map(fn {k, v} -> %{name: Macro.camelize("#{k}"), value: v} end) + end + + defp build_pg_url(%ConsoleInstance{ + configuration: %{dbuser: u, dbpassword: p, database: database}, + region: region, + cockroach: %CockroachCluster{endpoints: endpoints} + }) do + "postgresql://#{u}:#{p}@#{endpoints[region]}/#{database}" + end +end diff --git a/apps/core/lib/core/services/cloud/poller.ex b/apps/core/lib/core/services/cloud/poller.ex new file mode 100644 index 000000000..480765267 --- /dev/null +++ b/apps/core/lib/core/services/cloud/poller.ex @@ -0,0 +1,83 @@ +defmodule Core.Services.Cloud.Poller do + use GenServer + alias Core.Clients.Console + alias Core.Services.Cloud + alias Kazan.Apis.Core.V1, as: CoreV1 + + @poll :timer.minutes(5) + + defmodule State, do: defstruct [:client, :repo] + + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + def init(_) do + :timer.send_interval(@poll, :clusters) + :timer.send_interval(@poll, :roaches) + send self(), :repo + {:ok, %State{client: Console.new(Core.conf(:console_url), Core.conf(:console_token))}} + end + + def repository(), do: GenServer.call(__MODULE__, :repo) + + def handle_call(:repo, %{repo: id} = state) when is_binary(id), + do: {:reply, {:ok, id}, state} + def handle_call(:repo, state), do: {:reply, {:error, "repo not pulled"}, state} + + def handle_info(:repo, %{client: client} = state) do + case Console.repo(client, Core.conf(:mgmt_repo)) do + {:ok, id} -> {:noreply, %{state | repo: id}} + _ -> {:noreply, state} + end + end + + def handle_info(:clusters, %{client: client} = state) do + with {:ok, clusters} <- Console.clusters(client) do + Enum.each(clusters, &upsert_cluster/1) + end + + {:noreply, state} + end + + def handle_info(:roaches, state) do + with {:ok, roaches} <- read_secret() do + Enum.each(roaches, &upsert_roach/1) + end + {:noreply, state} + end + + def handle_info(_, state), do: {:noreply, state} + + defp upsert_cluster(%{"id" => id, "name" => name, "distro" => distro, "metadata" => meta}) do + Cloud.upsert_cluster(%{ + external_id: id, + cloud: to_cloud(distro), + region: meta["region"] + }, name) + end + + defp upsert_roach(%{"name" => name} = roach) do + Cloud.upsert_cockroach(%{ + cloud: roach["cloud"], + url: roach["url"], + certificate: roach["certificate"], + endpoints: roach["endpoints"] + }, name) + end + + defp read_secret() do + CoreV1.read_namespaced_secret!("plural", "plrl-cloud-config") + |> Kazan.run() + |> case do + {:ok, %CoreV1.Secret{data: %{"cockroaches" => roaches}}} -> + Jason.decode(roaches) + _ -> {:error, "could not find secret"} + end + end + + defp to_cloud("EKS"), do: :aws + defp to_cloud("GKE"), do: :gcp + defp to_cloud("AKS"), do: :azure + defp to_cloud(_), do: :aws +end diff --git a/apps/core/lib/core/services/cloud/workflow.ex b/apps/core/lib/core/services/cloud/workflow.ex new file mode 100644 index 000000000..3c626ead3 --- /dev/null +++ b/apps/core/lib/core/services/cloud/workflow.ex @@ -0,0 +1,159 @@ +defmodule Core.Services.Cloud.Workflow do + use Core.Services.Base + alias Core.Clients.Console + alias Core.Services.Cloud + alias Core.Services.Cloud.{Poller, Configuration} + alias Core.Schema.{ConsoleInstance, CockroachCluster} + alias Core.Repo + + require Logger + + def sync(%ConsoleInstance{external_id: id} = instance) when is_binary(id) do + instance = Repo.preload(instance, [:cluster, :cockroach]) + Console.update_service(console(), %{ + size: instance.size, + configuration: Configuration.build(instance) + }, id) + end + def sync(_), do: :ok + + def provision(%ConsoleInstance{} = instance) do + instance = Repo.preload(instance, [:cockroach, :cluster]) + + Enum.reduce_while(0..10, instance, fn _, acc -> + case up(acc) do + {:ok, %ConsoleInstance{status: :deployment_created} = inst} -> {:halt, inst} + {:ok, inst} -> {:cont, inst} + err -> + :timer.sleep(:timer.seconds(1)) + Logger.error "failed to transition provisioning console: #{inspect(err)}" + {:cont, acc} + end + end) + |> finalize(:up) + end + + def deprovision(%ConsoleInstance{} = instance) do + instance = Repo.preload(instance, [:cockroach, :cluster]) + + Enum.reduce_while(0..10, instance, fn _, acc -> + case down(acc) do + {:ok, %ConsoleInstance{status: :database_deleted} = inst} -> {:halt, inst} + {:ok, inst} -> {:cont, inst} + err -> + :timer.sleep(:timer.seconds(1)) + Logger.error "failed to transition deprovisioning console: #{inspect(err)}" + {:cont, acc} + end + end) + |> finalize(:down) + end + + defp up(%ConsoleInstance{status: :pending, cockroach: roach, configuration: conf} = inst) do + with {:ok, pid} <- connect(roach), + {:ok, _} <- Postgrex.transaction(pid, fn conn -> + Postgrex.query!(conn, "CREATE DATABASE #{conf.database}", []) + Postgrex.query!(conn, "CREATE USER #{conf.dbuser} WITH PASSWORD $1", [conf.dbpassword]) + Postgrex.query!(conn, "GRANT ALL ON DATABASE #{conf.database} TO #{conf.dbuser}", []) + end) do + ConsoleInstance.changeset(inst, %{ + instance_status: %{db: true}, + status: :database_created, + }) + |> Repo.update() + end + end + + defp up(%ConsoleInstance{instance_status: %{db: true}, name: name, cluster: cluster} = inst) do + with {:ok, id} <- Poller.repository(), + {:ok, svc_id} <- Console.create_service(console(), cluster.external_id, %{ + name: "console-cloud-#{name}", + namespace: "plrl-cloud-#{name}", + helm: %{ + url: "https://pluralsh.github.io/console", + chart: "console", + version: "x.x.x", + valuesFiles: ["console.yaml.liquid"] + }, + repository_id: id, + git: %{ref: "main", folder: "helm"}, + configuration: Configuration.build(inst), + }) do + ConsoleInstance.changeset(inst, %{ + external_id: svc_id, + instance_status: %{svc: true}, + status: :deployment_created + }) + |> Repo.update() + end + end + + defp down(%ConsoleInstance{instance_status: %{svc: false, db: true}, configuration: conf, cockroach: roach} = inst) do + with {:ok, pid} <- connect(roach), + {:ok, _} <- Postgrex.transaction(pid, fn conn -> + Postgrex.query!(conn, "DROP DATABASE #{conf.database}", []) + Postgrex.query!(conn, "DROP USER #{conf.dbuser}", []) + end) do + ConsoleInstance.changeset(inst, %{ + instance_status: %{db: false}, + status: :database_deleted, + }) + |> Repo.update() + end + end + + defp down(%ConsoleInstance{instance_status: %{svc: true}} = inst) do + with {:ok, _} <- Console.delete_service(console(), inst.external_id) do + ConsoleInstance.changeset(inst, %{ + instance_status: %{svc: false}, + status: :deployment_deleted, + }) + |> Repo.update() + end + end + + defp finalize(%ConsoleInstance{status: :deployment_created} = inst, :up) do + ConsoleInstance.changeset(inst, %{status: :provisioned}) + |> Repo.update() + end + + defp finalize(%ConsoleInstance{status: :database_deleted, cluster: cluster, cockroach: roach} = inst, :down) do + start_transaction() + |> add_operation(:inst, fn _ -> Repo.delete(inst) end) + |> add_operation(:cluster, fn _ -> Cloud.dec(cluster) end) + |> add_operation(:roach, fn _ -> Cloud.dec(roach) end) + |> execute(extract: :inst) + end + + defp finalize(inst, _) do + Logger.warn "failed to finalize console instance: #{inst.id}" + {:ok, inst} + end + + defp connect(%CockroachCluster{certificate: cert_pem} = roach) do + with [cert | _] <- :public_key.pem_decode(cert_pem) do + uri = URI.parse(roach.url) + user = userinfo(uri) + Postgrex.start_link( + database: uri.path && String.trim_leading(uri.path, "/"), + username: user[:username], + password: user[:password], + hostname: uri.host, + port: uri.port, + ssl: true, + ssl_opts: [cacerts: [:public_key.pem_entry_decode(cert)]] + ) + end + end + + defp userinfo(%URI{userinfo: info}) when is_binary(info) do + case String.split(info, ":") do + [user, pwd] -> %{username: user, password: pwd} + [user] -> %{username: user} + _ -> %{} + end + end + defp userinfo(_), do: %{} + + defp console(), do: Console.new(Core.conf(:console_url), Core.conf(:console_token)) +end diff --git a/apps/core/lib/core/services/clusters.ex b/apps/core/lib/core/services/clusters.ex index 4b5d64ed6..5aaae9c29 100644 --- a/apps/core/lib/core/services/clusters.ex +++ b/apps/core/lib/core/services/clusters.ex @@ -21,6 +21,9 @@ defmodule Core.Services.Clusters do @spec get_cluster_by_owner(binary) :: Cluster.t | nil def get_cluster_by_owner(user_id), do: Core.Repo.get_by(Cluster, owner_id: user_id) + @spec get_cluster_by_url(binary) :: Cluster.t | nil + def get_cluster_by_url(url), do: Core.Repo.get_by(Cluster, console_url: url) + @spec has_cluster?(User.t) :: boolean def has_cluster?(%User{id: user_id}) do Cluster.for_user(user_id) diff --git a/apps/core/lib/core/services/shell.ex b/apps/core/lib/core/services/shell.ex index dac8fe792..1e1016796 100644 --- a/apps/core/lib/core/services/shell.ex +++ b/apps/core/lib/core/services/shell.ex @@ -220,8 +220,8 @@ defmodule Core.Services.Shell do defp maybe_enable_oidc(_, _, _, _), do: {:ok, true} - defp oidc_bindings(nil, %User{id: uid}), do: [%{user_id: uid}] - defp oidc_bindings(%OIDCProvider{bindings: bindings}, %User{id: uid}) do + def oidc_bindings(nil, %User{id: uid}), do: [%{user_id: uid}] + def oidc_bindings(%OIDCProvider{bindings: bindings}, %User{id: uid}) do bindings = Enum.map(bindings, fn %{user_id: uid, id: id} when is_binary(uid) -> %{user_id: uid, id: id} %{group_id: gid, id: id} when is_binary(gid) -> %{group_id: gid, id: id} @@ -244,8 +244,8 @@ defmodule Core.Services.Shell do |> String.replace("{subdomain}", domain) end - defp merge_uris(uris, nil), do: uris - defp merge_uris(new, %OIDCProvider{redirect_uris: old}), do: Enum.uniq(new ++ old) + def merge_uris(uris, nil), do: uris + def merge_uris(new, %OIDCProvider{redirect_uris: old}), do: Enum.uniq(new ++ old) @doc """ updates a user's shell workspace context (eg for configuring bundles) diff --git a/apps/core/mix.exs b/apps/core/mix.exs index 2a6f7098e..6fe107e44 100644 --- a/apps/core/mix.exs +++ b/apps/core/mix.exs @@ -91,6 +91,11 @@ defmodule Core.MixProject do {:dictionary, "~> 0.1.0"}, {:mojito, "~> 0.7.0"}, {:nebulex, "== 2.4.2"}, + {:castore, "~> 0.1.7"}, + {:req, "~> 0.4.14", override: true}, + {:mint, "~> 1.4.0", override: true}, + {:finch, "~> 0.17.0", override: true}, + {:absinthe_client, "~> 0.1.0"}, {:kazan, "~> 0.11", github: "michaeljguarino/kazan", branch: "k8s-1.23"}, {:workos, "~> 0.1.2"}, {:decorator, "~> 1.3"}, #=> For using Caching Annotations @@ -119,7 +124,6 @@ defmodule Core.MixProject do {:swarm, "~> 3.4.0"}, {:poison, "~> 3.0"}, {:cloudflare, "~> 0.2"}, - {:mint, "~> 1.0", override: true}, {:mimic, "~> 1.1", only: :test}, {:google_api_iam, "~> 0.40"}, {:google_api_cloud_resource_manager, "~> 0.41"}, diff --git a/apps/core/priv/repo/migrations/20240806022151_add_cloud_schemas.exs b/apps/core/priv/repo/migrations/20240806022151_add_cloud_schemas.exs new file mode 100644 index 000000000..411913d75 --- /dev/null +++ b/apps/core/priv/repo/migrations/20240806022151_add_cloud_schemas.exs @@ -0,0 +1,59 @@ +defmodule Core.Repo.Migrations.AddCloudSchemas do + use Ecto.Migration + + def change do + create table(:cloud_clusters, primary_key: false) do + add :id, :uuid, primary_key: true + add :name, :string + add :external_id, :uuid + add :cloud, :integer + add :region, :string + add :count, :integer, default: 0 + + timestamps() + end + + create table(:cockroach_clusters, primary_key: false) do + add :id, :uuid, primary_key: true + add :name, :string + add :cloud, :integer + add :region, :string + add :url, :string + add :certificate, :binary + add :endpoints, :map + add :count, :integer, default: 0 + + timestamps() + end + + create table(:console_instances, primary_key: false) do + add :id, :uuid, primary_key: true + add :name, :string + add :cloud, :integer + add :size, :integer + add :region, :string + add :status, :integer + add :subdomain, :string + add :url, :string + add :external_id, :string + add :configuration, :map + add :deleted_at, :utc_datetime_usec + + add :instance_status, :map + + add :cockroach_id, references(:cockroach_clusters, type: :uuid) + add :cluster_id, references(:cloud_clusters, type: :uuid) + add :owner_id, references(:users, type: :uuid) + + timestamps() + end + + create unique_index(:console_instances, [:name]) + create unique_index(:console_instances, [:subdomain]) + create unique_index(:console_instances, [:url]) + + create unique_index(:cockroach_clusters, [:name]) + + create unique_index(:cloud_clusters, [:name]) + end +end diff --git a/apps/core/test/services/cloud/workflow_test.exs b/apps/core/test/services/cloud/workflow_test.exs new file mode 100644 index 000000000..cba27df33 --- /dev/null +++ b/apps/core/test/services/cloud/workflow_test.exs @@ -0,0 +1,46 @@ +defmodule Core.Services.Cloud.WorkflowTest do + use Core.SchemaCase, async: true + use Mimic + alias Core.Services.{Cloud, Cloud.Workflow} + + describe "up and down" do + test "it will consistently provision a cloud console's infrastructure" do + account = insert(:account) + user = admin_user(account) + %{external_id: cluster_id} = cluster = insert(:cloud_cluster) + roach = insert(:cockroach_cluster) + insert(:repository, name: "console") + + expect(HTTPoison, :post, fn _, _, _ -> + {:ok, %{status_code: 200, body: Jason.encode!(%{client_id: "123", client_secret: "secret"})}} + end) + + {:ok, instance} = Cloud.create_instance(%{ + name: "plrltest", + cloud: :aws, + region: "us-east-1", + size: :small + }, user) + + expect(Core.Services.Cloud.Poller, :repository, fn -> {:ok, "some-id"} end) + expect(Core.Clients.Console, :create_service, fn _, ^cluster_id, _ -> {:ok, Ecto.UUID.generate()} end) + + {:ok, %{external_id: svc_id} = instance} = Workflow.provision(instance) + + assert instance.status == :provisioned + assert instance.instance_status.db + assert instance.instance_status.svc + + expect(Core.Clients.Console, :delete_service, fn _, ^svc_id -> {:ok, svc_id} end) + + {:ok, instance} = Workflow.deprovision(instance) + + refute instance.instance_status.db + refute instance.instance_status.svc + refute refetch(instance) + + assert refetch(roach).count == 0 + assert refetch(cluster).count == 0 + end + end +end diff --git a/apps/core/test/services/cloud_test.exs b/apps/core/test/services/cloud_test.exs new file mode 100644 index 000000000..fd5dd94de --- /dev/null +++ b/apps/core/test/services/cloud_test.exs @@ -0,0 +1,91 @@ +defmodule Core.Services.CloudTest do + use Core.SchemaCase, async: true + use Mimic + alias Core.Services.Cloud + alias Core.PubSub + + describe "#create_instance/2" do + test "creates a new cloud console instance" do + account = insert(:account) + user = admin_user(account) + cluster = insert(:cloud_cluster) + cockroach = insert(:cockroach_cluster) + insert(:repository, name: "console") + + expect(HTTPoison, :post, fn _, _, _ -> + {:ok, %{status_code: 200, body: Jason.encode!(%{client_id: "123", client_secret: "secret"})}} + end) + + {:ok, instance} = Cloud.create_instance(%{ + name: "plrltest", + cloud: :aws, + region: "us-east-1", + size: :small + }, user) + + assert instance.name == "plrltest" + assert instance.cloud == :aws + assert instance.region == "us-east-1" + assert instance.size == :small + + assert refetch(cluster).count == 1 + assert refetch(cockroach).count == 1 + + assert_receive {:event, %PubSub.ConsoleInstanceCreated{item: ^instance}} + end + end + + describe "#update_instance/3" do + test "managers can update the configuration of a console instance" do + user = insert(:user) + sa = insert(:user, service_account: true) + insert(:impersonation_policy_binding, + policy: build(:impersonation_policy, user: sa), + user: user + ) + instance = insert(:console_instance, owner: sa) + + {:ok, updated} = Cloud.update_instance(%{size: :large}, instance.id, user) + + assert updated.id == instance.id + assert updated.size == :large + + assert_receive {:event, %PubSub.ConsoleInstanceUpdated{item: ^updated}} + end + + test "non-managers can update the configuration of a console instance" do + user = insert(:user) + sa = insert(:user, service_account: true) + instance = insert(:console_instance, owner: sa) + + {:error, _} = Cloud.update_instance(%{size: :large}, instance.id, user) + end + end + + describe "#delete_instance/2" do + test "managers can update the configuration of a console instance" do + user = insert(:user) + sa = insert(:user, service_account: true) + insert(:impersonation_policy_binding, + policy: build(:impersonation_policy, user: sa), + user: user + ) + instance = insert(:console_instance, owner: sa) + + {:ok, deleted} = Cloud.delete_instance(instance.id, user) + + assert deleted.id == instance.id + assert deleted.deleted_at + + assert_receive {:event, %PubSub.ConsoleInstanceDeleted{item: ^deleted}} + end + + test "non-managers can update the configuration of a console instance" do + user = insert(:user) + sa = insert(:user, service_account: true) + instance = insert(:console_instance, owner: sa) + + {:error, _} = Cloud.delete_instance(instance.id, user) + end + end +end diff --git a/apps/core/test/support/factory.ex b/apps/core/test/support/factory.ex index 07436022c..7770b510c 100644 --- a/apps/core/test/support/factory.ex +++ b/apps/core/test/support/factory.ex @@ -641,6 +641,41 @@ defmodule Core.Factory do } end + def console_instance_factory do + name = sequence(:console_instance, & "instance#{&1}") + %Schema.ConsoleInstance{ + name: name, + cloud: :aws, + external_id: Ecto.UUID.generate(), + status: :provisioned, + size: :small, + cluster: build(:cloud_cluster), + cockroach: build(:cockroach_cluster), + subdomain: "#{name}.cloud.plural.sh", + url: "console.#{name}.cloud.plural.sh", + instance_status: %{db: true, svc: true}, + region: "us-east-1" + } + end + + def cloud_cluster_factory do + %Schema.CloudCluster{ + name: sequence(:cloud_cluster, & "cluster-#{&1}"), + cloud: :aws, + region: "us-east-1", + external_id: Ecto.UUID.generate() + } + end + + def cockroach_cluster_factory do + %Schema.CockroachCluster{ + name: sequence(:cockroach, & "cockroach-#{&1}"), + cloud: :aws, + url: "postgresql://plrl:plural@localhost:26257/plural", + certificate: File.read!("../../test-certs/client.root.crt") + } + end + def with_password(%Schema.User{} = user, password) do Schema.User.changeset(user, %{password: password}) |> Ecto.Changeset.apply_changes() diff --git a/apps/core/test/test_helper.exs b/apps/core/test/test_helper.exs index 433893338..fa1d67468 100644 --- a/apps/core/test/test_helper.exs +++ b/apps/core/test/test_helper.exs @@ -33,5 +33,7 @@ Mimic.copy(Core.Services.Shell.Pods) Mimic.copy(Vault) Mimic.copy(System) Mimic.copy(Core.Clients.Vault) +Mimic.copy(Core.Clients.Console) +Mimic.copy(Core.Services.Cloud.Poller) {:ok, _} = Application.ensure_all_started(:ex_machina) diff --git a/apps/graphql/lib/graphql.ex b/apps/graphql/lib/graphql.ex index e28eabdb4..62cb20d24 100644 --- a/apps/graphql/lib/graphql.ex +++ b/apps/graphql/lib/graphql.ex @@ -26,6 +26,7 @@ defmodule GraphQl do import_types GraphQl.Schema.Test import_types GraphQl.Schema.AI import_types GraphQl.Schema.Cluster + import_types GraphQl.Schema.Cloud alias GraphQl.Resolvers.{ User, @@ -43,7 +44,8 @@ defmodule GraphQl do Dns, Test, Cluster, - Upgrade + Upgrade, + Cloud } @sources [ @@ -63,6 +65,7 @@ defmodule GraphQl do Test, Cluster, Upgrade, + Cloud, GraphQl.InstallationLoader, GraphQl.ShellLoader, GraphQl.LockLoader, @@ -124,6 +127,7 @@ defmodule GraphQl do import_fields :test_queries import_fields :ai_queries import_fields :cluster_queries + import_fields :cloud_queries end mutation do @@ -144,6 +148,7 @@ defmodule GraphQl do import_fields :rollout_mutations import_fields :test_mutations import_fields :cluster_mutations + import_fields :cloud_mutations end subscription do diff --git a/apps/graphql/lib/graphql/resolvers/cloud.ex b/apps/graphql/lib/graphql/resolvers/cloud.ex new file mode 100644 index 000000000..497217a3c --- /dev/null +++ b/apps/graphql/lib/graphql/resolvers/cloud.ex @@ -0,0 +1,24 @@ +defmodule GraphQl.Resolvers.Cloud do + use GraphQl.Resolvers.Base, model: Core.Schema.ConsoleInstance + alias Core.Services.{Cloud, Clusters} + + def resolve_instance(%{id: id}, %{context: %{current_user: user}}), + do: Cloud.visible(id, user) + + def resolve_cluster(%ConsoleInstance{url: url}, _, _), do: {:ok, Clusters.get_cluster_by_url(url)} + + def list_instances(args, %{context: %{current_user: user}}) do + ConsoleInstance.for_account(user.account_id) + |> ConsoleInstance.ordered() + |> paginate(args) + end + + def create_instance(%{attributes: attrs}, %{context: %{current_user: user}}), + do: Cloud.create_instance(attrs, user) + + def update_instance(%{id: id, attributes: attrs}, %{context: %{current_user: user}}), + do: Cloud.update_instance(attrs, id, user) + + def delete_instance(%{id: id}, %{context: %{current_user: user}}), + do: Cloud.delete_instance(id, user) +end diff --git a/apps/graphql/lib/graphql/schema/cloud.ex b/apps/graphql/lib/graphql/schema/cloud.ex new file mode 100644 index 000000000..3e4a7ff71 --- /dev/null +++ b/apps/graphql/lib/graphql/schema/cloud.ex @@ -0,0 +1,85 @@ +defmodule GraphQl.Schema.Cloud do + use GraphQl.Schema.Base + alias Core.Schema.{CloudCluster, ConsoleInstance} + alias GraphQl.Resolvers.{User, Cloud} + + ecto_enum :cloud_provider, CloudCluster.Cloud + ecto_enum :console_instance_status, ConsoleInstance.Status + ecto_enum :console_size, ConsoleInstance.Size + + input_object :console_instance_attributes do + field :name, non_null(:string), description: "the name of this instance (globally unique)" + field :size, non_null(:console_size), description: "a heuristic size of this instance" + field :cloud, non_null(:cloud_provider), description: "the cloud provider to deploy to" + field :region, non_null(:string), description: "the region to deploy to (provider specific)" + end + + input_object :console_instance_update_attributes do + field :size, :console_size + field :configuration, :console_configuration_update_attributes + end + + input_object :console_configuration_update_attributes do + field :encryption_key, :string + end + + object :console_instance do + field :id, non_null(:id) + field :name, non_null(:string), description: "the name of this instance (globally unique)" + field :subdomain, non_null(:string), description: "the subdomain this instance lives under" + field :url, non_null(:string), description: "full console url of this instance" + field :cloud, non_null(:cloud_provider), description: "the cloud provider hosting this instance" + field :size, non_null(:console_size), description: "the heuristic size of this instance" + field :region, non_null(:string), description: "the region this instance is hosted in" + field :status, non_null(:console_instance_status), + description: "the provisioning status of this instance, liveness is fetched through the console field" + + field :deleted_at, :datetime, description: "the time this instance was deleted on" + + field :console, :cluster, resolve: &Cloud.resolve_cluster/3 + field :owner, :user, resolve: dataloader(User) + + timestamps() + end + + connection node_type: :console_instance + + object :cloud_queries do + field :console_instance, :console_instance do + middleware Authenticated + arg :id, non_null(:id) + + safe_resolve &Cloud.resolve_instance/2 + end + + connection field :console_instances, node_type: :console_instance do + middleware Authenticated + + safe_resolve &Cloud.list_instances/2 + end + end + + object :cloud_mutations do + field :create_console_instance, :console_instance do + middleware Authenticated + arg :attributes, non_null(:console_instance_attributes) + + safe_resolve &Cloud.create_instance/2 + end + + field :update_console_instance, :console_instance do + middleware Authenticated + arg :id, non_null(:id) + arg :attributes, non_null(:console_instance_update_attributes) + + safe_resolve &Cloud.update_instance/2 + end + + field :delete_console_instance, :console_instance do + middleware Authenticated + arg :id, non_null(:id) + + safe_resolve &Cloud.delete_instance/2 + end + end +end diff --git a/apps/graphql/test/mutations/cloud_mutations_test.exs b/apps/graphql/test/mutations/cloud_mutations_test.exs new file mode 100644 index 000000000..46f37d8d0 --- /dev/null +++ b/apps/graphql/test/mutations/cloud_mutations_test.exs @@ -0,0 +1,88 @@ +defmodule GraphQl.CloudMutationsTest do + use Core.SchemaCase, async: true + use Mimic + import GraphQl.TestHelpers + + describe "createConsoleInstance" do + test "it can create an instance" do + account = insert(:account) + user = admin_user(account) + insert(:cloud_cluster) + insert(:cockroach_cluster) + insert(:repository, name: "console") + + expect(HTTPoison, :post, fn _, _, _ -> + {:ok, %{status_code: 200, body: Jason.encode!(%{client_id: "123", client_secret: "secret"})}} + end) + + {:ok, %{data: %{"createConsoleInstance" => created}}} = run_query(""" + mutation Create($attrs: ConsoleInstanceAttributes!) { + createConsoleInstance(attributes: $attrs) { + id + name + region + size + cloud + } + } + """, %{"attrs" => %{ + "name" => "plrltest", + "cloud" => "AWS", + "size" => "SMALL", + "region" => "us-east-1" + }}, %{current_user: user}) + + assert created["name"] == "plrltest" + assert created["cloud"] == "AWS" + assert created["size"] == "SMALL" + assert created["region"] == "us-east-1" + end + end + + describe "updateConsoleInstance" do + test "you can update an instance" do + user = insert(:user) + sa = insert(:user, service_account: true) + insert(:impersonation_policy_binding, + policy: build(:impersonation_policy, user: sa), + user: user + ) + instance = insert(:console_instance, owner: sa) + + {:ok, %{data: %{"updateConsoleInstance" => updated}}} = run_query(""" + mutation Update($id: ID!, $attrs: ConsoleInstanceUpdateAttributes!) { + updateConsoleInstance(id: $id, attributes: $attrs) { + id + size + } + } + """, %{"id" => instance.id, "attrs" => %{"size" => "MEDIUM"}}, %{current_user: user}) + + assert updated["id"] == instance.id + assert updated["size"] == "MEDIUM" + end + end + + describe "deleteConsoleInstance" do + test "you can delete your instance" do + user = insert(:user) + sa = insert(:user, service_account: true) + insert(:impersonation_policy_binding, + policy: build(:impersonation_policy, user: sa), + user: user + ) + instance = insert(:console_instance, owner: sa) + + {:ok, %{data: %{"deleteConsoleInstance" => deleted}}} = run_query(""" + mutation Deleted($id: ID!) { + deleteConsoleInstance(id: $id) { + id + deletedAt + } + } + """, %{"id" => instance.id}, %{current_user: user}) + + assert deleted["id"] == instance.id + end + end +end diff --git a/apps/graphql/test/queries/cloud_queries_test.exs b/apps/graphql/test/queries/cloud_queries_test.exs new file mode 100644 index 000000000..3cd410d0e --- /dev/null +++ b/apps/graphql/test/queries/cloud_queries_test.exs @@ -0,0 +1,49 @@ +defmodule GraphQl.CloudQueriesTest do + use Core.SchemaCase, async: true + import GraphQl.TestHelpers + + describe "consoleInstances" do + test "it can fetch the cloud instances in your account" do + user = insert(:user) + instances = insert_list(3, :console_instance, owner: insert(:user, account: user.account)) + insert_list(2, :console_instance) + + {:ok, %{data: %{"consoleInstances" => found}}} = run_query(""" + query { + consoleInstances(first: 5) { + edges { node { id } } + } + } + """, %{}, %{current_user: user}) + + assert from_connection(found) + |> ids_equal(instances) + end + end + + describe "consoleInstance" do + test "you can describe console instances you can see" do + user = insert(:user) + instance = insert(:console_instance, owner: insert(:user, account: user.account)) + + {:ok, %{data: %{"consoleInstance" => found}}} = run_query(""" + query Get($id: ID!) { + consoleInstance(id: $id) { id } + } + """, %{"id" => instance.id}, %{current_user: user}) + + assert found["id"] == instance.id + end + + test "you cannot describe console instances you cannot see" do + user = insert(:user) + instance = insert(:console_instance) + + {:ok, %{errors: [_ | _]}} = run_query(""" + query Get($id: ID!) { + consoleInstance(id: $id) { id } + } + """, %{"id" => instance.id}, %{current_user: user}) + end + end +end diff --git a/config/config.exs b/config/config.exs index 7bfdadf51..6edf601e0 100644 --- a/config/config.exs +++ b/config/config.exs @@ -141,7 +141,12 @@ config :core, github_demo_token: "test-pat", github_demo_org: "pluralsh-demos", trial_plan: "Pro Trial", - sysbox_emails: [] + console_token: "bogus", + console_url: "https://console.example.com", + sysbox_emails: [], + mgmt_repo: "https://github.com/pluralsh/plural.git", + cockroach_parameters: [], + cockroach_ssl: true config :briefly, directory: [{:system, "TMPDIR"}, {:system, "TMP"}, {:system, "TEMP"}, "/tmp"], diff --git a/config/test.exs b/config/test.exs index b0a0e0e65..b730ec8b2 100644 --- a/config/test.exs +++ b/config/test.exs @@ -90,4 +90,6 @@ config :worker, config :core, workos_webhook: "supersecret", enforce_pricing: true, - sysbox_emails: ["sysbox@plural.sh"] + sysbox_emails: ["sysbox@plural.sh"], + cockroach_parameters: [sslmode: "allow"], + cockroach_ssl: true diff --git a/docker-compose.yml b/docker-compose.yml index 55ea799d5..1130b6cdc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3' services: db: image: postgres:14 @@ -9,6 +8,20 @@ services: POSTGRES_PASSWORD: postgres volumes: - database_data:/var/lib/postgresql/data + cockroach: + image: cockroachdb/cockroach:v24.1.3 + restart: always + ports: + - 26257:26257 + - 8080:8080 + command: start-single-node --certs-dir /cockroach/certs + environment: + - COCKROACH_DATABASE=plural + - COCKROACH_USER=plrl + - COCKROACH_PASSWORD=plural + volumes: + - roach_data:/cockroach/cockroach-data + - ./test-certs:/cockroach/certs rabbit: image: rabbitmq:3-management hostname: "rabbit1" @@ -51,5 +64,7 @@ services: volumes: database_data: driver: local + roach_data: + driver: local influxdb: driver: local \ No newline at end of file diff --git a/mix.lock b/mix.lock index 7cf270080..b6487ead8 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,6 @@ %{ "absinthe": {:hex, :absinthe, "1.7.0", "36819e7b1fd5046c9c734f27fe7e564aed3bda59f0354c37cd2df88fd32dd014", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0 or ~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "566a5b5519afc9b29c4d367f0c6768162de3ec03e9bf9916f9dc2bcbe7c09643"}, + "absinthe_client": {:hex, :absinthe_client, "0.1.1", "1e778d587a27b85ecc35e4a5fedc64c85d9fdfd05395745c7af5345564dff54e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:slipstream, "~> 1.0", [hex: :slipstream, repo: "hexpm", optional: false]}], "hexpm", "e75a28c5bb647f485e9c03bbc3a47e7783742794bd4c10f3307a495a9e7273b6"}, "absinthe_phoenix": {:hex, :absinthe_phoenix, "2.0.2", "e607b438db900049b9b3760f8ecd0591017a46122fffed7057bf6989020992b5", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:absinthe_plug, "~> 1.5", [hex: :absinthe_plug, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "d36918925c380dc7d2ed7d039c9a3b4182ec36723f7417a68745ade5aab22f8d"}, "absinthe_plug": {:hex, :absinthe_plug, "1.5.8", "38d230641ba9dca8f72f1fed2dfc8abd53b3907d1996363da32434ab6ee5d6ab", [:mix], [{:absinthe, "~> 1.5", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bbb04176647b735828861e7b2705465e53e2cf54ccf5a73ddd1ebd855f996e5a"}, "absinthe_relay": {:hex, :absinthe_relay, "1.5.2", "cfb8aed70f4e4c7718d3f1c212332d2ea728f17c7fc0f68f1e461f0f5f0c4b9a", [:mix], [{:absinthe, "~> 1.5.0 or ~> 1.6.0 or ~> 1.7.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "0587ee913afa31512e1457a5064ee88427f8fe7bcfbeeecd41c71d9cff0b62b6"}, @@ -20,7 +21,7 @@ "bourne": {:hex, :bourne, "1.1.0", "60a8bb3d5ad533a3a18257de9304e70e6bb2f4d7f354a529ef3f47c327c2dd97", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "81a389611ad067dc305d5ba4674280ea2b99ef012bca5e7de48357545edc05bc"}, "briefly": {:git, "https://github.com/CargoSense/briefly", "b0fd495bf0c5ef2c44de2791a8cc7a20813c7d36", [ref: "b0fd495bf0c5ef2c44de2791a8cc7a20813c7d36"]}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, - "castore": {:hex, :castore, "0.1.19", "a2c3e46d62b7f3aa2e6f88541c21d7400381e53704394462b9fd4f06f6d42bb6", [:mix], [], "hexpm", "e96e0161a5dc82ef441da24d5fa74aefc40d920f3a6645d15e1f9f3e66bb2109"}, + "castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "cloudflare": {:hex, :cloudflare, "0.2.0", "e754fab186d5c9cd30cafdd336b8150b227e31e45ea6bd279d3c6cb20ff21ac1", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:restlax, "~> 0.1", [hex: :restlax, repo: "hexpm", optional: false]}], "hexpm", "0c2fced3058d92f8732c9911e309496a2f26f6f6d0f8216b8a2c62c2f8028748"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, @@ -59,7 +60,7 @@ "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "excoveralls": {:hex, :excoveralls, "0.15.2", "809c1016660d80b28bbcd8cb7fd761791300def53345c1af5bd97db1330619ad", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f359dda36f15ae885d3259a90919b09ae9318f37c583c403493fe23808b2b882"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "finch": {:hex, :finch, "0.13.0", "c881e5460ec563bf02d4f4584079e62201db676ed4c0ef3e59189331c4eddf7b", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "49957dcde10dcdc042a123a507a9c5ec5a803f53646d451db2f7dea696fba6cc"}, + "finch": {:hex, :finch, "0.17.0", "17d06e1d44d891d20dbd437335eebe844e2426a0cd7e3a3e220b461127c73f70", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8d014a661bb6a437263d4b5abf0bcbd3cf0deb26b1e8596f2a271d22e48934c7"}, "flow": {:hex, :flow, "0.15.0", "503717c0e367b5713336181d5305106840f64abbad32c75d7af5ef1bb0908e38", [:mix], [{:gen_stage, "~> 0.14.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "d7ecbd4dd38a188494bc996d5014ef8335f436a0b262140a1f6441ae94714581"}, "gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm", "8453e2289d94c3199396eb517d65d6715ef26bcae0ee83eb5ff7a84445458d76"}, "gen_state_machine": {:hex, :gen_state_machine, "2.1.0", "a38b0e53fad812d29ec149f0d354da5d1bc0d7222c3711f3a0bd5aa608b42992", [:mix], [], "hexpm", "ae367038808db25cee2f2c4b8d0531522ea587c4995eb6f96ee73410a60fa06b"}, @@ -98,12 +99,14 @@ "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mimic": {:hex, :mimic, "1.1.3", "3bad83d5271b4faa7bbfef587417a6605cbbc802a353395d446a1e5f46fe7115", [:mix], [], "hexpm", "0d93cb8fcd00a1013bae56050755879050bb9b8ef0c3d51b6fec5a2f1fc33d66"}, "mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"}, + "mint_web_socket": {:hex, :mint_web_socket, "1.0.4", "0b539116dbb3d3f861cdf5e15e269a933cb501c113a14db7001a3157d96ffafd", [:mix], [{:mint, ">= 1.4.1 and < 2.0.0-0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "027d4c5529c45a4ba0ce27a01c0f35f284a5468519c045ca15f43decb360a991"}, "mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, "mojito": {:hex, :mojito, "0.7.12", "0461764ba427e720cc7270a603ac2b157a0c70688ffdf19dd116815eba554576", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.1", [hex: :mint, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1fa274d0fab59c6986d1efcb9d40f01d0b1159403968665a141a17601c29daac"}, "nebulex": {:hex, :nebulex, "2.4.2", "b3d2d86d57b15896fb8e6d6dd49b4a9dee2eedd6eddfb3b69bfdb616a09c2817", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.0", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "c9f888e5770fd47614c95990d0a02c3515216d51dc72e3c830eaf28f5649ba52"}, - "nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_ownership": {:hex, :nimble_ownership, "0.3.2", "d4fa4056ade0ae33b5a9eb64554a1b3779689282e37513260125d2d6b32e4874", [:mix], [], "hexpm", "28b9a9f4094fda1aa8ca72f732ff3223eb54aa3eda4fed9022254de2c152b138"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, - "nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "oauth2": {:hex, :oauth2, "2.0.0", "338382079fe16c514420fa218b0903f8ad2d4bfc0ad0c9f988867dfa246731b0", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "881b8364ac7385f9fddc7949379cbe3f7081da37233a1aa7aab844670a91e7e7"}, "openid_connect": {:git, "https://github.com/pluralsh/openid_connect", "c3b2701b9adbe01fd89bbd09816ffa6c9e4a825e", []}, "parallax": {:hex, :parallax, "1.0.0", "decfed51b2d41c88ea4398d3bd8fadd2ceb1f913cf0f44ef2cb41e732ace31f5", [:mix], [], "hexpm", "8a8b91685bfacb483ff9370b98844ee01107544a11ee49b5f1d98df0eff2b1e4"}, @@ -135,16 +138,18 @@ "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "recon": {:hex, :recon, "2.5.2", "cba53fa8db83ad968c9a652e09c3ed7ddcc4da434f27c3eaa9ca47ffb2b1ff03", [:mix, :rebar3], [], "hexpm", "2c7523c8dee91dff41f6b3d63cba2bd49eb6d2fe5bf1eec0df7f87eb5e230e1c"}, "remote_ip": {:hex, :remote_ip, "0.2.1", "cd27cd8ea54ecaaf3532776ff4c5e353b3804e710302e88c01eadeaaf42e7e24", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:inet_cidr, "~> 1.0", [hex: :inet_cidr, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2e7ab1a461cc3cd5719f37e116a08f45c8b8493923063631b164315d6b7ee8e0"}, + "req": {:hex, :req, "0.4.14", "103de133a076a31044e5458e0f850d5681eef23dfabf3ea34af63212e3b902e2", [:mix], [{:aws_signature, "~> 0.3.2", [hex: :aws_signature, repo: "hexpm", optional: true]}, {:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:nimble_ownership, "~> 0.2.0 or ~> 0.3.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "2ddd3d33f9ab714ced8d3c15fd03db40c14dbf129003c4a3eb80fac2cc0b1b08"}, "restlax": {:hex, :restlax, "0.1.0", "753e00aff37802f703e6b709d3f85b7816e7740cc5f833c446d677a42ecf4824", [:mix], [{:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gun, "~> 1.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:tesla, "~> 1.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "f1db23c10f7ff06ecd1f7e95380d49393a11194baacf58a1714c1e40a9d61123"}, "reverse_proxy_plug": {:hex, :reverse_proxy_plug, "2.1.1", "c5494a56c32e47e2f7ca677df4633846aec31a18ba1695928fef51e2d413cb27", [:mix], [{:cowboy, "~> 2.4", [hex: :cowboy, repo: "hexpm", optional: false]}, {:httpoison, "~> 1.2", [hex: :httpoison, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: false]}, {:tesla, "~> 1.4", [hex: :tesla, repo: "hexpm", optional: true]}], "hexpm", "e8f65bdfd35ebffaea2006eb8422c9126cbd899d0bf1552aedf80730c30204b6"}, "sentry": {:hex, :sentry, "8.0.6", "c8de1bf0523bc120ec37d596c55260901029ecb0994e7075b0973328779ceef7", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.3", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "051a2d0472162f3137787c7c9d6e6e4ef239de9329c8c45b1f1bf1e9379e1883"}, "shards": {:hex, :shards, "1.0.1", "1bdbbf047db27f3c3eb800a829d4a47062c84d5543cbfebcfc4c14d038bf9220", [:make, :rebar3], [], "hexpm", "2c57788afbf053c4024366772892beee89b8b72e884e764fb0a075dfa7442041"}, + "slipstream": {:hex, :slipstream, "1.1.1", "7e56f62f1a9ee81351e3c36f57b9b187e00dc2f470e70ba46ea7ad16e80b061f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mint_web_socket, "~> 0.2 or ~> 1.0", [hex: :mint_web_socket, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.1 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c20e420cde1654329d38ec3aa1c0e4debbd4c91ca421491e7984ad4644e638a6"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "stripity_stripe": {:hex, :stripity_stripe, "2.17.2", "6e4c5f0b2deb3d3411f91e23fedbe9db4c031a52dfeb7074b4df4fdd22ad32cd", [:mix], [{:hackney, "~> 1.15", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:uri_query, "~> 0.1.2", [hex: :uri_query, repo: "hexpm", optional: false]}], "hexpm", "42bffb591d34f3cfa95cb37e862140e44a61ca4c64578bf7bb591ba60c6a66bc"}, "swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm", "94884f84783fc1ba027aba8fe8a7dae4aad78c98e9f9c76667ec3471585c08c6"}, "sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"}, - "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, "tesla": {:hex, :tesla, "1.4.3", "f5a494e08fb1abe4fd9c28abb17f3d9b62b8f6fc492860baa91efb1aab61c8a0", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "e0755bb664bf4d664af72931f320c97adbf89da4586670f4864bf259b5750386"}, diff --git a/rel/config/config.exs b/rel/config/config.exs index 5b65875cd..28130f082 100644 --- a/rel/config/config.exs +++ b/rel/config/config.exs @@ -90,7 +90,10 @@ config :core, openai_token: get_env("OPENAI_BEARER_TOKEN"), enforce_pricing: get_env("ENFORCE_PRICING"), stripe_webhook_secret: get_env("STRIPE_WEBHOOK_SECRET"), - github_demo_token: get_env("GITHUB_DEMO_TOKEN") + github_demo_token: get_env("GITHUB_DEMO_TOKEN"), + console_token: get_env("CONSOLE_SA_TOKEN"), + console_url: get_env("CONSOLE_URL"), + mgmt_repo: get_env("CONSOLE_MGMT_REPO") if get_env("VAULT_HOST") do diff --git a/schema/schema.graphql b/schema/schema.graphql index d68fd0b2f..3950e7b21 100644 --- a/schema/schema.graphql +++ b/schema/schema.graphql @@ -1114,6 +1114,17 @@ type PolicyBinding { updatedAt: DateTime } +type ConsoleInstanceEdge { + node: ConsoleInstance + cursor: String +} + +enum ConsoleSize { + SMALL + MEDIUM + LARGE +} + type UpgradeQueue { id: ID! acked: ID @@ -1143,6 +1154,42 @@ type AuditEdge { cursor: String } +type ConsoleInstance { + id: ID! + + "the name of this instance (globally unique)" + name: String! + + "the subdomain this instance lives under" + subdomain: String! + + "full console url of this instance" + url: String! + + "the cloud provider hosting this instance" + cloud: CloudProvider! + + "the heuristic size of this instance" + size: ConsoleSize! + + "the region this instance is hosted in" + region: String! + + "the provisioning status of this instance, liveness is fetched through the console field" + status: ConsoleInstanceStatus! + + "the time this instance was deleted on" + deletedAt: DateTime + + console: Cluster + + owner: User + + insertedAt: DateTime + + updatedAt: DateTime +} + type InvoiceEdge { node: Invoice cursor: String @@ -1320,6 +1367,15 @@ type RecipeTest { args: [TestArgument] } +enum ConsoleInstanceStatus { + PENDING + DATABASE_CREATED + DEPLOYMENT_CREATED + PROVISIONED + DEPLOYMENT_DELETED + DATABASE_DELETED +} + input IncidentAttributes { title: String severity: Int @@ -1350,6 +1406,10 @@ input ChartInstallationAttributes { versionId: ID } +input ConsoleConfigurationUpdateAttributes { + encryptionKey: String +} + enum TestStatus { QUEUED RUNNING @@ -1539,18 +1599,18 @@ input LockAttributes { lock: String! } -input IncidentFilter { - type: IncidentFilterType! - value: String - statuses: [IncidentStatus] -} - enum OauthProvider { GITHUB GOOGLE GITLAB } +input IncidentFilter { + type: IncidentFilterType! + value: String + statuses: [IncidentStatus] +} + type ScanError { message: String } @@ -1644,6 +1704,19 @@ type IncidentMessageDelta { payload: IncidentMessage } +type DnsRecordConnection { + pageInfo: PageInfo! + edges: [DnsRecordEdge] +} + +type Reaction { + name: String! + creator: User! + message: IncidentMessage! + insertedAt: DateTime + updatedAt: DateTime +} + type ShellConfiguration { workspace: ShellWorkspace git: GitConfiguration @@ -2014,19 +2087,17 @@ type RootMutationType { "The cluster's cloud provider." provider: Provider! ): Cluster -} -type DnsRecordConnection { - pageInfo: PageInfo! - edges: [DnsRecordEdge] + createConsoleInstance(attributes: ConsoleInstanceAttributes!): ConsoleInstance + + updateConsoleInstance(id: ID!, attributes: ConsoleInstanceUpdateAttributes!): ConsoleInstance + + deleteConsoleInstance(id: ID!): ConsoleInstance } -type Reaction { - name: String! - creator: User! - message: IncidentMessage! - insertedAt: DateTime - updatedAt: DateTime +type ConsoleInstanceConnection { + pageInfo: PageInfo! + edges: [ConsoleInstanceEdge] } "An installation of an application." @@ -2398,6 +2469,10 @@ type RootQueryType { "Get a list of clusters owned by the current account." clusters(after: String, first: Int, before: String, last: Int): ClusterConnection + consoleInstance(id: ID!): ConsoleInstance + + consoleInstances(after: String, first: Int, before: String, last: Int): ConsoleInstanceConnection + configuration: PluralConfiguration tags(after: String, first: Int, before: String, last: Int, id: ID, type: TagGroup!, q: String): GroupedTagConnection @@ -2817,6 +2892,11 @@ input OnboardingChecklistAttributes { dismissed: Boolean } +input ConsoleInstanceUpdateAttributes { + size: ConsoleSize + configuration: ConsoleConfigurationUpdateAttributes +} + enum RolloutStatus { QUEUED RUNNING @@ -3027,6 +3107,10 @@ type Contributor { updatedAt: DateTime } +enum CloudProvider { + AWS +} + type ImageLayer { digest: String diffId: String @@ -3641,6 +3725,20 @@ type PlatformMetrics { publishers: Int } +input ConsoleInstanceAttributes { + "the name of this instance (globally unique)" + name: String! + + "a heuristic size of this instance" + size: ConsoleSize! + + "the cloud provider to deploy to" + cloud: CloudProvider! + + "the region to deploy to (provider specific)" + region: String! +} + type OidcSettings { uriFormat: String uriFormats: [String] diff --git a/test-certs/ca.crt b/test-certs/ca.crt new file mode 100644 index 000000000..ea2e4b09c --- /dev/null +++ b/test-certs/ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJTCCAg2gAwIBAgIQcrxKxYw38+jYGFUlxCHoSDANBgkqhkiG9w0BAQsFADAr +MRIwEAYDVQQKEwlDb2Nrcm9hY2gxFTATBgNVBAMTDENvY2tyb2FjaCBDQTAeFw0y +NDA4MDUyMDA4NDVaFw0zNDA4MTQyMDA4NDVaMCsxEjAQBgNVBAoTCUNvY2tyb2Fj +aDEVMBMGA1UEAxMMQ29ja3JvYWNoIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArO18kE1tEOF2yZ6OIWXcD9FEE7vg/cWPZTnq8C1VCC2wQCB84naD +FgS/ic/PVFgxn6vr/LGN4BUo6P0TnrrvPVU83EyYrCrz8+ie/o4GIoR5NJpa6QBm +VgXNYGvYbA0H+TVeakCNlCy1SazCsUuWoe2tHimW8wfh/asogDzpmDfSLBeUEN0p +rXiuPoJldjOK9HolPF4SDmjJ0wEveFkEDNxB0KRJXxheRNHAS1hB1guYYGykxl3D +NWOIF3VNwE2u2VkiniKnTaEtoJk8HdE8H211FlkVmE7+40LX+2uE9kfpJL8GvRaG +sYDQafrl+a9C0m7lRKIZQkbyeElQXMqyhwIDAQABo0UwQzAOBgNVHQ8BAf8EBAMC +AuQwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUuhs6UvvhM2InE8Gr1Rjn +sCZ/DCEwDQYJKoZIhvcNAQELBQADggEBAEO2mg7wEBAOLqWlSfqE/f5+xtJRRgxu +I3TQeGe5zdxBDkFhekoyI6sX67ioL2n7wN/xYzE43lEMlf8nJ82NPJ62QtaHw7y9 +Vv9KpaL3T4onav7/8RPA+JGnGNLT9hCpSx8Pwr8Vk80FhTmtiLgELoQDLpat8RUd +MKI6nm4gcosa+0B5E7RSVCpwx3H3FtI9EmwdJUDVgN7FrxmaiMlZT3JtpzKy/+vL +tdZQBGDdmT0K/Nm+HJejvfAJYQ5gDgPK4txYYS+Y/xMbaK6onms5rQ/B0peJHpRx +6HFF1MsW5y3+QzwZxYPZ/MR5wtdY1SqUsfmEVRihAP5YZU/V4TZ51a4= +-----END CERTIFICATE----- diff --git a/test-certs/ca.key b/test-certs/ca.key new file mode 100644 index 000000000..bf2899bc0 --- /dev/null +++ b/test-certs/ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEArO18kE1tEOF2yZ6OIWXcD9FEE7vg/cWPZTnq8C1VCC2wQCB8 +4naDFgS/ic/PVFgxn6vr/LGN4BUo6P0TnrrvPVU83EyYrCrz8+ie/o4GIoR5NJpa +6QBmVgXNYGvYbA0H+TVeakCNlCy1SazCsUuWoe2tHimW8wfh/asogDzpmDfSLBeU +EN0prXiuPoJldjOK9HolPF4SDmjJ0wEveFkEDNxB0KRJXxheRNHAS1hB1guYYGyk +xl3DNWOIF3VNwE2u2VkiniKnTaEtoJk8HdE8H211FlkVmE7+40LX+2uE9kfpJL8G +vRaGsYDQafrl+a9C0m7lRKIZQkbyeElQXMqyhwIDAQABAoIBACcEcp2QirJLXOUC +/Gi2td9vxR96vbokkEUzuzmz0MLGpETjz6m9ObiHG7x5gfsy5wdy4A0k9rD/tTpU +l8+xt/cru06tjakTC/YVn4IqA2JYZ4yh4y89Ytm3923xB2Thz9G/4+yvYgJzz/eu +nkMonfUtTmlG0wNE3Q3CR6IfnEk2t9EvW0I7fV8q2cK6go/1tIakng3qKrYffZoT +CDnVILRf1TWF6/kxehhQ82E6/F6VzeDqIdpXVeP8V84JyVSByzv4VuJwoP2u/LoP +u1TLiZDdz3i1L2wa479aURTonJ5l4BKsstm0V1Rof3ke+Oht0lqt1jBJceKl0s9f +vSUHEWkCgYEA5OtpqjnOUUV+kOhPi/M9sJoPYixcJm1imRH1hFQPig/ANxniMknU +ATCHRoYY2UdoMbbpyzHnYgmlNgLJ8Ye10ln0b1iNIX4hKqh9KssC+eoKu/lckxBD +SLO37PBy1Sr5gFRtWL3sXTHxwRWET1CsVtxMkKsQ+wzJzyqcdBgX8h0CgYEAwWJr +HTagmy2CQumBjBlmwbYi3k2ebpNlT2Mu+xfeKhDEacVMZCOYEIbmHs62F7KpoCCR +rGdGBZCaH7HbTztKtDcsSAZQtyDi8gllmrlDbGRh8Czim6508Iq+alILaOHJY6mT +36Rbd6+WMdchW8pt4dMxtM2mM592tQCFbk2plfMCgYEAjMuNrbjoAGxxr8ZyRm1Q +n/HhTExkUO4uGl9FViItfIi7fChbSvrFfgXO7b1EQz78Arexi9whp9au2k1Uz0zc +s4fII0Ofad4doBMJCOzb48Hi4EI6gekauPhwQzaQKjPISPmkV1RYr+kr+8m0GuRP +Ww0buk68nuv6o4Rdvbmj4LECgYEAwVcWZESjWcGsBB3UqS8ULwNb5QmRJlgy56Xv +6JY6RbAxztW0xEFZ3Nwh4hckspirQi6NteIifZnOnXWCH7rVu0uV+ARoWk6E1xtR ++9jRceMCj88wWBmZTIxKR53zTk6+u+dksiluUUC5VucQsOa36RjvDyazzdTWJ6x3 +uonmzMUCgYEAu0ofZts1GphijdxoGReUNHsOJOz/FDvwQQ3mGvKa1Hm5oqDUmPos +OT4LlET0Hu8HTZbTsg4GJMCguerCtcd9vrioB/oHb+lQ6gMyIkeM5JbmOHa8iKIg +GfP09isaLqrFmMjoa1wIyMuG0OYHg9FBlm70+9Bm5yVc3SA+90vzmzw= +-----END RSA PRIVATE KEY----- diff --git a/test-certs/client.plrl.crt b/test-certs/client.plrl.crt new file mode 100644 index 000000000..c0e05956d --- /dev/null +++ b/test-certs/client.plrl.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIQOKE8BHkMMU5H/m6N+ZioRTANBgkqhkiG9w0BAQsFADAr +MRIwEAYDVQQKEwlDb2Nrcm9hY2gxFTATBgNVBAMTDENvY2tyb2FjaCBDQTAeFw0y +NDA4MDUyMTAxNTRaFw0yOTA4MTAyMTAxNTRaMCMxEjAQBgNVBAoTCUNvY2tyb2Fj +aDENMAsGA1UEAxMEcGxybDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AKJVfV4iVgRq/p/fvF5T9gIYmyoutadwBqS+06AxMCO1QrpvEjtLifQyjdmL3XTa +sErZo0URgcQQRoQoPzJzmpa6e2wOfXNbDK51sxdsPXDQ1rZveNfwtduQlyn8WGcv +F+ctK82Hp9UUx+tTsaZLpMZNbmfM+Y8gU/1XeeEbdtkPcXkJxqqsGh71bPhqzZ45 +8V2Ta78fXhMu2teddajOHAV3xVrCFLqgCG9+ugUv75My2QVMEGSZtUQtyW/VpS75 +i/y6qHyQrf72dGO0DeULAmc+X7hN5gWUoS7m7o4RPtCIzUZM5dXYoP6iyeIcagLv +NoKSuqriu2c46+swiFgh1s0CAwEAAaNIMEYwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud +JQQMMAoGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFLobOlL74TNiJxPBq9UY57Amfwwh +MA0GCSqGSIb3DQEBCwUAA4IBAQAlyLomJOlyFMV96frfsahAd1SWRllU8OuySHbw +zaQDAgox9XGFTS6ClXUHrpnlNEUkVWRHDh+Q0rni9XRMeoheHA5RU7kA1myXiYcS +yl06Eho5Jif3zb8PTM+ijqkf66MBo5OwhXPUycGwEM/7MtrMq5OfeV3dKds55msE +cn+PPLyY/FgmMwYllozVgmaWBKESmGZkkSvqI2fxZe1fVWPm+AHg8/TeQur/QPSi +VCyTb7JqUBJSq1vfXrJC/JtO7wV6pw5wyZE4l5J7PNyMDZP+m5yUJvCm3y+jTqOg +zsV6a7rJ8A1C4AqN4ECGhSZ3WKmUNNokflW8bhKaVPvelhsW +-----END CERTIFICATE----- diff --git a/test-certs/client.plrl.key b/test-certs/client.plrl.key new file mode 100644 index 000000000..a48c41986 --- /dev/null +++ b/test-certs/client.plrl.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAolV9XiJWBGr+n9+8XlP2AhibKi61p3AGpL7ToDEwI7VCum8S +O0uJ9DKN2YvddNqwStmjRRGBxBBGhCg/MnOalrp7bA59c1sMrnWzF2w9cNDWtm94 +1/C125CXKfxYZy8X5y0rzYen1RTH61Oxpkukxk1uZ8z5jyBT/Vd54Rt22Q9xeQnG +qqwaHvVs+GrNnjnxXZNrvx9eEy7a1511qM4cBXfFWsIUuqAIb366BS/vkzLZBUwQ +ZJm1RC3Jb9WlLvmL/LqofJCt/vZ0Y7QN5QsCZz5fuE3mBZShLubujhE+0IjNRkzl +1dig/qLJ4hxqAu82gpK6quK7Zzjr6zCIWCHWzQIDAQABAoIBAD+Z+RWBpGv0s7BV +y4q1oKQvmdOMG3XvqVqwvV99Bhxnby52SBFdRj6b9AGxD+IxsHKTFKoB8vKrYcVz +s1cD/iRiCetLPhGNMcnEqZdavDPQTt+JizInyEivPNqhWM2MoPPdOdU7+iC9FJr5 +3peF+ZgMTF50664M/3s547GzvfvKtjNoXB3TR53kByrKn/4eJKJC2QqCl4fQUm4J +9JcWSRAQQ4m7Yajhaw7FPe3ThJywx/l4CZWaIb4G5me2Blveyr7CvEZl70hpM7Mb +G+CyIS1U9nCVLyR1/w7SsNpnjHoviOPZA6fe6afjRZ+yl0qA0fCE1DpDlvXYW0TA +uQG4Nc0CgYEA1zyXCujN5HdyP8jMhiqGvFw+A6lo/Uez4b+uOYeT2w5f6Wvrprrh +nZYfWORZIY/DrVMzMfB38ZsV9XbqeaKGpOHjnYPVQaOmi7d/ySM1i9gT/3vx5YYJ +SAy5J2B2cQgrp9a3zgRJ/iwbE8zsnXWIZpGbGw7Sf2umkCk+rI+4/XsCgYEAwRP+ +hxgGX9X0zd1RYe9TqroiCb63vlv/RcyWWJBYNlBSUThgkfz2HtX60yoi+zIHuaVv +Y2Cfl6a71yL/bbDT8djVb/TfI5eTdu6EQhZPRyrLyGCIyjwn15/2hwe280rhk8AP +sHNdY43/Oga1RxU2Rho8R421WvwvyzOIf3I3dlcCgYASzHv0u/ifp78Wyxbx8Ozr +rrLL5xQAay7O3Mr+w1SqoUrHPGHMoi3qcZ4zSV5vi/rvPIEQL4yKUZ1NXyGDlipk +ZEZZD5aksQJu2YwTqTuYBztX6R9riJVu8Y2KXuZ5YNlPQ3WyTLWZuYhz5kYUSpur +ObRuXJEOB/z4QmHy+GqT2QKBgQCjAqF7J0KCeR5VDIzgNXIbC0kflyCEnCFaqc8p +eLyxJoFJR2Lo/nxBLoG09LdSAzxYdZ8kdPhozgF7MnYbAxb98gbJniEiaJ+84sVR +EoreV/hy9WWn8L/moYw/FrlUkR0AZEAaxlvLaKBaOj2khPksmIvaEX9TEOW9FoYg +q6q9wwKBgFK1opFBJLMsobt3ojB+zu+WMwYJCkrGR6D41gHYrBpqNJ6MZsYJ+n5g +9lJQB6MXg7i+H3l+6o2nbK7NdIGnJNH9AqfLvLeDxPzSNBbhuPmEVAtyrWebQOL1 +r/o4+WXFyGrFkukHWyBfjQqVTrCb73gRghJRPSFIJc+h7zPAI3l+ +-----END RSA PRIVATE KEY----- diff --git a/test-certs/client.plural.crt b/test-certs/client.plural.crt new file mode 100644 index 000000000..a87697310 --- /dev/null +++ b/test-certs/client.plural.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDIjCCAgqgAwIBAgIQEzqeZ8kW+sY8/2CwaKC2cjANBgkqhkiG9w0BAQsFADAr +MRIwEAYDVQQKEwlDb2Nrcm9hY2gxFTATBgNVBAMTDENvY2tyb2FjaCBDQTAeFw0y +NDA4MDUyMDA5MTVaFw0yOTA4MTAyMDA5MTVaMCUxEjAQBgNVBAoTCUNvY2tyb2Fj +aDEPMA0GA1UEAxMGcGx1cmFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAvIRgQf41YLOE+2PO/hVWkgkKBDE+gBqDIdwrt8Re3Jwtqi0QelmtVi5gUj90 +AJIHHpolQoYh+NXwqe4XmspI7XWb9lxyf1aA9t6k5BaTdtAEcwjMR5lRA5ps/dco +XbEHPkqMoScFsrNdO5sMVW77/ZnD4HmVOklAX1Xbq79p2m8GoSpsuZ1r8KyhtMIm +zCNRBTCApFS20ND+Z8w8GO3OY0U5oPiMbOBjNsfXEh+OWXj4rcbyxpKDUb3zXdWs +wZf62/bT7cf8wVh/1A9LF2nRHwCaFPBeIUf1ZAFRBoD3TqPZb25ho2zr+Ee1hs/p +Om8sk02lp4Y6ICfZYx/RS5VH6wIDAQABo0gwRjAOBgNVHQ8BAf8EBAMCBaAwEwYD +VR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0jBBgwFoAUuhs6UvvhM2InE8Gr1RjnsCZ/ +DCEwDQYJKoZIhvcNAQELBQADggEBAFGHlQuyR4hXFZyCq1gakxFBVlEZjkzvKgqT +NzHoC+2hTBGmTx24AVIR4NjZrXGjHAT/JlKp5T3JETgAfRIMtDW3VAoP03c/HkKp +GhoNUygIOCZW73u/zhVsLonZnoADz0sjMSpWYabTGeW2YdmT6i9J548Q2lVpeK/z +oLPnWDXZcYYJhNn7g6yh9YjvjEN5wRMTrqbVEqYeGi5hN9t5Jc9onS9Z8NFih2LO +1nqxLaKdmvMW70icif+zd5aGY8v24a1badJlCcKBdG8F/qAZIAUt3qGpcHh+D1hH +l0CACuHHwn1saIJJbgqRK0UuwvyiBaHk2B6el5kUFFgAl5Y+vfM= +-----END CERTIFICATE----- diff --git a/test-certs/client.plural.key b/test-certs/client.plural.key new file mode 100644 index 000000000..e83811a97 --- /dev/null +++ b/test-certs/client.plural.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAvIRgQf41YLOE+2PO/hVWkgkKBDE+gBqDIdwrt8Re3Jwtqi0Q +elmtVi5gUj90AJIHHpolQoYh+NXwqe4XmspI7XWb9lxyf1aA9t6k5BaTdtAEcwjM +R5lRA5ps/dcoXbEHPkqMoScFsrNdO5sMVW77/ZnD4HmVOklAX1Xbq79p2m8GoSps +uZ1r8KyhtMImzCNRBTCApFS20ND+Z8w8GO3OY0U5oPiMbOBjNsfXEh+OWXj4rcby +xpKDUb3zXdWswZf62/bT7cf8wVh/1A9LF2nRHwCaFPBeIUf1ZAFRBoD3TqPZb25h +o2zr+Ee1hs/pOm8sk02lp4Y6ICfZYx/RS5VH6wIDAQABAoIBAFy1pTkPMWT7JD0V +WqiuCcML2vcw2JHbd/3g70cStaw8ymBUhTQGgi5N5GTOe9gD+KheGlDa8j4TyKVp +SAaB0Nem7Sp9UHm7KvU9jU32pFLb/jQscXoMtkDVYyB6/YkGFI/Xzc07CZxWhVVY +7Pne+OJBComMgY0GlTfNW5jdK8uV0izOHq7iQai5aEH9pQ74CuN/d2Rz4U+J6SY3 +UIQIZV1nBIoE13fv+Qx0EW+n420f3YvvmksTw4UAUzPopUWkrf/U0PZ+q1K9TrIg +1zjySx3R1MnXrcG7cmIRvHmTN4AabBhCLfNE94CGyM4+DyPfyLtqf+A28GMuzO1W +AHvE5xECgYEA5A1PuItmdtL9nm6D2bqLZB9AcGwxKYjriUpAXXSQ2GRjwCSemCJG +VwDINY1YB271NFmOtkxMv+yA8K9kxvsmNfE88stSelPTNa04galAyQop/r339zuU +8PQ4hK9E3PfrbwoEo+RXvrePG4kx23RjErz+AZEyizdu8j7Im02rDz8CgYEA0567 +13yajqkW0Z6+ebP4NeGdLhHDtvAmM98QUeIKYBUucRsOPxuBNdgi4ef1vwRJgh5T +akFSRswBQyTrLV7KMUkxTX9MdkOTu/51/+udTKTJwBYSsjd0PFOqX16XpPKsDgvk +7x+5io3kY135Up+QgSIToUUOdsSCUX8Wx4ogyFUCgYBHQhmM8pH7wwXYgXPVjsKL +cHB0MHpUHiJq7baa/RddZlD88vRTWTJdcTMd+KJvAC7CQtGjCMhamA3aJxP4UPWp +mU2QGBQXgSXX+d5UNYy2xORGYxmeORHbEJSwcQu1eAoq0hiNFYaNY1HmKsJXblCe +xEsiNjpy4clbDF8ml+hBhQKBgGYyyICeun2fB1ggk9huo7ZRyPjQt3YHsEtotJb2 +1Q/l6tHQwSaU/+ejA+WZgkLHrUBbqOkttrBeIVXt5G2602bN9DN7ANA0fopRr2by +wLqc8poDju/mfAoOVb9NDWspjxIRiHQ7G/3f3KZoYdoXGVBjxpshoISXDmCRQc0H +1iEpAoGAEnSzV+8TbC+WLOFbf70eakN05N5/qHzNHm/Gto3r3lCIE20NJqO1dCm+ +c2KIrd0iohxzMbH1s/0rJybnTqzgXl7C96niH9GA5/RSepE43QAW0JL5zDGWYufJ +U5eizyAo4kjsQJmozhUF5rUXeOgvI/VPA7iGtQqDsbmni7Vh3rM= +-----END RSA PRIVATE KEY----- diff --git a/test-certs/client.root.crt b/test-certs/client.root.crt new file mode 100644 index 000000000..23368b291 --- /dev/null +++ b/test-certs/client.root.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIQKx6fqFWV70V5qGpDcqd70jANBgkqhkiG9w0BAQsFADAr +MRIwEAYDVQQKEwlDb2Nrcm9hY2gxFTATBgNVBAMTDENvY2tyb2FjaCBDQTAeFw0y +NDA4MDUyMDU5NTNaFw0yOTA4MTAyMDU5NTNaMCMxEjAQBgNVBAoTCUNvY2tyb2Fj +aDENMAsGA1UEAxMEcm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALDEZyfFRByM6CGCzKXoniLBkHoF+jMT8DX+Fp8b0Z6mutWGj52iRX8mQzLLbxBy +eLfzfeYmszvKIhOV6VQb4h4bDRIjsUpJqJZ4b56tBusOQ0Xf+dRbxLkXvpTY6zbP +rTOxc5LglAgb/enEiMxIQAlxmZlKUfSAz+0MslIMOjYfxtsRXdiEQNxfOd0ivz3q +gaOQkeRAQvCFcjxtWkWw7xRU6M594wA2jRB/xX+0JTZe4kXXEEJYL4YAshpM7RDg +BxMfTBy/ZaMPqY7j6xYG0gFo+FEabFspM+DC0VUDs4eaNGPedyqaxaR424Q3VbGo +llcsxZ1FX/GaR1HUJhrINAECAwEAAaNIMEYwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud +JQQMMAoGCCsGAQUFBwMCMB8GA1UdIwQYMBaAFLobOlL74TNiJxPBq9UY57Amfwwh +MA0GCSqGSIb3DQEBCwUAA4IBAQA8zFhunyeNLrt2XYb4hklP7OvVANJVltyK4qxc +aYg5wEz37DlJvMorxt4z8b5dt+/7mWoohBr+2vvpab9hiGcCbjcbBXuQpEtwMLDL +1dFfcxLyBrMJ/JGmotZNPJbPuO1nTRhNiIQvnXJ0C16UWR8kTohsIlXBIzYXj4CA +AJaLIyU0GXOXOsIaTCmmval8o+yXj2og1noHQSuxrZeh5kyBUg9iufLd5PGV6i4N +hzSINw5ALI4vUgK5hvoDXqzZSLoDBUCQoaS8DybwKIYFLnrCiE2ETpJ3KQBKZ/Uf +oVnDvkM+qfUoc8g2BXwhjqOO6wOTt+pfB5K7pclvod5Zu7je +-----END CERTIFICATE----- diff --git a/test-certs/client.root.key b/test-certs/client.root.key new file mode 100644 index 000000000..e109ff6ea --- /dev/null +++ b/test-certs/client.root.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAsMRnJ8VEHIzoIYLMpeieIsGQegX6MxPwNf4WnxvRnqa61YaP +naJFfyZDMstvEHJ4t/N95iazO8oiE5XpVBviHhsNEiOxSkmolnhvnq0G6w5DRd/5 +1FvEuRe+lNjrNs+tM7FzkuCUCBv96cSIzEhACXGZmUpR9IDP7QyyUgw6Nh/G2xFd +2IRA3F853SK/PeqBo5CR5EBC8IVyPG1aRbDvFFTozn3jADaNEH/Ff7QlNl7iRdcQ +QlgvhgCyGkztEOAHEx9MHL9low+pjuPrFgbSAWj4URpsWykz4MLRVQOzh5o0Y953 +KprFpHjbhDdVsaiWVyzFnUVf8ZpHUdQmGsg0AQIDAQABAoIBAHCvI1tVA5B3utrx ++OeinHxnjGRdPxwkj7K7oUwYfc53Vw8bUp4YfdkKWTgeO1TuxEnvrMh0zzlEJ+j4 +Xd/CTqkGcZSosDI9Nn2FlRJAFvLtCrGWkDpxrbyYiGN2zKsKRM15R8XP6E4rGfhV +0yb2v+vFQTdZC1mka+gfeeWX6dvp4Xr2Zge+NtvwzSPqAiCq3d3C3fpPipyNSYXA +O7MogpKN2MjCVNBOFrU0j52MtOIz9UKyao+3fEkFHrRY3XU8NiCLzlIHTsRnvuZ0 +Klj+TmlkcUDhlye4pWTn4At41AsbILEB6dY7r4IgXvA68mg6kMtSgJIfQbjjOSBX +KQZzjgECgYEAwY+zzWtiEPKJ/0S+vkNlQoeoApjsaGBwWtVUmyI7JFVh2+I/CpWl +5Ih4YWXYUFTmcJI4QbtUN0RxUnnwq9MvB79CguBZ2hQFzDxnqCnw0J1BUG4ek8H9 +YHBBCuieTD5drzHLRhqZ///HYnhuW+JVrn2Qzy0uGof3obYrPSZKN3ECgYEA6cnX +J5szFV4ZwO7YK857NMlHbeQ2/KsqyGMBQbWwQ3D9vbnFgjvBuN83nEQjKq+7xx3j +O3zw/6SWDelhO08PIkZhz8G5ThBKo0e03Q/LM/hzU/qkvEr74GViNkTUryOX5Y7O +iFTOk3vVwQ3hpEkWCjCgN0OqqoFeM4OpifRnHZECgYAL8NyLwKMlHoFK4SfPPwMh +LpM7zclPyxtDT0PP6jWx0R6fXP68nWzFg3HBuHG2yvYIXczgqTjRzAQdrW4ee4G8 +PZ1Ry5tfa7rL1bIjWqsDg6AC4Tg7r0z9RQ+OJ7mURIitBSpgIcX8WAAp3/oW6wiU +IiTr0wK0Gk05ia5zMEiF0QKBgQCBM/5HMjuCFJZRDO7FDwECRDbIobZN317ujeC0 +6u915i0lPFTOS64dOKeoIfP5dFNnkLnzN3KNJe2T1KXavC+V9qW5xstdPB2zrT5k +aYUYvLcn8z41nY8kDYPAkDumaxpYUJsNtyjPU0Hnxn8sZwPaL9Nrl2brgkGIYLxC +tKKEMQKBgHg/TfHP17KVHhlmRP65clge9jtCKASe4KwkJRtC5btIr63SZpgBNX55 +jmp052iDzfknHnWUE2U+0Ruonolz8oFJKp8KJIQeBJ+N/d2LQW2jxn2+lQy2MGtz +B+N2+Jq42CK5B/KQnxotK7rFsFGN2KUkBvT2DJ33oVHcfk4Jex2v +-----END RSA PRIVATE KEY----- diff --git a/test-certs/node.crt b/test-certs/node.crt new file mode 100644 index 000000000..b3cec2438 --- /dev/null +++ b/test-certs/node.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDRzCCAi+gAwIBAgIRAKygNDoW2fdrtTGg3FrAJ78wDQYJKoZIhvcNAQELBQAw +KzESMBAGA1UEChMJQ29ja3JvYWNoMRUwEwYDVQQDEwxDb2Nrcm9hY2ggQ0EwHhcN +MjQwODA1MjA1NDEwWhcNMjkwODEwMjA1NDEwWjAjMRIwEAYDVQQKEwlDb2Nrcm9h +Y2gxDTALBgNVBAMTBG5vZGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQC6nXPmcEjqBTGyHacf6bSOyIitXhi9ABZKtaLFruKambVW1T6WsVWaoMEookao +7VNwkyg+uiEW45zQbtnkifuC1b4whffuAeK9gyCs9k8D4vhcpukAgjNKjEBrfkCD +dKjeUg+d5JbFhZe1biIAIU2Ni45R9ikK/Ll3PU3bqNhjL+K2+/6NpjbBLQmqFpgw +Jc6BiVH+8xvIge+BMR2t3HaXSDrC+pOHGRVFxGUslla8jnYClGbV8U9Cy7tGBC8R +KW58nbFAZyXFkYTwKQYd4ZqugVDKrNK8gIwQkVZSmIzaW6pGou25oiST1wMtAJIp +MhmeTEtPIlRQiu2xv2czd8mpAgMBAAGjbjBsMA4GA1UdDwEB/wQEAwIFoDAdBgNV +HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHwYDVR0jBBgwFoAUuhs6UvvhM2In +E8Gr1RjnsCZ/DCEwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3 +DQEBCwUAA4IBAQBmJySfq03wu9TLeQVlA+tP/ebUHPPIOHriI4vdYLTdshHb3xdE +YwEJIfqneK8qniaq4TEoQXr1TUglK2Agw782WROawhI9sYwlCGUP4gJiSD8f4wao +9zppKAm5vXjAdHzoFw1mLaOZyGzNsoo5F3kn75lTx5n4sRLfF/wIIMba1oq16VPb +fjAsBE5npdvayhR61jnDuGs8ezIPXY2PB/9WlJa2gvVtKbOKUfo8OQ3LwKvMYJIy +bpdweodEPdh8GCg/VQFg84rzRPsc9RmIv/3NWYFiSJe2715bwHwcipV3Uukgnc8a +KmFG1kA/8kTV4Ft18+gRwDn53fWxFLUtW0I+ +-----END CERTIFICATE----- diff --git a/test-certs/node.key b/test-certs/node.key new file mode 100644 index 000000000..ebb096968 --- /dev/null +++ b/test-certs/node.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAup1z5nBI6gUxsh2nH+m0jsiIrV4YvQAWSrWixa7impm1VtU+ +lrFVmqDBKKJGqO1TcJMoProhFuOc0G7Z5In7gtW+MIX37gHivYMgrPZPA+L4XKbp +AIIzSoxAa35Ag3So3lIPneSWxYWXtW4iACFNjYuOUfYpCvy5dz1N26jYYy/itvv+ +jaY2wS0JqhaYMCXOgYlR/vMbyIHvgTEdrdx2l0g6wvqThxkVRcRlLJZWvI52ApRm +1fFPQsu7RgQvESlufJ2xQGclxZGE8CkGHeGaroFQyqzSvICMEJFWUpiM2luqRqLt +uaIkk9cDLQCSKTIZnkxLTyJUUIrtsb9nM3fJqQIDAQABAoIBAQCfM9A6mTQ7IW9C +CvYWzaQR45ZmgM4N/1MFLWvNxeLgDZ2clJOur17eaaIf7kDlwIHcmk5NJTExe8C+ +OLN+uxBsj7iPYNsgm086afGoXQEPycQkFujUXD8xq/ERQ0X6X73o3I0iIG9X1x6T +038CKWcmCBbEaYKeMvhud0hCywq/ohYskng/keB7m0+t38cHBXntybdiF/J1Hdku +Y6iyjOZCVBakZb0G4IVWemMm6T3W6uSoOmMn9G7R7bQVQslT6wLdj5ysX9sF0g7t +1E1i0EOiYeF31L0SOm7vzx3cGELJ85UO/mugiTs+Q8qcY15PKQQ4LFj1YmlEh4Fr +mqEK0IFhAoGBAO5XvRMpUTTM/5nhL5S9QGt1KbqXnHUgeIzi/+Bv6h69pztnHAFG +Z78X3KWwQjalHXrGyXhaSwA/Em/HORpihEyofMP2ANxrOJkcvB5NRj99lcvRVjsZ +qh3Qb+nUBLQnQC0dOKDFRf3kdo9SfX1AL0x4G9werkLEC2+9gZnymNqNAoGBAMhw +rclYCGnW9zx6hnOuLgxD7xzbnYKBru87PauUpKyPq7UZDdO3ptT4dCmrw1c11Pvq +TccfR3EY7iuyHhUU+GFMEoVe2uuQAXsyh8SuagEFzJhQ65wCTGcBeWSarntYqlxb +UTcU9P32sPPpiiqceZr82WxL9NuR3F5M56Kb75KNAoGAC1UInFyei9AjZwGZ7YyB +/5n2ffSvb7I3iRtR5y3rB8MkrGJepoDedeuAhjA3NbnsCOZiteI1JrHZAo+KW4LL +n4lzRS3xJInEiyMenqI18NbbxHETlIPH3tsGNyvpfVdQadS12I8IoOW37WCSrt3a +YdxkFrcuueXwVUPBYbxzIBECgYBTLuAQlSY4tmu4f6jYzUpy8KaFNWcyyBsPUWiQ +komcy6sY5yLj4G2uKMYcnRneUYroWR6aKpL9jrkZDTyKxz8lMerlyq3T4NmchSXv +K+r2La/w7nQ5NIL8pJH6fG845V0eIw+bamkaFIzvTNZ4RKP6/EBHP2aI0QnBYO0o +gMp16QKBgQDtvd5gGzkwKkgFppa6MG6YY/VS20QvqOVXKB5kTgBBGrU65JO1kLaM +TZhb6hZVEwgFLzvsH2vwT/4AWFRiltuC2CvTZLgG4roHLRZtK+wKRtr6yBrU4wjI +G9PEwShxkccW+MfDIPj5dsCMDHXS8xKSRadmwYqlLPYpKKb+gnTcmw== +-----END RSA PRIVATE KEY----- diff --git a/www/src/generated/graphql.ts b/www/src/generated/graphql.ts index 312ea7e96..ac13b3482 100644 --- a/www/src/generated/graphql.ts +++ b/www/src/generated/graphql.ts @@ -12,7 +12,7 @@ export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | const defaultOptions = {} as const; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { - ID: { input: string | number; output: string; } + ID: { input: string; output: string; } String: { input: string; output: string; } Boolean: { input: boolean; output: boolean; } Int: { input: number; output: number; } @@ -381,6 +381,10 @@ export type ClosureItem = { terraform?: Maybe; }; +export enum CloudProvider { + Aws = 'AWS' +} + export type CloudShell = { __typename?: 'CloudShell'; aesKey: Scalars['String']['output']; @@ -552,6 +556,78 @@ export type ConsentRequest = { skip?: Maybe; }; +export type ConsoleConfigurationUpdateAttributes = { + encryptionKey?: InputMaybe; +}; + +export type ConsoleInstance = { + __typename?: 'ConsoleInstance'; + /** the cloud provider hosting this instance */ + cloud: CloudProvider; + console?: Maybe; + /** the time this instance was deleted on */ + deletedAt?: Maybe; + id: Scalars['ID']['output']; + insertedAt?: Maybe; + /** the name of this instance (globally unique) */ + name: Scalars['String']['output']; + owner?: Maybe; + /** the region this instance is hosted in */ + region: Scalars['String']['output']; + /** the heuristic size of this instance */ + size: ConsoleSize; + /** the provisioning status of this instance, liveness is fetched through the console field */ + status: ConsoleInstanceStatus; + /** the subdomain this instance lives under */ + subdomain: Scalars['String']['output']; + updatedAt?: Maybe; + /** full console url of this instance */ + url: Scalars['String']['output']; +}; + +export type ConsoleInstanceAttributes = { + /** the cloud provider to deploy to */ + cloud: CloudProvider; + /** the name of this instance (globally unique) */ + name: Scalars['String']['input']; + /** the region to deploy to (provider specific) */ + region: Scalars['String']['input']; + /** a heuristic size of this instance */ + size: ConsoleSize; +}; + +export type ConsoleInstanceConnection = { + __typename?: 'ConsoleInstanceConnection'; + edges?: Maybe>>; + pageInfo: PageInfo; +}; + +export type ConsoleInstanceEdge = { + __typename?: 'ConsoleInstanceEdge'; + cursor?: Maybe; + node?: Maybe; +}; + +export enum ConsoleInstanceStatus { + DatabaseCreated = 'DATABASE_CREATED', + DatabaseDeleted = 'DATABASE_DELETED', + DeploymentCreated = 'DEPLOYMENT_CREATED', + DeploymentDeleted = 'DEPLOYMENT_DELETED', + Pending = 'PENDING', + Provisioned = 'PROVISIONED' +} + +export type ConsoleInstanceUpdateAttributes = { + configuration?: InputMaybe; + size?: InputMaybe; +}; + +export enum ConsoleSize { + Large = 'LARGE', + Medium = 'MEDIUM', + Small = 'SMALL' +} + export type ContextAttributes = { buckets?: InputMaybe>>; configuration: Scalars['Map']['input']; @@ -2699,6 +2775,7 @@ export type RootMutationType = { createCluster?: Maybe; /** adds a dependency for this cluster to gate future upgrades */ createClusterDependency?: Maybe; + createConsoleInstance?: Maybe; createCrd?: Maybe; createDemoProject?: Maybe; createDnsRecord?: Maybe; @@ -2743,6 +2820,7 @@ export type RootMutationType = { deleteCluster?: Maybe; /** deletes a dependency for this cluster and potentially disables promotions entirely */ deleteClusterDependency?: Maybe; + deleteConsoleInstance?: Maybe; deleteDemoProject?: Maybe; deleteDnsRecord?: Maybe; deleteDomain?: Maybe; @@ -2815,6 +2893,7 @@ export type RootMutationType = { updateAccount?: Maybe; updateChart?: Maybe; updateChartInstallation?: Maybe; + updateConsoleInstance?: Maybe; updateDockerRepository?: Maybe; updateDomain?: Maybe; updateGroup?: Maybe; @@ -2889,6 +2968,11 @@ export type RootMutationTypeCreateClusterDependencyArgs = { }; +export type RootMutationTypeCreateConsoleInstanceArgs = { + attributes: ConsoleInstanceAttributes; +}; + + export type RootMutationTypeCreateCrdArgs = { attributes: CrdAttributes; chartId?: InputMaybe; @@ -3116,6 +3200,11 @@ export type RootMutationTypeDeleteClusterDependencyArgs = { }; +export type RootMutationTypeDeleteConsoleInstanceArgs = { + id: Scalars['ID']['input']; +}; + + export type RootMutationTypeDeleteDnsRecordArgs = { name: Scalars['String']['input']; type: DnsRecordType; @@ -3455,6 +3544,12 @@ export type RootMutationTypeUpdateChartInstallationArgs = { }; +export type RootMutationTypeUpdateConsoleInstanceArgs = { + attributes: ConsoleInstanceUpdateAttributes; + id: Scalars['ID']['input']; +}; + + export type RootMutationTypeUpdateDockerRepositoryArgs = { attributes: DockerRepositoryAttributes; id: Scalars['ID']['input']; @@ -3627,6 +3722,8 @@ export type RootQueryType = { /** Get a list of clusters owned by the current account. */ clusters?: Maybe; configuration?: Maybe; + consoleInstance?: Maybe; + consoleInstances?: Maybe; deferredUpdates?: Maybe; demoProject?: Maybe; dnsDomain?: Maybe; @@ -3771,6 +3868,19 @@ export type RootQueryTypeClustersArgs = { }; +export type RootQueryTypeConsoleInstanceArgs = { + id: Scalars['ID']['input']; +}; + + +export type RootQueryTypeConsoleInstancesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + export type RootQueryTypeDeferredUpdatesArgs = { after?: InputMaybe; before?: InputMaybe; @@ -7430,8 +7540,13 @@ export function useListArtifactsLazyQuery(baseOptions?: Apollo.LazyQueryHookOpti const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(ListArtifactsDocument, options); } +export function useListArtifactsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(ListArtifactsDocument, options); + } export type ListArtifactsQueryHookResult = ReturnType; export type ListArtifactsLazyQueryHookResult = ReturnType; +export type ListArtifactsSuspenseQueryHookResult = ReturnType; export type ListArtifactsQueryResult = Apollo.QueryResult; export const CreateArtifactDocument = gql` mutation CreateArtifact($repoName: String!, $name: String!, $readme: String!, $artifactType: String!, $platform: String!, $blob: UploadOrUrl!, $arch: String) { @@ -7511,8 +7626,13 @@ export function useGetChartsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions< const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GetChartsDocument, options); } +export function useGetChartsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetChartsDocument, options); + } export type GetChartsQueryHookResult = ReturnType; export type GetChartsLazyQueryHookResult = ReturnType; +export type GetChartsSuspenseQueryHookResult = ReturnType; export type GetChartsQueryResult = Apollo.QueryResult; export const GetVersionsDocument = gql` query GetVersions($id: ID!) { @@ -7550,8 +7670,13 @@ export function useGetVersionsLazyQuery(baseOptions?: Apollo.LazyQueryHookOption const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GetVersionsDocument, options); } +export function useGetVersionsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetVersionsDocument, options); + } export type GetVersionsQueryHookResult = ReturnType; export type GetVersionsLazyQueryHookResult = ReturnType; +export type GetVersionsSuspenseQueryHookResult = ReturnType; export type GetVersionsQueryResult = Apollo.QueryResult; export const GetChartInstallationsDocument = gql` query GetChartInstallations($id: ID!) { @@ -7589,8 +7714,13 @@ export function useGetChartInstallationsLazyQuery(baseOptions?: Apollo.LazyQuery const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GetChartInstallationsDocument, options); } +export function useGetChartInstallationsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetChartInstallationsDocument, options); + } export type GetChartInstallationsQueryHookResult = ReturnType; export type GetChartInstallationsLazyQueryHookResult = ReturnType; +export type GetChartInstallationsSuspenseQueryHookResult = ReturnType; export type GetChartInstallationsQueryResult = Apollo.QueryResult; export const GetPackageInstallationsDocument = gql` query GetPackageInstallations($id: ID!) { @@ -7636,8 +7766,13 @@ export function useGetPackageInstallationsLazyQuery(baseOptions?: Apollo.LazyQue const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GetPackageInstallationsDocument, options); } +export function useGetPackageInstallationsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetPackageInstallationsDocument, options); + } export type GetPackageInstallationsQueryHookResult = ReturnType; export type GetPackageInstallationsLazyQueryHookResult = ReturnType; +export type GetPackageInstallationsSuspenseQueryHookResult = ReturnType; export type GetPackageInstallationsQueryResult = Apollo.QueryResult; export const CreateCrdDocument = gql` mutation CreateCrd($chartName: ChartName!, $name: String!, $blob: UploadOrUrl!) { @@ -7785,8 +7920,13 @@ export function useClustersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions(ClustersDocument, options); } +export function useClustersSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(ClustersDocument, options); + } export type ClustersQueryHookResult = ReturnType; export type ClustersLazyQueryHookResult = ReturnType; +export type ClustersSuspenseQueryHookResult = ReturnType; export type ClustersQueryResult = Apollo.QueryResult; export const GetDnsRecordsDocument = gql` query GetDnsRecords($cluster: String!, $provider: Provider!) { @@ -7825,8 +7965,13 @@ export function useGetDnsRecordsLazyQuery(baseOptions?: Apollo.LazyQueryHookOpti const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GetDnsRecordsDocument, options); } +export function useGetDnsRecordsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetDnsRecordsDocument, options); + } export type GetDnsRecordsQueryHookResult = ReturnType; export type GetDnsRecordsLazyQueryHookResult = ReturnType; +export type GetDnsRecordsSuspenseQueryHookResult = ReturnType; export type GetDnsRecordsQueryResult = Apollo.QueryResult; export const CreateDnsRecordDocument = gql` mutation CreateDnsRecord($cluster: String!, $provider: Provider!, $attributes: DnsRecordAttributes!) { @@ -7971,8 +8116,13 @@ export function useGroupMembersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptio const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GroupMembersDocument, options); } +export function useGroupMembersSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GroupMembersDocument, options); + } export type GroupMembersQueryHookResult = ReturnType; export type GroupMembersLazyQueryHookResult = ReturnType; +export type GroupMembersSuspenseQueryHookResult = ReturnType; export type GroupMembersQueryResult = Apollo.QueryResult; export const CreateGroupMemberDocument = gql` mutation CreateGroupMember($groupId: ID!, $userId: ID!) { @@ -8183,8 +8333,13 @@ export function useGroupsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions(GroupsDocument, options); } +export function useGroupsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GroupsDocument, options); + } export type GroupsQueryHookResult = ReturnType; export type GroupsLazyQueryHookResult = ReturnType; +export type GroupsSuspenseQueryHookResult = ReturnType; export type GroupsQueryResult = Apollo.QueryResult; export const SignupInviteDocument = gql` mutation SignupInvite($attributes: UserAttributes!, $inviteId: String!) { @@ -8297,8 +8452,13 @@ export function useInviteLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions(InviteDocument, options); } +export function useInviteSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(InviteDocument, options); + } export type InviteQueryHookResult = ReturnType; export type InviteLazyQueryHookResult = ReturnType; +export type InviteSuspenseQueryHookResult = ReturnType; export type InviteQueryResult = Apollo.QueryResult; export const CreateInviteDocument = gql` mutation CreateInvite($attributes: InviteAttributes!) { @@ -8368,8 +8528,13 @@ export function useKeyBackupsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(KeyBackupsDocument, options); } +export function useKeyBackupsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(KeyBackupsDocument, options); + } export type KeyBackupsQueryHookResult = ReturnType; export type KeyBackupsLazyQueryHookResult = ReturnType; +export type KeyBackupsSuspenseQueryHookResult = ReturnType; export type KeyBackupsQueryResult = Apollo.QueryResult; export const KeyBackupDocument = gql` query KeyBackup($name: String!) { @@ -8403,8 +8568,13 @@ export function useKeyBackupLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions< const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(KeyBackupDocument, options); } +export function useKeyBackupSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(KeyBackupDocument, options); + } export type KeyBackupQueryHookResult = ReturnType; export type KeyBackupLazyQueryHookResult = ReturnType; +export type KeyBackupSuspenseQueryHookResult = ReturnType; export type KeyBackupQueryResult = Apollo.QueryResult; export const DeleteKeyBackupDocument = gql` mutation DeleteKeyBackup($name: String!) { @@ -8503,8 +8673,13 @@ export function useSubscriptionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptio const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(SubscriptionDocument, options); } +export function useSubscriptionSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(SubscriptionDocument, options); + } export type SubscriptionQueryHookResult = ReturnType; export type SubscriptionLazyQueryHookResult = ReturnType; +export type SubscriptionSuspenseQueryHookResult = ReturnType; export type SubscriptionQueryResult = Apollo.QueryResult; export const UpdateAccountBillingDocument = gql` mutation UpdateAccountBilling($attributes: AccountAttributes!) { @@ -8655,8 +8830,13 @@ export function useCardsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions(CardsDocument, options); } +export function useCardsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(CardsDocument, options); + } export type CardsQueryHookResult = ReturnType; export type CardsLazyQueryHookResult = ReturnType; +export type CardsSuspenseQueryHookResult = ReturnType; export type CardsQueryResult = Apollo.QueryResult; export const SetupIntentDocument = gql` mutation SetupIntent($address: AddressAttributes!) { @@ -8790,8 +8970,13 @@ export function useInvoicesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions(InvoicesDocument, options); } +export function useInvoicesSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(InvoicesDocument, options); + } export type InvoicesQueryHookResult = ReturnType; export type InvoicesLazyQueryHookResult = ReturnType; +export type InvoicesSuspenseQueryHookResult = ReturnType; export type InvoicesQueryResult = Apollo.QueryResult; export const GetRecipeDocument = gql` query GetRecipe($repo: String, $name: String) { @@ -8829,8 +9014,13 @@ export function useGetRecipeLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions< const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GetRecipeDocument, options); } +export function useGetRecipeSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetRecipeDocument, options); + } export type GetRecipeQueryHookResult = ReturnType; export type GetRecipeLazyQueryHookResult = ReturnType; +export type GetRecipeSuspenseQueryHookResult = ReturnType; export type GetRecipeQueryResult = Apollo.QueryResult; export const ListRecipesDocument = gql` query ListRecipes($repositoryName: String, $provider: Provider) { @@ -8869,8 +9059,13 @@ export function useListRecipesLazyQuery(baseOptions?: Apollo.LazyQueryHookOption const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(ListRecipesDocument, options); } +export function useListRecipesSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(ListRecipesDocument, options); + } export type ListRecipesQueryHookResult = ReturnType; export type ListRecipesLazyQueryHookResult = ReturnType; +export type ListRecipesSuspenseQueryHookResult = ReturnType; export type ListRecipesQueryResult = Apollo.QueryResult; export const CreateRecipeDocument = gql` mutation CreateRecipe($name: String!, $attributes: RecipeAttributes!) { @@ -9005,8 +9200,13 @@ export function useGetStackLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions(GetStackDocument, options); } +export function useGetStackSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetStackDocument, options); + } export type GetStackQueryHookResult = ReturnType; export type GetStackLazyQueryHookResult = ReturnType; +export type GetStackSuspenseQueryHookResult = ReturnType; export type GetStackQueryResult = Apollo.QueryResult; export const ListStacksDocument = gql` query ListStacks($featured: Boolean, $cursor: String) { @@ -9045,8 +9245,13 @@ export function useListStacksLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(ListStacksDocument, options); } +export function useListStacksSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(ListStacksDocument, options); + } export type ListStacksQueryHookResult = ReturnType; export type ListStacksLazyQueryHookResult = ReturnType; +export type ListStacksSuspenseQueryHookResult = ReturnType; export type ListStacksQueryResult = Apollo.QueryResult; export const RepositoryDocument = gql` query Repository($id: ID, $name: String) { @@ -9116,8 +9321,13 @@ export function useRepositoryLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(RepositoryDocument, options); } +export function useRepositorySuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(RepositoryDocument, options); + } export type RepositoryQueryHookResult = ReturnType; export type RepositoryLazyQueryHookResult = ReturnType; +export type RepositorySuspenseQueryHookResult = ReturnType; export type RepositoryQueryResult = Apollo.QueryResult; export const CreateResourceDefinitionDocument = gql` mutation CreateResourceDefinition($name: String!, $input: ResourceDefinitionAttributes!) { @@ -9399,8 +9609,13 @@ export function useMarketplaceRepositoriesLazyQuery(baseOptions?: Apollo.LazyQue const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(MarketplaceRepositoriesDocument, options); } +export function useMarketplaceRepositoriesSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(MarketplaceRepositoriesDocument, options); + } export type MarketplaceRepositoriesQueryHookResult = ReturnType; export type MarketplaceRepositoriesLazyQueryHookResult = ReturnType; +export type MarketplaceRepositoriesSuspenseQueryHookResult = ReturnType; export type MarketplaceRepositoriesQueryResult = Apollo.QueryResult; export const ScaffoldsDocument = gql` query Scaffolds($app: String!, $pub: String!, $cat: Category!, $ing: Boolean, $pg: Boolean) { @@ -9445,8 +9660,13 @@ export function useScaffoldsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions< const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(ScaffoldsDocument, options); } +export function useScaffoldsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(ScaffoldsDocument, options); + } export type ScaffoldsQueryHookResult = ReturnType; export type ScaffoldsLazyQueryHookResult = ReturnType; +export type ScaffoldsSuspenseQueryHookResult = ReturnType; export type ScaffoldsQueryResult = Apollo.QueryResult; export const DeleteRepositoryDocument = gql` mutation DeleteRepository($id: ID!) { @@ -9542,8 +9762,13 @@ export function useGetTfProvidersLazyQuery(baseOptions?: Apollo.LazyQueryHookOpt const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GetTfProvidersDocument, options); } +export function useGetTfProvidersSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetTfProvidersDocument, options); + } export type GetTfProvidersQueryHookResult = ReturnType; export type GetTfProvidersLazyQueryHookResult = ReturnType; +export type GetTfProvidersSuspenseQueryHookResult = ReturnType; export type GetTfProvidersQueryResult = Apollo.QueryResult; export const GetTfProviderScaffoldDocument = gql` query GetTfProviderScaffold($name: Provider!, $vsn: String) { @@ -9579,8 +9804,13 @@ export function useGetTfProviderScaffoldLazyQuery(baseOptions?: Apollo.LazyQuery const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GetTfProviderScaffoldDocument, options); } +export function useGetTfProviderScaffoldSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetTfProviderScaffoldDocument, options); + } export type GetTfProviderScaffoldQueryHookResult = ReturnType; export type GetTfProviderScaffoldLazyQueryHookResult = ReturnType; +export type GetTfProviderScaffoldSuspenseQueryHookResult = ReturnType; export type GetTfProviderScaffoldQueryResult = Apollo.QueryResult; export const GetShellDocument = gql` query GetShell { @@ -9613,8 +9843,13 @@ export function useGetShellLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions(GetShellDocument, options); } +export function useGetShellSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetShellDocument, options); + } export type GetShellQueryHookResult = ReturnType; export type GetShellLazyQueryHookResult = ReturnType; +export type GetShellSuspenseQueryHookResult = ReturnType; export type GetShellQueryResult = Apollo.QueryResult; export const DeleteShellDocument = gql` mutation DeleteShell { @@ -9684,8 +9919,13 @@ export function useGetTerraformLazyQuery(baseOptions?: Apollo.LazyQueryHookOptio const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GetTerraformDocument, options); } +export function useGetTerraformSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetTerraformDocument, options); + } export type GetTerraformQueryHookResult = ReturnType; export type GetTerraformLazyQueryHookResult = ReturnType; +export type GetTerraformSuspenseQueryHookResult = ReturnType; export type GetTerraformQueryResult = Apollo.QueryResult; export const GetTerraformInstallationsDocument = gql` query GetTerraformInstallations($id: ID!) { @@ -9723,8 +9963,13 @@ export function useGetTerraformInstallationsLazyQuery(baseOptions?: Apollo.LazyQ const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GetTerraformInstallationsDocument, options); } +export function useGetTerraformInstallationsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetTerraformInstallationsDocument, options); + } export type GetTerraformInstallationsQueryHookResult = ReturnType; export type GetTerraformInstallationsLazyQueryHookResult = ReturnType; +export type GetTerraformInstallationsSuspenseQueryHookResult = ReturnType; export type GetTerraformInstallationsQueryResult = Apollo.QueryResult; export const UploadTerraformDocument = gql` mutation UploadTerraform($repoName: String!, $name: String!, $uploadOrUrl: UploadOrUrl!) { @@ -9998,8 +10243,13 @@ export function useMeLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions(MeDocument, options); } +export function useMeSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(MeDocument, options); + } export type MeQueryHookResult = ReturnType; export type MeLazyQueryHookResult = ReturnType; +export type MeSuspenseQueryHookResult = ReturnType; export type MeQueryResult = Apollo.QueryResult; export const GetLoginMethodDocument = gql` query GetLoginMethod($email: String!) { @@ -10034,8 +10284,13 @@ export function useGetLoginMethodLazyQuery(baseOptions?: Apollo.LazyQueryHookOpt const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GetLoginMethodDocument, options); } +export function useGetLoginMethodSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetLoginMethodDocument, options); + } export type GetLoginMethodQueryHookResult = ReturnType; export type GetLoginMethodLazyQueryHookResult = ReturnType; +export type GetLoginMethodSuspenseQueryHookResult = ReturnType; export type GetLoginMethodQueryResult = Apollo.QueryResult; export const ListTokensDocument = gql` query ListTokens { @@ -10072,8 +10327,13 @@ export function useListTokensLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(ListTokensDocument, options); } +export function useListTokensSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(ListTokensDocument, options); + } export type ListTokensQueryHookResult = ReturnType; export type ListTokensLazyQueryHookResult = ReturnType; +export type ListTokensSuspenseQueryHookResult = ReturnType; export type ListTokensQueryResult = Apollo.QueryResult; export const ListKeysDocument = gql` query ListKeys($emails: [String]) { @@ -10111,8 +10371,13 @@ export function useListKeysLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions(ListKeysDocument, options); } +export function useListKeysSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(ListKeysDocument, options); + } export type ListKeysQueryHookResult = ReturnType; export type ListKeysLazyQueryHookResult = ReturnType; +export type ListKeysSuspenseQueryHookResult = ReturnType; export type ListKeysQueryResult = Apollo.QueryResult; export const GetEabCredentialDocument = gql` query GetEabCredential($cluster: String!, $provider: Provider!) { @@ -10147,8 +10412,13 @@ export function useGetEabCredentialLazyQuery(baseOptions?: Apollo.LazyQueryHookO const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(GetEabCredentialDocument, options); } +export function useGetEabCredentialSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(GetEabCredentialDocument, options); + } export type GetEabCredentialQueryHookResult = ReturnType; export type GetEabCredentialLazyQueryHookResult = ReturnType; +export type GetEabCredentialSuspenseQueryHookResult = ReturnType; export type GetEabCredentialQueryResult = Apollo.QueryResult; export const DevLoginDocument = gql` mutation DevLogin { @@ -10418,8 +10688,13 @@ export function useLoginMethodLazyQuery(baseOptions?: Apollo.LazyQueryHookOption const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(LoginMethodDocument, options); } +export function useLoginMethodSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(LoginMethodDocument, options); + } export type LoginMethodQueryHookResult = ReturnType; export type LoginMethodLazyQueryHookResult = ReturnType; +export type LoginMethodSuspenseQueryHookResult = ReturnType; export type LoginMethodQueryResult = Apollo.QueryResult; export const SignupDocument = gql` mutation Signup($attributes: UserAttributes!, $account: AccountAttributes, $deviceToken: String) { @@ -10557,8 +10832,13 @@ export function useOauthUrlsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions< const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(OauthUrlsDocument, options); } +export function useOauthUrlsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(OauthUrlsDocument, options); + } export type OauthUrlsQueryHookResult = ReturnType; export type OauthUrlsLazyQueryHookResult = ReturnType; +export type OauthUrlsSuspenseQueryHookResult = ReturnType; export type OauthUrlsQueryResult = Apollo.QueryResult; export const AcceptLoginDocument = gql` mutation AcceptLogin($challenge: String!) { @@ -10691,8 +10971,13 @@ export function useResetTokenLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions const options = {...defaultOptions, ...baseOptions} return Apollo.useLazyQuery(ResetTokenDocument, options); } +export function useResetTokenSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(ResetTokenDocument, options); + } export type ResetTokenQueryHookResult = ReturnType; export type ResetTokenLazyQueryHookResult = ReturnType; +export type ResetTokenSuspenseQueryHookResult = ReturnType; export type ResetTokenQueryResult = Apollo.QueryResult; export const UsersDocument = gql` query Users($q: String, $serviceAccount: Boolean, $all: Boolean, $cursor: String, $first: Int = 20) { @@ -10748,8 +11033,13 @@ export function useUsersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions(UsersDocument, options); } +export function useUsersSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(UsersDocument, options); + } export type UsersQueryHookResult = ReturnType; export type UsersLazyQueryHookResult = ReturnType; +export type UsersSuspenseQueryHookResult = ReturnType; export type UsersQueryResult = Apollo.QueryResult; export const ReadNotificationsDocument = gql` mutation ReadNotifications($incidentId: ID) {