summaryrefslogtreecommitdiff
path: root/app/models/ci
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/ci')
-rw-r--r--app/models/ci/build.rb49
-rw-r--r--app/models/ci/build_metadata.rb5
-rw-r--r--app/models/ci/job_token/project_scope_link.rb5
-rw-r--r--app/models/ci/job_token/scope.rb2
-rw-r--r--app/models/ci/pipeline.rb54
-rw-r--r--app/models/ci/pipeline_metadata.rb14
-rw-r--r--app/models/ci/runner.rb38
-rw-r--r--app/models/ci/secure_file.rb39
8 files changed, 166 insertions, 40 deletions
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 4e58f877217..b8511536e32 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -108,10 +108,12 @@ module Ci
validates :ref, presence: true
scope :not_interruptible, -> do
- joins(:metadata).where.not('ci_builds_metadata.id' => Ci::BuildMetadata.scoped_build.with_interruptible.select(:id))
+ joins(:metadata)
+ .where.not(Ci::BuildMetadata.table_name => { id: Ci::BuildMetadata.scoped_build.with_interruptible.select(:id) })
end
scope :unstarted, -> { where(runner_id: nil) }
+
scope :with_downloadable_artifacts, -> do
where('EXISTS (?)',
Ci::JobArtifact.select(1)
@@ -120,6 +122,14 @@ module Ci
)
end
+ scope :with_erasable_artifacts, -> do
+ where('EXISTS (?)',
+ Ci::JobArtifact.select(1)
+ .where('ci_builds.id = ci_job_artifacts.job_id')
+ .where(file_type: Ci::JobArtifact.erasable_file_types)
+ )
+ end
+
scope :in_pipelines, ->(pipelines) do
where(pipeline: pipelines)
end
@@ -178,7 +188,7 @@ module Ci
scope :license_management_jobs, -> { where(name: %i(license_management license_scanning)) } # handle license rename https://gitlab.com/gitlab-org/gitlab/issues/8911
scope :with_secure_reports_from_config_options, -> (job_types) do
- joins(:metadata).where("ci_builds_metadata.config_options -> 'artifacts' -> 'reports' ?| array[:job_types]", job_types: job_types)
+ joins(:metadata).where("#{Ci::BuildMetadata.quoted_table_name}.config_options -> 'artifacts' -> 'reports' ?| array[:job_types]", job_types: job_types)
end
scope :with_coverage, -> { where.not(coverage: nil) }
@@ -218,7 +228,7 @@ module Ci
yaml_variables when environment coverage_regex
description tag_list protected needs_attributes
job_variables_attributes resource_group scheduling_type
- ci_stage partition_id].freeze
+ ci_stage partition_id id_tokens].freeze
end
end
@@ -407,18 +417,10 @@ module Ci
pipeline.manual_actions.reject { |action| action.name == self.name }
end
- def environment_manual_actions
- pipeline.manual_actions.filter { |action| action.expanded_environment_name == self.expanded_environment_name }
- end
-
def other_scheduled_actions
pipeline.scheduled_actions.reject { |action| action.name == self.name }
end
- def environment_scheduled_actions
- pipeline.scheduled_actions.filter { |action| action.expanded_environment_name == self.expanded_environment_name }
- end
-
def pages_generator?
Gitlab.config.pages.enabled &&
self.name == 'pages'
@@ -445,8 +447,7 @@ module Ci
def prevent_rollback_deployment?
strong_memoize(:prevent_rollback_deployment) do
- Feature.enabled?(:prevent_outdated_deployment_jobs, project) &&
- starts_environment? &&
+ starts_environment? &&
project.ci_forward_deployment_enabled? &&
deployment&.older_than_last_successful_deployment?
end
@@ -1195,6 +1196,14 @@ module Ci
end
def job_jwt_variables
+ if project.ci_cd_settings.opt_in_jwt?
+ id_tokens_variables
+ else
+ legacy_jwt_variables.concat(id_tokens_variables)
+ end
+ end
+
+ def legacy_jwt_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless Feature.enabled?(:ci_job_jwt, project)
@@ -1208,6 +1217,20 @@ module Ci
end
end
+ def id_tokens_variables
+ return [] unless id_tokens?
+
+ 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'])
+
+ variables.append(key: var_name, value: token, public: false, masked: true)
+ end
+ rescue OpenSSL::PKey::RSAError, Gitlab::Ci::Jwt::NoSigningKeyError => e
+ Gitlab::ErrorTracking.track_exception(e)
+ end
+ end
+
def cache_for_online_runners(&block)
Rails.cache.fetch(
['has-online-runners', id],
diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb
index 3bdf2f90acb..33092e881f0 100644
--- a/app/models/ci/build_metadata.rb
+++ b/app/models/ci/build_metadata.rb
@@ -6,11 +6,14 @@ module Ci
class BuildMetadata < Ci::ApplicationRecord
BuildTimeout = Struct.new(:value, :source)
+ include Ci::Partitionable
include Presentable
include ChronicDurationAttribute
include Gitlab::Utils::StrongMemoize
self.table_name = 'ci_builds_metadata'
+ self.primary_key = 'id'
+ partitionable scope: :build
belongs_to :build, class_name: 'CommitStatus'
belongs_to :project
@@ -27,7 +30,7 @@ module Ci
chronic_duration_attr_reader :timeout_human_readable, :timeout
- scope :scoped_build, -> { where('ci_builds_metadata.build_id = ci_builds.id') }
+ scope :scoped_build, -> { where("#{quoted_table_name}.build_id = #{Ci::Build.quoted_table_name}.id") }
scope :with_interruptible, -> { where(interruptible: true) }
scope :with_exposed_artifacts, -> { where(has_exposed_artifacts: true) }
diff --git a/app/models/ci/job_token/project_scope_link.rb b/app/models/ci/job_token/project_scope_link.rb
index c2ab8ca0929..3fdf07123e6 100644
--- a/app/models/ci/job_token/project_scope_link.rb
+++ b/app/models/ci/job_token/project_scope_link.rb
@@ -19,6 +19,11 @@ module Ci
validates :target_project, presence: true
validate :not_self_referential_link
+ enum direction: {
+ outbound: 0,
+ inbound: 1
+ }
+
def self.for_source_and_target(source_project, target_project)
self.find_by(source_project: source_project, target_project: target_project)
end
diff --git a/app/models/ci/job_token/scope.rb b/app/models/ci/job_token/scope.rb
index 26a49d6a730..1aa49b95201 100644
--- a/app/models/ci/job_token/scope.rb
+++ b/app/models/ci/job_token/scope.rb
@@ -23,7 +23,7 @@ module Ci
def includes?(target_project)
# if the setting is disabled any project is considered to be in scope.
- return true unless source_project.ci_job_token_scope_enabled?
+ return true unless source_project.ci_outbound_job_token_scope_enabled?
target_project.id == source_project.id ||
Ci::JobToken::ProjectScopeLink.from_project(source_project).to_project(target_project).exists?
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 1e328c3c573..950e0a583bc 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -112,6 +112,8 @@ module Ci
has_one :pipeline_config, class_name: 'Ci::PipelineConfig', inverse_of: :pipeline
+ has_one :pipeline_metadata, class_name: 'Ci::PipelineMetadata', inverse_of: :pipeline
+
has_many :daily_build_group_report_results, class_name: 'Ci::DailyBuildGroupReportResult', foreign_key: :last_pipeline_id
has_many :latest_builds_report_results, through: :latest_builds, source: :report_results
has_many :pipeline_artifacts, class_name: 'Ci::PipelineArtifact', inverse_of: :pipeline, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -119,6 +121,7 @@ module Ci
accepts_nested_attributes_for :variables, reject_if: :persisted?
delegate :full_path, to: :project, prefix: true
+ delegate :title, to: :pipeline_metadata, allow_nil: true
validates :sha, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? }
@@ -614,6 +617,15 @@ module Ci
# auto_canceled_by_pipeline_id - store the pipeline_id of the pipeline that triggered cancellation
# execute_async - if true cancel the children asyncronously
def cancel_running(retries: 1, cascade_to_children: true, auto_canceled_by_pipeline_id: nil, execute_async: true)
+ Gitlab::AppJsonLogger.info(
+ event: 'pipeline_cancel_running',
+ pipeline_id: id,
+ auto_canceled_by_pipeline_id: auto_canceled_by_pipeline_id,
+ cascade_to_children: cascade_to_children,
+ execute_async: execute_async,
+ **Gitlab::ApplicationContext.current
+ )
+
update(auto_canceled_by_id: auto_canceled_by_pipeline_id) if auto_canceled_by_pipeline_id
cancel_jobs(cancelable_statuses, retries: retries, auto_canceled_by_pipeline_id: auto_canceled_by_pipeline_id)
@@ -760,8 +772,14 @@ module Ci
# There is no ActiveRecord relation between Ci::Pipeline and notes
# as they are related to a commit sha. This method helps importing
# them using the +Gitlab::ImportExport::Project::RelationFactory+ class.
- def notes=(notes)
- notes.each do |note|
+ def notes=(notes_to_save)
+ notes_to_save.reject! do |note_to_save|
+ notes.any? do |note|
+ [note_to_save.note, note_to_save.created_at.to_i] == [note.note, note.created_at.to_i]
+ end
+ end
+
+ notes_to_save.each do |note|
note[:id] = nil
note[:commit_id] = sha
note[:noteable_id] = self['id']
@@ -850,7 +868,6 @@ module Ci
variables.append(key: 'CI_COMMIT_REF_NAME', value: source_ref)
variables.append(key: 'CI_COMMIT_REF_SLUG', value: source_ref_slug)
variables.append(key: 'CI_COMMIT_BRANCH', value: ref) if branch?
- variables.append(key: 'CI_COMMIT_TAG', value: ref) if tag?
variables.append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message.to_s)
variables.append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s)
variables.append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description.to_s)
@@ -863,7 +880,8 @@ module Ci
variables.append(key: 'CI_BUILD_BEFORE_SHA', value: before_sha)
variables.append(key: 'CI_BUILD_REF_NAME', value: source_ref)
variables.append(key: 'CI_BUILD_REF_SLUG', value: source_ref_slug)
- variables.append(key: 'CI_BUILD_TAG', value: ref) if tag?
+
+ variables.concat(predefined_commit_tag_variables)
end
end
end
@@ -888,6 +906,20 @@ module Ci
end
end
+ def predefined_commit_tag_variables
+ strong_memoize(:predefined_commit_ref_variables) do
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ next variables unless tag?
+
+ variables.append(key: 'CI_COMMIT_TAG', value: ref)
+ variables.append(key: 'CI_COMMIT_TAG_MESSAGE', value: project.repository.find_tag(ref).message)
+
+ # legacy variable
+ variables.append(key: 'CI_BUILD_TAG', value: ref)
+ end
+ end
+ end
+
def queued_duration
return unless started_at
@@ -972,8 +1004,8 @@ module Ci
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/340781#note_699114700
expanded_environment_names =
builds_in_self_and_project_descendants.joins(:metadata)
- .where.not('ci_builds_metadata.expanded_environment_name' => nil)
- .distinct('ci_builds_metadata.expanded_environment_name')
+ .where.not(Ci::BuildMetadata.table_name => { expanded_environment_name: nil })
+ .distinct("#{Ci::BuildMetadata.quoted_table_name}.expanded_environment_name")
.limit(100)
.pluck(:expanded_environment_name)
@@ -1162,6 +1194,10 @@ module Ci
complete? && builds.latest.with_exposed_artifacts.exists?
end
+ def has_erasable_artifacts?
+ complete? && builds.latest.with_erasable_artifacts.exists?
+ end
+
def branch_updated?
strong_memoize(:branch_updated) do
push_details.branch_updated?
@@ -1328,9 +1364,9 @@ module Ci
self.builds.latest.build_matchers(project)
end
- def authorized_cluster_agents
- strong_memoize(:authorized_cluster_agents) do
- ::Clusters::AgentAuthorizationsFinder.new(project).execute.map(&:agent)
+ def cluster_agent_authorizations
+ strong_memoize(:cluster_agent_authorizations) do
+ ::Clusters::AgentAuthorizationsFinder.new(project).execute
end
end
diff --git a/app/models/ci/pipeline_metadata.rb b/app/models/ci/pipeline_metadata.rb
new file mode 100644
index 00000000000..c96b395b45f
--- /dev/null
+++ b/app/models/ci/pipeline_metadata.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Ci
+ class PipelineMetadata < Ci::ApplicationRecord
+ self.primary_key = :pipeline_id
+
+ belongs_to :pipeline, class_name: "Ci::Pipeline", inverse_of: :pipeline_metadata
+ belongs_to :project, class_name: "Project", inverse_of: :pipeline_metadata
+
+ validates :pipeline, presence: true
+ validates :project, presence: true
+ validates :title, presence: true, length: { minimum: 1, maximum: 255 }
+ end
+end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 28d9edcc135..3be627989b1 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -14,7 +14,7 @@ module Ci
include Presentable
include EachBatch
- add_authentication_token_field :token, encrypted: :optional, expires_at: :compute_token_expiration, expiration_enforced?: :token_expiration_enforced?
+ add_authentication_token_field :token, encrypted: :optional, expires_at: :compute_token_expiration
enum access_level: {
not_protected: 0,
@@ -99,27 +99,26 @@ module Ci
}
scope :belonging_to_group, -> (group_id) {
- joins(:runner_namespaces)
- .where(ci_runner_namespaces: { namespace_id: group_id })
+ joins(:runner_namespaces).where(ci_runner_namespaces: { namespace_id: group_id })
}
scope :belonging_to_group_or_project_descendants, -> (group_id) {
group_ids = Ci::NamespaceMirror.by_group_and_descendants(group_id).select(:namespace_id)
project_ids = Ci::ProjectMirror.by_namespace_id(group_ids).select(:project_id)
- group_runners = joins(:runner_namespaces).where(ci_runner_namespaces: { namespace_id: group_ids })
- project_runners = joins(:runner_projects).where(ci_runner_projects: { project_id: project_ids })
+ group_runners = belonging_to_group(group_ids)
+ project_runners = belonging_to_project(project_ids).distinct
- union_sql = ::Gitlab::SQL::Union.new([group_runners, project_runners]).to_sql
-
- from("(#{union_sql}) #{table_name}")
+ from_union(
+ [group_runners, project_runners],
+ remove_duplicates: false
+ )
}
scope :belonging_to_group_and_ancestors, -> (group_id) {
group_self_and_ancestors_ids = ::Group.find_by(id: group_id)&.self_and_ancestor_ids
- joins(:runner_namespaces)
- .where(ci_runner_namespaces: { namespace_id: group_self_and_ancestors_ids })
+ belonging_to_group(group_self_and_ancestors_ids)
}
scope :belonging_to_parent_group_of_project, -> (project_id) {
@@ -153,6 +152,17 @@ module Ci
)
end
+ scope :usable_from_scope, -> (group) do
+ from_union(
+ [
+ belonging_to_group(group.ancestor_ids),
+ belonging_to_group_or_project_descendants(group.id),
+ group.shared_runners
+ ],
+ remove_duplicates: false
+ )
+ end
+
scope :assignable_for, ->(project) do
# FIXME: That `to_sql` is needed to workaround a weird Rails bug.
# Without that, placeholders would miss one and couldn't match.
@@ -205,7 +215,7 @@ module Ci
validates :maintenance_note, length: { maximum: 1024 }
- alias_attribute :maintenance_note, :maintainer_note
+ alias_attribute :maintenance_note, :maintainer_note # NOTE: Need to keep until REST v5 is implemented
# Searches for runners matching the given query.
#
@@ -335,7 +345,7 @@ module Ci
end
# DEPRECATED
- # TODO Remove in %16.0 in favor of `status` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/344648
+ # TODO Remove in v5 in favor of `status` for REST calls, see https://gitlab.com/gitlab-org/gitlab/-/issues/344648
def deprecated_rest_status
return :stale if stale?
@@ -470,10 +480,6 @@ module Ci
end
end
- def self.token_expiration_enforced?
- Feature.enabled?(:enforce_runner_token_expires_at)
- end
-
private
scope :with_upgrade_status, ->(upgrade_status) do
diff --git a/app/models/ci/secure_file.rb b/app/models/ci/secure_file.rb
index 9a35f1876c9..ffff7eebbee 100644
--- a/app/models/ci/secure_file.rb
+++ b/app/models/ci/secure_file.rb
@@ -7,6 +7,7 @@ module Ci
FILE_SIZE_LIMIT = 5.megabytes.freeze
CHECKSUM_ALGORITHM = 'sha256'
+ PARSABLE_EXTENSIONS = %w[cer p12 mobileprovision].freeze
self.limit_scope = :project
self.limit_name = 'project_ci_secure_files'
@@ -16,6 +17,7 @@ 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 }
+ validates :metadata, json_schema: { filename: "ci_secure_file_metadata" }, allow_nil: true
after_initialize :generate_key_data
before_validation :assign_checksum
@@ -23,6 +25,8 @@ module Ci
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
+
default_value_for(:file_store) { Ci::SecureFileUploader.default_store }
mount_file_store_uploader Ci::SecureFileUploader
@@ -31,6 +35,41 @@ module Ci
CHECKSUM_ALGORITHM
end
+ def file_extension
+ File.extname(name).delete_prefix('.')
+ end
+
+ def metadata_parsable?
+ PARSABLE_EXTENSIONS.include?(file_extension)
+ end
+
+ def metadata_parser
+ return unless metadata_parsable?
+
+ case file_extension
+ when 'cer'
+ Gitlab::Ci::SecureFiles::Cer.new(file.read)
+ when 'p12'
+ Gitlab::Ci::SecureFiles::P12.new(file.read)
+ when 'mobileprovision'
+ Gitlab::Ci::SecureFiles::MobileProvision.new(file.read)
+ end
+ end
+
+ def update_metadata!
+ return unless metadata_parser
+
+ begin
+ parser = metadata_parser
+ self.metadata = parser.metadata
+ self.expires_at = parser.metadata[:expires_at]
+ save!
+ rescue StandardError => err
+ Gitlab::AppLogger.error("Secure File Parser Failure (#{id}): #{err.message} - #{parser.error}.")
+ nil
+ end
+ end
+
private
def assign_checksum