From d2b4d642978714ccc25a89defb198fb06dec3e58 Mon Sep 17 00:00:00 2001 From: Furkan Ayhan <furkanayhn@gmail.com> Date: Mon, 12 Sep 2022 13:04:22 +0000 Subject: [PATCH] Refactor project CI config calculation This commit copies the project CI config calculation from pipeline/chain to a new class. This will make it easier to use the same logic from other places. These changes are behind the feature flag "ci_project_pipeline_config_refactoring". With the feature flag removal, old places will be removed. --- .../gitlab/feature_available_usage.yml | 1 + ...ci_project_pipeline_config_refactoring.yml | 8 + ee/lib/ee/gitlab/ci/project_config.rb | 20 ++ ee/lib/gitlab/ci/project_config/compliance.rb | 39 ++++ ee/spec/lib/gitlab/ci/project_config_spec.rb | 105 +++++++++++ .../ci/pipeline/chain/config/content.rb | 23 ++- .../pipeline/chain/config/content/source.rb | 1 + lib/gitlab/ci/project_config.rb | 52 +++++ lib/gitlab/ci/project_config/auto_devops.rb | 28 +++ lib/gitlab/ci/project_config/bridge.rb | 19 ++ .../ci/project_config/external_project.rb | 45 +++++ lib/gitlab/ci/project_config/parameter.rb | 21 +++ lib/gitlab/ci/project_config/remote.rb | 21 +++ lib/gitlab/ci/project_config/repository.rb | 32 ++++ lib/gitlab/ci/project_config/source.rb | 41 ++++ .../ci/pipeline/chain/config/content_spec.rb | 14 +- .../ci/project_config/repository_spec.rb | 47 +++++ .../gitlab/ci/project_config/source_spec.rb | 23 +++ spec/lib/gitlab/ci/project_config_spec.rb | 177 ++++++++++++++++++ 19 files changed, 711 insertions(+), 6 deletions(-) create mode 100644 config/feature_flags/development/ci_project_pipeline_config_refactoring.yml create mode 100644 ee/lib/ee/gitlab/ci/project_config.rb create mode 100644 ee/lib/gitlab/ci/project_config/compliance.rb create mode 100644 ee/spec/lib/gitlab/ci/project_config_spec.rb create mode 100644 lib/gitlab/ci/project_config.rb create mode 100644 lib/gitlab/ci/project_config/auto_devops.rb create mode 100644 lib/gitlab/ci/project_config/bridge.rb create mode 100644 lib/gitlab/ci/project_config/external_project.rb create mode 100644 lib/gitlab/ci/project_config/parameter.rb create mode 100644 lib/gitlab/ci/project_config/remote.rb create mode 100644 lib/gitlab/ci/project_config/repository.rb create mode 100644 lib/gitlab/ci/project_config/source.rb create mode 100644 spec/lib/gitlab/ci/project_config/repository_spec.rb create mode 100644 spec/lib/gitlab/ci/project_config/source_spec.rb create mode 100644 spec/lib/gitlab/ci/project_config_spec.rb diff --git a/.rubocop_todo/gitlab/feature_available_usage.yml b/.rubocop_todo/gitlab/feature_available_usage.yml index 68cc91a18392..0daacdfe2b1f 100644 --- a/.rubocop_todo/gitlab/feature_available_usage.yml +++ b/.rubocop_todo/gitlab/feature_available_usage.yml @@ -140,6 +140,7 @@ Gitlab/FeatureAvailableUsage: - ee/lib/ee/gitlab/tree_summary.rb - ee/lib/gitlab/alert_management.rb - ee/lib/gitlab/ci/pipeline/chain/config/content/compliance.rb + - ee/lib/gitlab/ci/project_config/compliance.rb - ee/lib/gitlab/code_owners.rb - ee/lib/gitlab/incident_management.rb - ee/lib/gitlab/path_locks_finder.rb diff --git a/config/feature_flags/development/ci_project_pipeline_config_refactoring.yml b/config/feature_flags/development/ci_project_pipeline_config_refactoring.yml new file mode 100644 index 000000000000..0338b81caf71 --- /dev/null +++ b/config/feature_flags/development/ci_project_pipeline_config_refactoring.yml @@ -0,0 +1,8 @@ +--- +name: ci_project_pipeline_config_refactoring +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97240 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/372867 +milestone: '15.4' +type: development +group: group::pipeline authoring +default_enabled: false diff --git a/ee/lib/ee/gitlab/ci/project_config.rb b/ee/lib/ee/gitlab/ci/project_config.rb new file mode 100644 index 000000000000..d83f594e9ec5 --- /dev/null +++ b/ee/lib/ee/gitlab/ci/project_config.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module EE + module Gitlab + module Ci + module ProjectConfig + extend ::Gitlab::Utils::Override + + EE_SOURCES = [::Gitlab::Ci::ProjectConfig::Compliance].freeze + + private + + override :sources + def sources + EE_SOURCES + super + end + end + end + end +end diff --git a/ee/lib/gitlab/ci/project_config/compliance.rb b/ee/lib/gitlab/ci/project_config/compliance.rb new file mode 100644 index 000000000000..778fc529194a --- /dev/null +++ b/ee/lib/gitlab/ci/project_config/compliance.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class ProjectConfig + class Compliance < Gitlab::Ci::ProjectConfig::Source + def content + strong_memoize(:content) do + next unless available? + next unless pipeline_configuration_full_path.present? + next if pipeline_source_bridge + next if pipeline_source == :security_orchestration_policy + + path_file, path_project = pipeline_configuration_full_path.split('@', 2) + YAML.dump('include' => [{ 'project' => path_project, 'file' => path_file }]) + end + end + + def source + :compliance_source + end + + private + + def pipeline_configuration_full_path + strong_memoize(:pipeline_configuration_full_path) do + next unless project + + project.compliance_pipeline_configuration_full_path + end + end + + def available? + project.feature_available?(:evaluate_group_level_compliance_pipeline) + end + end + end + end +end diff --git a/ee/spec/lib/gitlab/ci/project_config_spec.rb b/ee/spec/lib/gitlab/ci/project_config_spec.rb new file mode 100644 index 000000000000..4be81f97efd9 --- /dev/null +++ b/ee/spec/lib/gitlab/ci/project_config_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Gitlab::Ci::ProjectConfig do + let(:project) { create(:project, ci_config_path: nil) } + let(:sha) { '123456' } + let(:content) { nil } + let(:source) { :push } + let(:bridge) { nil } + + let(:content_result) do + <<~CICONFIG + --- + include: + - project: compliance/hippa + file: ".compliance-gitlab-ci.yml" + CICONFIG + end + + subject(:config) do + described_class.new(project: project, sha: sha, + custom_content: content, pipeline_source: source, pipeline_source_bridge: bridge) + end + + shared_examples 'does not include compliance pipeline configuration content' do + it do + expect(config.source).not_to eq(:compliance_source) + expect(config.content).not_to eq(content_result) + end + end + + context 'when project has compliance label defined' do + let(:compliance_group) { create(:group, :private, name: "compliance") } + let(:compliance_project) { create(:project, namespace: compliance_group, name: "hippa") } + + context 'when feature is available' do + before do + stub_licensed_features(evaluate_group_level_compliance_pipeline: true) + end + + context 'when compliance pipeline configuration is defined' do + let(:framework) do + create(:compliance_framework, + namespace: compliance_group, + pipeline_configuration_full_path: ".compliance-gitlab-ci.yml@compliance/hippa") + end + + let!(:framework_project_setting) do + create(:compliance_framework_project_setting, project: project, compliance_management_framework: framework) + end + + it 'includes compliance pipeline configuration content' do + expect(config.source).to eq(:compliance_source) + expect(config.content).to eq(content_result) + end + + context 'when pipeline is downstream of a bridge' do + let(:bridge) { create(:ci_bridge) } + + it_behaves_like 'does not include compliance pipeline configuration content' + end + end + + context 'when compliance pipeline configuration is not defined' do + let(:framework) { create(:compliance_framework, namespace: compliance_group) } + let!(:framework_project_setting) do + create(:compliance_framework_project_setting, project: project, compliance_management_framework: framework) + end + + it_behaves_like 'does not include compliance pipeline configuration content' + end + + context 'when compliance pipeline configuration is empty' do + let(:framework) do + create(:compliance_framework, namespace: compliance_group, pipeline_configuration_full_path: '') + end + + let!(:framework_project_setting) do + create(:compliance_framework_project_setting, project: project, compliance_management_framework: framework) + end + + it_behaves_like 'does not include compliance pipeline configuration content' + end + end + + context 'when feature is not licensed' do + before do + stub_licensed_features(evaluate_group_level_compliance_pipeline: false) + end + + it_behaves_like 'does not include compliance pipeline configuration content' + end + end + + context 'when project does not have compliance label defined' do + context 'when feature is available' do + before do + stub_licensed_features(evaluate_group_level_compliance_pipeline: true) + end + + it_behaves_like 'does not include compliance pipeline configuration content' + end + end +end diff --git a/lib/gitlab/ci/pipeline/chain/config/content.rb b/lib/gitlab/ci/pipeline/chain/config/content.rb index 3c150ca26bb7..a14dec48619c 100644 --- a/lib/gitlab/ci/pipeline/chain/config/content.rb +++ b/lib/gitlab/ci/pipeline/chain/config/content.rb @@ -7,6 +7,7 @@ module Chain module Config class Content < Chain::Base include Chain::Helpers + include ::Gitlab::Utils::StrongMemoize SOURCES = [ Gitlab::Ci::Pipeline::Chain::Config::Content::Parameter, @@ -18,10 +19,10 @@ class Content < Chain::Base ].freeze def perform! - if config = find_config - @pipeline.build_pipeline_config(content: config.content) - @command.config_content = config.content - @pipeline.config_source = config.source + if pipeline_config&.exists? + @pipeline.build_pipeline_config(content: pipeline_config.content) + @command.config_content = pipeline_config.content + @pipeline.config_source = pipeline_config.source else error('Missing CI config file') end @@ -33,7 +34,19 @@ def break? private - def find_config + def pipeline_config + strong_memoize(:pipeline_config) do + next legacy_find_config if ::Feature.disabled?(:ci_project_pipeline_config_refactoring, project) + + ::Gitlab::Ci::ProjectConfig.new( + project: project, sha: @pipeline.sha, + custom_content: @command.content, + pipeline_source: @command.source, pipeline_source_bridge: @command.bridge + ) + end + end + + def legacy_find_config sources.each do |source| config = source.new(@pipeline, @command) return config if config.exists? diff --git a/lib/gitlab/ci/pipeline/chain/config/content/source.rb b/lib/gitlab/ci/pipeline/chain/config/content/source.rb index 8bc172f93d3b..69dca1568b65 100644 --- a/lib/gitlab/ci/pipeline/chain/config/content/source.rb +++ b/lib/gitlab/ci/pipeline/chain/config/content/source.rb @@ -6,6 +6,7 @@ module Pipeline module Chain module Config class Content + # When removing ci_project_pipeline_config_refactoring, this and its subclasses will be removed. class Source include Gitlab::Utils::StrongMemoize diff --git a/lib/gitlab/ci/project_config.rb b/lib/gitlab/ci/project_config.rb new file mode 100644 index 000000000000..ded6877ef291 --- /dev/null +++ b/lib/gitlab/ci/project_config.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + # Locates project CI config + class ProjectConfig + # The order of sources is important: + # - EE uses Compliance first since it must be used first if compliance templates are enabled. + # (see ee/lib/ee/gitlab/ci/project_config.rb) + # - Parameter is used by on-demand security scanning which passes the actual CI YAML to use as argument. + # - Bridge is used for downstream pipelines since the config is defined in the bridge job. If lower in priority, + # it would evaluate the project's YAML file instead. + # - Repository / ExternalProject / Remote: their order is not important between each other. + # - AutoDevops is used as default option if nothing else is found and if AutoDevops is enabled. + SOURCES = [ + ProjectConfig::Parameter, + ProjectConfig::Bridge, + ProjectConfig::Repository, + ProjectConfig::ExternalProject, + ProjectConfig::Remote, + ProjectConfig::AutoDevops + ].freeze + + def initialize(project:, sha:, custom_content: nil, pipeline_source: nil, pipeline_source_bridge: nil) + @config = find_config(project, sha, custom_content, pipeline_source, pipeline_source_bridge) + end + + delegate :content, :source, to: :@config, allow_nil: true + + def exists? + !!@config&.exists? + end + + private + + def find_config(project, sha, custom_content, pipeline_source, pipeline_source_bridge) + sources.each do |source| + config = source.new(project, sha, custom_content, pipeline_source, pipeline_source_bridge) + return config if config.exists? + end + + nil + end + + def sources + SOURCES + end + end + end +end + +Gitlab::Ci::ProjectConfig.prepend_mod_with('Gitlab::Ci::ProjectConfig') diff --git a/lib/gitlab/ci/project_config/auto_devops.rb b/lib/gitlab/ci/project_config/auto_devops.rb new file mode 100644 index 000000000000..c6905f480a2a --- /dev/null +++ b/lib/gitlab/ci/project_config/auto_devops.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class ProjectConfig + class AutoDevops < Source + def content + strong_memoize(:content) do + next unless project&.auto_devops_enabled? + + template = Gitlab::Template::GitlabCiYmlTemplate.find(template_name) + YAML.dump('include' => [{ 'template' => template.full_name }]) + end + end + + def source + :auto_devops_source + end + + private + + def template_name + 'Auto-DevOps' + end + end + end + end +end diff --git a/lib/gitlab/ci/project_config/bridge.rb b/lib/gitlab/ci/project_config/bridge.rb new file mode 100644 index 000000000000..c342ab2c215b --- /dev/null +++ b/lib/gitlab/ci/project_config/bridge.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class ProjectConfig + class Bridge < Source + def content + return unless pipeline_source_bridge + + pipeline_source_bridge.yaml_for_downstream + end + + def source + :bridge_source + end + end + end + end +end diff --git a/lib/gitlab/ci/project_config/external_project.rb b/lib/gitlab/ci/project_config/external_project.rb new file mode 100644 index 000000000000..0ed5d6fa2268 --- /dev/null +++ b/lib/gitlab/ci/project_config/external_project.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class ProjectConfig + class ExternalProject < Source + def content + strong_memoize(:content) do + next unless external_project_path? + + path_file, path_project, ref = extract_location_tokens + + config_location = { 'project' => path_project, 'file' => path_file } + config_location['ref'] = ref if ref.present? + + YAML.dump('include' => [config_location]) + end + end + + def source + :external_project_source + end + + private + + # Example: path/to/.gitlab-ci.yml@another-group/another-project + def external_project_path? + ci_config_path =~ /\A.+(yml|yaml)@.+\z/ + end + + # Example: path/to/.gitlab-ci.yml@another-group/another-project:refname + def extract_location_tokens + path_file, path_project = ci_config_path.split('@', 2) + + if path_project.include? ":" + project, ref = path_project.split(':', 2) + [path_file, project, ref] + else + [path_file, path_project] + end + end + end + end + end +end diff --git a/lib/gitlab/ci/project_config/parameter.rb b/lib/gitlab/ci/project_config/parameter.rb new file mode 100644 index 000000000000..69e699c27f10 --- /dev/null +++ b/lib/gitlab/ci/project_config/parameter.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class ProjectConfig + class Parameter < Source + def content + strong_memoize(:content) do + next unless custom_content.present? + + custom_content + end + end + + def source + :parameter_source + end + end + end + end +end diff --git a/lib/gitlab/ci/project_config/remote.rb b/lib/gitlab/ci/project_config/remote.rb new file mode 100644 index 000000000000..cf1292706d23 --- /dev/null +++ b/lib/gitlab/ci/project_config/remote.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class ProjectConfig + class Remote < Source + def content + strong_memoize(:content) do + next unless ci_config_path =~ URI::DEFAULT_PARSER.make_regexp(%w[http https]) + + YAML.dump('include' => [{ 'remote' => ci_config_path }]) + end + end + + def source + :remote_source + end + end + end + end +end diff --git a/lib/gitlab/ci/project_config/repository.rb b/lib/gitlab/ci/project_config/repository.rb new file mode 100644 index 000000000000..435ad4d42fe6 --- /dev/null +++ b/lib/gitlab/ci/project_config/repository.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class ProjectConfig + class Repository < Source + def content + strong_memoize(:content) do + next unless file_in_repository? + + YAML.dump('include' => [{ 'local' => ci_config_path }]) + end + end + + def source + :repository_source + end + + private + + def file_in_repository? + return unless project + return unless sha + + project.repository.gitlab_ci_yml_for(sha, ci_config_path).present? + rescue GRPC::NotFound, GRPC::Internal + nil + end + end + end + end +end diff --git a/lib/gitlab/ci/project_config/source.rb b/lib/gitlab/ci/project_config/source.rb new file mode 100644 index 000000000000..ebe5728163b0 --- /dev/null +++ b/lib/gitlab/ci/project_config/source.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class ProjectConfig + class Source + include Gitlab::Utils::StrongMemoize + + def initialize(project, sha, custom_content, pipeline_source, pipeline_source_bridge) + @project = project + @sha = sha + @custom_content = custom_content + @pipeline_source = pipeline_source + @pipeline_source_bridge = pipeline_source_bridge + end + + def exists? + strong_memoize(:exists) do + content.present? + end + end + + def content + raise NotImplementedError + end + + def source + raise NotImplementedError + end + + private + + attr_reader :project, :sha, :custom_content, :pipeline_source, :pipeline_source_bridge + + def ci_config_path + @ci_config_path ||= project.ci_config_path_or_default + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb index e0d656f456ed..f451bd6bfef5 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb @@ -11,7 +11,9 @@ subject { described_class.new(pipeline, command) } - describe '#perform!' do + # TODO: change this to `describe` and remove rubocop-disable + # when removing the FF ci_project_pipeline_config_refactoring + shared_context '#perform!' do # rubocop:disable RSpec/ContextWording context 'when bridge job is passed in as parameter' do let(:ci_config_path) { nil } let(:bridge) { create(:ci_bridge) } @@ -201,4 +203,14 @@ end end end + + it_behaves_like '#perform!' + + context 'when the FF ci_project_pipeline_config_refactoring is disabled' do + before do + stub_feature_flags(ci_project_pipeline_config_refactoring: false) + end + + it_behaves_like '#perform!' + end end diff --git a/spec/lib/gitlab/ci/project_config/repository_spec.rb b/spec/lib/gitlab/ci/project_config/repository_spec.rb new file mode 100644 index 000000000000..2105b691d9ee --- /dev/null +++ b/spec/lib/gitlab/ci/project_config/repository_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::ProjectConfig::Repository do + let(:project) { create(:project, :custom_repo, files: files) } + let(:sha) { project.repository.head_commit.sha } + let(:files) { { 'README.md' => 'hello' } } + + subject(:config) { described_class.new(project, sha, nil, nil, nil) } + + describe '#content' do + subject(:content) { config.content } + + context 'when file is in repository' do + let(:config_content_result) do + <<~CICONFIG + --- + include: + - local: ".gitlab-ci.yml" + CICONFIG + end + + let(:files) { { '.gitlab-ci.yml' => 'content' } } + + it { is_expected.to eq(config_content_result) } + end + + context 'when file is not in repository' do + it { is_expected.to be_nil } + end + + context 'when Gitaly raises error' do + before do + allow(project.repository).to receive(:gitlab_ci_yml_for).and_raise(GRPC::Internal) + end + + it { is_expected.to be_nil } + end + end + + describe '#source' do + subject { config.source } + + it { is_expected.to eq(:repository_source) } + end +end diff --git a/spec/lib/gitlab/ci/project_config/source_spec.rb b/spec/lib/gitlab/ci/project_config/source_spec.rb new file mode 100644 index 000000000000..dda5c7cdce83 --- /dev/null +++ b/spec/lib/gitlab/ci/project_config/source_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::ProjectConfig::Source do + let_it_be(:custom_config_class) { Class.new(described_class) } + let_it_be(:project) { build_stubbed(:project) } + let_it_be(:sha) { '123456' } + + subject(:custom_config) { custom_config_class.new(project, sha, nil, nil, nil) } + + describe '#content' do + subject(:content) { custom_config.content } + + it { expect { content }.to raise_error(NotImplementedError) } + end + + describe '#source' do + subject(:source) { custom_config.source } + + it { expect { source }.to raise_error(NotImplementedError) } + end +end diff --git a/spec/lib/gitlab/ci/project_config_spec.rb b/spec/lib/gitlab/ci/project_config_spec.rb new file mode 100644 index 000000000000..c4b179c9ef53 --- /dev/null +++ b/spec/lib/gitlab/ci/project_config_spec.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::ProjectConfig do + let(:project) { create(:project, :empty_repo, ci_config_path: ci_config_path) } + let(:sha) { '123456' } + let(:content) { nil } + let(:source) { :push } + let(:bridge) { nil } + + subject(:config) do + described_class.new(project: project, sha: sha, + custom_content: content, pipeline_source: source, pipeline_source_bridge: bridge) + end + + context 'when bridge job is passed in as parameter' do + let(:ci_config_path) { nil } + let(:bridge) { create(:ci_bridge) } + + before do + allow(bridge).to receive(:yaml_for_downstream).and_return('the-yaml') + end + + it 'returns the content already available in command' do + expect(config.source).to eq(:bridge_source) + expect(config.content).to eq('the-yaml') + end + end + + context 'when config is defined in a custom path in the repository' do + let(:ci_config_path) { 'path/to/config.yml' } + let(:config_content_result) do + <<~CICONFIG + --- + include: + - local: #{ci_config_path} + CICONFIG + end + + before do + allow(project.repository) + .to receive(:gitlab_ci_yml_for) + .with(sha, ci_config_path) + .and_return('the-content') + end + + it 'returns root config including the local custom file' do + expect(config.source).to eq(:repository_source) + expect(config.content).to eq(config_content_result) + end + end + + context 'when config is defined remotely' do + let(:ci_config_path) { 'http://example.com/path/to/ci/config.yml' } + let(:config_content_result) do + <<~CICONFIG + --- + include: + - remote: #{ci_config_path} + CICONFIG + end + + it 'returns root config including the remote config' do + expect(config.source).to eq(:remote_source) + expect(config.content).to eq(config_content_result) + end + end + + context 'when config is defined in a separate repository' do + let(:ci_config_path) { 'path/to/.gitlab-ci.yml@another-group/another-repo' } + let(:config_content_result) do + <<~CICONFIG + --- + include: + - project: another-group/another-repo + file: path/to/.gitlab-ci.yml + CICONFIG + end + + it 'returns root config including the path to another repository' do + expect(config.source).to eq(:external_project_source) + expect(config.content).to eq(config_content_result) + end + + context 'when path specifies a refname' do + let(:ci_config_path) { 'path/to/.gitlab-ci.yml@another-group/another-repo:refname' } + let(:config_content_result) do + <<~CICONFIG + --- + include: + - project: another-group/another-repo + file: path/to/.gitlab-ci.yml + ref: refname + CICONFIG + end + + it 'returns root config including the path and refname to another repository' do + expect(config.source).to eq(:external_project_source) + expect(config.content).to eq(config_content_result) + end + end + end + + context 'when config is defined in the default .gitlab-ci.yml' do + let(:ci_config_path) { nil } + let(:config_content_result) do + <<~CICONFIG + --- + include: + - local: ".gitlab-ci.yml" + CICONFIG + end + + before do + allow(project.repository) + .to receive(:gitlab_ci_yml_for) + .with(sha, '.gitlab-ci.yml') + .and_return('the-content') + end + + it 'returns root config including the canonical CI config file' do + expect(config.source).to eq(:repository_source) + expect(config.content).to eq(config_content_result) + end + end + + context 'when config is the Auto-Devops template' do + let(:ci_config_path) { nil } + let(:config_content_result) do + <<~CICONFIG + --- + include: + - template: Auto-DevOps.gitlab-ci.yml + CICONFIG + end + + before do + allow(project).to receive(:auto_devops_enabled?).and_return(true) + end + + it 'returns root config including the auto-devops template' do + expect(config.source).to eq(:auto_devops_source) + expect(config.content).to eq(config_content_result) + end + end + + context 'when config is passed as a parameter' do + let(:source) { :ondemand_dast_scan } + let(:ci_config_path) { nil } + let(:content) do + <<~CICONFIG + --- + stages: + - dast + CICONFIG + end + + it 'returns the parameter content' do + expect(config.source).to eq(:parameter_source) + expect(config.content).to eq(content) + end + end + + context 'when config is not defined anywhere' do + let(:ci_config_path) { nil } + + before do + allow(project).to receive(:auto_devops_enabled?).and_return(false) + end + + it 'returns nil' do + expect(config.source).to be_nil + expect(config.content).to be_nil + end + end +end -- GitLab