diff --git a/app/graphql/mutations/ci/pipeline/create.rb b/app/graphql/mutations/ci/pipeline/create.rb index 2b5c34f12f5da57197d1ede25d8b8a399abe12f0..ccc64cdbb92174590d372b5367763716cc385251 100644 --- a/app/graphql/mutations/ci/pipeline/create.rb +++ b/app/graphql/mutations/ci/pipeline/create.rb @@ -44,10 +44,14 @@ class Create < BaseMutation def resolve(project_path:, ref:, async: false, variables: {}) project = authorized_find!(project_path) - creation_params = { ref: ref, variables_attributes: variables.map(&:to_h) } + inputs = variables.to_h do |var| + [var[:key], var[:value]] + end + creation_params = { ref: ref } + execute_params = { inputs: inputs } service = ::Ci::CreatePipelineService.new(project, current_user, creation_params) - response = execute_service(service, source, async) + response = execute_service(service, source, execute_params, async) if response.success? if async @@ -62,11 +66,11 @@ def resolve(project_path:, ref:, async: false, variables: {}) private - def execute_service(service, source, async) + def execute_service(service, source, execute_params, async) if async - service.execute_async(source, EXECUTE_OPTIONS) + service.execute_async(source, EXECUTE_OPTIONS.merge(execute_params)) else - service.execute(source, **EXECUTE_OPTIONS) + service.execute(source, **EXECUTE_OPTIONS.merge(execute_params)) end end diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb index 5183e149617a896e32dccd921d50981dd9d1c38c..71ebe939113fcd48e73a18e29f0fc3bd6eeecdf4 100644 --- a/app/models/ci/bridge.rb +++ b/app/models/ci/bridge.rb @@ -325,7 +325,8 @@ def cross_project_params }, execute_params: { ignore_skip_ci: true, - bridge: self + bridge: self, + inputs: options&.dig(:trigger, :inputs) } } end diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 6a7a810bd392d6926716f5dab8735b9980df23ad..f9b1775dc794b4f2b27284627b92985341149803 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -154,8 +154,8 @@ def validate_options!(_) # :nocov: # rubocop:enable Gitlab/NoCodeCoverageComment - def extra_options(content: nil, dry_run: false) - { content: content, dry_run: dry_run } + def extra_options(content: nil, dry_run: false, inputs: {}) + { content: content, dry_run: dry_run, inputs: inputs } end def build_logger diff --git a/app/services/ci/list_config_variables_service.rb b/app/services/ci/list_config_variables_service.rb index 1e40241f2aecf8c60cd720510216ff4664200beb..72cee2d7b0d2eb7a26ae761ad43dd943be35975f 100644 --- a/app/services/ci/list_config_variables_service.rb +++ b/app/services/ci/list_config_variables_service.rb @@ -2,59 +2,36 @@ module Ci class ListConfigVariablesService < ::BaseService - include ReactiveCaching - - self.reactive_cache_key = ->(service) { [service.class.name, service.id] } - self.reactive_cache_work_type = :external_dependency - self.reactive_cache_worker_finder = ->(id, *_args) { from_cache(id) } - - def self.from_cache(id) - project_id, user_id = id.split('-') - - project = Project.find(project_id) - user = User.find(user_id) - - new(project, user) - end - def execute(ref) - # "ref" is not a enough for a cache key because the name is static but that branch can be changed any time sha = project.commit(ref).try(:sha) - - with_reactive_cache(sha, ref) { |result| result } - end - - def calculate_reactive_cache(sha, ref) config = ::Gitlab::Ci::ProjectConfig.new(project: project, sha: sha) - return {} unless config.exists? + return {} if !config.project_setting? || !config.exists? - result = execute_yaml_processor(sha, ref, config) - - result.valid? ? result.root_variables_with_prefill_data : {} - end + result = spec(config, sha) - # Required for ReactiveCaching, it is also used in `reactive_cache_worker_finder` - def id - "#{project.id}-#{current_user.id}" + result[:inputs].transform_values do |details| + { value: details[:default], description: details[:description] } + end end private - def execute_yaml_processor(sha, ref, config) - # The `ref` parameter should be branch or tag name. However, the API also accepts a commit SHA and we can't - # change it to not introduce breaking changes. Instead, here we're checking if a commit SHA is passed - # as `ref`. If so, we should verify the sha whether it belongs to the project in YamlProcessor. - sha_passed_as_ref_parameter = !project.repository.branch_or_tag?(ref) - - Gitlab::Ci::YamlProcessor.new( - config.content, - project: project, - user: current_user, - sha: sha, - ref: ref, - verify_project_sha: sha_passed_as_ref_parameter - ).execute + def spec(config, sha) + context = ::Gitlab::Ci::Config::External::Context.new(project: project, user: current_user, sha: sha) + yaml_content = ::Gitlab::Ci::Config::Yaml.load!(config.content) + locations = yaml_content[:include] + files = ::Gitlab::Ci::Config::External::Mapper::Matcher.new(context).process(locations) + + file = files.first + + file.validate_location! + file.preload_context if file.valid? + file.validate_context! if file.valid? + file.preload_content if file.valid? + file.validate_content! if file.valid? + + Gitlab::Ci::Config::Yaml::Loader.new(file.content).load_uninterpolated_yaml.spec end end end diff --git a/lib/gitlab/ci/config/entry/trigger.rb b/lib/gitlab/ci/config/entry/trigger.rb index a9d509c46cba2b180eecd9de690ad626dde27535..41816edbbd7d043a8d299738ed4bcd276c5080b9 100644 --- a/lib/gitlab/ci/config/entry/trigger.rb +++ b/lib/gitlab/ci/config/entry/trigger.rb @@ -35,8 +35,8 @@ class CrossProjectTrigger < ::Gitlab::Config::Entry::Node include ::Gitlab::Config::Entry::Attributable include ::Gitlab::Config::Entry::Configurable - ALLOWED_KEYS = %i[project branch strategy forward].freeze - attributes :project, :branch, :strategy + ALLOWED_KEYS = %i[project branch strategy forward inputs].freeze + attributes :project, :branch, :strategy, :inputs validations do validates :config, presence: true @@ -53,7 +53,8 @@ def value { project: project, branch: branch, strategy: strategy, - forward: forward_value }.compact + forward: forward_value, + inputs: inputs }.compact end end diff --git a/lib/gitlab/ci/config/interpolation/inputs/boolean_input.rb b/lib/gitlab/ci/config/interpolation/inputs/boolean_input.rb index 51845a2fea89c28d22abb507675016a8ba5d6be8..960235ef67bd5fcfa3fe95a5ecbaefe8ae6ce132 100644 --- a/lib/gitlab/ci/config/interpolation/inputs/boolean_input.rb +++ b/lib/gitlab/ci/config/interpolation/inputs/boolean_input.rb @@ -14,7 +14,8 @@ def self.type_name override :validate_type def validate_type(value, default) - return if [true, false].include?(value) + bool_value = Gitlab::Utils.to_boolean(value) + return if [true, false].include?(bool_value) error("#{default ? 'default' : 'provided'} value is not a boolean") end diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb index 33cc3b190df90690aae6eeb184f58e65c803054b..e5df3c75be748eaf21b25f8e03d8ab241ecc06c1 100644 --- a/lib/gitlab/ci/pipeline/chain/command.rb +++ b/lib/gitlab/ci/pipeline/chain/command.rb @@ -13,7 +13,7 @@ module Chain :chat_data, :allow_mirror_update, :bridge, :content, :dry_run, :logger, :pipeline_policy_context, # These attributes are set by Chains during processing: :config_content, :yaml_processor_result, :workflow_rules_result, :pipeline_seed, - :pipeline_config, :partition_id, + :pipeline_config, :partition_id, :inputs, keyword_init: true ) do include Gitlab::Utils::StrongMemoize diff --git a/lib/gitlab/ci/pipeline/chain/config/content.rb b/lib/gitlab/ci/pipeline/chain/config/content.rb index 378ff58d31d9dedc7b1902cb57e14e7052e5fb63..cdd9a0825d35bc0315a0573c109d63c71aed986f 100644 --- a/lib/gitlab/ci/pipeline/chain/config/content.rb +++ b/lib/gitlab/ci/pipeline/chain/config/content.rb @@ -34,7 +34,8 @@ def pipeline_config pipeline_source: @command.source, pipeline_source_bridge: @command.bridge, triggered_for_branch: @pipeline.branch?, ref: @pipeline.ref, - pipeline_policy_context: @command.pipeline_policy_context + pipeline_policy_context: @command.pipeline_policy_context, + inputs: @command.inputs ) end end diff --git a/lib/gitlab/ci/project_config.rb b/lib/gitlab/ci/project_config.rb index 4d789685666719bfe5a648dfd652650e321b6fb7..3007557a22c23b8fa19120bdcf35d52876ad5c84 100644 --- a/lib/gitlab/ci/project_config.rb +++ b/lib/gitlab/ci/project_config.rb @@ -24,9 +24,9 @@ class ProjectConfig FALLBACK_POLICY_SOURCE = ProjectConfig::SecurityPolicyDefault - def initialize( + def initialize( # rubocop:disable Metrics/ParameterLists -- we need all these parameters project:, sha:, custom_content: nil, pipeline_source: nil, pipeline_source_bridge: nil, - triggered_for_branch: nil, ref: nil, pipeline_policy_context: nil) + triggered_for_branch: nil, ref: nil, pipeline_policy_context: nil, inputs: nil) unless pipeline_policy_context&.applying_config_override? @config = find_source(project: project, @@ -35,7 +35,8 @@ def initialize( pipeline_source: pipeline_source, pipeline_source_bridge: pipeline_source_bridge, triggered_for_branch: triggered_for_branch, - ref: ref + ref: ref, + inputs: inputs ) return if @config @@ -59,10 +60,15 @@ def exists? !!@config&.exists? end + def project_setting? + @config.is_a?(ProjectConfig::ProjectSetting) + end + private def find_source( - project:, sha:, custom_content:, pipeline_source:, pipeline_source_bridge:, triggered_for_branch:, ref:) + project:, sha:, custom_content:, pipeline_source:, pipeline_source_bridge:, triggered_for_branch:, ref:, + inputs:) STANDARD_SOURCES.each do |source| source_config = source.new(project: project, sha: sha, @@ -70,7 +76,8 @@ def find_source( pipeline_source: pipeline_source, pipeline_source_bridge: pipeline_source_bridge, triggered_for_branch: triggered_for_branch, - ref: ref + ref: ref, + inputs: inputs ) return source_config if source_config.exists? diff --git a/lib/gitlab/ci/project_config/project_setting.rb b/lib/gitlab/ci/project_config/project_setting.rb index 9c1405b6bd27075d24a6147e2aa2c2285aedef8f..d9333e2aaba14dfe3c96b5865e925416cad1ae5c 100644 --- a/lib/gitlab/ci/project_config/project_setting.rb +++ b/lib/gitlab/ci/project_config/project_setting.rb @@ -78,7 +78,7 @@ def remote_config_path? end def ci_yaml_include(config) - YAML.dump('include' => [config]) + YAML.dump('include' => [config.merge('inputs' => inputs)]) end end end diff --git a/lib/gitlab/ci/project_config/source.rb b/lib/gitlab/ci/project_config/source.rb index d376b9f4540dd0aa9649098c63f1bffb8a252971..8431bed5253141188ddc77bfc0a386c07f6056b0 100644 --- a/lib/gitlab/ci/project_config/source.rb +++ b/lib/gitlab/ci/project_config/source.rb @@ -8,7 +8,7 @@ class Source def initialize( project:, sha:, custom_content: nil, pipeline_source: nil, pipeline_source_bridge: nil, - triggered_for_branch: false, ref: nil) + triggered_for_branch: false, ref: nil, inputs: {}) @project = project @sha = sha @custom_content = custom_content @@ -16,6 +16,7 @@ def initialize( @pipeline_source_bridge = pipeline_source_bridge @triggered_for_branch = triggered_for_branch @ref = ref + @inputs = inputs end def exists? @@ -44,7 +45,7 @@ def url private attr_reader :project, :sha, :custom_content, :pipeline_source, :pipeline_source_bridge, :triggered_for_branch, - :ref + :ref, :inputs def ci_config_path @ci_config_path ||= project.ci_config_path_or_default