diff options
author | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2017-09-06 14:29:17 +0200 |
---|---|---|
committer | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2017-09-06 14:29:17 +0200 |
commit | 3b9f9aa00bc0c3afb65d803c3f7071fa7a113628 (patch) | |
tree | ffd17afd5d11304fa561831ce409fba7c4a0aa45 /lib | |
parent | deaa7f54e016b6ae1051c38abb95586451f470c1 (diff) | |
parent | d1b60cbc67dc14b21820ef3f823a8e1ea851697d (diff) | |
download | gitlab-ce-3b9f9aa00bc0c3afb65d803c3f7071fa7a113628.tar.gz |
Merge commit 'd1b60cbc67dc14b21820ef3f823a8e1ea851697d' into feature/gb/download-single-job-artifact-using-api
* commit 'd1b60cbc67dc14b21820ef3f823a8e1ea851697d': (210 commits)
Diffstat (limited to 'lib')
33 files changed, 1020 insertions, 220 deletions
diff --git a/lib/api/branches.rb b/lib/api/branches.rb index a989394ad91..642c1140fcc 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -24,17 +24,22 @@ module API present paginate(branches), with: Entities::RepoBranch, project: user_project end - desc 'Get a single branch' do - success Entities::RepoBranch - end - params do - requires :branch, type: String, desc: 'The name of the branch' - end - get ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do - branch = user_project.repository.find_branch(params[:branch]) - not_found!("Branch") unless branch + resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do + desc 'Get a single branch' do + success Entities::RepoBranch + end + params do + requires :branch, type: String, desc: 'The name of the branch' + end + head do + user_project.repository.branch_exists?(params[:branch]) ? status(204) : status(404) + end + get do + branch = user_project.repository.find_branch(params[:branch]) + not_found!('Branch') unless branch - present branch, with: Entities::RepoBranch, project: user_project + present branch, with: Entities::RepoBranch, project: user_project + end end # Note: This API will be deprecated in favor of the protected branches API. diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 6314ea63197..829eef18795 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -103,7 +103,7 @@ module API when 'success' status.success! when 'failed' - status.drop! + status.drop!(:api_failure) when 'canceled' status.cancel! else diff --git a/lib/api/entities.rb b/lib/api/entities.rb index f13f2d723bb..031dd02c6eb 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -491,6 +491,10 @@ module API expose :user, using: Entities::UserPublic end + class GPGKey < Grape::Entity + expose :id, :key, :created_at + end + class Note < Grape::Entity # Only Issue and MergeRequest have iid NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze @@ -819,7 +823,7 @@ module API class Variable < Grape::Entity expose :key, :value - expose :protected?, as: :protected + expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) } end class Pipeline < PipelineBasic @@ -840,6 +844,7 @@ module API class PipelineScheduleDetails < PipelineSchedule expose :last_pipeline, using: Entities::PipelineBasic + expose :variables, using: Entities::Variable end class EnvironmentBasic < Grape::Entity diff --git a/lib/api/issues.rb b/lib/api/issues.rb index e4c2c390853..1729df2aad0 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -36,6 +36,7 @@ module API optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID' optional :scope, type: String, values: %w[created-by-me assigned-to-me all], desc: 'Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`' + optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' use :pagination end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 7bcbf9f20ff..56d72d511da 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -40,6 +40,7 @@ module API optional :assignee_id, type: Integer, desc: 'Return merge requests which are assigned to the user with the given ID' optional :scope, type: String, values: %w[created-by-me assigned-to-me all], desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`' + optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' use :pagination end end diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb index ef01cbc7875..37f32411296 100644 --- a/lib/api/pipeline_schedules.rb +++ b/lib/api/pipeline_schedules.rb @@ -31,10 +31,6 @@ module API requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' end get ':id/pipeline_schedules/:pipeline_schedule_id' do - authorize! :read_pipeline_schedule, user_project - - not_found!('PipelineSchedule') unless pipeline_schedule - present pipeline_schedule, with: Entities::PipelineScheduleDetails end @@ -74,9 +70,6 @@ module API optional :active, type: Boolean, desc: 'The activation of pipeline schedule' end put ':id/pipeline_schedules/:pipeline_schedule_id' do - authorize! :read_pipeline_schedule, user_project - - not_found!('PipelineSchedule') unless pipeline_schedule authorize! :update_pipeline_schedule, pipeline_schedule if pipeline_schedule.update(declared_params(include_missing: false)) @@ -93,9 +86,6 @@ module API requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' end post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do - authorize! :read_pipeline_schedule, user_project - - not_found!('PipelineSchedule') unless pipeline_schedule authorize! :update_pipeline_schedule, pipeline_schedule if pipeline_schedule.own!(current_user) @@ -112,21 +102,84 @@ module API requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' end delete ':id/pipeline_schedules/:pipeline_schedule_id' do - authorize! :read_pipeline_schedule, user_project - - not_found!('PipelineSchedule') unless pipeline_schedule authorize! :admin_pipeline_schedule, pipeline_schedule destroy_conditionally!(pipeline_schedule) end + + desc 'Create a new pipeline schedule variable' do + success Entities::Variable + end + params do + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' + requires :key, type: String, desc: 'The key of the variable' + requires :value, type: String, desc: 'The value of the variable' + end + post ':id/pipeline_schedules/:pipeline_schedule_id/variables' do + authorize! :update_pipeline_schedule, pipeline_schedule + + variable_params = declared_params(include_missing: false) + variable = pipeline_schedule.variables.create(variable_params) + if variable.persisted? + present variable, with: Entities::Variable + else + render_validation_error!(variable) + end + end + + desc 'Edit a pipeline schedule variable' do + success Entities::Variable + end + params do + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' + requires :key, type: String, desc: 'The key of the variable' + optional :value, type: String, desc: 'The value of the variable' + end + put ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do + authorize! :update_pipeline_schedule, pipeline_schedule + + if pipeline_schedule_variable.update(declared_params(include_missing: false)) + present pipeline_schedule_variable, with: Entities::Variable + else + render_validation_error!(pipeline_schedule_variable) + end + end + + desc 'Delete a pipeline schedule variable' do + success Entities::Variable + end + params do + requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id' + requires :key, type: String, desc: 'The key of the variable' + end + delete ':id/pipeline_schedules/:pipeline_schedule_id/variables/:key' do + authorize! :admin_pipeline_schedule, pipeline_schedule + + status :accepted + present pipeline_schedule_variable.destroy, with: Entities::Variable + end end helpers do def pipeline_schedule @pipeline_schedule ||= - user_project.pipeline_schedules - .preload(:owner, :last_pipeline) - .find_by(id: params.delete(:pipeline_schedule_id)) + user_project + .pipeline_schedules + .preload(:owner, :last_pipeline) + .find_by(id: params.delete(:pipeline_schedule_id)).tap do |pipeline_schedule| + unless can?(current_user, :read_pipeline_schedule, pipeline_schedule) + not_found!('Pipeline Schedule') + end + end + end + + def pipeline_schedule_variable + @pipeline_schedule_variable ||= + pipeline_schedule.variables.find_by(key: params[:key]).tap do |pipeline_schedule_variable| + unless pipeline_schedule_variable + not_found!('Pipeline Schedule Variable') + end + end end end end diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 11999354594..a3987c560dd 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -114,6 +114,8 @@ module API requires :id, type: Integer, desc: %q(Job's ID) optional :trace, type: String, desc: %q(Job's full trace) optional :state, type: String, desc: %q(Job's status: success, failed) + optional :failure_reason, type: String, values: CommitStatus.failure_reasons.keys, + desc: %q(Job's failure_reason) end put '/:id' do job = authenticate_job! @@ -127,7 +129,7 @@ module API when 'success' job.success when 'failed' - job.drop + job.drop(params[:failure_reason] || :unknown_failure) end end diff --git a/lib/api/users.rb b/lib/api/users.rb index 96f47bb618a..1825c90a23b 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -233,6 +233,86 @@ module API destroy_conditionally!(key) end + desc 'Add a GPG key to a specified user. Available only for admins.' do + detail 'This feature was added in GitLab 10.0' + success Entities::GPGKey + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + requires :key, type: String, desc: 'The new GPG key' + end + post ':id/gpg_keys' do + authenticated_as_admin! + + user = User.find_by(id: params.delete(:id)) + not_found!('User') unless user + + key = user.gpg_keys.new(declared_params(include_missing: false)) + + if key.save + present key, with: Entities::GPGKey + else + render_validation_error!(key) + end + end + + desc 'Get the GPG keys of a specified user. Available only for admins.' do + detail 'This feature was added in GitLab 10.0' + success Entities::GPGKey + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + use :pagination + end + get ':id/gpg_keys' do + authenticated_as_admin! + + user = User.find_by(id: params[:id]) + not_found!('User') unless user + + present paginate(user.gpg_keys), with: Entities::GPGKey + end + + desc 'Delete an existing GPG key from a specified user. Available only for admins.' do + detail 'This feature was added in GitLab 10.0' + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + requires :key_id, type: Integer, desc: 'The ID of the GPG key' + end + delete ':id/gpg_keys/:key_id' do + authenticated_as_admin! + + user = User.find_by(id: params[:id]) + not_found!('User') unless user + + key = user.gpg_keys.find_by(id: params[:key_id]) + not_found!('GPG Key') unless key + + status 204 + key.destroy + end + + desc 'Revokes an existing GPG key from a specified user. Available only for admins.' do + detail 'This feature was added in GitLab 10.0' + end + params do + requires :id, type: Integer, desc: 'The ID of the user' + requires :key_id, type: Integer, desc: 'The ID of the GPG key' + end + post ':id/gpg_keys/:key_id/revoke' do + authenticated_as_admin! + + user = User.find_by(id: params[:id]) + not_found!('User') unless user + + key = user.gpg_keys.find_by(id: params[:key_id]) + not_found!('GPG Key') unless key + + key.revoke + status :accepted + end + desc 'Add an email address to a specified user. Available only for admins.' do success Entities::Email end @@ -492,6 +572,76 @@ module API destroy_conditionally!(key) end + desc "Get the currently authenticated user's GPG keys" do + detail 'This feature was added in GitLab 10.0' + success Entities::GPGKey + end + params do + use :pagination + end + get 'gpg_keys' do + present paginate(current_user.gpg_keys), with: Entities::GPGKey + end + + desc 'Get a single GPG key owned by currently authenticated user' do + detail 'This feature was added in GitLab 10.0' + success Entities::GPGKey + end + params do + requires :key_id, type: Integer, desc: 'The ID of the GPG key' + end + get 'gpg_keys/:key_id' do + key = current_user.gpg_keys.find_by(id: params[:key_id]) + not_found!('GPG Key') unless key + + present key, with: Entities::GPGKey + end + + desc 'Add a new GPG key to the currently authenticated user' do + detail 'This feature was added in GitLab 10.0' + success Entities::GPGKey + end + params do + requires :key, type: String, desc: 'The new GPG key' + end + post 'gpg_keys' do + key = current_user.gpg_keys.new(declared_params) + + if key.save + present key, with: Entities::GPGKey + else + render_validation_error!(key) + end + end + + desc 'Revoke a GPG key owned by currently authenticated user' do + detail 'This feature was added in GitLab 10.0' + end + params do + requires :key_id, type: Integer, desc: 'The ID of the GPG key' + end + post 'gpg_keys/:key_id/revoke' do + key = current_user.gpg_keys.find_by(id: params[:key_id]) + not_found!('GPG Key') unless key + + key.revoke + status :accepted + end + + desc 'Delete a GPG key from the currently authenticated user' do + detail 'This feature was added in GitLab 10.0' + end + params do + requires :key_id, type: Integer, desc: 'The ID of the SSH key' + end + delete 'gpg_keys/:key_id' do + key = current_user.gpg_keys.find_by(id: params[:key_id]) + not_found!('GPG Key') unless key + + status 204 + key.destroy + end + desc "Get the currently authenticated user's email addresses" do success Entities::Email end diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb index e9d4c35307b..534911fde5c 100644 --- a/lib/api/v3/triggers.rb +++ b/lib/api/v3/triggers.rb @@ -16,25 +16,31 @@ module API optional :variables, type: Hash, desc: 'The list of variables to be injected into build' end post ":id/(ref/:ref/)trigger/builds", requirements: { ref: /.+/ } do - project = find_project(params[:id]) - trigger = Ci::Trigger.find_by_token(params[:token].to_s) - not_found! unless project && trigger - unauthorized! unless trigger.project == project - # validate variables - variables = params[:variables].to_h - unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) } + params[:variables] = params[:variables].to_h + unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) } render_api_error!('variables needs to be a map of key-valued strings', 400) end - # create request and trigger builds - result = Ci::CreateTriggerRequestService.execute(project, trigger, params[:ref].to_s, variables) - pipeline = result.pipeline + project = find_project(params[:id]) + not_found! unless project + + result = Ci::PipelineTriggerService.new(project, nil, params).execute + not_found! unless result - if pipeline.persisted? - present result.trigger_request, with: ::API::V3::Entities::TriggerRequest + if result[:http_status] + render_api_error!(result[:message], result[:http_status]) else - render_validation_error!(pipeline) + pipeline = result[:pipeline] + + # We switched to Ci::PipelineVariable from Ci::TriggerRequest.variables. + # Ci::TriggerRequest doesn't save variables anymore. + # Here is copying Ci::PipelineVariable to Ci::TriggerRequest.variables for presenting the variables. + # The same endpoint in v4 API pressents Pipeline instead of TriggerRequest, so it doesn't need such a process. + trigger_request = pipeline.trigger_requests.last + trigger_request.variables = params[:variables] + + present trigger_request, with: ::API::V3::Entities::TriggerRequest end end diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 3a4911b23b0..62b44389b15 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -20,24 +20,6 @@ module Ci raise ValidationError, e.message end - def jobs_for_ref(ref, tag = false, source = nil) - @jobs.select do |_, job| - process?(job[:only], job[:except], ref, tag, source) - end - end - - def jobs_for_stage_and_ref(stage, ref, tag = false, source = nil) - jobs_for_ref(ref, tag, source).select do |_, job| - job[:stage] == stage - end - end - - def builds_for_ref(ref, tag = false, source = nil) - jobs_for_ref(ref, tag, source).map do |name, _| - build_attributes(name) - end - end - def builds_for_stage_and_ref(stage, ref, tag = false, source = nil) jobs_for_stage_and_ref(stage, ref, tag, source).map do |name, _| build_attributes(name) @@ -52,8 +34,7 @@ module Ci def stage_seeds(pipeline) seeds = @stages.uniq.map do |stage| - builds = builds_for_stage_and_ref( - stage, pipeline.ref, pipeline.tag?, pipeline.source) + builds = pipeline_stage_builds(stage, pipeline) Gitlab::Ci::Stage::Seed.new(pipeline, stage, builds) if builds.any? end @@ -101,6 +82,34 @@ module Ci private + def pipeline_stage_builds(stage, pipeline) + builds = builds_for_stage_and_ref( + stage, pipeline.ref, pipeline.tag?, pipeline.source) + + builds.select do |build| + job = @jobs[build.fetch(:name).to_sym] + has_kubernetes = pipeline.has_kubernetes_active? + only_kubernetes = job.dig(:only, :kubernetes) + except_kubernetes = job.dig(:except, :kubernetes) + + [!only_kubernetes && !except_kubernetes, + only_kubernetes && has_kubernetes, + except_kubernetes && !has_kubernetes].any? + end + end + + def jobs_for_ref(ref, tag = false, source = nil) + @jobs.select do |_, job| + process?(job.dig(:only, :refs), job.dig(:except, :refs), ref, tag, source) + end + end + + def jobs_for_stage_and_ref(stage, ref, tag = false, source = nil) + jobs_for_ref(ref, tag, source).select do |_, job| + job[:stage] == stage + end + end + def initial_parsing ## # Global config diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb index 3cdae1cee4f..0027e9ec8c5 100644 --- a/lib/gitlab/ci/config/entry/policy.rb +++ b/lib/gitlab/ci/config/entry/policy.rb @@ -7,6 +7,7 @@ module Gitlab # class Policy < Simplifiable strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) } + strategy :ComplexPolicy, if: -> (config) { config.is_a?(Hash) } class RefsPolicy < Entry::Node include Entry::Validatable @@ -14,6 +15,27 @@ module Gitlab validations do validates :config, array_of_strings_or_regexps: true end + + def value + { refs: @config } + end + end + + class ComplexPolicy < Entry::Node + include Entry::Validatable + include Entry::Attributable + + attributes :refs, :kubernetes + + validations do + validates :config, presence: true + validates :config, allowed_keys: %i[refs kubernetes] + + with_options allow_nil: true do + validates :refs, array_of_strings_or_regexps: true + validates :kubernetes, allowed_values: %w[active] + end + end end class UnknownStrategy < Entry::Node diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb index b2ca3c881e4..0159179f0a9 100644 --- a/lib/gitlab/ci/config/entry/validators.rb +++ b/lib/gitlab/ci/config/entry/validators.rb @@ -14,6 +14,14 @@ module Gitlab end end + class AllowedValuesValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + unless options[:in].include?(value.to_s) + record.errors.add(attribute, "unknown value: #{value}") + end + end + end + class ArrayOfStringsValidator < ActiveModel::EachValidator include LegacyValidationHelpers diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index d671867e7c7..90f83e0f810 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -18,7 +18,7 @@ module Gitlab new(merge_request, project).tap do |file_collection| project .repository - .with_repo_branch_commit(merge_request.target_project.repository, merge_request.target_branch) do + .with_repo_branch_commit(merge_request.target_project.repository.raw_repository, merge_request.target_branch) do yield file_collection end diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index b6449f27034..8c9acbc9fbe 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -5,6 +5,7 @@ module Gitlab BRANCH_REF_PREFIX = "refs/heads/".freeze CommandError = Class.new(StandardError) + CommitError = Class.new(StandardError) class << self include Gitlab::EncodingHelper diff --git a/lib/gitlab/git/operation_service.rb b/lib/gitlab/git/operation_service.rb new file mode 100644 index 00000000000..9e6fca8c80c --- /dev/null +++ b/lib/gitlab/git/operation_service.rb @@ -0,0 +1,168 @@ +module Gitlab + module Git + class OperationService + attr_reader :committer, :repository + + def initialize(committer, new_repository) + committer = Gitlab::Git::Committer.from_user(committer) if committer.is_a?(User) + @committer = committer + + # Refactoring aid + unless new_repository.is_a?(Gitlab::Git::Repository) + raise "expected a Gitlab::Git::Repository, got #{new_repository}" + end + + @repository = new_repository + end + + def add_branch(branch_name, newrev) + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name + oldrev = Gitlab::Git::BLANK_SHA + + update_ref_in_hooks(ref, newrev, oldrev) + end + + def rm_branch(branch) + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch.name + oldrev = branch.target + newrev = Gitlab::Git::BLANK_SHA + + update_ref_in_hooks(ref, newrev, oldrev) + end + + def add_tag(tag_name, newrev, options = {}) + ref = Gitlab::Git::TAG_REF_PREFIX + tag_name + oldrev = Gitlab::Git::BLANK_SHA + + with_hooks(ref, newrev, oldrev) do |service| + # We want to pass the OID of the tag object to the hooks. For an + # annotated tag we don't know that OID until after the tag object + # (raw_tag) is created in the repository. That is why we have to + # update the value after creating the tag object. Only the + # "post-receive" hook will receive the correct value in this case. + raw_tag = repository.rugged.tags.create(tag_name, newrev, options) + service.newrev = raw_tag.target_id + end + end + + def rm_tag(tag) + ref = Gitlab::Git::TAG_REF_PREFIX + tag.name + oldrev = tag.target + newrev = Gitlab::Git::BLANK_SHA + + update_ref_in_hooks(ref, newrev, oldrev) do + repository.rugged.tags.delete(tag_name) + end + end + + # Whenever `start_branch_name` is passed, if `branch_name` doesn't exist, + # it would be created from `start_branch_name`. + # If `start_project` is passed, and the branch doesn't exist, + # it would try to find the commits from it instead of current repository. + def with_branch( + branch_name, + start_branch_name: nil, + start_repository: repository, + &block) + + # Refactoring aid + unless start_repository.is_a?(Gitlab::Git::Repository) + raise "expected a Gitlab::Git::Repository, got #{start_repository}" + end + + start_branch_name = nil if start_repository.empty_repo? + + if start_branch_name && !start_repository.branch_exists?(start_branch_name) + raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.full_path}" + end + + update_branch_with_hooks(branch_name) do + repository.with_repo_branch_commit( + start_repository, + start_branch_name || branch_name, + &block) + end + end + + private + + # Returns [newrev, should_run_after_create, should_run_after_create_branch] + def update_branch_with_hooks(branch_name) + update_autocrlf_option + + was_empty = repository.empty? + + # Make commit + newrev = yield + + unless newrev + raise Gitlab::Git::CommitError.new('Failed to create commit') + end + + branch = repository.find_branch(branch_name) + oldrev = find_oldrev_from_branch(newrev, branch) + + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name + update_ref_in_hooks(ref, newrev, oldrev) + + [newrev, was_empty, was_empty || Gitlab::Git.blank_ref?(oldrev)] + end + + def find_oldrev_from_branch(newrev, branch) + return Gitlab::Git::BLANK_SHA unless branch + + oldrev = branch.target + + if oldrev == repository.rugged.merge_base(newrev, branch.target) + oldrev + else + raise Gitlab::Git::CommitError.new('Branch diverged') + end + end + + def update_ref_in_hooks(ref, newrev, oldrev) + with_hooks(ref, newrev, oldrev) do + update_ref(ref, newrev, oldrev) + end + end + + def with_hooks(ref, newrev, oldrev) + Gitlab::Git::HooksService.new.execute( + committer, + repository, + oldrev, + newrev, + ref) do |service| + + yield(service) + end + end + + # Gitaly note: JV: wait with migrating #update_ref until we know how to migrate its call sites. + def update_ref(ref, newrev, oldrev) + # We use 'git update-ref' because libgit2/rugged currently does not + # offer 'compare and swap' ref updates. Without compare-and-swap we can + # (and have!) accidentally reset the ref to an earlier state, clobbering + # commits. See also https://github.com/libgit2/libgit2/issues/1534. + command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z] + _, status = Gitlab::Popen.popen( + command, + repository.path) do |stdin| + stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00") + end + + unless status.zero? + raise Gitlab::Git::CommitError.new( + "Could not update branch #{Gitlab::Git.branch_name(ref)}." \ + " Please refresh and try again.") + end + end + + def update_autocrlf_option + if repository.autocrlf != :input + repository.autocrlf = :input + end + end + end + end +end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 8709f82bcc4..75d4efc0bc5 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -73,6 +73,10 @@ module Gitlab delegate :exists?, to: :gitaly_repository_client + def ==(other) + path == other.path + end + # Default branch in the repository def root_ref @root_ref ||= gitaly_migrate(:root_ref) do |is_enabled| @@ -130,15 +134,19 @@ module Gitlab # This is to work around a bug in libgit2 that causes in-memory refs to # be stale/invalid when packed-refs is changed. # See https://gitlab.com/gitlab-org/gitlab-ce/issues/15392#note_14538333 - # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/474 def find_branch(name, force_reload = false) - reload_rugged if force_reload + gitaly_migrate(:find_branch) do |is_enabled| + if is_enabled + gitaly_ref_client.find_branch(name) + else + reload_rugged if force_reload - rugged_ref = rugged.branches[name] - if rugged_ref - target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target) - Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit) + rugged_ref = rugged.branches[name] + if rugged_ref + target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target) + Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit) + end + end end end @@ -601,6 +609,49 @@ module Gitlab # TODO: implement this method end + def add_branch(branch_name, committer:, target:) + target_object = Ref.dereference_object(lookup(target)) + raise InvalidRef.new("target not found: #{target}") unless target_object + + OperationService.new(committer, self).add_branch(branch_name, target_object.oid) + find_branch(branch_name) + rescue Rugged::ReferenceError => ex + raise InvalidRef, ex + end + + def add_tag(tag_name, committer:, target:, message: nil) + target_object = Ref.dereference_object(lookup(target)) + raise InvalidRef.new("target not found: #{target}") unless target_object + + committer = Committer.from_user(committer) if committer.is_a?(User) + + options = nil # Use nil, not the empty hash. Rugged cares about this. + if message + options = { + message: message, + tagger: Gitlab::Git.committer_hash(email: committer.email, name: committer.name) + } + end + + OperationService.new(committer, self).add_tag(tag_name, target_object.oid, options) + + find_tag(tag_name) + rescue Rugged::ReferenceError => ex + raise InvalidRef, ex + end + + def rm_branch(branch_name, committer:) + OperationService.new(committer, self).rm_branch(find_branch(branch_name)) + end + + def rm_tag(tag_name, committer:) + OperationService.new(committer, self).rm_tag(find_tag(tag_name)) + end + + def find_tag(name) + tags.find { |tag| tag.name == name } + end + # Delete the specified branch from the repository # # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/476 @@ -740,6 +791,106 @@ module Gitlab end end + def with_repo_branch_commit(start_repository, start_branch_name) + raise "expected Gitlab::Git::Repository, got #{start_repository}" unless start_repository.is_a?(Gitlab::Git::Repository) + + return yield nil if start_repository.empty_repo? + + if start_repository == self + yield commit(start_branch_name) + else + sha = start_repository.commit(start_branch_name).sha + + if branch_commit = commit(sha) + yield branch_commit + else + with_repo_tmp_commit( + start_repository, start_branch_name, sha) do |tmp_commit| + yield tmp_commit + end + end + end + end + + def with_repo_tmp_commit(start_repository, start_branch_name, sha) + tmp_ref = fetch_ref( + start_repository.path, + "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}", + "refs/tmp/#{SecureRandom.hex}/head" + ) + + yield commit(sha) + ensure + delete_refs(tmp_ref) if tmp_ref + end + + def fetch_source_branch(source_repository, source_branch, local_ref) + with_repo_branch_commit(source_repository, source_branch) do |commit| + if commit + write_ref(local_ref, commit.sha) + else + raise Rugged::ReferenceError, 'source repository is empty' + end + end + end + + def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:) + with_repo_branch_commit(source_repository, source_branch_name) do |commit| + break unless commit + + Gitlab::Git::Compare.new( + self, + target_branch_name, + commit.sha, + straight: straight + ) + end + end + + def write_ref(ref_path, sha) + rugged.references.create(ref_path, sha, force: true) + end + + def fetch_ref(source_path, source_ref, target_ref) + args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref}) + message, status = run_git(args) + + # Make sure ref was created, and raise Rugged::ReferenceError when not + raise Rugged::ReferenceError, message if status != 0 + + target_ref + end + + # Refactoring aid; allows us to copy code from app/models/repository.rb + def run_git(args) + circuit_breaker.perform do + popen([Gitlab.config.git.bin_path, *args], path) + end + end + + # Refactoring aid; allows us to copy code from app/models/repository.rb + def commit(ref = 'HEAD') + Gitlab::Git::Commit.find(self, ref) + end + + # Refactoring aid; allows us to copy code from app/models/repository.rb + def empty_repo? + !exists? || !has_visible_content? + end + + # + # Git repository can contains some hidden refs like: + # /refs/notes/* + # /refs/git-as-svn/* + # /refs/pulls/* + # This refs by default not visible in project page and not cloned to client side. + # + # This method return true if repository contains some content visible in project page. + # + def has_visible_content? + branch_count > 0 + end + def gitaly_repository Gitlab::GitalyClient::Util.repository(@storage, @relative_path) end diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb index 8c0008c6971..a1a25cf2079 100644 --- a/lib/gitlab/gitaly_client/ref_service.rb +++ b/lib/gitlab/gitaly_client/ref_service.rb @@ -78,6 +78,20 @@ module Gitlab raise ArgumentError, e.message end + def find_branch(branch_name) + request = Gitaly::DeleteBranchRequest.new( + repository: @gitaly_repo, + name: GitalyClient.encode(branch_name) + ) + + response = GitalyClient.call(@repository.storage, :ref_service, :find_branch, request) + branch = response.branch + return unless branch + + target_commit = Gitlab::Git::Commit.decorate(@repository, branch.target_commit) + Gitlab::Git::Branch.new(@repository, encode!(branch.name.dup), branch.target_commit.id, target_commit) + end + private def consume_refs_response(response) diff --git a/lib/gitlab/gpg.rb b/lib/gitlab/gpg.rb index 45e9f9d65ae..025f826e65f 100644 --- a/lib/gitlab/gpg.rb +++ b/lib/gitlab/gpg.rb @@ -39,7 +39,7 @@ module Gitlab fingerprints = CurrentKeyChain.fingerprints_from_key(key) GPGME::Key.find(:public, fingerprints).flat_map do |raw_key| - raw_key.uids.map { |uid| { name: uid.name, email: uid.email } } + raw_key.uids.map { |uid| { name: uid.name, email: uid.email.downcase } } end end end diff --git a/lib/gitlab/gpg/commit.rb b/lib/gitlab/gpg/commit.rb index 606c7576f70..86bd9f5b125 100644 --- a/lib/gitlab/gpg/commit.rb +++ b/lib/gitlab/gpg/commit.rb @@ -1,17 +1,12 @@ module Gitlab module Gpg class Commit - def self.for_commit(commit) - new(commit.project, commit.sha) - end - - def initialize(project, sha) - @project = project - @sha = sha + def initialize(commit) + @commit = commit @signature_text, @signed_text = begin - Rugged::Commit.extract_signature(project.repository.rugged, sha) + Rugged::Commit.extract_signature(@commit.project.repository.rugged, @commit.sha) rescue Rugged::OdbError nil end @@ -26,7 +21,7 @@ module Gitlab return @signature if @signature - cached_signature = GpgSignature.find_by(commit_sha: @sha) + cached_signature = GpgSignature.find_by(commit_sha: @commit.sha) return @signature = cached_signature if cached_signature.present? @signature = create_cached_signature! @@ -73,20 +68,31 @@ module Gitlab def attributes(gpg_key) user_infos = user_infos(gpg_key) + verification_status = verification_status(gpg_key) { - commit_sha: @sha, - project: @project, + commit_sha: @commit.sha, + project: @commit.project, gpg_key: gpg_key, gpg_key_primary_keyid: gpg_key&.primary_keyid || verified_signature.fingerprint, gpg_key_user_name: user_infos[:name], gpg_key_user_email: user_infos[:email], - valid_signature: gpg_signature_valid_signature_value(gpg_key) + verification_status: verification_status } end - def gpg_signature_valid_signature_value(gpg_key) - !!(gpg_key && gpg_key.verified? && verified_signature.valid?) + def verification_status(gpg_key) + return :unknown_key unless gpg_key + return :unverified_key unless gpg_key.verified? + return :unverified unless verified_signature.valid? + + if gpg_key.verified_and_belongs_to_email?(@commit.committer_email) + :verified + elsif gpg_key.user.all_emails.include?(@commit.committer_email) + :same_user_different_email + else + :other_user + end end def user_infos(gpg_key) diff --git a/lib/gitlab/gpg/invalid_gpg_signature_updater.rb b/lib/gitlab/gpg/invalid_gpg_signature_updater.rb index a525ee7a9ee..e085eab26c9 100644 --- a/lib/gitlab/gpg/invalid_gpg_signature_updater.rb +++ b/lib/gitlab/gpg/invalid_gpg_signature_updater.rb @@ -8,7 +8,7 @@ module Gitlab def run GpgSignature .select(:id, :commit_sha, :project_id) - .where('gpg_key_id IS NULL OR valid_signature = ?', false) + .where('gpg_key_id IS NULL OR verification_status <> ?', GpgSignature.verification_statuses[:verified]) .where(gpg_key_primary_keyid: @gpg_key.primary_keyid) .find_each { |sig| sig.gpg_commit.update_signature!(sig) } end diff --git a/lib/gitlab/i18n/po_linter.rb b/lib/gitlab/i18n/po_linter.rb index 2e02787a4f4..7d3ff8c7f58 100644 --- a/lib/gitlab/i18n/po_linter.rb +++ b/lib/gitlab/i18n/po_linter.rb @@ -1,5 +1,3 @@ -require 'simple_po_parser' - module Gitlab module I18n class PoLinter diff --git a/lib/gitlab/issuables_count_for_state.rb b/lib/gitlab/issuables_count_for_state.rb new file mode 100644 index 00000000000..505810964bc --- /dev/null +++ b/lib/gitlab/issuables_count_for_state.rb @@ -0,0 +1,50 @@ +module Gitlab + # Class for counting and caching the number of issuables per state. + class IssuablesCountForState + # The name of the RequestStore cache key. + CACHE_KEY = :issuables_count_for_state + + # The state values that can be safely casted to a Symbol. + STATES = %w[opened closed merged all].freeze + + # finder - The finder class to use for retrieving the issuables. + def initialize(finder) + @finder = finder + @cache = + if RequestStore.active? + RequestStore[CACHE_KEY] ||= initialize_cache + else + initialize_cache + end + end + + def for_state_or_opened(state = nil) + self[state || :opened] + end + + # Returns the count for the given state. + # + # state - The name of the state as either a String or a Symbol. + # + # Returns an Integer. + def [](state) + state = state.to_sym if cast_state_to_symbol?(state) + + cache_for_finder[state] || 0 + end + + private + + def cache_for_finder + @cache[@finder] + end + + def cast_state_to_symbol?(state) + state.is_a?(String) && STATES.include?(state) + end + + def initialize_cache + Hash.new { |hash, finder| hash[finder] = finder.count_by_state } + end + end +end diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb index b42bc67ccfc..7c2d1d8f887 100644 --- a/lib/gitlab/sql/pattern.rb +++ b/lib/gitlab/sql/pattern.rb @@ -4,6 +4,7 @@ module Gitlab extend ActiveSupport::Concern MIN_CHARS_FOR_PARTIAL_MATCHING = 3 + REGEX_QUOTED_WORD = /(?<=^| )"[^"]+"(?= |$)/ class_methods do def to_pattern(query) @@ -17,6 +18,28 @@ module Gitlab def partial_matching?(query) query.length >= MIN_CHARS_FOR_PARTIAL_MATCHING end + + def to_fuzzy_arel(column, query) + words = select_fuzzy_words(query) + + matches = words.map { |word| arel_table[column].matches(to_pattern(word)) } + + matches.reduce { |result, match| result.and(match) } + end + + def select_fuzzy_words(query) + quoted_words = query.scan(REGEX_QUOTED_WORD) + + query = quoted_words.reduce(query) { |q, quoted_word| q.sub(quoted_word, '') } + + words = query.split(/\s+/) + + quoted_words.map! { |quoted_word| quoted_word[1..-2] } + + words.concat(quoted_words) + + words.select { |word| partial_matching?(word) } + end end end end diff --git a/lib/system_check/app/git_user_default_ssh_config_check.rb b/lib/system_check/app/git_user_default_ssh_config_check.rb new file mode 100644 index 00000000000..7b486d78cf0 --- /dev/null +++ b/lib/system_check/app/git_user_default_ssh_config_check.rb @@ -0,0 +1,69 @@ +module SystemCheck + module App + class GitUserDefaultSSHConfigCheck < SystemCheck::BaseCheck + # These files are allowed in the .ssh directory. The `config` file is not + # whitelisted as it may change the SSH client's behaviour dramatically. + WHITELIST = %w[ + authorized_keys + authorized_keys2 + known_hosts + ].freeze + + set_name 'Git user has default SSH configuration?' + set_skip_reason 'skipped (git user is not present or configured)' + + def skip? + !home_dir || !File.directory?(home_dir) + end + + def check? + forbidden_files.empty? + end + + def show_error + backup_dir = "~/gitlab-check-backup-#{Time.now.to_i}" + + instructions = forbidden_files.map do |filename| + "sudo mv #{Shellwords.escape(filename)} #{backup_dir}" + end + + try_fixing_it("mkdir #{backup_dir}", *instructions) + for_more_information('doc/ssh/README.md in section "SSH on the GitLab server"') + fix_and_rerun + end + + private + + def git_user + Gitlab.config.gitlab.user + end + + def home_dir + return @home_dir if defined?(@home_dir) + + @home_dir = + begin + File.expand_path("~#{git_user}") + rescue ArgumentError + nil + end + end + + def ssh_dir + return nil unless home_dir + + File.join(home_dir, '.ssh') + end + + def forbidden_files + @forbidden_files ||= + begin + present = Dir[File.join(ssh_dir, '*')] + whitelisted = WHITELIST.map { |basename| File.join(ssh_dir, basename) } + + present - whitelisted + end + end + end + end +end diff --git a/lib/system_check/app/init_script_up_to_date_check.rb b/lib/system_check/app/init_script_up_to_date_check.rb index 015c7ed1731..53a47eb0f42 100644 --- a/lib/system_check/app/init_script_up_to_date_check.rb +++ b/lib/system_check/app/init_script_up_to_date_check.rb @@ -7,26 +7,22 @@ module SystemCheck set_skip_reason 'skipped (omnibus-gitlab has no init script)' def skip? - omnibus_gitlab? - end + return true if omnibus_gitlab? - def multi_check - recipe_path = Rails.root.join('lib/support/init.d/', 'gitlab') + unless init_file_exists? + self.skip_reason = "can't check because of previous errors" - unless File.exist?(SCRIPT_PATH) - $stdout.puts "can't check because of previous errors".color(:magenta) - return + true end + end + + def check? + recipe_path = Rails.root.join('lib/support/init.d/', 'gitlab') recipe_content = File.read(recipe_path) script_content = File.read(SCRIPT_PATH) - if recipe_content == script_content - $stdout.puts 'yes'.color(:green) - else - $stdout.puts 'no'.color(:red) - show_error - end + recipe_content == script_content end def show_error @@ -38,6 +34,12 @@ module SystemCheck ) fix_and_rerun end + + private + + def init_file_exists? + File.exist?(SCRIPT_PATH) + end end end end diff --git a/lib/system_check/base_check.rb b/lib/system_check/base_check.rb index 7f9e2ffffc2..0f5742dd67f 100644 --- a/lib/system_check/base_check.rb +++ b/lib/system_check/base_check.rb @@ -62,6 +62,25 @@ module SystemCheck call_or_return(@skip_reason) || 'skipped' end + # Define a reason why we skipped the SystemCheck (during runtime) + # + # This is used when you need dynamic evaluation like when you have + # multiple reasons why a check can fail + # + # @param [String] reason to be displayed + def skip_reason=(reason) + @skip_reason = reason + end + + # Skip reason defined during runtime + # + # This value have precedence over the one defined in the subclass + # + # @return [String] the reason + def skip_reason + @skip_reason + end + # Does the check support automatically repair routine? # # @return [Boolean] whether check implemented `#repair!` method or not diff --git a/lib/system_check/incoming_email/foreman_configured_check.rb b/lib/system_check/incoming_email/foreman_configured_check.rb new file mode 100644 index 00000000000..1db7bf2b782 --- /dev/null +++ b/lib/system_check/incoming_email/foreman_configured_check.rb @@ -0,0 +1,23 @@ +module SystemCheck + module IncomingEmail + class ForemanConfiguredCheck < SystemCheck::BaseCheck + set_name 'Foreman configured correctly?' + + def check? + path = Rails.root.join('Procfile') + + File.exist?(path) && File.read(path) =~ /^mail_room:/ + end + + def show_error + try_fixing_it( + 'Enable mail_room in your Procfile.' + ) + for_more_information( + 'doc/administration/reply_by_email.md' + ) + fix_and_rerun + end + end + end +end diff --git a/lib/system_check/incoming_email/imap_authentication_check.rb b/lib/system_check/incoming_email/imap_authentication_check.rb new file mode 100644 index 00000000000..dee108d987b --- /dev/null +++ b/lib/system_check/incoming_email/imap_authentication_check.rb @@ -0,0 +1,45 @@ +module SystemCheck + module IncomingEmail + class ImapAuthenticationCheck < SystemCheck::BaseCheck + set_name 'IMAP server credentials are correct?' + + def check? + if mailbox_config + begin + imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl]) + imap.starttls if config[:start_tls] + imap.login(config[:email], config[:password]) + connected = true + rescue + connected = false + end + end + + connected + end + + def show_error + try_fixing_it( + 'Check that the information in config/gitlab.yml is correct' + ) + for_more_information( + 'doc/administration/reply_by_email.md' + ) + fix_and_rerun + end + + private + + def mailbox_config + return @config if @config + + config_path = Rails.root.join('config', 'mail_room.yml').to_s + erb = ERB.new(File.read(config_path)) + erb.filename = config_path + config_file = YAML.load(erb.result) + + @config = config_file[:mailboxes]&.first + end + end + end +end diff --git a/lib/system_check/incoming_email/initd_configured_check.rb b/lib/system_check/incoming_email/initd_configured_check.rb new file mode 100644 index 00000000000..ea23b8ef49c --- /dev/null +++ b/lib/system_check/incoming_email/initd_configured_check.rb @@ -0,0 +1,32 @@ +module SystemCheck + module IncomingEmail + class InitdConfiguredCheck < SystemCheck::BaseCheck + set_name 'Init.d configured correctly?' + + def skip? + omnibus_gitlab? + end + + def check? + mail_room_configured? + end + + def show_error + try_fixing_it( + 'Enable mail_room in the init.d configuration.' + ) + for_more_information( + 'doc/administration/reply_by_email.md' + ) + fix_and_rerun + end + + private + + def mail_room_configured? + path = '/etc/default/gitlab' + File.exist?(path) && File.read(path).include?('mail_room_enabled=true') + end + end + end +end diff --git a/lib/system_check/incoming_email/mail_room_running_check.rb b/lib/system_check/incoming_email/mail_room_running_check.rb new file mode 100644 index 00000000000..c1807501829 --- /dev/null +++ b/lib/system_check/incoming_email/mail_room_running_check.rb @@ -0,0 +1,43 @@ +module SystemCheck + module IncomingEmail + class MailRoomRunningCheck < SystemCheck::BaseCheck + set_name 'MailRoom running?' + + def skip? + return true if omnibus_gitlab? + + unless mail_room_configured? + self.skip_reason = "can't check because of previous errors" + true + end + end + + def check? + mail_room_running? + end + + def show_error + try_fixing_it( + sudo_gitlab('RAILS_ENV=production bin/mail_room start') + ) + for_more_information( + see_installation_guide_section('Install Init Script'), + 'see log/mail_room.log for possible errors' + ) + fix_and_rerun + end + + private + + def mail_room_configured? + path = '/etc/default/gitlab' + File.exist?(path) && File.read(path).include?('mail_room_enabled=true') + end + + def mail_room_running? + ps_ux, _ = Gitlab::Popen.popen(%w(ps uxww)) + ps_ux.include?("mail_room") + end + end + end +end diff --git a/lib/system_check/simple_executor.rb b/lib/system_check/simple_executor.rb index 6604b1078cf..00221f77cf4 100644 --- a/lib/system_check/simple_executor.rb +++ b/lib/system_check/simple_executor.rb @@ -23,7 +23,7 @@ module SystemCheck # # @param [BaseCheck] check class def <<(check) - raise ArgumentError unless check < BaseCheck + raise ArgumentError unless check.is_a?(Class) && check < BaseCheck @checks << check end @@ -48,7 +48,7 @@ module SystemCheck # When implements skip method, we run it first, and if true, skip the check if check.can_skip? && check.skip? - $stdout.puts check_klass.skip_reason.color(:magenta) + $stdout.puts check.skip_reason.try(:color, :magenta) || check_klass.skip_reason.color(:magenta) return end diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake index e1491f29b5e..35ba729c156 100644 --- a/lib/tasks/gettext.rake +++ b/lib/tasks/gettext.rake @@ -22,6 +22,8 @@ namespace :gettext do desc 'Lint all po files in `locale/' task lint: :environment do + require 'simple_po_parser' + FastGettext.silence_errors files = Dir.glob(Rails.root.join('locale/*/gitlab.po')) diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 1bd36bbe20a..654f638c454 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -33,6 +33,7 @@ namespace :gitlab do SystemCheck::App::RedisVersionCheck, SystemCheck::App::RubyVersionCheck, SystemCheck::App::GitVersionCheck, + SystemCheck::App::GitUserDefaultSSHConfigCheck, SystemCheck::App::ActiveUsersCheck ] @@ -308,133 +309,24 @@ namespace :gitlab do desc "GitLab | Check the configuration of Reply by email" task check: :environment do warn_user_is_not_gitlab - start_checking "Reply by email" if Gitlab.config.incoming_email.enabled - check_imap_authentication + checks = [ + SystemCheck::IncomingEmail::ImapAuthenticationCheck + ] if Rails.env.production? - check_initd_configured_correctly - check_mail_room_running + checks << SystemCheck::IncomingEmail::InitdConfiguredCheck + checks << SystemCheck::IncomingEmail::MailRoomRunningCheck else - check_foreman_configured_correctly + checks << SystemCheck::IncomingEmail::ForemanConfiguredCheck end - else - puts 'Reply by email is disabled in config/gitlab.yml' - end - - finished_checking "Reply by email" - end - - # Checks - ######################## - - def check_initd_configured_correctly - return if omnibus_gitlab? - - print "Init.d configured correctly? ... " - - path = "/etc/default/gitlab" - - if File.exist?(path) && File.read(path).include?("mail_room_enabled=true") - puts "yes".color(:green) - else - puts "no".color(:red) - try_fixing_it( - "Enable mail_room in the init.d configuration." - ) - for_more_information( - "doc/administration/reply_by_email.md" - ) - fix_and_rerun - end - end - - def check_foreman_configured_correctly - print "Foreman configured correctly? ... " - path = Rails.root.join("Procfile") - - if File.exist?(path) && File.read(path) =~ /^mail_room:/ - puts "yes".color(:green) + SystemCheck.run('Reply by email', checks) else - puts "no".color(:red) - try_fixing_it( - "Enable mail_room in your Procfile." - ) - for_more_information( - "doc/administration/reply_by_email.md" - ) - fix_and_rerun - end - end - - def check_mail_room_running - return if omnibus_gitlab? - - print "MailRoom running? ... " - - path = "/etc/default/gitlab" - - unless File.exist?(path) && File.read(path).include?("mail_room_enabled=true") - puts "can't check because of previous errors".color(:magenta) - return - end - - if mail_room_running? - puts "yes".color(:green) - else - puts "no".color(:red) - try_fixing_it( - sudo_gitlab("RAILS_ENV=production bin/mail_room start") - ) - for_more_information( - see_installation_guide_section("Install Init Script"), - "see log/mail_room.log for possible errors" - ) - fix_and_rerun - end - end - - def check_imap_authentication - print "IMAP server credentials are correct? ... " - - config_path = Rails.root.join('config', 'mail_room.yml').to_s - erb = ERB.new(File.read(config_path)) - erb.filename = config_path - config_file = YAML.load(erb.result) - - config = config_file[:mailboxes].first - - if config - begin - imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl]) - imap.starttls if config[:start_tls] - imap.login(config[:email], config[:password]) - connected = true - rescue - connected = false - end - end - - if connected - puts "yes".color(:green) - else - puts "no".color(:red) - try_fixing_it( - "Check that the information in config/gitlab.yml is correct" - ) - for_more_information( - "doc/administration/reply_by_email.md" - ) - fix_and_rerun + puts 'Reply by email is disabled in config/gitlab.yml' end end - - def mail_room_running? - ps_ux, _ = Gitlab::Popen.popen(%w(ps uxww)) - ps_ux.include?("mail_room") - end end namespace :ldap do |