diff --git a/app/models/environment.rb b/app/models/environment.rb index 71f421fb55d6b3be8c29d4944e96efbab6c572bf..af9f45cd787e2279e9100493991847259542c7c8 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -84,6 +84,10 @@ class Environment < ApplicationRecord allow_nil: true, if: :description_html_changed? + validates :latest_configured_auto_stop_in, + length: { maximum: 255 }, + allow_nil: true + validates :kubernetes_namespace, allow_nil: true, length: 1..63, @@ -527,6 +531,10 @@ def auto_stop_in=(value) parser = ::Gitlab::Ci::Build::DurationParser.new(value) + # The raw value is stored to allow the auto stop + # time to be recalculated without needing to fetch + # the original value from the CI job that set it. + self.latest_configured_auto_stop_in = value self.auto_stop_at = parser.seconds_from_now rescue ChronicDuration::DurationParseError => ex Gitlab::ErrorTracking.track_exception(ex, project_id: self.project_id, environment_id: self.id) diff --git a/app/services/environments/recalculate_auto_stop_service.rb b/app/services/environments/recalculate_auto_stop_service.rb index 8bcd7cac68f456c1caff0e551e924232ba3a4482..c82eb05508285d75b16f5b7fa255888596ea9097 100644 --- a/app/services/environments/recalculate_auto_stop_service.rb +++ b/app/services/environments/recalculate_auto_stop_service.rb @@ -13,13 +13,22 @@ def execute return unless can_set_auto_stop? && environment.present? auto_stop_in = deployable.expanded_auto_stop_in - auto_stop_in ||= last_successful_deployable&.expanded_auto_stop_in if can_reset_timer? + auto_stop_in ||= last_deployable_auto_stop_in if can_reset_timer? environment.update!(auto_stop_in: auto_stop_in) if auto_stop_in.present? end private + # TODO: We are transitioning from using the value stored on the last + # successful deployment job to the one stored on the environment. The + # fallback can be removed once the transition is complete (at this + # point the value will no longer exist at the old location). + # See https://gitlab.com/gitlab-org/gitlab/-/issues/545659. + def last_deployable_auto_stop_in + environment.latest_configured_auto_stop_in || last_successful_deployable&.expanded_auto_stop_in + end + # Jobs that start an environment (using `action: start`) can also # specify a stop time, however this is handled by the deployment # process. Actions other than `start` do not create deployments, diff --git a/db/migrate/20250613003344_add_latest_auto_stop_in_to_environments.rb b/db/migrate/20250613003344_add_latest_auto_stop_in_to_environments.rb new file mode 100644 index 0000000000000000000000000000000000000000..47c48a3839ffefb98bd5fec59bff1702b776c212 --- /dev/null +++ b/db/migrate/20250613003344_add_latest_auto_stop_in_to_environments.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class AddLatestAutoStopInToEnvironments < Gitlab::Database::Migration[2.3] + disable_ddl_transaction! + milestone '18.1' + + def up + with_lock_retries do + add_column :environments, :latest_configured_auto_stop_in, :text, if_not_exists: true + end + + add_text_limit :environments, :latest_configured_auto_stop_in, 255 + end + + def down + with_lock_retries do + remove_column :environments, :latest_configured_auto_stop_in + end + end +end diff --git a/db/schema_migrations/20250613003344 b/db/schema_migrations/20250613003344 new file mode 100644 index 0000000000000000000000000000000000000000..007e0c7a02b8d8bbcbb9a2bf516ab8bc40c8266d --- /dev/null +++ b/db/schema_migrations/20250613003344 @@ -0,0 +1 @@ +4f6c23fb39965b5fc4a4485003f45294b9998d396f35a100f819c246ba78ec21 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 0d3e95a71c26f189cde12192cd8803e6b280f0ff..cc62b10bcc3eba1c9ed9fb7c526d0f473ea07a58 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -14224,7 +14224,9 @@ CREATE TABLE environments ( description_html text, cached_markdown_version integer, auto_stop_setting smallint DEFAULT 0 NOT NULL, + latest_configured_auto_stop_in text, CONSTRAINT check_23b1eb18a2 CHECK ((char_length(flux_resource_path) <= 255)), + CONSTRAINT check_4a037337eb CHECK ((char_length(latest_configured_auto_stop_in) <= 255)), CONSTRAINT check_ad5e1ed5e1 CHECK ((char_length(description) <= 10000)), CONSTRAINT check_b5373a1804 CHECK ((char_length(kubernetes_namespace) <= 63)), CONSTRAINT check_d26221226f CHECK ((char_length(description_html) <= 50000)) diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 5e4a66ee1d436465d551a77497170dca431142d5..a685e109f80bc6e4917dffc1be12123cce07be71 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -43,6 +43,7 @@ it { is_expected.to validate_length_of(:flux_resource_path).is_at_most(255) } it { is_expected.to validate_length_of(:description).is_at_most(10000) } it { is_expected.to validate_length_of(:description_html).is_at_most(50000) } + it { is_expected.to validate_length_of(:latest_configured_auto_stop_in).is_at_most(255) } describe 'validation' do it 'does not become invalid record when external_url is empty' do @@ -1978,6 +1979,7 @@ def create_deployment_with_stop_action(status, pipeline, stop_action_name) if expected_result.is_a?(Integer) || expected_result.nil? subject + expect(environment.latest_configured_auto_stop_in).to eq(value) expect(environment.auto_stop_in).to eq(expected_result) else expect { subject }.to raise_error(expected_result) diff --git a/spec/services/environments/recalculate_auto_stop_service_spec.rb b/spec/services/environments/recalculate_auto_stop_service_spec.rb index 4443ce8a85f7fafbd64418091d3b22152836c40e..32c1ceb82990b83b797f023fadbd241786ee40a1 100644 --- a/spec/services/environments/recalculate_auto_stop_service_spec.rb +++ b/spec/services/environments/recalculate_auto_stop_service_spec.rb @@ -90,6 +90,24 @@ it 'does not update the environment' do expect { recalculate }.not_to change { environment.reload.auto_stop_at } end + + context 'when the latest_configured_auto_stop_in is stored on the environment' do + before do + environment.update!(latest_configured_auto_stop_in: '1 week') + end + + if can_reset_timer + it 'updates the environment auto_stop_at' do + expected_stop_at = be_like_time(1.week.from_now) + + expect { recalculate }.to change { environment.reload.auto_stop_at }.from(nil).to(expected_stop_at) + end + else + it 'does not update the environment' do + expect { recalculate }.not_to change { environment.reload.auto_stop_at } + end + end + end end end end