From 6dd67468e86ccd3bf01c7835cd7cf4cfb892f5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Meadows-J=C3=B6nsson?= Date: Tue, 14 Jul 2026 21:34:24 +0200 Subject: [PATCH] Allow disabling the Preview queue consumer --- config/config.exs | 1 + config/runtime.exs | 39 +++++++--- lib/preview/application.ex | 17 ++-- test/preview/application_test.exs | 124 ++++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 18 deletions(-) create mode 100644 test/preview/application_test.exs diff --git a/config/config.exs b/config/config.exs index 412b10b..1039cce 100644 --- a/config/config.exs +++ b/config/config.exs @@ -4,6 +4,7 @@ config :preview, queue_id: "dummy", queue_producer: Broadway.DummyProducer, queue_concurrency: 2, + queue_enabled: true, package_store_impl: Preview.Package.DefaultStore, package_updater_impl: Preview.Package.Updater, hex_impl: Preview.Hex.HTTP, diff --git a/config/runtime.exs b/config/runtime.exs index 33e3d09..de7a1d7 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -1,18 +1,39 @@ import Config if config_env() == :prod do + queue_enabled = + case System.get_env("PREVIEW_QUEUE_ENABLED", "true") do + "true" -> + true + + "false" -> + false + + value -> + raise "invalid PREVIEW_QUEUE_ENABLED #{inspect(value)}; expected \"true\" or \"false\"" + end + config :preview, host: System.fetch_env!("PREVIEW_HOST"), repo_url: System.fetch_env!("PREVIEW_REPO_URL"), repo_public_key: System.fetch_env!("PREVIEW_REPO_PUBLIC_KEY"), - queue_id: System.fetch_env!("PREVIEW_QUEUE_ID"), - queue_concurrency: String.to_integer(System.fetch_env!("PREVIEW_QUEUE_CONCURRENCY")), - fastly_key: System.fetch_env!("PREVIEW_FASTLY_KEY"), - fastly_repo: System.fetch_env!("PREVIEW_FASTLY_REPO") + queue_enabled: queue_enabled - config :preview, :repo_bucket, - implementation: Preview.Storage.S3, - name: System.fetch_env!("PREVIEW_REPO_BUCKET") + if queue_enabled do + config :preview, + queue_id: System.fetch_env!("PREVIEW_QUEUE_ID"), + queue_concurrency: String.to_integer(System.fetch_env!("PREVIEW_QUEUE_CONCURRENCY")), + fastly_key: System.fetch_env!("PREVIEW_FASTLY_KEY"), + fastly_repo: System.fetch_env!("PREVIEW_FASTLY_REPO") + + config :preview, :repo_bucket, + implementation: Preview.Storage.S3, + name: System.fetch_env!("PREVIEW_REPO_BUCKET") + + config :ex_aws, + access_key_id: System.fetch_env!("PREVIEW_AWS_ACCESS_KEY_ID"), + secret_access_key: System.fetch_env!("PREVIEW_AWS_ACCESS_KEY_SECRET") + end config :preview, :preview_bucket, implementation: Preview.Storage.GCS, @@ -23,10 +44,6 @@ if config_env() == :prod do url: [host: System.fetch_env!("PREVIEW_HOST")], secret_key_base: System.fetch_env!("PREVIEW_SECRET_KEY_BASE") - config :ex_aws, - access_key_id: System.fetch_env!("PREVIEW_AWS_ACCESS_KEY_ID"), - secret_access_key: System.fetch_env!("PREVIEW_AWS_ACCESS_KEY_SECRET") - config :sentry, dsn: System.fetch_env!("PREVIEW_SENTRY_DSN"), environment_name: System.fetch_env!("PREVIEW_ENV") diff --git a/lib/preview/application.ex b/lib/preview/application.ex index 7df4558..dc1671b 100644 --- a/lib/preview/application.ex +++ b/lib/preview/application.ex @@ -9,23 +9,26 @@ defmodule Preview.Application do :logger.add_handler(:my_sentry_handler, Sentry.LoggerHandler, %{}) setup_tmp_dir() - children = [ + children = children(Application.fetch_env!(:preview, :queue_enabled)) + + opts = [strategy: :one_for_one, name: Preview.Supervisor] + Supervisor.start_link(children, opts) + end + + def children(queue_enabled) when is_boolean(queue_enabled) do + common = [ Preview.TmpDir, PreviewWeb.Telemetry, {Phoenix.PubSub, name: Preview.PubSub}, {Task.Supervisor, name: Preview.Tasks}, {Finch, name: Preview.Finch, pools: finch_pools()}, goth_spec(), - {Preview.Debouncer, name: Preview.Debouncer}, - Preview.Queue, PreviewWeb.Endpoint, package_spec() ] - # See https://hexdocs.pm/elixir/Supervisor.html - # for other strategies and supported options - opts = [strategy: :one_for_one, name: Preview.Supervisor] - Supervisor.start_link(children, opts) + queue = [{Preview.Debouncer, name: Preview.Debouncer}, Preview.Queue] + common ++ if(queue_enabled, do: queue, else: []) end # Tell Phoenix to update the endpoint configuration diff --git a/test/preview/application_test.exs b/test/preview/application_test.exs new file mode 100644 index 0000000..2134f6a --- /dev/null +++ b/test/preview/application_test.exs @@ -0,0 +1,124 @@ +defmodule Preview.ApplicationTest do + use ExUnit.Case, async: true + + alias Preview.Application + + test "queue children can be disabled without removing the web endpoint" do + enabled = child_ids(Application.children(true)) + disabled = child_ids(Application.children(false)) + + assert Preview.Queue in enabled + assert Preview.Debouncer in enabled + refute Preview.Queue in disabled + refute Preview.Debouncer in disabled + assert PreviewWeb.Endpoint in enabled + assert PreviewWeb.Endpoint in disabled + end + + test "disabled production configuration does not require queue credentials" do + elixir = System.find_executable("elixir") + erl = System.find_executable("erl") + path = [Path.dirname(elixir), Path.dirname(erl), "/usr/bin", "/bin"] |> Enum.uniq() + + env = [ + "PATH=#{Enum.join(path, ":")}", + "PREVIEW_QUEUE_ENABLED=false", + "PREVIEW_HOST=preview.hex.pm", + "PREVIEW_REPO_URL=https://repo.hex.pm", + "PREVIEW_REPO_PUBLIC_KEY=public-key", + "PREVIEW_BUCKET=preview-bucket", + "PREVIEW_PORT=4005", + "PREVIEW_SECRET_KEY_BASE=secret-key-base", + "PREVIEW_SENTRY_DSN=sentry-dsn", + "PREVIEW_ENV=prod", + "BEAM_PORT=14005" + ] + + expression = ~S|Config.Reader.read!("config/runtime.exs", env: :prod); IO.write("configured")| + + assert {"configured", 0} = + System.cmd(System.find_executable("env"), ["-i" | env] ++ [elixir, "-e", expression]) + end + + test "production configuration defaults to an enabled queue" do + env = runtime_env() ++ queue_env() + + expression = + ~S""" + config = Config.Reader.read!("config/runtime.exs", env: :prod) + preview = config[:preview] + ex_aws = config[:ex_aws] + + IO.write( + Enum.join( + [ + preview[:queue_id], + preview[:queue_concurrency], + preview[:fastly_key], + preview[:fastly_repo], + preview[:repo_bucket][:name], + ex_aws[:access_key_id], + ex_aws[:secret_access_key] + ], + "|" + ) + ) + """ + + assert {"queue|10|fastly-key|fastly-repo|repo-bucket|aws-key|aws-secret", 0} = + System.cmd( + System.find_executable("env"), + ["-i" | env] ++ [System.find_executable("elixir"), "-e", expression] + ) + end + + test "production configuration rejects invalid queue settings" do + env = ["PREVIEW_QUEUE_ENABLED=invalid" | runtime_env()] + expression = ~S|Config.Reader.read!("config/runtime.exs", env: :prod)| + + assert {output, status} = + System.cmd( + System.find_executable("env"), + ["-i" | env] ++ [System.find_executable("elixir"), "-e", expression], + stderr_to_stdout: true + ) + + assert status != 0 + assert output =~ "invalid PREVIEW_QUEUE_ENABLED" + end + + defp runtime_env do + elixir = System.find_executable("elixir") + erl = System.find_executable("erl") + path = [Path.dirname(elixir), Path.dirname(erl), "/usr/bin", "/bin"] |> Enum.uniq() + + [ + "PATH=#{Enum.join(path, ":")}", + "PREVIEW_HOST=preview.hex.pm", + "PREVIEW_REPO_URL=https://repo.hex.pm", + "PREVIEW_REPO_PUBLIC_KEY=public-key", + "PREVIEW_BUCKET=preview-bucket", + "PREVIEW_PORT=4005", + "PREVIEW_SECRET_KEY_BASE=secret-key-base", + "PREVIEW_SENTRY_DSN=sentry-dsn", + "PREVIEW_ENV=prod", + "BEAM_PORT=14005" + ] + end + + defp queue_env do + [ + "PREVIEW_QUEUE_ID=queue", + "PREVIEW_QUEUE_CONCURRENCY=10", + "PREVIEW_FASTLY_KEY=fastly-key", + "PREVIEW_FASTLY_REPO=fastly-repo", + "PREVIEW_REPO_BUCKET=repo-bucket", + "PREVIEW_AWS_ACCESS_KEY_ID=aws-key", + "PREVIEW_AWS_ACCESS_KEY_SECRET=aws-secret" + ] + end + + defp child_ids(children) do + MapSet.new(children, fn child -> Supervisor.child_spec(child, []).id end) + end +end