Commit a3566e71 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent b5de70e680e5
......@@ -52,9 +52,15 @@ class Pipeline < ApplicationRecord
has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id'
has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id'
has_many :sourced_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :source_pipeline_id
has_one :source_pipeline, class_name: 'Ci::Sources::Pipeline', inverse_of: :pipeline
has_one :chat_data, class_name: 'Ci::PipelineChatData'
has_many :triggered_pipelines, through: :sourced_pipelines, source: :pipeline
has_one :triggered_by_pipeline, through: :source_pipeline, source: :source_pipeline
has_one :source_job, through: :source_pipeline, source: :source_job
accepts_nested_attributes_for :variables, reject_if: :persisted?
delegate :id, to: :project, prefix: true
......
......@@ -22,6 +22,7 @@ def self.sources
schedule: 4,
api: 5,
external: 6,
pipeline: 7,
chat: 8,
merge_request_event: 10,
external_pull_request_event: 11
......
# frozen_string_literal: true
module Ci
module Sources
class Pipeline < ApplicationRecord
self.table_name = "ci_sources_pipelines"
belongs_to :project, class_name: "Project"
belongs_to :pipeline, class_name: "Ci::Pipeline", inverse_of: :source_pipeline
belongs_to :source_project, class_name: "Project", foreign_key: :source_project_id
belongs_to :source_job, class_name: "CommitStatus", foreign_key: :source_job_id
belongs_to :source_pipeline, class_name: "Ci::Pipeline", foreign_key: :source_pipeline_id
validates :project, presence: true
validates :pipeline, presence: true
validates :source_project, presence: true
validates :source_job, presence: true
validates :source_pipeline, presence: true
end
end
end
::Ci::Sources::Pipeline.prepend_if_ee('::EE::Ci::Sources::Pipeline')
......@@ -259,6 +259,10 @@ def has_maintainer?(user)
members_with_parents.maintainers.exists?(user_id: user)
end
def has_container_repositories?
container_repositories.exists?
end
# @deprecated
alias_method :has_master?, :has_maintainer?
......
......@@ -297,6 +297,9 @@ class Project < ApplicationRecord
has_many :external_pull_requests, inverse_of: :project
has_many :sourced_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :source_project_id
has_many :source_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :project_id
has_one :pages_metadatum, class_name: 'ProjectPagesMetadatum', inverse_of: :project
accepts_nested_attributes_for :variables, allow_destroy: true
......
......@@ -10,6 +10,7 @@ class PipelineDetailsEntity < PipelineEntity
expose :manual_actions, using: BuildActionEntity
expose :scheduled_actions, using: BuildActionEntity
end
end
PipelineDetailsEntity.prepend_if_ee('EE::PipelineDetailsEntity')
expose :triggered_by_pipeline, as: :triggered_by, with: TriggeredPipelineEntity
expose :triggered_pipelines, as: :triggered, using: TriggeredPipelineEntity
end
......@@ -54,9 +54,9 @@ def preloaded_relations
artifacts: {
project: [:route, { namespace: :route }]
}
}
},
{ triggered_by_pipeline: [:project, :user] },
{ triggered_pipelines: [:project, :user] }
]
end
end
PipelineSerializer.prepend_if_ee('EE::PipelineSerializer')
# frozen_string_literal: true
class TriggeredPipelineEntity < Grape::Entity
include RequestAwareEntity
MAX_EXPAND_DEPTH = 3
expose :id
expose :user, using: UserEntity
expose :active?, as: :active
expose :coverage
expose :source
expose :path do |pipeline|
project_pipeline_path(pipeline.project, pipeline)
end
expose :details do
expose :detailed_status, as: :status, with: DetailedStatusEntity
expose :ordered_stages,
as: :stages, using: StageEntity,
if: -> (_, opts) { can_read_details? && expand?(opts) }
end
expose :triggered_by_pipeline,
as: :triggered_by, with: TriggeredPipelineEntity,
if: -> (_, opts) { can_read_details? && expand_for_path?(opts) }
expose :triggered_pipelines,
as: :triggered, using: TriggeredPipelineEntity,
if: -> (_, opts) { can_read_details? && expand_for_path?(opts) }
expose :project, using: ProjectEntity
private
alias_method :pipeline, :object
def can_read_details?
can?(request.current_user, :read_pipeline, pipeline)
end
def detailed_status
pipeline.detailed_status(request.current_user)
end
def expand?(opts)
opts[:expanded].to_a.include?(pipeline.id)
end
def expand_for_path?(opts)
# The `opts[:attr_path]` holds a list of all `exposes` in path
# The check ensures that we always expand only `triggered_by`, `triggered_by`, ...
# but not the `triggered_by`, `triggered` which would result in dead loop
attr_path = opts[:attr_path]
current_expose = attr_path.last
# We expand at most to depth of MAX_DEPTH
# We ensure that we expand in one direction: triggered_by,... or triggered, ...
attr_path.length < MAX_EXPAND_DEPTH &&
attr_path.all?(current_expose) &&
expand?(opts)
end
end
......@@ -38,11 +38,34 @@ def trigger_from_token
end
def create_pipeline_from_job(job)
# overridden in EE
# this check is to not leak the presence of the project if user cannot read it
return unless can?(job.user, :read_project, project)
return error("400 Job has to be running", 400) unless job.running?
pipeline = Ci::CreatePipelineService.new(project, job.user, ref: params[:ref])
.execute(:pipeline, ignore_skip_ci: true) do |pipeline|
source = job.sourced_pipelines.build(
source_pipeline: job.pipeline,
source_project: job.project,
pipeline: pipeline,
project: project)
pipeline.source_pipeline = source
pipeline.variables.build(variables)
end
if pipeline.persisted?
success(pipeline: pipeline)
else
error(pipeline.errors.messages, 400)
end
end
def job_from_token
# overridden in EE
strong_memoize(:job) do
Ci::Build.find_by_token(params[:token].to_s)
end
end
def variables
......@@ -52,5 +75,3 @@ def variables
end
end
end
Ci::PipelineTriggerService.prepend_if_ee('EE::Ci::PipelineTriggerService')
......@@ -7,7 +7,8 @@ class TransferService < Groups::BaseService
namespace_with_same_path: s_('TransferGroup|The parent group already has a subgroup with the same path.'),
group_is_already_root: s_('TransferGroup|Group is already a root group.'),
same_parent_as_current: s_('TransferGroup|Group is already associated to the parent group.'),
invalid_policies: s_("TransferGroup|You don't have enough permissions.")
invalid_policies: s_("TransferGroup|You don't have enough permissions."),
group_contains_images: s_('TransferGroup|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again.')
}.freeze
TransferError = Class.new(StandardError)
......@@ -46,6 +47,7 @@ def ensure_allowed_transfer
raise_transfer_error(:same_parent_as_current) if same_parent?
raise_transfer_error(:invalid_policies) unless valid_policies?
raise_transfer_error(:namespace_with_same_path) if namespace_with_same_path?
raise_transfer_error(:group_contains_images) if group_projects_contain_registry_images?
end
def group_is_already_root?
......@@ -72,6 +74,10 @@ def namespace_with_same_path?
end
# rubocop: enable CodeReuse/ActiveRecord
def group_projects_contain_registry_images?
@group.has_container_repositories?
end
def update_group_attributes
if @new_parent_group && @new_parent_group.visibility_level < @group.visibility_level
update_children_and_projects_visibility
......
......@@ -8,6 +8,11 @@ def execute
reject_parent_id!
remove_unallowed_params
if renaming_group_with_container_registry_images?
group.errors.add(:base, container_images_error)
return false
end
return false unless valid_visibility_level_change?(group, params[:visibility_level])
return false unless valid_share_with_group_lock_change?
......@@ -35,6 +40,17 @@ def before_assignment_hook(group, params)
# overridden in EE
end
def renaming_group_with_container_registry_images?
new_path = params[:path]
new_path && new_path != group.path &&
group.has_container_repositories?
end
def container_images_error
s_("GroupSettings|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again.")
end
def after_update
if group.previous_changes.include?(:visibility_level) && group.private?
# don't enqueue immediately to prevent todos removal in case of a mistake
......
---
title: Prevents a group path change when a project inside the group has container
registry images
merge_request: 17583
author:
type: fixed
---
title: Allow cross-project pipeline triggering with CI_JOB_TOKEN in core
merge_request: 17251
author:
type: added
......@@ -34,7 +34,9 @@ def start
@mutex.synchronize do
break thread if thread?
@thread = Thread.new { start_working }
if start_working
@thread = Thread.new { run_thread }
end
end
end
......@@ -57,10 +59,18 @@ def stop
private
# Executed in lock context before starting thread
# Needs to return success
def start_working
true
end
# Executed in separate thread
def run_thread
raise NotImplementedError
end
# Executed in lock context
def stop_working
# no-ops
end
......
# frozen_string_literal: true
require 'net/http'
require 'json'
module Gitlab
module Danger
module RequestHelper
HTTPError = Class.new(RuntimeError)
# @param [String] url
def self.http_get_json(url)
rsp = Net::HTTP.get_response(URI.parse(url))
unless rsp.is_a?(Net::HTTPOK)
raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}"
end
JSON.parse(rsp.body)
end
end
end
end
# frozen_string_literal: true
require 'net/http'
require 'json'
require 'cgi'
require_relative 'teammate'
module Gitlab
module Danger
module Roulette
ROULETTE_DATA_URL = 'https://about.gitlab.com/roulette.json'
HTTPError = Class.new(RuntimeError)
# Looks up the current list of GitLab team members and parses it into a
# useful form
......@@ -19,7 +14,7 @@ module Roulette
def team
@team ||=
begin
data = http_get_json(ROULETTE_DATA_URL)
data = Gitlab::Danger::RequestHelper.http_get_json(ROULETTE_DATA_URL)
data.map { |hash| ::Gitlab::Danger::Teammate.new(hash) }
rescue JSON::ParserError
raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
......@@ -44,6 +39,7 @@ def new_random(seed)
# Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
# selection will change on next spin
# @param [Array<Teammate>] people
def spin_for_person(people, random:)
people.shuffle(random: random)
.find(&method(:valid_person?))
......@@ -51,32 +47,17 @@ def spin_for_person(people, random:)
private
# @param [Teammate] person
# @return [Boolean]
def valid_person?(person)
!mr_author?(person) && !out_of_office?(person)
!mr_author?(person) && !person.out_of_office?
end
# @param [Teammate] person
# @return [Boolean]
def mr_author?(person)
person.username == gitlab.mr_author
end
def out_of_office?(person)
username = CGI.escape(person.username)
api_endpoint = "https://gitlab.com/api/v4/users/#{username}/status"
response = http_get_json(api_endpoint)
response["message"]&.match?(/OOO/i)
rescue HTTPError, JSON::ParserError
false # this is no worse than not checking for OOO
end
def http_get_json(url)
rsp = Net::HTTP.get_response(URI.parse(url))
unless rsp.is_a?(Net::HTTPSuccess)
raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}"
end
JSON.parse(rsp.body)
end
end
end
end
# frozen_string_literal: true
require 'cgi'
module Gitlab
module Danger
class Teammate
......@@ -34,6 +36,18 @@ def maintainer?(project, category, labels)
has_capability?(project, category, :maintainer, labels)
end
def status
api_endpoint = "https://gitlab.com/api/v4/users/#{CGI.escape(username)}/status"
@status ||= Gitlab::Danger::RequestHelper.http_get_json(api_endpoint)
rescue Gitlab::Danger::RequestHelper::HTTPError, JSON::ParserError
nil # better no status than a crashing Danger
end
# @return [Boolean]
def out_of_office?
status&.dig("message")&.match?(/OOO/i) || false
end
private
def has_capability?(project, category, kind, labels)
......
......@@ -40,7 +40,14 @@ def start_working
::Gitlab::HealthChecks::Probes::Liveness.new, req, res)
end
server.mount '/', Rack::Handler::WEBrick, rack_app
server.start
true
end
def run_thread
server&.start
rescue IOError
# ignore forcibily closed servers
end
def stop_working
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment