summaryrefslogtreecommitdiff
path: root/lib/gitlab/database_importers/self_monitoring/project/create_service.rb
blob: 3a170e8b5f87a2f94e5de7d5f12cd1a46f8fe50d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# frozen_string_literal: true

module Gitlab
  module DatabaseImporters
    module SelfMonitoring
      module Project
        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 if Gitlab.config.prometheus
          rescue Settingslogic::MissingSetting
            log_error(_('prometheus.enable is not present in gitlab.yml'))

            false
          end

          def prometheus_listen_address
            Gitlab.config.prometheus.listen_address if Gitlab.config.prometheus
          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