From 2515c0cd44db9e14ebea9a9218b05853b9afcaad Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Fri, 23 Aug 2019 22:27:41 +0000 Subject: Add a link to docs in project description Add to the service and migration both. --- .../self_monitoring/project/create_service.rb | 219 ----------------- changelogs/unreleased/56883-migration.yml | 5 + ...7_add_gitlab_instance_administration_project.rb | 16 ++ ...2_set_self_monitoring_project_alerting_token.rb | 73 ++++++ .../self_monitoring/project/create_service.rb | 251 +++++++++++++++++++ locale/gitlab.pot | 30 +++ .../self_monitoring/project/create_service_spec.rb | 267 +++++++++++++++++++++ ..._gitlab_instance_administration_project_spec.rb | 252 +++++++++++++++++++ .../self_monitoring/project/create_service_spec.rb | 234 ------------------ spec/support/helpers/stub_configuration.rb | 4 + 10 files changed, 898 insertions(+), 453 deletions(-) delete mode 100644 app/services/self_monitoring/project/create_service.rb create mode 100644 changelogs/unreleased/56883-migration.yml create mode 100644 db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb create mode 100644 db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb create mode 100644 lib/gitlab/database_importers/self_monitoring/project/create_service.rb create mode 100644 spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb create mode 100644 spec/migrations/add_gitlab_instance_administration_project_spec.rb delete mode 100644 spec/services/self_monitoring/project/create_service_spec.rb diff --git a/app/services/self_monitoring/project/create_service.rb b/app/services/self_monitoring/project/create_service.rb deleted file mode 100644 index c925c6a1610..00000000000 --- a/app/services/self_monitoring/project/create_service.rb +++ /dev/null @@ -1,219 +0,0 @@ -# frozen_string_literal: true - -module SelfMonitoring - module Project - class CreateService < ::BaseService - include Stepable - include Gitlab::Utils::StrongMemoize - - VISIBILITY_LEVEL = Gitlab::VisibilityLevel::INTERNAL - PROJECT_NAME = 'GitLab Instance Administration' - PROJECT_DESCRIPTION = <<~HEREDOC - This project is automatically generated and will be used to help monitor this GitLab instance. - HEREDOC - - GROUP_NAME = 'GitLab Instance Administrators' - GROUP_PATH = 'gitlab-instance-administrators' - - steps :validate_admins, - :create_group, - :create_project, - :save_project_id, - :add_group_members, - :add_to_whitelist, - :add_prometheus_manual_configuration - - def initialize - super(nil) - end - - def execute - execute_steps - end - - private - - def validate_admins - unless instance_admins.any? - log_error('No active admin user found') - return error('No active admin user found') - end - - success - end - - def create_group - if project_created? - log_info(_('Instance administrators group already exists')) - @group = application_settings.instance_administration_project.owner - return success(group: @group) - end - - admin_user = group_owner - @group = ::Groups::CreateService.new(admin_user, create_group_params).execute - - if @group.persisted? - success(group: @group) - else - error('Could not create group') - end - end - - def create_project - if project_created? - log_info(_('Instance administration project already exists')) - @project = application_settings.instance_administration_project - return success(project: project) - end - - admin_user = group_owner - @project = ::Projects::CreateService.new(admin_user, create_project_params).execute - - if project.persisted? - success(project: project) - else - log_error(_("Could not create instance administration project. Errors: %{errors}") % { errors: project.errors.full_messages }) - error(_('Could not create project')) - end - end - - def save_project_id - return success if project_created? - - result = ApplicationSettings::UpdateService.new( - application_settings, - group_owner, - { instance_administration_project_id: @project.id } - ).execute - - if result - success - else - log_error(_("Could not save instance administration project ID, errors: %{errors}") % { errors: application_settings.errors.full_messages }) - error(_('Could not save project ID')) - end - end - - def add_group_members - members = @group.add_users(group_maintainers, Gitlab::Access::MAINTAINER) - errors = members.flat_map { |member| member.errors.full_messages } - - if errors.any? - log_error("Could not add admins as members to self-monitoring project. Errors: #{errors}") - error('Could not add admins as members') - else - success - end - end - - def add_to_whitelist - return success unless prometheus_enabled? - return success unless prometheus_listen_address.present? - - uri = parse_url(internal_prometheus_listen_address_uri) - return error(_('Prometheus listen_address is not a valid URI')) unless uri - - result = ApplicationSettings::UpdateService.new( - application_settings, - group_owner, - add_to_outbound_local_requests_whitelist: [uri.normalized_host] - ).execute - - if result - success - else - log_error(_("Could not add prometheus URL to whitelist, errors: %{errors}") % { errors: application_settings.errors.full_messages }) - error(_('Could not add prometheus URL to whitelist')) - end - end - - def add_prometheus_manual_configuration - return success unless prometheus_enabled? - return success unless prometheus_listen_address.present? - - service = project.find_or_initialize_service('prometheus') - - unless service.update(prometheus_service_attributes) - log_error("Could not save prometheus manual configuration for self-monitoring project. Errors: #{service.errors.full_messages}") - return error('Could not save prometheus manual configuration') - end - - success - end - - def application_settings - strong_memoize(:application_settings) do - Gitlab::CurrentSettings.expire_current_application_settings - Gitlab::CurrentSettings.current_application_settings - end - end - - def project_created? - application_settings.instance_administration_project.present? - end - - def parse_url(uri_string) - Addressable::URI.parse(uri_string) - rescue Addressable::URI::InvalidURIError, TypeError - end - - def prometheus_enabled? - Gitlab.config.prometheus.enable - rescue Settingslogic::MissingSetting - false - end - - def prometheus_listen_address - Gitlab.config.prometheus.listen_address - rescue Settingslogic::MissingSetting - end - - def instance_admins - @instance_admins ||= User.admins.active - end - - def group_owner - instance_admins.first - end - - def group_maintainers - # Exclude the first so that the group_owner is not added again as a member. - instance_admins - [group_owner] - end - - def create_group_params - { - name: GROUP_NAME, - path: "#{GROUP_PATH}-#{SecureRandom.hex(4)}", - visibility_level: VISIBILITY_LEVEL - } - end - - def create_project_params - { - initialize_with_readme: true, - visibility_level: VISIBILITY_LEVEL, - name: PROJECT_NAME, - description: PROJECT_DESCRIPTION, - namespace_id: @group.id - } - end - - def internal_prometheus_listen_address_uri - if prometheus_listen_address.starts_with?('http') - prometheus_listen_address - else - 'http://' + prometheus_listen_address - end - end - - def prometheus_service_attributes - { - api_url: internal_prometheus_listen_address_uri, - manual_configuration: true, - active: true - } - end - end - end -end diff --git a/changelogs/unreleased/56883-migration.yml b/changelogs/unreleased/56883-migration.yml new file mode 100644 index 00000000000..d6eb49e62e1 --- /dev/null +++ b/changelogs/unreleased/56883-migration.yml @@ -0,0 +1,5 @@ +--- +title: Create a project for self-monitoring the GitLab instance +merge_request: 31389 +author: +type: added diff --git a/db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb b/db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb new file mode 100644 index 00000000000..8b2cf7b3d76 --- /dev/null +++ b/db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddGitlabInstanceAdministrationProject < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def up + Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService.new.execute! + end + + def down + ApplicationSetting.current_without_cache + &.instance_administration_project + &.owner + &.destroy! + end +end diff --git a/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb b/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb new file mode 100644 index 00000000000..0c4faebc548 --- /dev/null +++ b/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +class SetSelfMonitoringProjectAlertingToken < ActiveRecord::Migration[5.2] + DOWNTIME = false + + module Migratable + module Alerting + class ProjectAlertingSetting < ApplicationRecord + self.table_name = 'project_alerting_settings' + + belongs_to :project + + validates :token, presence: true + + attr_encrypted :token, + mode: :per_attribute_iv, + key: Settings.attr_encrypted_db_key_base_truncated, + algorithm: 'aes-256-gcm' + + before_validation :ensure_token + + private + + def ensure_token + self.token ||= generate_token + end + + def generate_token + SecureRandom.hex + end + end + end + + class Project < ApplicationRecord + has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting' + end + + class ApplicationSetting < ApplicationRecord + self.table_name = 'application_settings' + + belongs_to :instance_administration_project, class_name: 'Project' + + def self.current_without_cache + last + end + end + end + + def setup_alertmanager_token(project) + return unless License.feature_available?(:prometheus_alerts) + + project.create_alerting_setting! + end + + def up + Gitlab.ee do + project = Migratable::ApplicationSetting.current_without_cache&.instance_administration_project + + if project + setup_alertmanager_token(project) + end + end + end + + def down + Gitlab.ee do + Migratable::ApplicationSetting.current_without_cache + &.instance_administration_project + &.alerting_setting + &.destroy! + end + end +end diff --git a/lib/gitlab/database_importers/self_monitoring/project/create_service.rb b/lib/gitlab/database_importers/self_monitoring/project/create_service.rb new file mode 100644 index 00000000000..164854e1e1a --- /dev/null +++ b/lib/gitlab/database_importers/self_monitoring/project/create_service.rb @@ -0,0 +1,251 @@ +# frozen_string_literal: true + +module Gitlab + module DatabaseImporters + module SelfMonitoring + module Project + include Stepable + + class CreateService < ::BaseService + include Stepable + + STEPS_ALLOWED_TO_FAIL = [ + :validate_application_settings, :validate_project_created, :validate_admins + ].freeze + + VISIBILITY_LEVEL = Gitlab::VisibilityLevel::INTERNAL + PROJECT_NAME = 'GitLab Instance Administration' + + steps :validate_application_settings, + :validate_project_created, + :validate_admins, + :create_group, + :create_project, + :save_project_id, + :add_group_members, + :add_to_whitelist, + :add_prometheus_manual_configuration + + def initialize + super(nil) + end + + def execute! + result = execute_steps + + if result[:status] == :success + result + elsif STEPS_ALLOWED_TO_FAIL.include?(result[:failed_step]) + success + else + raise StandardError, result[:message] + end + end + + private + + def validate_application_settings + return success if application_settings + + log_error(_('No application_settings found')) + error(_('No application_settings found')) + end + + def validate_project_created + return success unless project_created? + + log_error(_('Project already created')) + error(_('Project already created')) + end + + def validate_admins + unless instance_admins.any? + log_error(_('No active admin user found')) + return error(_('No active admin user found')) + end + + success + end + + def create_group + if project_created? + log_info(_('Instance administrators group already exists')) + @group = application_settings.instance_administration_project.owner + return success(group: @group) + end + + @group = ::Groups::CreateService.new(group_owner, create_group_params).execute + + if @group.persisted? + success(group: @group) + else + error(_('Could not create group')) + end + end + + def create_project + if project_created? + log_info(_('Instance administration project already exists')) + @project = application_settings.instance_administration_project + return success(project: project) + end + + @project = ::Projects::CreateService.new(group_owner, create_project_params).execute + + if project.persisted? + success(project: project) + else + log_error(_("Could not create instance administration project. Errors: %{errors}") % { errors: project.errors.full_messages }) + error(_('Could not create project')) + end + end + + def save_project_id + return success if project_created? + + result = application_settings.update(instance_administration_project_id: @project.id) + + if result + success + else + log_error(_("Could not save instance administration project ID, errors: %{errors}") % { errors: application_settings.errors.full_messages }) + error(_('Could not save project ID')) + end + end + + def add_group_members + members = @group.add_users(members_to_add, Gitlab::Access::MAINTAINER) + errors = members.flat_map { |member| member.errors.full_messages } + + if errors.any? + log_error(_('Could not add admins as members to self-monitoring project. Errors: %{errors}') % { errors: errors }) + error(_('Could not add admins as members')) + else + success + end + end + + def add_to_whitelist + return success unless prometheus_enabled? + return success unless prometheus_listen_address.present? + + uri = parse_url(internal_prometheus_listen_address_uri) + return error(_('Prometheus listen_address is not a valid URI')) unless uri + + application_settings.add_to_outbound_local_requests_whitelist([uri.normalized_host]) + result = application_settings.save + + if result + # Expire the Gitlab::CurrentSettings cache after updating the whitelist. + # This happens automatically in an after_commit hook, but in migrations, + # the after_commit hook only runs at the end of the migration. + Gitlab::CurrentSettings.expire_current_application_settings + success + else + log_error(_("Could not add prometheus URL to whitelist, errors: %{errors}") % { errors: application_settings.errors.full_messages }) + error(_('Could not add prometheus URL to whitelist')) + end + end + + def add_prometheus_manual_configuration + return success unless prometheus_enabled? + return success unless prometheus_listen_address.present? + + service = project.find_or_initialize_service('prometheus') + + unless service.update(prometheus_service_attributes) + log_error(_('Could not save prometheus manual configuration for self-monitoring project. Errors: %{errors}') % { errors: service.errors.full_messages }) + return error(_('Could not save prometheus manual configuration')) + end + + success + end + + def application_settings + @application_settings ||= ApplicationSetting.current_without_cache + end + + def project_created? + application_settings.instance_administration_project.present? + end + + def parse_url(uri_string) + Addressable::URI.parse(uri_string) + rescue Addressable::URI::InvalidURIError, TypeError + end + + def prometheus_enabled? + Gitlab.config.prometheus.enable + rescue Settingslogic::MissingSetting + log_error(_('prometheus.enable is not present in gitlab.yml')) + + false + end + + def prometheus_listen_address + Gitlab.config.prometheus.listen_address + rescue Settingslogic::MissingSetting + log_error(_('prometheus.listen_address is not present in gitlab.yml')) + + nil + end + + def instance_admins + @instance_admins ||= User.admins.active + end + + def group_owner + instance_admins.first + end + + def members_to_add + # Exclude admins who are already members of group because + # `@group.add_users(users)` returns an error if the users parameter contains + # users who are already members of the group. + instance_admins - @group.members.collect(&:user) + end + + def create_group_params + { + name: 'GitLab Instance Administrators', + path: "gitlab-instance-administrators-#{SecureRandom.hex(4)}", + visibility_level: VISIBILITY_LEVEL + } + end + + def docs_path + Rails.application.routes.url_helpers.help_page_path( + 'administration/monitoring/gitlab_instance_administration_project/index' + ) + end + + def create_project_params + { + initialize_with_readme: true, + visibility_level: VISIBILITY_LEVEL, + name: PROJECT_NAME, + description: "This project is automatically generated and will be used to help monitor this GitLab instance. [More information](#{docs_path})", + namespace_id: @group.id + } + end + + def internal_prometheus_listen_address_uri + if prometheus_listen_address.starts_with?('http') + prometheus_listen_address + else + 'http://' + prometheus_listen_address + end + end + + def prometheus_service_attributes + { + api_url: internal_prometheus_listen_address_uri, + manual_configuration: true, + active: true + } + end + end + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f8cebc22d93..61642fbbd59 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3353,6 +3353,12 @@ msgstr "" msgid "Copy token to clipboard" msgstr "" +msgid "Could not add admins as members" +msgstr "" + +msgid "Could not add admins as members to self-monitoring project. Errors: %{errors}" +msgstr "" + msgid "Could not add prometheus URL to whitelist" msgstr "" @@ -3371,6 +3377,9 @@ msgstr "" msgid "Could not create Wiki Repository at this time. Please try again later." msgstr "" +msgid "Could not create group" +msgstr "" + msgid "Could not create instance administration project. Errors: %{errors}" msgstr "" @@ -3398,6 +3407,12 @@ msgstr "" msgid "Could not save project ID" msgstr "" +msgid "Could not save prometheus manual configuration" +msgstr "" + +msgid "Could not save prometheus manual configuration for self-monitoring project. Errors: %{errors}" +msgstr "" + msgid "Coverage" msgstr "" @@ -7400,9 +7415,15 @@ msgstr "" msgid "No Tag" msgstr "" +msgid "No active admin user found" +msgstr "" + msgid "No activities found" msgstr "" +msgid "No application_settings found" +msgstr "" + msgid "No available namespaces to fork the project." msgstr "" @@ -8746,6 +8767,9 @@ msgstr "" msgid "Project access must be granted explicitly to each user." msgstr "" +msgid "Project already created" +msgstr "" + msgid "Project and wiki repositories" msgstr "" @@ -13961,6 +13985,12 @@ msgstr "" msgid "project avatar" msgstr "" +msgid "prometheus.enable is not present in gitlab.yml" +msgstr "" + +msgid "prometheus.listen_address is not present in gitlab.yml" +msgstr "" + msgid "quick actions" msgstr "" diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb new file mode 100644 index 00000000000..9bd0d800086 --- /dev/null +++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb @@ -0,0 +1,267 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do + describe '#execute' do + let(:result) { subject.execute! } + + let(:prometheus_settings) do + { + enable: true, + listen_address: 'localhost:9090' + } + end + + before do + stub_config(prometheus: prometheus_settings) + end + + context 'without application_settings' do + it 'does not fail' do + expect(subject).to receive(:log_error).and_call_original + expect(result).to eq( + status: :success + ) + + expect(Project.count).to eq(0) + expect(Group.count).to eq(0) + end + end + + context 'without admin users' do + let(:application_setting) { Gitlab::CurrentSettings.current_application_settings } + + before do + allow(ApplicationSetting).to receive(:current_without_cache) { application_setting } + end + + it 'does not fail' do + expect(subject).to receive(:log_error).and_call_original + expect(result).to eq( + status: :success + ) + + expect(Project.count).to eq(0) + expect(Group.count).to eq(0) + end + end + + context 'with admin users' do + let(:project) { result[:project] } + let(:group) { result[:group] } + let(:application_setting) { Gitlab::CurrentSettings.current_application_settings } + + let!(:user) { create(:user, :admin) } + + before do + allow(ApplicationSetting).to receive(:current_without_cache) { application_setting } + application_setting.allow_local_requests_from_web_hooks_and_services = true + end + + shared_examples 'has prometheus service' do |listen_address| + it do + expect(result[:status]).to eq(:success) + + prometheus = project.prometheus_service + expect(prometheus).not_to eq(nil) + expect(prometheus.api_url).to eq(listen_address) + expect(prometheus.active).to eq(true) + expect(prometheus.manual_configuration).to eq(true) + end + end + + it_behaves_like 'has prometheus service', 'http://localhost:9090' + + it 'creates group' do + expect(result[:status]).to eq(:success) + expect(group).to be_persisted + expect(group.name).to eq('GitLab Instance Administrators') + expect(group.path).to start_with('gitlab-instance-administrators') + expect(group.path.split('-').last.length).to eq(8) + expect(group.visibility_level).to eq(described_class::VISIBILITY_LEVEL) + end + + it 'creates project with internal visibility' do + expect(result[:status]).to eq(:success) + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) + expect(project).to be_persisted + end + + it 'creates project with internal visibility even when internal visibility is restricted' do + application_setting.restricted_visibility_levels = [Gitlab::VisibilityLevel::INTERNAL] + + expect(result[:status]).to eq(:success) + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) + expect(project).to be_persisted + end + + it 'creates project with correct name and description' do + path = 'administration/monitoring/gitlab_instance_administration_project/index' + docs_path = Rails.application.routes.url_helpers.help_page_path(path) + + expect(result[:status]).to eq(:success) + expect(project.name).to eq(described_class::PROJECT_NAME) + expect(project.description).to eq( + 'This project is automatically generated and will be used to help monitor this GitLab instance. ' \ + "[More information](#{docs_path})" + ) + expect(File).to exist("doc/#{path}.md") + end + + it 'adds all admins as maintainers' do + admin1 = create(:user, :admin) + admin2 = create(:user, :admin) + create(:user) + + expect(result[:status]).to eq(:success) + expect(project.owner).to eq(group) + expect(group.members.collect(&:user)).to contain_exactly(user, admin1, admin2) + expect(group.members.collect(&:access_level)).to contain_exactly( + Gitlab::Access::OWNER, + Gitlab::Access::MAINTAINER, + Gitlab::Access::MAINTAINER + ) + end + + it 'saves the project id' do + expect(result[:status]).to eq(:success) + expect(application_setting.instance_administration_project_id).to eq(project.id) + end + + it 'returns error when saving project ID fails' do + allow(application_setting).to receive(:update) { false } + + expect { result }.to raise_error(StandardError, 'Could not save project ID') + end + + context 'when project already exists' do + let(:existing_group) { create(:group) } + let(:existing_project) { create(:project, namespace: existing_group) } + + before do + admin1 = create(:user, :admin) + admin2 = create(:user, :admin) + + existing_group.add_owner(user) + existing_group.add_users([admin1, admin2], Gitlab::Access::MAINTAINER) + + application_setting.instance_administration_project_id = existing_project.id + end + + it 'does not fail' do + expect(subject).to receive(:log_error).and_call_original + expect(result[:status]).to eq(:success) + + expect(Project.count).to eq(1) + expect(Group.count).to eq(1) + end + end + + context 'when local requests from hooks and services are not allowed' do + before do + application_setting.allow_local_requests_from_web_hooks_and_services = false + end + + it_behaves_like 'has prometheus service', 'http://localhost:9090' + + it 'does not overwrite the existing whitelist' do + application_setting.outbound_local_requests_whitelist = ['example.com'] + + expect(result[:status]).to eq(:success) + expect(application_setting.outbound_local_requests_whitelist).to contain_exactly( + 'example.com', 'localhost' + ) + end + end + + context 'with non default prometheus address' do + let(:prometheus_settings) do + { + enable: true, + listen_address: 'https://localhost:9090' + } + end + + it_behaves_like 'has prometheus service', 'https://localhost:9090' + end + + context 'when prometheus setting is not present in gitlab.yml' do + before do + allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting) + end + + it 'does not fail' do + expect(result).to include(status: :success) + expect(project.prometheus_service).to be_nil + end + end + + context 'when prometheus setting is disabled in gitlab.yml' do + let(:prometheus_settings) do + { + enable: false, + listen_address: 'http://localhost:9090' + } + end + + it 'does not configure prometheus' do + expect(result).to include(status: :success) + expect(project.prometheus_service).to be_nil + end + end + + context 'when prometheus listen address is blank in gitlab.yml' do + let(:prometheus_settings) { { enable: true, listen_address: '' } } + + it 'does not configure prometheus' do + expect(result).to include(status: :success) + expect(project.prometheus_service).to be_nil + end + end + + context 'when project cannot be created' do + let(:project) { build(:project) } + + before do + project.errors.add(:base, "Test error") + + expect_next_instance_of(::Projects::CreateService) do |project_create_service| + expect(project_create_service).to receive(:execute) + .and_return(project) + end + end + + it 'returns error' do + expect(subject).to receive(:log_error).and_call_original + expect { result }.to raise_error(StandardError, 'Could not create project') + end + end + + context 'when user cannot be added to project' do + before do + subject.instance_variable_set(:@instance_admins, [user, build(:user, :admin)]) + end + + it 'returns error' do + expect(subject).to receive(:log_error).and_call_original + expect { result }.to raise_error(StandardError, 'Could not add admins as members') + end + end + + context 'when prometheus manual configuration cannot be saved' do + let(:prometheus_settings) do + { + enable: true, + listen_address: 'httpinvalid://localhost:9090' + } + end + + it 'returns error' do + expect(subject).to receive(:log_error).and_call_original + expect { result }.to raise_error(StandardError, 'Could not save prometheus manual configuration') + end + end + end + end +end diff --git a/spec/migrations/add_gitlab_instance_administration_project_spec.rb b/spec/migrations/add_gitlab_instance_administration_project_spec.rb new file mode 100644 index 00000000000..08e20a4e8ff --- /dev/null +++ b/spec/migrations/add_gitlab_instance_administration_project_spec.rb @@ -0,0 +1,252 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190801072937_add_gitlab_instance_administration_project.rb') + +describe AddGitlabInstanceAdministrationProject, :migration do + let(:application_settings) { table(:application_settings) } + let(:users) { table(:users) } + let(:projects) { table(:projects) } + let(:namespaces) { table(:namespaces) } + let(:members) { table(:members) } + + let(:service_class) do + Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService + end + + let(:prometheus_settings) do + { + enable: true, + listen_address: 'localhost:9090' + } + end + + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + + stub_config(prometheus: prometheus_settings) + end + + describe 'down' do + let!(:application_setting) { application_settings.create! } + let!(:user) { users.create!(admin: true, email: 'admin1@example.com', projects_limit: 10, state: :active) } + + it 'deletes group and project' do + migrate! + + expect(Project.count).to eq(1) + expect(Group.count).to eq(1) + + schema_migrate_down! + + expect(Project.count).to eq(0) + expect(Group.count).to eq(0) + end + end + + describe 'up' do + context 'without application_settings' do + it 'does not fail' do + migrate! + + expect(Project.count).to eq(0) + end + end + + context 'without admin users' do + let!(:application_setting) { application_settings.create! } + + it 'does not fail' do + migrate! + + expect(Project.count).to eq(0) + end + end + + context 'with admin users' do + let(:project) { Project.last } + let(:group) { Group.last } + let!(:application_setting) { application_settings.create! } + let!(:user) { users.create!(admin: true, email: 'admin1@example.com', projects_limit: 10, state: :active) } + + before do + stub_application_setting(allow_local_requests_from_web_hooks_and_services: true) + end + + shared_examples 'has prometheus service' do |listen_address| + it do + migrate! + + prometheus = project.prometheus_service + expect(prometheus).to be_persisted + expect(prometheus).not_to eq(nil) + expect(prometheus.api_url).to eq(listen_address) + expect(prometheus.active).to eq(true) + expect(prometheus.manual_configuration).to eq(true) + end + end + + it_behaves_like 'has prometheus service', 'http://localhost:9090' + + it 'creates GitLab Instance Administrator group' do + migrate! + + expect(group).to be_persisted + expect(group.name).to eq('GitLab Instance Administrators') + expect(group.path).to start_with('gitlab-instance-administrators') + expect(group.path.split('-').last.length).to eq(8) + expect(group.visibility_level).to eq(service_class::VISIBILITY_LEVEL) + end + + it 'creates project with internal visibility' do + migrate! + + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) + expect(project).to be_persisted + end + + it 'creates project with correct name and description' do + migrate! + + path = 'administration/monitoring/gitlab_instance_administration_project/index' + docs_path = Rails.application.routes.url_helpers.help_page_path(path) + + expect(project.name).to eq(service_class::PROJECT_NAME) + expect(project.description).to eq( + 'This project is automatically generated and will be used to help monitor this GitLab instance. ' \ + "[More information](#{docs_path})" + ) + expect(File).to exist("doc/#{path}.md") + end + + it 'adds all admins as maintainers' do + admin1 = users.create!(admin: true, email: 'admin2@example.com', projects_limit: 10, state: :active) + admin2 = users.create!(admin: true, email: 'admin3@example.com', projects_limit: 10, state: :active) + users.create!(email: 'nonadmin1@example.com', projects_limit: 10, state: :active) + + migrate! + + expect(project.owner).to eq(group) + expect(group.members.collect(&:user).collect(&:id)).to contain_exactly(user.id, admin1.id, admin2.id) + expect(group.members.collect(&:access_level)).to contain_exactly( + Gitlab::Access::OWNER, + Gitlab::Access::MAINTAINER, + Gitlab::Access::MAINTAINER + ) + end + + it 'saves the project id' do + migrate! + + application_setting.reload + expect(application_setting.instance_administration_project_id).to eq(project.id) + end + + it 'does not fail when a project already exists' do + group = namespaces.create!( + path: 'gitlab-instance-administrators', + name: 'GitLab Instance Administrators', + type: 'Group' + ) + project = projects.create!( + namespace_id: group.id, + name: 'GitLab Instance Administration' + ) + + admin1 = users.create!(admin: true, email: 'admin4@example.com', projects_limit: 10, state: :active) + admin2 = users.create!(admin: true, email: 'admin5@example.com', projects_limit: 10, state: :active) + + members.create!( + user_id: admin1.id, + source_id: group.id, + source_type: 'Namespace', + type: 'GroupMember', + access_level: GroupMember::MAINTAINER, + notification_level: NotificationSetting.levels[:global] + ) + members.create!( + user_id: admin2.id, + source_id: group.id, + source_type: 'Namespace', + type: 'GroupMember', + access_level: GroupMember::MAINTAINER, + notification_level: NotificationSetting.levels[:global] + ) + + stub_application_setting(instance_administration_project: project) + + migrate! + + expect(Project.last.id).to eq(project.id) + expect(Group.last.id).to eq(group.id) + end + + context 'when local requests from hooks and services are not allowed' do + before do + stub_application_setting(allow_local_requests_from_web_hooks_and_services: false) + end + + it_behaves_like 'has prometheus service', 'http://localhost:9090' + + it 'does not overwrite the existing whitelist' do + application_setting.update!(outbound_local_requests_whitelist: ['example.com']) + + migrate! + + application_setting.reload + expect(application_setting.outbound_local_requests_whitelist).to contain_exactly( + 'example.com', 'localhost' + ) + end + end + + context 'with non default prometheus address' do + let(:prometheus_settings) do + { + enable: true, + listen_address: 'https://localhost:9090' + } + end + + it_behaves_like 'has prometheus service', 'https://localhost:9090' + end + + context 'when prometheus setting is not present in gitlab.yml' do + before do + allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting) + end + + it 'does not fail' do + migrate! + + expect(project.prometheus_service).to be_nil + end + end + + context 'when prometheus setting is disabled in gitlab.yml' do + let(:prometheus_settings) do + { + enable: false, + listen_address: 'localhost:9090' + } + end + + it 'does not configure prometheus' do + migrate! + + expect(project.prometheus_service).to be_nil + end + end + + context 'when prometheus listen address is blank in gitlab.yml' do + let(:prometheus_settings) { { enable: true, listen_address: '' } } + + it 'does not configure prometheus' do + migrate! + + expect(project.prometheus_service).to be_nil + end + end + end + end +end diff --git a/spec/services/self_monitoring/project/create_service_spec.rb b/spec/services/self_monitoring/project/create_service_spec.rb deleted file mode 100644 index def20448bd9..00000000000 --- a/spec/services/self_monitoring/project/create_service_spec.rb +++ /dev/null @@ -1,234 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe SelfMonitoring::Project::CreateService do - describe '#execute' do - let(:result) { subject.execute } - - let(:prometheus_settings) do - OpenStruct.new( - enable: true, - listen_address: 'localhost:9090' - ) - end - - before do - allow(Gitlab.config).to receive(:prometheus).and_return(prometheus_settings) - end - - context 'without admin users' do - it 'returns error' do - expect(subject).to receive(:log_error).and_call_original - expect(result).to eq( - status: :error, - message: 'No active admin user found', - failed_step: :validate_admins - ) - end - end - - context 'with admin users' do - let(:project) { result[:project] } - let(:group) { result[:group] } - let(:application_setting) { Gitlab::CurrentSettings.current_application_settings } - - let!(:user) { create(:user, :admin) } - - before do - application_setting.allow_local_requests_from_web_hooks_and_services = true - end - - shared_examples 'has prometheus service' do |listen_address| - it do - expect(result[:status]).to eq(:success) - - prometheus = project.prometheus_service - expect(prometheus).not_to eq(nil) - expect(prometheus.api_url).to eq(listen_address) - expect(prometheus.active).to eq(true) - expect(prometheus.manual_configuration).to eq(true) - end - end - - it_behaves_like 'has prometheus service', 'http://localhost:9090' - - it 'creates group' do - expect(result[:status]).to eq(:success) - expect(group).to be_persisted - expect(group.name).to eq(described_class::GROUP_NAME) - expect(group.path).to start_with(described_class::GROUP_PATH) - expect(group.path.split('-').last.length).to eq(8) - expect(group.visibility_level).to eq(described_class::VISIBILITY_LEVEL) - end - - it 'creates project with internal visibility' do - expect(result[:status]).to eq(:success) - expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) - expect(project).to be_persisted - end - - it 'creates project with internal visibility even when internal visibility is restricted' do - application_setting.restricted_visibility_levels = [Gitlab::VisibilityLevel::INTERNAL] - - expect(result[:status]).to eq(:success) - expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) - expect(project).to be_persisted - end - - it 'creates project with correct name and description' do - expect(result[:status]).to eq(:success) - expect(project.name).to eq(described_class::PROJECT_NAME) - expect(project.description).to eq(described_class::PROJECT_DESCRIPTION) - end - - it 'adds all admins as maintainers' do - admin1 = create(:user, :admin) - admin2 = create(:user, :admin) - create(:user) - - expect(result[:status]).to eq(:success) - expect(project.owner).to eq(group) - expect(group.members.collect(&:user)).to contain_exactly(user, admin1, admin2) - expect(group.members.collect(&:access_level)).to contain_exactly( - Gitlab::Access::OWNER, - Gitlab::Access::MAINTAINER, - Gitlab::Access::MAINTAINER - ) - end - - it 'saves the project id' do - expect(result[:status]).to eq(:success) - expect(application_setting.instance_administration_project_id).to eq(project.id) - end - - it 'returns error when saving project ID fails' do - allow(application_setting).to receive(:update) { false } - - expect(result[:status]).to eq(:error) - expect(result[:failed_step]).to eq(:save_project_id) - expect(result[:message]).to eq('Could not save project ID') - end - - it 'does not fail when a project already exists' do - expect(result[:status]).to eq(:success) - - second_result = subject.execute - - expect(second_result[:status]).to eq(:success) - expect(second_result[:project]).to eq(project) - expect(second_result[:group]).to eq(group) - end - - context 'when local requests from hooks and services are not allowed' do - before do - application_setting.allow_local_requests_from_web_hooks_and_services = false - end - - it_behaves_like 'has prometheus service', 'http://localhost:9090' - - it 'does not overwrite the existing whitelist' do - application_setting.outbound_local_requests_whitelist = ['example.com'] - - expect(result[:status]).to eq(:success) - expect(application_setting.outbound_local_requests_whitelist).to contain_exactly( - 'example.com', 'localhost' - ) - end - end - - context 'with non default prometheus address' do - before do - prometheus_settings.listen_address = 'https://localhost:9090' - end - - it_behaves_like 'has prometheus service', 'https://localhost:9090' - end - - context 'when prometheus setting is not present in gitlab.yml' do - before do - allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting) - end - - it 'does not fail' do - expect(result).to include(status: :success) - expect(project.prometheus_service).to be_nil - end - end - - context 'when prometheus setting is disabled in gitlab.yml' do - before do - prometheus_settings.enable = false - end - - it 'does not configure prometheus' do - expect(result).to include(status: :success) - expect(project.prometheus_service).to be_nil - end - end - - context 'when prometheus listen address is blank in gitlab.yml' do - before do - prometheus_settings.listen_address = '' - end - - it 'does not configure prometheus' do - expect(result).to include(status: :success) - expect(project.prometheus_service).to be_nil - end - end - - context 'when project cannot be created' do - let(:project) { build(:project) } - - before do - project.errors.add(:base, "Test error") - - expect_next_instance_of(::Projects::CreateService) do |project_create_service| - expect(project_create_service).to receive(:execute) - .and_return(project) - end - end - - it 'returns error' do - expect(subject).to receive(:log_error).and_call_original - expect(result).to eq({ - status: :error, - message: 'Could not create project', - failed_step: :create_project - }) - end - end - - context 'when user cannot be added to project' do - before do - subject.instance_variable_set(:@instance_admins, [user, build(:user, :admin)]) - end - - it 'returns error' do - expect(subject).to receive(:log_error).and_call_original - expect(result).to eq({ - status: :error, - message: 'Could not add admins as members', - failed_step: :add_group_members - }) - end - end - - context 'when prometheus manual configuration cannot be saved' do - before do - prometheus_settings.listen_address = 'httpinvalid://localhost:9090' - end - - it 'returns error' do - expect(subject).to receive(:log_error).and_call_original - expect(result).to eq( - status: :error, - message: 'Could not save prometheus manual configuration', - failed_step: :add_prometheus_manual_configuration - ) - end - end - end - end -end diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb index c8b2bf040e6..dec7898d8d2 100644 --- a/spec/support/helpers/stub_configuration.rb +++ b/spec/support/helpers/stub_configuration.rb @@ -30,6 +30,10 @@ module StubConfiguration allow(Gitlab.config.gitlab).to receive_messages(to_settings(messages)) end + def stub_config(messages) + allow(Gitlab.config).to receive_messages(to_settings(messages)) + end + def stub_default_url_options(host: "localhost", protocol: "http") url_options = { host: host, protocol: protocol } allow(Rails.application.routes).to receive(:default_url_options).and_return(url_options) -- cgit v1.2.1