diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-20 17:22:11 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-20 17:22:11 +0300 |
commit | 0c872e02b2c822e3397515ec324051ff540f0cd5 (patch) | |
tree | ce2fb6ce7030e4dad0f4118d21ab6453e5938cdd /app/models/ci | |
parent | f7e05a6853b12f02911494c4b3fe53d9540d74fc (diff) |
Add latest changes from gitlab-org/gitlab@15-7-stable-eev15.7.0-rc42
Diffstat (limited to 'app/models/ci')
28 files changed, 295 insertions, 119 deletions
diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb index d6051d70503..662fb3cffa8 100644 --- a/app/models/ci/bridge.rb +++ b/app/models/ci/bridge.rb @@ -18,8 +18,11 @@ module Ci belongs_to :project belongs_to :trigger_request + + # To be removed upon :ci_bridge_remove_sourced_pipelines feature flag removal has_many :sourced_pipelines, class_name: "::Ci::Sources::Pipeline", - foreign_key: :source_job_id + foreign_key: :source_job_id, + inverse_of: :source_bridge has_one :downstream_pipeline, through: :sourced_pipeline, source: :pipeline @@ -86,8 +89,20 @@ module Ci end end + def sourced_pipelines + if Feature.enabled?(:ci_bridge_remove_sourced_pipelines, project) + raise 'Ci::Bridge does not have sourced_pipelines association' + end + + super + end + def has_downstream_pipeline? - sourced_pipelines.exists? + if Feature.enabled?(:ci_bridge_remove_sourced_pipelines, project) + sourced_pipeline.present? + else + sourced_pipelines.exists? + end end def downstream_pipeline_params diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index f44ba124fe2..7f42b21bc87 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -7,7 +7,6 @@ module Ci include Ci::Contextable include TokenAuthenticatable include AfterCommitQueue - include ObjectStorage::BackgroundMove include Presentable include Importable include Ci::HasRef @@ -47,7 +46,7 @@ module Ci # DELETE queries when the Ci::Build is destroyed. The next step is to remove `dependent: :destroy`. # Details: https://gitlab.com/gitlab-org/gitlab/-/issues/24644#note_689472685 has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy, inverse_of: :job # rubocop:disable Cop/ActiveRecordDependent - has_many :job_variables, class_name: 'Ci::JobVariable', foreign_key: :job_id + has_many :job_variables, class_name: 'Ci::JobVariable', foreign_key: :job_id, inverse_of: :job has_many :sourced_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :source_job_id has_many :pages_deployments, inverse_of: :ci_build @@ -71,6 +70,7 @@ module Ci delegate :harbor_integration, to: :project delegate :trigger_short_token, to: :trigger_request, allow_nil: true delegate :ensure_persistent_ref, to: :pipeline + delegate :enable_debug_trace!, to: :metadata serialize :options # rubocop:disable Cop/ActiveRecordSerialize serialize :yaml_variables, Gitlab::Serializer::Ci::Variables # rubocop:disable Cop/ActiveRecordSerialize @@ -90,7 +90,7 @@ module Ci scope :with_downloadable_artifacts, -> do where('EXISTS (?)', Ci::JobArtifact.select(1) - .where('ci_builds.id = ci_job_artifacts.job_id') + .where("#{Ci::Build.quoted_table_name}.id = #{Ci::JobArtifact.quoted_table_name}.job_id") .where(file_type: Ci::JobArtifact::DOWNLOADABLE_TYPES) ) end @@ -98,7 +98,7 @@ module Ci scope :with_erasable_artifacts, -> do where('EXISTS (?)', Ci::JobArtifact.select(1) - .where('ci_builds.id = ci_job_artifacts.job_id') + .where("#{Ci::Build.quoted_table_name}.id = #{Ci::JobArtifact.quoted_table_name}.job_id") .where(file_type: Ci::JobArtifact.erasable_file_types) ) end @@ -108,11 +108,11 @@ module Ci end scope :with_existing_job_artifacts, ->(query) do - where('EXISTS (?)', ::Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').merge(query)) + where('EXISTS (?)', ::Ci::JobArtifact.select(1).where("#{Ci::Build.quoted_table_name}.id = #{Ci::JobArtifact.quoted_table_name}.job_id").merge(query)) end scope :without_archived_trace, -> do - where('NOT EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace) + where('NOT EXISTS (?)', Ci::JobArtifact.select(1).where("#{Ci::Build.quoted_table_name}.id = #{Ci::JobArtifact.quoted_table_name}.job_id").trace) end scope :with_artifacts, ->(artifact_scope) do @@ -155,7 +155,7 @@ module Ci scope :manual_actions, -> { where(when: :manual, status: COMPLETED_STATUSES + %i[manual]) } scope :scheduled_actions, -> { where(when: :delayed, status: COMPLETED_STATUSES + %i[scheduled]) } scope :ref_protected, -> { where(protected: true) } - scope :with_live_trace, -> { where('EXISTS (?)', Ci::BuildTraceChunk.where('ci_builds.id = ci_build_trace_chunks.build_id').select(1)) } + scope :with_live_trace, -> { where('EXISTS (?)', Ci::BuildTraceChunk.where("#{quoted_table_name}.id = #{Ci::BuildTraceChunk.quoted_table_name}.build_id").select(1)) } scope :with_stale_live_trace, -> { with_live_trace.finished_before(12.hours.ago) } scope :finished_before, -> (date) { finished.where('finished_at < ?', date) } scope :license_management_jobs, -> { where(name: %i(license_management license_scanning)) } # handle license rename https://gitlab.com/gitlab-org/gitlab/issues/8911 @@ -172,8 +172,6 @@ module Ci add_authentication_token_field :token, encrypted: :required - before_save :ensure_token, unless: :assign_token_on_scheduling? - after_save :stick_build_if_status_changed after_create unless: :importing? do |build| @@ -247,11 +245,8 @@ module Ci !build.waiting_for_deployment_approval? # If false is returned, it stops the transition end - before_transition any => [:pending] do |build, transition| - if build.assign_token_on_scheduling? - build.ensure_token - end - + before_transition any => [:pending] do |build| + build.ensure_token true end @@ -419,12 +414,12 @@ module Ci end def waiting_for_deployment_approval? - manual? && starts_environment? && deployment&.blocked? + manual? && deployment_job? && deployment&.blocked? end def outdated_deployment? strong_memoize(:outdated_deployment) do - starts_environment? && + deployment_job? && incomplete? && project.ci_forward_deployment_enabled? && deployment&.older_than_last_successful_deployment? @@ -528,7 +523,7 @@ module Ci environment.present? end - def starts_environment? + def deployment_job? has_environment_keyword? && self.environment_action == 'start' end @@ -722,7 +717,7 @@ module Ci end def ensure_trace_metadata! - Ci::BuildTraceMetadata.find_or_upsert_for!(id) + Ci::BuildTraceMetadata.find_or_upsert_for!(id, partition_id) end def artifacts_expose_as @@ -866,6 +861,10 @@ module Ci Gitlab::Ci::Build::Step.from_after_script(self)].compact end + def runtime_hooks + Gitlab::Ci::Build::Hook.from_hooks(self) + end + def image Gitlab::Ci::Build::Image.from_image(self) end @@ -995,7 +994,7 @@ module Ci # Virtual deployment status depending on the environment status. def deployment_status - return unless starts_environment? + return unless deployment_job? if success? return successful_deployment_status @@ -1136,8 +1135,15 @@ module Ci end end - def assign_token_on_scheduling? - ::Feature.enabled?(:ci_assign_job_token_on_scheduling, project) + def partition_id_token_prefix + partition_id.to_s(16) if Feature.enabled?(:ci_build_partition_id_token_prefix, project) + end + + override :format_token + def format_token(token) + return token if partition_id_token_prefix.nil? + + "#{partition_id_token_prefix}_#{token}" end protected @@ -1208,11 +1214,11 @@ module Ci if project.ci_cd_settings.opt_in_jwt? id_tokens_variables else - legacy_jwt_variables.concat(id_tokens_variables) + predefined_jwt_variables.concat(id_tokens_variables) end end - def legacy_jwt_variables + def predefined_jwt_variables Gitlab::Ci::Variables::Collection.new.tap do |variables| jwt = Gitlab::Ci::Jwt.for_build(self) jwt_v2 = Gitlab::Ci::JwtV2.for_build(self) @@ -1229,7 +1235,7 @@ module Ci Gitlab::Ci::Variables::Collection.new.tap do |variables| id_tokens.each do |var_name, token_data| - token = Gitlab::Ci::JwtV2.for_build(self, aud: token_data['id_token']['aud']) + token = Gitlab::Ci::JwtV2.for_build(self, aud: token_data['aud']) variables.append(key: var_name, value: token, public: false, masked: true) end diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index 2f28509f812..9b4794abb2e 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -5,21 +5,16 @@ module Ci # Data that should be persisted forever, should be stored with Ci::Build model. class BuildMetadata < Ci::ApplicationRecord BuildTimeout = Struct.new(:value, :source) - ROUTING_FEATURE_FLAG = :ci_partitioning_use_ci_builds_metadata_routing_table include Ci::Partitionable include Presentable include ChronicDurationAttribute include Gitlab::Utils::StrongMemoize - self.table_name = 'ci_builds_metadata' + self.table_name = 'p_ci_builds_metadata' self.primary_key = 'id' - self.sequence_name = 'ci_builds_metadata_id_seq' - partitionable scope: :build, through: { - table: :p_ci_builds_metadata, - flag: ROUTING_FEATURE_FLAG - } + partitionable scope: :build belongs_to :build, class_name: 'CommitStatus' belongs_to :project @@ -63,6 +58,12 @@ module Ci runtime_runner_features[:cancel_gracefully] == true end + def enable_debug_trace! + self.debug_trace_enabled = true + save! if changes.any? + true + end + private def set_build_project diff --git a/app/models/ci/build_need.rb b/app/models/ci/build_need.rb index d4cbbfac4ab..3fa17d6d286 100644 --- a/app/models/ci/build_need.rb +++ b/app/models/ci/build_need.rb @@ -2,15 +2,18 @@ module Ci class BuildNeed < Ci::ApplicationRecord + include Ci::Partitionable include BulkInsertSafe belongs_to :build, class_name: "Ci::Processable", foreign_key: :build_id, inverse_of: :needs + partitionable scope: :build + validates :build, presence: true validates :name, presence: true, length: { maximum: 128 } validates :optional, inclusion: { in: [true, false] } - scope :scoped_build, -> { where('ci_builds.id=ci_build_needs.build_id') } + scope :scoped_build, -> { where("#{Ci::Build.quoted_table_name}.id = #{quoted_table_name}.build_id") } scope :artifacts, -> { where(artifacts: true) } end end diff --git a/app/models/ci/build_pending_state.rb b/app/models/ci/build_pending_state.rb index 53cf0697e2e..3684dac06c7 100644 --- a/app/models/ci/build_pending_state.rb +++ b/app/models/ci/build_pending_state.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true class Ci::BuildPendingState < Ci::ApplicationRecord + include Ci::Partitionable + belongs_to :build, class_name: 'Ci::Build', foreign_key: :build_id + partitionable scope: :build + enum state: Ci::Stage.statuses enum failure_reason: CommitStatus.failure_reasons diff --git a/app/models/ci/build_report_result.rb b/app/models/ci/build_report_result.rb index b674c1b1a0e..b2d99fab295 100644 --- a/app/models/ci/build_report_result.rb +++ b/app/models/ci/build_report_result.rb @@ -2,11 +2,15 @@ module Ci class BuildReportResult < Ci::ApplicationRecord + include Ci::Partitionable + self.primary_key = :build_id belongs_to :build, class_name: "Ci::Build", inverse_of: :report_results belongs_to :project, class_name: "Project", inverse_of: :build_report_results + partitionable scope: :build + validates :build, :project, presence: true validates :data, json_schema: { filename: "build_report_result_data" } diff --git a/app/models/ci/build_runner_session.rb b/app/models/ci/build_runner_session.rb index 0f37ce70964..20c0b04e228 100644 --- a/app/models/ci/build_runner_session.rb +++ b/app/models/ci/build_runner_session.rb @@ -4,6 +4,8 @@ module Ci # The purpose of this class is to store Build related runner session. # Data will be removed after transitioning from running to any state. class BuildRunnerSession < Ci::ApplicationRecord + include Ci::Partitionable + TERMINAL_SUBPROTOCOL = 'terminal.gitlab.com' DEFAULT_SERVICE_NAME = 'build' DEFAULT_PORT_NAME = 'default_port' @@ -12,6 +14,8 @@ module Ci belongs_to :build, class_name: 'Ci::Build', inverse_of: :runner_session + partitionable scope: :build + validates :build, presence: true validates :url, public_url: { schemes: %w(https) } diff --git a/app/models/ci/build_trace_chunk.rb b/app/models/ci/build_trace_chunk.rb index 7baa98b59f9..57d8b9ba368 100644 --- a/app/models/ci/build_trace_chunk.rb +++ b/app/models/ci/build_trace_chunk.rb @@ -2,6 +2,7 @@ module Ci class BuildTraceChunk < Ci::ApplicationRecord + include Ci::Partitionable include ::Comparable include ::FastDestroyAll include ::Checksummable @@ -10,6 +11,8 @@ module Ci belongs_to :build, class_name: "Ci::Build", foreign_key: :build_id + partitionable scope: :build + attribute :data_store, default: :redis_trace_chunks after_create { metrics.increment_trace_operation(operation: :chunked) } @@ -28,8 +31,8 @@ module Ci redis_trace_chunks: 4 }.freeze - STORE_TYPES = DATA_STORES.keys.to_h do |store| - [store, "Ci::BuildTraceChunks::#{store.to_s.camelize}".constantize] + STORE_TYPES = DATA_STORES.keys.index_with do |store| + "Ci::BuildTraceChunks::#{store.to_s.camelize}".constantize end.freeze LIVE_STORES = %i[redis redis_trace_chunks].freeze diff --git a/app/models/ci/build_trace_metadata.rb b/app/models/ci/build_trace_metadata.rb index 86de90983ff..00cf1531483 100644 --- a/app/models/ci/build_trace_metadata.rb +++ b/app/models/ci/build_trace_metadata.rb @@ -2,6 +2,8 @@ module Ci class BuildTraceMetadata < Ci::ApplicationRecord + include Ci::Partitionable + MAX_ATTEMPTS = 5 self.table_name = 'ci_build_trace_metadata' self.primary_key = :build_id @@ -9,15 +11,17 @@ module Ci belongs_to :build, class_name: 'Ci::Build' belongs_to :trace_artifact, class_name: 'Ci::JobArtifact' + partitionable scope: :build + validates :build, presence: true validates :archival_attempts, presence: true - def self.find_or_upsert_for!(build_id) - record = find_by(build_id: build_id) + def self.find_or_upsert_for!(build_id, partition_id) + record = find_by(build_id: build_id, partition_id: partition_id) return record if record - upsert({ build_id: build_id }, unique_by: :build_id) - find_by!(build_id: build_id) + upsert({ build_id: build_id, partition_id: partition_id }, unique_by: :build_id) + find_by!(build_id: build_id, partition_id: partition_id) end # The job is retried around 5 times during the 7 days retention period for diff --git a/app/models/ci/freeze_period.rb b/app/models/ci/freeze_period.rb index da0bbbacddd..1bf32e04a15 100644 --- a/app/models/ci/freeze_period.rb +++ b/app/models/ci/freeze_period.rb @@ -4,6 +4,10 @@ module Ci class FreezePeriod < Ci::ApplicationRecord include StripAttribute include Ci::NamespacedModelName + include Gitlab::Utils::StrongMemoize + + STATUS_ACTIVE = :active + STATUS_INACTIVE = :inactive default_scope { order(created_at: :asc) } # rubocop:disable Cop/DefaultScope @@ -14,5 +18,60 @@ module Ci validates :freeze_start, cron: true, presence: true validates :freeze_end, cron: true, presence: true validates :cron_timezone, cron_freeze_period_timezone: true, presence: true + + def active? + status == STATUS_ACTIVE + end + + def status + Gitlab::SafeRequestStore.fetch("ci:freeze_period:#{id}:status") do + within_freeze_period? ? STATUS_ACTIVE : STATUS_INACTIVE + end + end + + def time_start + Gitlab::SafeRequestStore.fetch("ci:freeze_period:#{id}:time_start") do + freeze_start_parsed_cron.previous_time_from(time_zone_now) + end + end + + def next_time_start + Gitlab::SafeRequestStore.fetch("ci:freeze_period:#{id}:next_time_start") do + freeze_start_parsed_cron.next_time_from(time_zone_now) + end + end + + def time_end_from_now + Gitlab::SafeRequestStore.fetch("ci:freeze_period:#{id}:time_end_from_now") do + freeze_end_parsed_cron.next_time_from(time_zone_now) + end + end + + def time_end_from_start + Gitlab::SafeRequestStore.fetch("ci:freeze_period:#{id}:time_end_from_start") do + freeze_end_parsed_cron.next_time_from(time_start) + end + end + + private + + def within_freeze_period? + time_start <= time_zone_now && time_zone_now <= time_end_from_start + end + + def freeze_start_parsed_cron + Gitlab::Ci::CronParser.new(freeze_start, cron_timezone) + end + strong_memoize_attr :freeze_start_parsed_cron + + def freeze_end_parsed_cron + Gitlab::Ci::CronParser.new(freeze_end, cron_timezone) + end + strong_memoize_attr :freeze_end_parsed_cron + + def time_zone_now + Time.zone.now + end + strong_memoize_attr :time_zone_now end end diff --git a/app/models/ci/freeze_period_status.rb b/app/models/ci/freeze_period_status.rb deleted file mode 100644 index e810bb3f229..00000000000 --- a/app/models/ci/freeze_period_status.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -module Ci - class FreezePeriodStatus - attr_reader :project - - def initialize(project:) - @project = project - end - - def execute - project.freeze_periods.any? { |period| within_freeze_period?(period) } - end - - def within_freeze_period?(period) - start_freeze_cron = Gitlab::Ci::CronParser.new(period.freeze_start, period.cron_timezone) - end_freeze_cron = Gitlab::Ci::CronParser.new(period.freeze_end, period.cron_timezone) - - start_freeze = start_freeze_cron.previous_time_from(time_zone_now) - end_freeze = end_freeze_cron.next_time_from(start_freeze) - - start_freeze <= time_zone_now && time_zone_now <= end_freeze - end - - private - - def time_zone_now - @time_zone_now ||= Time.zone.now - end - end -end diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index 922806a21c3..53c358f4eba 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -5,7 +5,6 @@ module Ci include Ci::Partitionable include IgnorableColumns include AfterCommitQueue - include ObjectStorage::BackgroundMove include UpdateProjectStatistics include UsageStatistics include Sortable @@ -52,7 +51,8 @@ module Ci cobertura: 'cobertura-coverage.xml', terraform: 'tfplan.json', cluster_applications: 'gl-cluster-applications.json', # DEPRECATED: https://gitlab.com/gitlab-org/gitlab/-/issues/361094 - requirements: 'requirements.json', + requirements: 'requirements.json', # Will be DEPRECATED soon: https://gitlab.com/groups/gitlab-org/-/epics/9203 + requirements_v2: 'requirements_v2.json', coverage_fuzzing: 'gl-coverage-fuzzing.json', api_fuzzing: 'gl-api-fuzzing-report.json', cyclonedx: 'gl-sbom.cdx.json' @@ -95,6 +95,7 @@ module Ci load_performance: :raw, terraform: :raw, requirements: :raw, + requirements_v2: :raw, coverage_fuzzing: :raw, api_fuzzing: :raw }.freeze @@ -119,6 +120,7 @@ module Ci sast secret_detection requirements + requirements_v2 cluster_image_scanning cyclonedx ].freeze @@ -209,7 +211,8 @@ module Ci load_performance: 25, ## EE-specific api_fuzzing: 26, ## EE-specific cluster_image_scanning: 27, ## EE-specific - cyclonedx: 28 ## EE-specific + cyclonedx: 28, ## EE-specific + requirements_v2: 29 ## EE-specific } # `file_location` indicates where actual files are stored. diff --git a/app/models/ci/job_token/allowlist.rb b/app/models/ci/job_token/allowlist.rb new file mode 100644 index 00000000000..9e9a0a68ebd --- /dev/null +++ b/app/models/ci/job_token/allowlist.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true +module Ci + module JobToken + class Allowlist + def initialize(source_project, direction:) + @source_project = source_project + @direction = direction + end + + def includes?(target_project) + source_links + .with_target(target_project) + .exists? + end + + def projects + Project.from_union(target_projects, remove_duplicates: false) + end + + private + + def source_links + Ci::JobToken::ProjectScopeLink + .with_source(@source_project) + .where(direction: @direction) + end + + def target_project_ids + source_links + # pluck needed to avoid ci and main db join + .pluck(:target_project_id) + end + + def target_projects + [ + Project.id_in(@source_project), + Project.id_in(target_project_ids) + ] + end + end + end +end diff --git a/app/models/ci/job_token/project_scope_link.rb b/app/models/ci/job_token/project_scope_link.rb index 3fdf07123e6..b784f93651a 100644 --- a/app/models/ci/job_token/project_scope_link.rb +++ b/app/models/ci/job_token/project_scope_link.rb @@ -12,8 +12,8 @@ module Ci belongs_to :target_project, class_name: 'Project' belongs_to :added_by, class_name: 'User' - scope :from_project, ->(project) { where(source_project: project) } - scope :to_project, ->(project) { where(target_project: project) } + scope :with_source, ->(project) { where(source_project: project) } + scope :with_target, ->(project) { where(target_project: project) } validates :source_project, presence: true validates :target_project, presence: true diff --git a/app/models/ci/job_token/scope.rb b/app/models/ci/job_token/scope.rb index 1aa49b95201..e320c0f92d1 100644 --- a/app/models/ci/job_token/scope.rb +++ b/app/models/ci/job_token/scope.rb @@ -1,49 +1,58 @@ # frozen_string_literal: true -# This model represents the surface where a CI_JOB_TOKEN can be used. -# A Scope is initialized with the project that the job token belongs to, -# and indicates what are all the other projects that the token could access. +# This model represents the scope of access for a CI_JOB_TOKEN. # -# By default a job token can only access its own project, which is the same -# project that defines the scope. -# By adding ScopeLinks to the scope we can allow other projects to be accessed -# by the job token. This works as an allowlist of projects for a job token. +# A scope is initialized with a project. +# +# Projects can be added to the scope by adding ScopeLinks to +# create an allowlist of projects in either access direction (inbound, outbound). +# +# Currently, projects in the outbound allowlist can be accessed via the token +# in the source project. +# +# TODO(Issue #346298) Projects in the inbound allowlist can use their token to access +# the source project. +# +# CI_JOB_TOKEN should be considered untrusted without these features enabled. # -# If a project is not included in the scope we should not allow the job user -# to access it since operations using CI_JOB_TOKEN should be considered untrusted. module Ci module JobToken class Scope - attr_reader :source_project + attr_reader :current_project - def initialize(project) - @source_project = project + def initialize(current_project) + @current_project = current_project end - def includes?(target_project) - # if the setting is disabled any project is considered to be in scope. - return true unless source_project.ci_outbound_job_token_scope_enabled? + def allows?(accessed_project) + self_referential?(accessed_project) || outbound_allows?(accessed_project) + end - target_project.id == source_project.id || - Ci::JobToken::ProjectScopeLink.from_project(source_project).to_project(target_project).exists? + def outbound_projects + outbound_allowlist.projects end + # Deprecated: use outbound_projects, TODO(Issue #346298) remove references to all_project def all_projects - Project.from_union(target_projects, remove_duplicates: false) + outbound_projects end private - def target_project_ids - Ci::JobToken::ProjectScopeLink.from_project(source_project).pluck(:target_project_id) + def outbound_allows?(accessed_project) + # if the setting is disabled any project is considered to be in scope. + return true unless @current_project.ci_outbound_job_token_scope_enabled? + + outbound_allowlist.includes?(accessed_project) + end + + def outbound_allowlist + Ci::JobToken::Allowlist.new(@current_project, direction: :outbound) end - def target_projects - [ - Project.id_in(source_project), - Project.id_in(target_project_ids) - ] + def self_referential?(accessed_project) + @current_project.id == accessed_project.id end end end diff --git a/app/models/ci/job_variable.rb b/app/models/ci/job_variable.rb index 332a78b66ae..998f0647ad5 100644 --- a/app/models/ci/job_variable.rb +++ b/app/models/ci/job_variable.rb @@ -2,12 +2,15 @@ module Ci class JobVariable < Ci::ApplicationRecord + include Ci::Partitionable include Ci::NewHasVariable include Ci::RawVariable include BulkInsertSafe belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id + partitionable scope: :job + alias_attribute :secret_value, :value validates :key, uniqueness: { scope: :job_id }, unless: :dotenv_source? diff --git a/app/models/ci/pending_build.rb b/app/models/ci/pending_build.rb index 0fa6a234a3d..2b1eb67d4f2 100644 --- a/app/models/ci/pending_build.rb +++ b/app/models/ci/pending_build.rb @@ -3,11 +3,14 @@ module Ci class PendingBuild < Ci::ApplicationRecord include EachBatch + include Ci::Partitionable belongs_to :project belongs_to :build, class_name: 'Ci::Build' belongs_to :namespace, inverse_of: :pending_builds, class_name: 'Namespace' + partitionable scope: :build + validates :namespace, presence: true scope :ref_protected, -> { where(protected: true) } diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 020f5cf9d8e..05207fb1ca0 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -350,9 +350,13 @@ module Ci scope :for_sha_or_source_sha, -> (sha) { for_sha(sha).or(for_source_sha(sha)) } scope :for_ref, -> (ref) { where(ref: ref) } scope :for_branch, -> (branch) { for_ref(branch).where(tag: false) } - scope :for_id, -> (id) { where(id: id) } scope :for_iid, -> (iid) { where(iid: iid) } scope :for_project, -> (project_id) { where(project_id: project_id) } + scope :for_name, -> (name) do + name_column = Ci::PipelineMetadata.arel_table[:name] + + joins(:pipeline_metadata).where(name_column.lower.eq(name.downcase)) + end scope :created_after, -> (time) { where(arel_table[:created_at].gt(time)) } scope :created_before_id, -> (id) { where(arel_table[:id].lt(id)) } scope :before_pipeline, -> (pipeline) { created_before_id(pipeline.id).outside_pipeline_family(pipeline) } @@ -721,7 +725,7 @@ module Ci def freeze_period? strong_memoize(:freeze_period) do - Ci::FreezePeriodStatus.new(project: project).execute + project.freeze_periods.any?(&:active?) end end @@ -1341,13 +1345,14 @@ module Ci persistent_ref.create end + # For dependent bridge jobs we reset the upstream bridge recursively + # to reflect that a downstream pipeline is running again def reset_source_bridge!(current_user) # break recursion when no source_pipeline bridge (first upstream pipeline) return unless bridge_waiting? return unless current_user.can?(:update_pipeline, source_bridge.pipeline) - source_bridge.pending! - Ci::AfterRequeueJobService.new(project, current_user).execute(source_bridge) # rubocop:disable CodeReuse/ServiceClass + Ci::EnqueueJobService.new(source_bridge, current_user: current_user).execute(&:pending!) # rubocop:disable CodeReuse/ServiceClass end # EE-only diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb index 96e5567e85e..20ff07e88ba 100644 --- a/app/models/ci/pipeline_schedule.rb +++ b/app/models/ci/pipeline_schedule.rb @@ -16,7 +16,7 @@ module Ci belongs_to :owner, class_name: 'User' has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline' has_many :pipelines - has_many :variables, class_name: 'Ci::PipelineScheduleVariable', validate: false + has_many :variables, class_name: 'Ci::PipelineScheduleVariable' validates :cron, unless: :importing?, cron: true, presence: { unless: :importing? } validates :cron_timezone, cron_timezone: true, presence: { unless: :importing? } @@ -78,8 +78,6 @@ module Ci ref.start_with? 'refs/tags/' end - private - def worker_cron_expression Settings.cron_jobs['pipeline_schedule_worker']['cron'] end diff --git a/app/models/ci/pipeline_schedule_variable.rb b/app/models/ci/pipeline_schedule_variable.rb index 718ed14edeb..00251ea06fd 100644 --- a/app/models/ci/pipeline_schedule_variable.rb +++ b/app/models/ci/pipeline_schedule_variable.rb @@ -9,6 +9,6 @@ module Ci alias_attribute :secret_value, :value - validates :key, uniqueness: { scope: :pipeline_schedule_id } + validates :key, presence: true, uniqueness: { scope: :pipeline_schedule_id } end end diff --git a/app/models/ci/processable.rb b/app/models/ci/processable.rb index eb805ffae0a..37c82c125aa 100644 --- a/app/models/ci/processable.rb +++ b/app/models/ci/processable.rb @@ -104,8 +104,8 @@ module Ci to: :pipeline def clone(current_user:, new_job_variables_attributes: []) - new_attributes = self.class.clone_accessors.to_h do |attribute| - [attribute, public_send(attribute)] # rubocop:disable GitlabSecurity/PublicSend + new_attributes = self.class.clone_accessors.index_with do |attribute| + public_send(attribute) # rubocop:disable GitlabSecurity/PublicSend end if persisted_environment.present? diff --git a/app/models/ci/resource_group.rb b/app/models/ci/resource_group.rb index 6d25f747a9d..b788e4f58c1 100644 --- a/app/models/ci/resource_group.rb +++ b/app/models/ci/resource_group.rb @@ -24,11 +24,18 @@ module Ci # NOTE: This is concurrency-safe method that the subquery in the `UPDATE` # works as explicit locking. def assign_resource_to(processable) - resources.free.limit(1).update_all(build_id: processable.id) > 0 + attrs = { + build_id: processable.id, + partition_id: processable.partition_id + } + + resources.free.limit(1).update_all(attrs) > 0 end def release_resource_from(processable) - resources.retained_by(processable).update_all(build_id: nil) > 0 + attrs = { build_id: nil, partition_id: nil } + + resources.retained_by(processable).update_all(attrs) > 0 end def upcoming_processables diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 3be627989b1..a7f3ff938c3 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -89,6 +89,9 @@ module Ci scope :ordered, -> { order(id: :desc) } scope :with_recent_runner_queue, -> { where('contacted_at > ?', recent_queue_deadline) } + scope :with_running_builds, -> do + where('EXISTS(?)', ::Ci::Build.running.select(1).where('ci_builds.runner_id = ci_runners.id')) + end # BACKWARD COMPATIBILITY: There are needed to maintain compatibility with `AVAILABLE_SCOPES` used by `lib/api/runners.rb` scope :deprecated_shared, -> { instance_type } diff --git a/app/models/ci/runner_namespace.rb b/app/models/ci/runner_namespace.rb index 82390ccc538..502ceae3675 100644 --- a/app/models/ci/runner_namespace.rb +++ b/app/models/ci/runner_namespace.rb @@ -15,6 +15,8 @@ module Ci validates :runner_id, uniqueness: { scope: :namespace_id } validate :group_runner_type + scope :for_runner, ->(runner_id) { where(runner_id: runner_id) } + def recent_runners ::Ci::Runner.belonging_to_group(namespace_id).recent end diff --git a/app/models/ci/running_build.rb b/app/models/ci/running_build.rb index ae38d54862d..43214b0c336 100644 --- a/app/models/ci/running_build.rb +++ b/app/models/ci/running_build.rb @@ -1,7 +1,18 @@ # frozen_string_literal: true module Ci + # This model represents metadata for a running build. + # Despite the generic RunningBuild name, in this first iteration it applies only to shared runners + # (see Ci::RunningBuild.upsert_shared_runner_build!). + # The decision to insert all of the running builds here was deferred to avoid the pressure on the database as + # at this time that was not necessary. + # We can reconsider the decision to limit this only to shared runners when there is more evidence that inserting all + # of the running builds there is worth the additional pressure. class RunningBuild < Ci::ApplicationRecord + include Ci::Partitionable + + partitionable scope: :build + belongs_to :project belongs_to :build, class_name: 'Ci::Build' belongs_to :runner, class_name: 'Ci::Runner' diff --git a/app/models/ci/secure_file.rb b/app/models/ci/secure_file.rb index df38398e5a9..1e6c48bbef5 100644 --- a/app/models/ci/secure_file.rb +++ b/app/models/ci/secure_file.rb @@ -17,20 +17,19 @@ module Ci validates :file, presence: true, file_size: { maximum: FILE_SIZE_LIMIT } validates :checksum, :file_store, :name, :project_id, presence: true validates :name, uniqueness: { scope: :project } + + attribute :metadata, :ind_jsonb validates :metadata, json_schema: { filename: "ci_secure_file_metadata" }, allow_nil: true + attribute :file_store, default: -> { Ci::SecureFileUploader.default_store } + mount_file_store_uploader Ci::SecureFileUploader + after_initialize :generate_key_data before_validation :assign_checksum scope :order_by_created_at, -> { order(created_at: :desc) } scope :project_id_in, ->(ids) { where(project_id: ids) } - serialize :metadata, Serializers::Json # rubocop:disable Cop/ActiveRecordSerialize - - attribute :file_store, default: -> { Ci::SecureFileUploader.default_store } - - mount_file_store_uploader Ci::SecureFileUploader - def checksum_algorithm CHECKSUM_ALGORITHM end diff --git a/app/models/ci/sources/pipeline.rb b/app/models/ci/sources/pipeline.rb index 2df504cd3de..855e68d1db1 100644 --- a/app/models/ci/sources/pipeline.rb +++ b/app/models/ci/sources/pipeline.rb @@ -3,6 +3,7 @@ module Ci module Sources class Pipeline < Ci::ApplicationRecord + include Ci::Partitionable include Ci::NamespacedModelName self.table_name = "ci_sources_pipelines" @@ -15,6 +16,11 @@ module Ci belongs_to :source_bridge, class_name: "Ci::Bridge", foreign_key: :source_job_id belongs_to :source_pipeline, class_name: "Ci::Pipeline", foreign_key: :source_pipeline_id + partitionable scope: :pipeline + + before_validation :set_source_partition_id, on: :create + validates :source_partition_id, presence: true + validates :project, presence: true validates :pipeline, presence: true @@ -23,6 +29,15 @@ module Ci validates :source_pipeline, presence: true scope :same_project, -> { where(arel_table[:source_project_id].eq(arel_table[:project_id])) } + + private + + def set_source_partition_id + return if source_partition_id_changed? && source_partition_id.present? + return unless source_job + + self.source_partition_id = source_job.partition_id + end end end end diff --git a/app/models/ci/unit_test_failure.rb b/app/models/ci/unit_test_failure.rb index a5aa3b70e37..cfef1249164 100644 --- a/app/models/ci/unit_test_failure.rb +++ b/app/models/ci/unit_test_failure.rb @@ -2,6 +2,8 @@ module Ci class UnitTestFailure < Ci::ApplicationRecord + include Ci::Partitionable + REPORT_WINDOW = 14.days validates :unit_test, :build, :failed_at, presence: true @@ -9,6 +11,8 @@ module Ci belongs_to :unit_test, class_name: "Ci::UnitTest", foreign_key: :unit_test_id belongs_to :build, class_name: "Ci::Build", foreign_key: :build_id + partitionable scope: :build + scope :deletable, -> { where('failed_at < ?', REPORT_WINDOW.ago) } def self.recent_failures_count(project:, unit_test_keys:, date_range: REPORT_WINDOW.ago..Time.current) |