Commit eb3f4688 authored by Mark Chao's avatar Mark Chao Committed by Luke Duncalfe

Add future_subscriptions column

This is for self-managed EE to store an array of JSON objects.
ApplicationSetting is used instead of a new table because this will be
overwritten during each sync, and won't be updated.

There will ever store a handful of items and are displayed as a whole.

Changelog: added
parent ba80ab37
# frozen_string_literal: true
class AddFutureSubscriptionsToApplicationSettings < Gitlab::Database::Migration[1.0]
def change
add_column :application_settings, :future_subscriptions, :jsonb, null: false, default: []
end
end
c5282e48f31c0896a3ce21fe238eb602dc006b0bfe62aa4f12ee39bbd620c76c
\ No newline at end of file
...@@ -10479,6 +10479,7 @@ CREATE TABLE application_settings ( ...@@ -10479,6 +10479,7 @@ CREATE TABLE application_settings (
sentry_environment text, sentry_environment text,
max_ssh_key_lifetime integer, max_ssh_key_lifetime integer,
static_objects_external_storage_auth_token_encrypted text, static_objects_external_storage_auth_token_encrypted text,
future_subscriptions jsonb DEFAULT '[]'::jsonb NOT NULL,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)), CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
...@@ -90,6 +90,8 @@ module EE ...@@ -90,6 +90,8 @@ module EE
allow_blank: true, allow_blank: true,
length: { maximum: EMAIL_ADDITIONAL_TEXT_CHARACTER_LIMIT } length: { maximum: EMAIL_ADDITIONAL_TEXT_CHARACTER_LIMIT }
validates :future_subscriptions, json_schema: { filename: 'future_subscriptions' }
validates :geo_node_allowed_ips, length: { maximum: 255 }, presence: true validates :geo_node_allowed_ips, length: { maximum: 255 }, presence: true
validates :required_instance_ci_template, presence: true, allow_nil: true validates :required_instance_ci_template, presence: true, allow_nil: true
...@@ -124,6 +126,8 @@ module EE ...@@ -124,6 +126,8 @@ module EE
after_commit :update_personal_access_tokens_lifetime, if: :saved_change_to_max_personal_access_token_lifetime? after_commit :update_personal_access_tokens_lifetime, if: :saved_change_to_max_personal_access_token_lifetime?
after_commit :resume_elasticsearch_indexing after_commit :resume_elasticsearch_indexing
serialize :future_subscriptions, Serializers::Json # rubocop:disable Cop/ActiveRecordSerialize
end end
class_methods do class_methods do
...@@ -154,6 +158,7 @@ module EE ...@@ -154,6 +158,7 @@ module EE
email_additional_text: nil, email_additional_text: nil,
enforce_namespace_storage_limit: false, enforce_namespace_storage_limit: false,
enforce_pat_expiration: true, enforce_pat_expiration: true,
future_subscriptions: [],
geo_node_allowed_ips: '0.0.0.0/0, ::/0', geo_node_allowed_ips: '0.0.0.0/0, ::/0',
git_two_factor_session_expiry: 15, git_two_factor_session_expiry: 15,
lock_memberships_to_ldap: false, lock_memberships_to_ldap: false,
......
{
"description": "A list of future subscriptions",
"type": "array",
"items": {
"type": "object"
},
"maxItems": 100
}
...@@ -28,6 +28,7 @@ class SyncSeatLinkRequestWorker ...@@ -28,6 +28,7 @@ class SyncSeatLinkRequestWorker
if response.success? if response.success?
reset_license!(response['license']) if response['license'] reset_license!(response['license']) if response['license']
save_future_subscriptions(response)
save_reconciliation_dates!(response) save_reconciliation_dates!(response)
else else
raise RequestError, request_error_message(response) raise RequestError, request_error_message(response)
...@@ -77,4 +78,12 @@ class SyncSeatLinkRequestWorker ...@@ -77,4 +78,12 @@ class SyncSeatLinkRequestWorker
GitlabSubscriptions::UpcomingReconciliation.create!(attributes) GitlabSubscriptions::UpcomingReconciliation.create!(attributes)
end end
end end
def save_future_subscriptions(response)
return if response['future_subscriptions'].blank?
Gitlab::CurrentSettings.current_application_settings.update!(future_subscriptions: response['future_subscriptions'])
rescue StandardError => err
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(err)
end
end end
...@@ -63,6 +63,10 @@ RSpec.describe ApplicationSetting do ...@@ -63,6 +63,10 @@ RSpec.describe ApplicationSetting do
it { is_expected.to allow_value('a' * 255).for(:elasticsearch_username) } it { is_expected.to allow_value('a' * 255).for(:elasticsearch_username) }
it { is_expected.not_to allow_value('a' * 256).for(:elasticsearch_username) } it { is_expected.not_to allow_value('a' * 256).for(:elasticsearch_username) }
it { is_expected.to allow_value([{}]).for(:future_subscriptions) }
it { is_expected.not_to allow_value({}).for(:future_subscriptions) }
it { is_expected.not_to allow_value(nil).for(:future_subscriptions) }
it { is_expected.to allow_value(nil).for(:required_instance_ci_template) } it { is_expected.to allow_value(nil).for(:required_instance_ci_template) }
it { is_expected.not_to allow_value("").for(:required_instance_ci_template) } it { is_expected.not_to allow_value("").for(:required_instance_ci_template) }
it { is_expected.not_to allow_value(" ").for(:required_instance_ci_template) } it { is_expected.not_to allow_value(" ").for(:required_instance_ci_template) }
......
...@@ -158,6 +158,45 @@ RSpec.describe SyncSeatLinkRequestWorker, type: :worker do ...@@ -158,6 +158,45 @@ RSpec.describe SyncSeatLinkRequestWorker, type: :worker do
end end
end end
context 'when response contains future subscription information' do
let(:future_subscriptions) { [{ 'foo' => 'bar' }] }
let(:body) { { success: true, future_subscriptions: future_subscriptions }.to_json }
let(:today) { Date.current }
before do
stub_request(:post, seat_link_url).to_return(
status: 200,
body: body,
headers: { content_type: 'application/json' }
)
end
it 'persists future subscription information' do
expect { sync_seat_link }.to change { Gitlab::CurrentSettings.current_application_settings.future_subscriptions }.from([]).to(future_subscriptions)
end
context 'when future subscription is empty' do
let(:future_subscriptions) { [] }
before do
Gitlab::CurrentSettings.current_application_settings.update!(future_subscriptions: [{}])
end
it 'does nothing' do
expect { sync_seat_link }.not_to change { Gitlab::CurrentSettings.current_application_settings.future_subscriptions }.from([{}])
end
end
context 'when saving fails' do
it 'logs error' do
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:save!).and_raise('saving fails')
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
expect { sync_seat_link }.not_to raise_error
end
end
end
shared_examples 'unsuccessful request' do shared_examples 'unsuccessful request' do
context 'when the request is not successful' do context 'when the request is not successful' do
before do before do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment