mirror of
https://gitlab.com/gitlab-org/gitlab-foss.git
synced 2025-08-03 16:04:30 +00:00
339 lines
16 KiB
Ruby
339 lines
16 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Gitlab
|
|
module Ci
|
|
module Variables
|
|
class Builder
|
|
include ::Gitlab::Utils::StrongMemoize
|
|
|
|
def initialize(pipeline)
|
|
@pipeline = pipeline
|
|
@pipeline_variables_builder = Builder::Pipeline.new(pipeline)
|
|
@instance_variables_builder = Builder::Instance.new
|
|
@project_variables_builder = Builder::Project.new(project)
|
|
@group_variables_builder = Builder::Group.new(project&.group)
|
|
@release_variables_builder = Builder::Release.new(release)
|
|
end
|
|
|
|
# When adding new variables, consider either adding or commenting out them in the following methods:
|
|
# - unprotected_scoped_variables
|
|
# - scoped_variables_for_pipeline_seed
|
|
def scoped_variables(job, environment:, dependencies:)
|
|
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
|
if pipeline.only_workload_variables?
|
|
# predefined_project_variables includes things like $CI_PROJECT_PATH which are used by the runner to clone
|
|
# the repo
|
|
variables.concat(project.predefined_project_variables)
|
|
|
|
# yaml_variables is how we inject dynamic configuration into a workload
|
|
variables.concat(job.yaml_variables)
|
|
next
|
|
end
|
|
|
|
variables.concat(predefined_variables(job, environment))
|
|
variables.concat(project.predefined_variables)
|
|
variables.concat(pipeline_variables_builder.predefined_variables)
|
|
variables.concat(job.runner.predefined_variables) if job.runnable? && job.runner
|
|
variables.concat(kubernetes_variables_from_job(environment: environment, job: job))
|
|
variables.concat(job.yaml_variables)
|
|
variables.concat(user_variables(job.user))
|
|
variables.concat(job.dependency_variables) if dependencies
|
|
variables.concat(
|
|
user_defined_variables(options: job.options, environment: environment, job_variables: job.manual_variables)
|
|
)
|
|
variables.concat(release_variables)
|
|
end
|
|
end
|
|
|
|
def unprotected_scoped_variables(job, expose_project_variables:, expose_group_variables:, environment:, dependencies:)
|
|
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
|
if pipeline.only_workload_variables?
|
|
# predefined_project_variables includes things like $CI_PROJECT_PATH which are used by the runner to clone
|
|
# the repo
|
|
variables.concat(project.predefined_project_variables)
|
|
|
|
# yaml_variables is how we inject dynamic configuration into a workload
|
|
variables.concat(job.yaml_variables)
|
|
next
|
|
end
|
|
|
|
variables.concat(predefined_variables(job, environment))
|
|
variables.concat(project.predefined_variables)
|
|
variables.concat(pipeline_variables_builder.predefined_variables)
|
|
variables.concat(job.runner.predefined_variables) if job.runnable? && job.runner
|
|
variables.concat(kubernetes_variables_from_job(environment: environment, job: job))
|
|
variables.concat(job.yaml_variables)
|
|
variables.concat(user_variables(job.user))
|
|
variables.concat(job.dependency_variables) if dependencies
|
|
variables.concat(
|
|
user_defined_variables(
|
|
options: job.options, environment: environment, job_variables: job.manual_variables,
|
|
expose_project_variables: expose_project_variables, expose_group_variables: expose_group_variables
|
|
)
|
|
)
|
|
variables.concat(release_variables)
|
|
end
|
|
end
|
|
|
|
def scoped_variables_for_pipeline_seed(job_attr, environment:, kubernetes_namespace:, user:, trigger:)
|
|
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
|
if pipeline.only_workload_variables?
|
|
# predefined_project_variables includes things like $CI_PROJECT_PATH which are used by the runner to clone
|
|
# the repo
|
|
variables.concat(project.predefined_project_variables)
|
|
|
|
# yaml_variables is how we inject dynamic configuration into a workload
|
|
variables.concat(job_attr[:yaml_variables])
|
|
next
|
|
end
|
|
|
|
variables.concat(predefined_variables_from_job_attr(job_attr, environment, trigger))
|
|
variables.concat(project.predefined_variables)
|
|
variables.concat(pipeline_variables_builder.predefined_variables)
|
|
# job.runner.predefined_variables: No need because it's not available in the Seed step.
|
|
variables.concat(kubernetes_variables_from_attr(environment: environment, kubernetes_namespace: kubernetes_namespace))
|
|
variables.concat(job_attr[:yaml_variables])
|
|
variables.concat(user_variables(user))
|
|
# job.dependency_variables: No need because dependencies are not in the Seed step.
|
|
variables.concat(user_defined_variables(options: job_attr[:options], environment: environment))
|
|
variables.concat(release_variables)
|
|
end
|
|
end
|
|
|
|
def config_variables
|
|
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
|
break variables unless project
|
|
next if pipeline.only_workload_variables?
|
|
|
|
variables.concat(project.predefined_variables)
|
|
variables.concat(pipeline_variables_builder.predefined_variables)
|
|
variables.concat(user_defined_variables(options: {}, environment: nil))
|
|
end
|
|
end
|
|
|
|
def kubernetes_variables(environment:, token:, kubernetes_namespace:)
|
|
::Gitlab::Ci::Variables::Collection.new.tap do |collection|
|
|
# NOTE: deployment_variables will be removed as part of cleanup for
|
|
# https://gitlab.com/groups/gitlab-org/configure/-/epics/8
|
|
# Until then, we need to make both the old and the new KUBECONFIG contexts available
|
|
collection.concat(deployment_variables(environment, kubernetes_namespace))
|
|
collection.concat(kubeconfig_variables(environment, kubernetes_namespace, token, collection['KUBECONFIG']&.value))
|
|
end
|
|
end
|
|
|
|
def deployment_variables(environment, kubernetes_namespace)
|
|
strong_memoize_with(:deployment_variables, environment, kubernetes_namespace) do
|
|
next [] unless environment
|
|
|
|
project.deployment_variables(
|
|
environment: environment,
|
|
kubernetes_namespace: kubernetes_namespace
|
|
)
|
|
end
|
|
end
|
|
|
|
def kubeconfig_variables(environment, kubernetes_namespace, token, kubeconfig_yaml)
|
|
# kubernetes_namespace is part of the cache key because the value of KUBECONFIG depends on it.
|
|
# And we don't want to use `kubeconfig_yaml` in the cache key because it can be too large.
|
|
strong_memoize_with(:kubeconfig_variables, environment, token, kubernetes_namespace) do
|
|
template = ::Ci::GenerateKubeconfigService.new(pipeline, token: token, environment: environment).execute
|
|
template.merge_yaml(kubeconfig_yaml) if kubeconfig_yaml.present?
|
|
|
|
next [] unless template.valid?
|
|
|
|
::Gitlab::Ci::Variables::Collection.new.tap do |collection|
|
|
collection.append(key: 'KUBECONFIG', value: template.to_yaml, public: false, file: true)
|
|
end
|
|
end
|
|
end
|
|
|
|
def user_variables(user)
|
|
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
|
break variables if user.blank?
|
|
|
|
variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s)
|
|
variables.append(key: 'GITLAB_USER_EMAIL', value: user.email)
|
|
variables.append(key: 'GITLAB_USER_LOGIN', value: user.username)
|
|
variables.append(key: 'GITLAB_USER_NAME', value: user.name)
|
|
end
|
|
end
|
|
|
|
def secret_instance_variables
|
|
strong_memoize(:secret_instance_variables) do
|
|
instance_variables_builder
|
|
.secret_variables(protected_ref: protected_ref?)
|
|
end
|
|
end
|
|
|
|
def secret_group_variables(environment:, include_protected_vars: protected_ref?)
|
|
strong_memoize_with(:secret_group_variables, environment, include_protected_vars) do
|
|
group_variables_builder
|
|
.secret_variables(
|
|
environment: environment,
|
|
protected_ref: include_protected_vars)
|
|
end
|
|
end
|
|
|
|
def secret_project_variables(environment:, include_protected_vars: protected_ref?)
|
|
strong_memoize_with(:secret_project_variables, environment, include_protected_vars) do
|
|
project_variables_builder
|
|
.secret_variables(
|
|
environment: environment,
|
|
protected_ref: include_protected_vars)
|
|
end
|
|
end
|
|
|
|
def release_variables
|
|
strong_memoize(:release_variables) do
|
|
release_variables_builder.variables
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :pipeline
|
|
attr_reader :pipeline_variables_builder
|
|
attr_reader :instance_variables_builder
|
|
attr_reader :project_variables_builder
|
|
attr_reader :group_variables_builder
|
|
attr_reader :release_variables_builder
|
|
|
|
delegate :project, to: :pipeline
|
|
|
|
def predefined_variables(job, environment)
|
|
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
|
variables.append(key: 'CI_JOB_NAME', value: job.name)
|
|
variables.append(key: 'CI_JOB_NAME_SLUG', value: job_name_slug(job.name))
|
|
variables.append(key: 'CI_JOB_GROUP_NAME', value: Gitlab::Utils::Job.group_name(job.name))
|
|
variables.append(key: 'CI_JOB_STAGE', value: job.stage_name)
|
|
variables.append(key: 'CI_JOB_MANUAL', value: 'true') if job.action?
|
|
|
|
if job.pipeline.trigger_id
|
|
variables.append(key: 'CI_PIPELINE_TRIGGERED', value: 'true')
|
|
variables.append(key: 'CI_TRIGGER_SHORT_TOKEN', value: job.trigger_short_token)
|
|
end
|
|
|
|
variables.append(key: 'CI_NODE_INDEX', value: job.options[:instance].to_s) if job.options&.include?(:instance)
|
|
variables.append(key: 'CI_NODE_TOTAL', value: ci_node_total_value(job.options).to_s)
|
|
|
|
if environment.present?
|
|
variables.append(key: 'CI_ENVIRONMENT_NAME', value: environment)
|
|
variables.append(key: 'CI_ENVIRONMENT_ACTION', value: job.environment_action)
|
|
variables.append(key: 'CI_ENVIRONMENT_TIER', value: job.environment_tier)
|
|
variables.append(key: 'CI_ENVIRONMENT_URL', value: job.environment_url) if job.environment_url
|
|
end
|
|
end
|
|
end
|
|
|
|
def predefined_variables_from_job_attr(job_attr, environment, trigger)
|
|
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
|
variables.append(key: 'CI_JOB_NAME', value: job_attr[:name])
|
|
variables.append(key: 'CI_JOB_NAME_SLUG', value: job_name_slug(job_attr[:name]))
|
|
variables.append(key: 'CI_JOB_GROUP_NAME', value: Gitlab::Utils::Job.group_name(job_attr[:name]))
|
|
variables.append(key: 'CI_JOB_STAGE', value: job_attr[:stage])
|
|
variables.append(key: 'CI_JOB_MANUAL', value: 'true') if ::Ci::Processable::ACTIONABLE_WHEN.include?(job_attr[:when])
|
|
variables.append(key: 'CI_PIPELINE_TRIGGERED', value: 'true') if trigger
|
|
variables.append(key: 'CI_TRIGGER_SHORT_TOKEN', value: trigger.trigger_short_token) if trigger
|
|
variables.append(key: 'CI_NODE_INDEX', value: job_attr[:options][:instance].to_s) if job_attr[:options]&.include?(:instance)
|
|
variables.append(key: 'CI_NODE_TOTAL', value: ci_node_total_value(job_attr[:options]).to_s)
|
|
|
|
if environment.present?
|
|
variables.append(key: 'CI_ENVIRONMENT_NAME', value: environment)
|
|
|
|
if job_attr[:options].present?
|
|
variables.append(key: 'CI_ENVIRONMENT_ACTION', value: environment_action_from_job_options(job_attr[:options]))
|
|
variables.append(key: 'CI_ENVIRONMENT_TIER', value: environment_tier_from_job_options(job_attr[:options], environment))
|
|
variables.append(key: 'CI_ENVIRONMENT_URL', value: environment_url_from_job_options(job_attr[:options], environment))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def pipeline_schedule_variables
|
|
strong_memoize(:pipeline_schedule_variables) do
|
|
variables = if pipeline.pipeline_schedule
|
|
pipeline.pipeline_schedule.job_variables
|
|
else
|
|
[]
|
|
end
|
|
|
|
Gitlab::Ci::Variables::Collection.new(variables)
|
|
end
|
|
end
|
|
|
|
def kubernetes_variables_from_attr(environment:, kubernetes_namespace:)
|
|
kubernetes_variables(
|
|
environment: environment,
|
|
token: nil,
|
|
kubernetes_namespace: kubernetes_namespace
|
|
)
|
|
end
|
|
|
|
def kubernetes_variables_from_job(environment:, job:)
|
|
kubernetes_variables(
|
|
environment: environment,
|
|
token: job.try(:token),
|
|
kubernetes_namespace: environment ? job.expanded_kubernetes_namespace : nil
|
|
# environment.nil? means also that this is called from `simple_variables`.
|
|
)
|
|
end
|
|
|
|
def user_defined_variables(options:, environment:, job_variables: nil, expose_group_variables: protected_ref?, expose_project_variables: protected_ref?) # rubocop:disable Lint/UnusedMethodArgument -- options will be used in EE
|
|
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
|
variables.concat(secret_instance_variables)
|
|
variables.concat(secret_group_variables(environment: environment, include_protected_vars: expose_group_variables))
|
|
variables.concat(secret_project_variables(environment: environment, include_protected_vars: expose_project_variables))
|
|
variables.concat(pipeline.variables)
|
|
variables.concat(pipeline_schedule_variables)
|
|
variables.concat(job_variables)
|
|
end
|
|
end
|
|
|
|
def job_name_slug(job_name)
|
|
job_name && Gitlab::Utils.slugify(job_name)
|
|
end
|
|
|
|
def ci_node_total_value(job_options)
|
|
parallel = job_options&.dig(:parallel)
|
|
parallel = parallel[:total] if parallel.is_a?(Hash)
|
|
parallel || 1
|
|
end
|
|
|
|
def protected_ref?
|
|
strong_memoize(:protected_ref) do
|
|
pipeline.protected_ref?
|
|
end
|
|
end
|
|
|
|
def release
|
|
return unless @pipeline.tag?
|
|
|
|
project.releases.find_by_tag(@pipeline.ref)
|
|
end
|
|
|
|
def environment_action_from_job_options(options)
|
|
options.fetch(:environment, {}).fetch(:action, 'start')
|
|
end
|
|
|
|
# We use the `environment` parameter instead of `options[:environment]` because `environment` is expanded.
|
|
def environment_tier_from_job_options(options, environment)
|
|
options.dig(:environment, :deployment_tier) || persisted_environment(environment).try(:tier)
|
|
end
|
|
|
|
# We use the `environment` parameter instead of `options[:environment]` because `environment` is expanded.
|
|
def environment_url_from_job_options(options, environment)
|
|
options.dig(:environment, :url) || persisted_environment(environment).try(:external_url)
|
|
end
|
|
|
|
def persisted_environment(environment)
|
|
strong_memoize_with(:persisted_environment, environment) do
|
|
project.batch_loaded_environment_by_name(environment)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
Gitlab::Ci::Variables::Builder.prepend_mod_with('Gitlab::Ci::Variables::Builder')
|