diff --git a/zebra/lib/zebra/workers/job_request_factory.ex b/zebra/lib/zebra/workers/job_request_factory.ex index cb74a62ae..0b28552a7 100644 --- a/zebra/lib/zebra/workers/job_request_factory.ex +++ b/zebra/lib/zebra/workers/job_request_factory.ex @@ -7,6 +7,7 @@ defmodule Zebra.Workers.JobRequestFactory do Artifacthub, Cache, CallbackToken, + GitCheckout, JobRequest, Loghub2, Machine, @@ -102,6 +103,7 @@ defmodule Zebra.Workers.JobRequestFactory do cache_env_vars ++ ToolboxInstall.env_vars(job) ++ TestResults.env_vars(org_id) ++ + GitCheckout.env_vars(job, org_id) ++ open_id_token_env_vars ++ repo_env_vars ++ Enum.flat_map(all_secrets.job_secrets, & &1.env_vars) ++ diff --git a/zebra/lib/zebra/workers/job_request_factory/git_checkout.ex b/zebra/lib/zebra/workers/job_request_factory/git_checkout.ex new file mode 100644 index 000000000..cf63680d8 --- /dev/null +++ b/zebra/lib/zebra/workers/job_request_factory/git_checkout.ex @@ -0,0 +1,42 @@ +defmodule Zebra.Workers.JobRequestFactory.GitCheckout do + @moduledoc """ + Handles resilient git checkout configuration for jobs. + + When the :git_clone_slow_retry feature is enabled for an organization, + this module adds the SEMAPHORE_GIT_CLONE_SLOW_RETRY environment variable, + which opts the toolbox `checkout` into slow-clone detection and resilient + retry (speed monitoring, retries, and alternative-endpoint fallback). + + Only injected on cloud agents. The resilient behaviour (GeoDNS-based + alternative-endpoint fallback, DoH lookups) targets GitHub.com reachability + from Semaphore's cloud egress; on self-hosted agents the network is the + customer's own, so injecting it there is inappropriate (and the DoH + endpoint may well be blocked). + + The toolbox keeps sensible defaults for the tuning knobs + (threshold/timeout/grace/retries), so only the on/off switch is injected + here; the feature is a no-op in the toolbox when this var is absent. + """ + + alias Zebra.Models.Job + alias Zebra.Workers.JobRequestFactory.JobRequest + + @doc """ + Returns environment variables for resilient git checkout. + + Adds SEMAPHORE_GIT_CLONE_SLOW_RETRY=true when the job runs on a cloud agent + and the :git_clone_slow_retry feature is enabled for the organization. + """ + def env_vars(job, org_id) do + if inject?(job, org_id) do + [JobRequest.env_var("SEMAPHORE_GIT_CLONE_SLOW_RETRY", "true")] + else + [] + end + end + + defp inject?(job, org_id) do + not Job.self_hosted?(job.machine_type) and + FeatureProvider.feature_enabled?(:git_clone_slow_retry, param: org_id) + end +end diff --git a/zebra/test/support/stubbed_provider.ex b/zebra/test/support/stubbed_provider.ex index b9d4c94b8..2fbacfba4 100644 --- a/zebra/test/support/stubbed_provider.ex +++ b/zebra/test/support/stubbed_provider.ex @@ -6,6 +6,7 @@ defmodule Support.StubbedProvider do @exclude_from_brownouts_org_id "org-exclude-from-brownouts-enabled" @test_results_no_trim_org_id "org-test-results-no-trim-enabled" + @git_clone_slow_retry_org_id "org-git-clone-slow-retry-enabled" @impl FeatureProvider.Provider def provide_features(org_id \\ nil, _opts \\ []) do @@ -18,11 +19,13 @@ defmodule Support.StubbedProvider do feature("e1_to_f1_migration", e1_to_f1_traits(org_id)), feature("e2_to_f1_migration", e2_to_f1_traits(org_id)), feature("test_results_no_trim", test_results_no_trim_traits(org_id)), + feature("git_clone_slow_retry", git_clone_slow_retry_traits(org_id)), feature("exclude_from_brownouts", exclude_from_brownouts_traits(org_id)) ]} end def test_results_no_trim_org_id, do: @test_results_no_trim_org_id + def git_clone_slow_retry_org_id, do: @git_clone_slow_retry_org_id def exclude_from_brownouts_org_id, do: @exclude_from_brownouts_org_id def e1_to_f1_org_id, do: @e1_to_f1_org_id @@ -57,6 +60,9 @@ defmodule Support.StubbedProvider do defp test_results_no_trim_traits(@test_results_no_trim_org_id), do: [:enabled] defp test_results_no_trim_traits(_org_id), do: [:hidden] + defp git_clone_slow_retry_traits(@git_clone_slow_retry_org_id), do: [:enabled] + defp git_clone_slow_retry_traits(_org_id), do: [:hidden] + defp exclude_from_brownouts_traits(@exclude_from_brownouts_org_id), do: [:enabled] defp exclude_from_brownouts_traits(_org_id), do: [:hidden] diff --git a/zebra/test/zebra/workers/feature_provider_invalidator_worker_test.exs b/zebra/test/zebra/workers/feature_provider_invalidator_worker_test.exs index ac7621f0e..2a527fafa 100644 --- a/zebra/test/zebra/workers/feature_provider_invalidator_worker_test.exs +++ b/zebra/test/zebra/workers/feature_provider_invalidator_worker_test.exs @@ -99,7 +99,7 @@ defmodule Zebra.Workers.FeatureProviderInvalidatorWorkerTest do Worker.features_changed(callback_message) {:ok, features} = FeatureProvider.list_features() - assert length(features) == 8 + assert length(features) == 9 end test "when the organization feature state changes, organization feature caches are invalidated" do diff --git a/zebra/test/zebra/workers/job_request_factory/git_checkout_test.exs b/zebra/test/zebra/workers/job_request_factory/git_checkout_test.exs new file mode 100644 index 000000000..75e48a348 --- /dev/null +++ b/zebra/test/zebra/workers/job_request_factory/git_checkout_test.exs @@ -0,0 +1,34 @@ +defmodule Zebra.Workers.JobRequestFactory.GitCheckoutTest do + use Zebra.DataCase + + alias Zebra.Models.Job + alias Zebra.Workers.JobRequestFactory.GitCheckout + + @cloud_job %Job{machine_type: "e1-standard-2"} + @self_hosted_job %Job{machine_type: "s1-local"} + + describe "env_vars/2" do + test "returns empty list when feature is disabled" do + org_id = Ecto.UUID.generate() + + assert GitCheckout.env_vars(@cloud_job, org_id) == [] + end + + test "returns SEMAPHORE_GIT_CLONE_SLOW_RETRY env var on a cloud agent when feature is enabled" do + org_id = Support.StubbedProvider.git_clone_slow_retry_org_id() + + env_vars = GitCheckout.env_vars(@cloud_job, org_id) + + assert length(env_vars) == 1 + [env_var] = env_vars + assert env_var["name"] == "SEMAPHORE_GIT_CLONE_SLOW_RETRY" + assert env_var["value"] == Base.encode64("true") + end + + test "returns empty list on a self-hosted agent even when feature is enabled" do + org_id = Support.StubbedProvider.git_clone_slow_retry_org_id() + + assert GitCheckout.env_vars(@self_hosted_job, org_id) == [] + end + end +end