Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-12-20 17:22:11 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-12-20 17:22:11 +0300
commit0c872e02b2c822e3397515ec324051ff540f0cd5 (patch)
treece2fb6ce7030e4dad0f4118d21ab6453e5938cdd /app/models/ci
parentf7e05a6853b12f02911494c4b3fe53d9540d74fc (diff)
Add latest changes from gitlab-org/gitlab@15-7-stable-eev15.7.0-rc42
Diffstat (limited to 'app/models/ci')
-rw-r--r--app/models/ci/bridge.rb19
-rw-r--r--app/models/ci/build.rb54
-rw-r--r--app/models/ci/build_metadata.rb15
-rw-r--r--app/models/ci/build_need.rb5
-rw-r--r--app/models/ci/build_pending_state.rb4
-rw-r--r--app/models/ci/build_report_result.rb4
-rw-r--r--app/models/ci/build_runner_session.rb4
-rw-r--r--app/models/ci/build_trace_chunk.rb7
-rw-r--r--app/models/ci/build_trace_metadata.rb12
-rw-r--r--app/models/ci/freeze_period.rb59
-rw-r--r--app/models/ci/freeze_period_status.rb31
-rw-r--r--app/models/ci/job_artifact.rb9
-rw-r--r--app/models/ci/job_token/allowlist.rb42
-rw-r--r--app/models/ci/job_token/project_scope_link.rb4
-rw-r--r--app/models/ci/job_token/scope.rb59
-rw-r--r--app/models/ci/job_variable.rb3
-rw-r--r--app/models/ci/pending_build.rb3
-rw-r--r--app/models/ci/pipeline.rb13
-rw-r--r--app/models/ci/pipeline_schedule.rb4
-rw-r--r--app/models/ci/pipeline_schedule_variable.rb2
-rw-r--r--app/models/ci/processable.rb4
-rw-r--r--app/models/ci/resource_group.rb11
-rw-r--r--app/models/ci/runner.rb3
-rw-r--r--app/models/ci/runner_namespace.rb2
-rw-r--r--app/models/ci/running_build.rb11
-rw-r--r--app/models/ci/secure_file.rb11
-rw-r--r--app/models/ci/sources/pipeline.rb15
-rw-r--r--app/models/ci/unit_test_failure.rb4
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)