diff options
| author | Fabio Pitino <fpitino@gitlab.com> | 2019-08-09 11:40:45 +0200 |
|---|---|---|
| committer | Fabio Pitino <fpitino@gitlab.com> | 2019-09-05 15:53:48 +0100 |
| commit | ca6a1f33f91a8cceadebfb9c4e9ac6afa340f71d (patch) | |
| tree | bd9ddad975384be7c022eea49a248ce78ee76445 /app/models | |
| parent | 273ba34c9e85269c6f7653727caafc49fa151fd1 (diff) | |
| download | gitlab-ce-ce-detect-github-pull-requests.tar.gz | |
CE port for pipelines for external pull requestsce-detect-github-pull-requests
Detect if pipeline runs for a GitHub pull request
When using a mirror for CI/CD only we register a pull_request
webhook. When a pull_request webhook is received, if the
source branch SHA matches the actual head of the branch in the
repository we create immediately a new pipeline for the
external pull request. Otherwise we store the
pull request info for when the push webhook is received.
When using "only/except: external_pull_requests" we can detect
if the pipeline has a open pull request on GitHub and create or
not the job based on that.
Diffstat (limited to 'app/models')
| -rw-r--r-- | app/models/ci/pipeline.rb | 10 | ||||
| -rw-r--r-- | app/models/ci/pipeline_enums.rb | 3 | ||||
| -rw-r--r-- | app/models/external_pull_request.rb | 96 | ||||
| -rw-r--r-- | app/models/project.rb | 2 |
4 files changed, 110 insertions, 1 deletions
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 2b6f10ef79f..e34c6204742 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -23,6 +23,7 @@ module Ci belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline' belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule' belongs_to :merge_request, class_name: 'MergeRequest' + belongs_to :external_pull_request has_internal_id :iid, scope: :project, presence: false, init: ->(s) do s&.project&.all_pipelines&.maximum(:iid) || s&.project&.all_pipelines&.count @@ -64,6 +65,11 @@ module Ci validates :merge_request, presence: { if: :merge_request_event? } validates :merge_request, absence: { unless: :merge_request_event? } validates :tag, inclusion: { in: [false], if: :merge_request_event? } + + validates :external_pull_request, presence: { if: :external_pull_request_event? } + validates :external_pull_request, absence: { unless: :external_pull_request_event? } + validates :tag, inclusion: { in: [false], if: :external_pull_request_event? } + validates :status, presence: { unless: :importing? } validate :valid_commit_sha, unless: :importing? validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create @@ -675,6 +681,10 @@ module Ci variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s) variables.concat(merge_request.predefined_variables) end + + if external_pull_request_event? && external_pull_request + variables.concat(external_pull_request.predefined_variables) + end end end diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb index 571c4271475..0c2bd0aa8eb 100644 --- a/app/models/ci/pipeline_enums.rb +++ b/app/models/ci/pipeline_enums.rb @@ -23,7 +23,8 @@ module Ci api: 5, external: 6, chat: 8, - merge_request_event: 10 + merge_request_event: 10, + external_pull_request_event: 11 } end diff --git a/app/models/external_pull_request.rb b/app/models/external_pull_request.rb new file mode 100644 index 00000000000..65ae8d95500 --- /dev/null +++ b/app/models/external_pull_request.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +# This model stores pull requests coming from external providers, such as +# GitHub, when GitLab project is set as CI/CD only and remote mirror. +# +# When setting up a remote mirror with GitHub we subscribe to push and +# pull_request webhook events. When a pull request is opened on GitHub, +# a webhook is sent out, we create or update the status of the pull +# request locally. +# +# When the mirror is updated and changes are pushed to branches we check +# if there are open pull requests for the source and target branch. +# If so, we create pipelines for external pull requests. +class ExternalPullRequest < ApplicationRecord + include Gitlab::Utils::StrongMemoize + include ShaAttribute + + belongs_to :project + + sha_attribute :source_sha + sha_attribute :target_sha + + validates :source_branch, presence: true + validates :target_branch, presence: true + validates :source_sha, presence: true + validates :target_sha, presence: true + validates :source_repository, presence: true + validates :target_repository, presence: true + validates :status, presence: true + + enum status: { + open: 1, + closed: 2 + } + + # We currently don't support pull requests from fork, so + # we are going to return an error to the webhook + validate :not_from_fork + + scope :by_source_branch, ->(branch) { where(source_branch: branch) } + scope :by_source_repository, -> (repository) { where(source_repository: repository) } + + def self.create_or_update_from_params(params) + find_params = params.slice(:project_id, :source_branch, :target_branch) + + safe_find_or_initialize_and_update(find: find_params, update: params) do |pull_request| + yield(pull_request) if block_given? + end + end + + def actual_branch_head? + actual_source_branch_sha == source_sha + end + + def from_fork? + source_repository != target_repository + end + + def source_ref + Gitlab::Git::BRANCH_REF_PREFIX + source_branch + end + + def predefined_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_IID', value: pull_request_iid.to_s) + variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_SHA', value: source_sha) + variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_SHA', value: target_sha) + variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_NAME', value: source_branch) + variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME', value: target_branch) + end + end + + private + + def actual_source_branch_sha + project.commit(source_ref)&.sha + end + + def not_from_fork + if from_fork? + errors.add(:base, 'Pull requests from fork are not supported') + end + end + + def self.safe_find_or_initialize_and_update(find:, update:) + safe_ensure_unique(retries: 1) do + model = find_or_initialize_by(find) + + if model.update(update) + yield(model) if block_given? + end + + model + end + end +end diff --git a/app/models/project.rb b/app/models/project.rb index 17b52d0578e..d948410e397 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -291,6 +291,8 @@ class Project < ApplicationRecord has_many :remote_mirrors, inverse_of: :project has_many :cycle_analytics_stages, class_name: 'Analytics::CycleAnalytics::ProjectStage' + has_many :external_pull_requests, inverse_of: :project + accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :project_feature, update_only: true accepts_nested_attributes_for :import_data |
