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>2021-11-18 16:16:36 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-11-18 16:16:36 +0300
commit311b0269b4eb9839fa63f80c8d7a58f32b8138a0 (patch)
tree07e7870bca8aed6d61fdcc810731c50d2c40af47 /spec/lib/gitlab/ci
parent27909cef6c4170ed9205afa7426b8d3de47cbb0c (diff)
Add latest changes from gitlab-org/gitlab@14-5-stable-eev14.5.0-rc42
Diffstat (limited to 'spec/lib/gitlab/ci')
-rw-r--r--spec/lib/gitlab/ci/artifact_file_reader_spec.rb11
-rw-r--r--spec/lib/gitlab/ci/artifacts/metrics_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/build/auto_retry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/config/entry/processable_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/extendable_spec.rb44
-rw-r--r--spec/lib/gitlab/ci/config/external/processor_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/external/rules_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb90
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb56
-rw-r--r--spec/lib/gitlab/ci/reports/security/report_spec.rb22
-rw-r--r--spec/lib/gitlab/ci/reports/security/reports_spec.rb21
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb30
-rw-r--r--spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb65
-rw-r--r--spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb20
-rw-r--r--spec/lib/gitlab/ci/templates/kaniko_gitlab_ci_yaml_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/archive_spec.rb169
-rw-r--r--spec/lib/gitlab/ci/trace/metrics_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/trace_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/variables/builder_spec.rb38
-rw-r--r--spec/lib/gitlab/ci/variables/collection_spec.rb482
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb58
26 files changed, 798 insertions, 461 deletions
diff --git a/spec/lib/gitlab/ci/artifact_file_reader_spec.rb b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb
index 83a37655ea9..e982f0eb015 100644
--- a/spec/lib/gitlab/ci/artifact_file_reader_spec.rb
+++ b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb
@@ -18,17 +18,6 @@ RSpec.describe Gitlab::Ci::ArtifactFileReader do
expect(YAML.safe_load(subject).keys).to contain_exactly('rspec', 'time', 'custom')
end
- context 'when FF ci_new_artifact_file_reader is disabled' do
- before do
- stub_feature_flags(ci_new_artifact_file_reader: false)
- end
-
- it 'returns the content at the path' do
- is_expected.to be_present
- expect(YAML.safe_load(subject).keys).to contain_exactly('rspec', 'time', 'custom')
- end
- end
-
context 'when path does not exist' do
let(:path) { 'file/does/not/exist.txt' }
let(:expected_error) do
diff --git a/spec/lib/gitlab/ci/artifacts/metrics_spec.rb b/spec/lib/gitlab/ci/artifacts/metrics_spec.rb
index 3a2095498ec..0ce76285b03 100644
--- a/spec/lib/gitlab/ci/artifacts/metrics_spec.rb
+++ b/spec/lib/gitlab/ci/artifacts/metrics_spec.rb
@@ -10,9 +10,9 @@ RSpec.describe Gitlab::Ci::Artifacts::Metrics, :prometheus do
let(:counter) { metrics.send(:destroyed_artifacts_counter) }
it 'increments a single counter' do
- subject.increment_destroyed_artifacts(10)
- subject.increment_destroyed_artifacts(20)
- subject.increment_destroyed_artifacts(30)
+ subject.increment_destroyed_artifacts_count(10)
+ subject.increment_destroyed_artifacts_count(20)
+ subject.increment_destroyed_artifacts_count(30)
expect(counter.get).to eq 60
expect(counter.values.count).to eq 1
diff --git a/spec/lib/gitlab/ci/build/auto_retry_spec.rb b/spec/lib/gitlab/ci/build/auto_retry_spec.rb
index fc5999d59ac..9ff9200322e 100644
--- a/spec/lib/gitlab/ci/build/auto_retry_spec.rb
+++ b/spec/lib/gitlab/ci/build/auto_retry_spec.rb
@@ -25,6 +25,8 @@ RSpec.describe Gitlab::Ci::Build::AutoRetry do
"quota is exceeded" | 0 | { max: 2 } | :ci_quota_exceeded | false
"no matching runner" | 0 | { max: 2 } | :no_matching_runner | false
"missing dependencies" | 0 | { max: 2 } | :missing_dependency_failure | false
+ "forward deployment failure" | 0 | { max: 2 } | :forward_deployment_failure | false
+ "environment creation failure" | 0 | { max: 2 } | :environment_creation_failure | false
end
with_them do
diff --git a/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb b/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb
index 86dd5569a96..f192862c1c4 100644
--- a/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules/rule/clause/exists_spec.rb
@@ -3,10 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do
- describe '#satisfied_by?' do
- let(:pipeline) { build(:ci_pipeline, project: project, sha: project.repository.head_commit.sha) }
-
- subject { described_class.new(globs).satisfied_by?(pipeline, nil) }
+ shared_examples 'an exists rule with a context' do
+ subject { described_class.new(globs).satisfied_by?(pipeline, context) }
it_behaves_like 'a glob matching rule' do
let(:project) { create(:project, :custom_repo, files: files) }
@@ -24,4 +22,26 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do
it { is_expected.to be_truthy }
end
end
+
+ describe '#satisfied_by?' do
+ let(:pipeline) { build(:ci_pipeline, project: project, sha: project.repository.head_commit.sha) }
+
+ context 'when context is Build::Context::Build' do
+ it_behaves_like 'an exists rule with a context' do
+ let(:context) { Gitlab::Ci::Build::Context::Build.new(pipeline, sha: 'abc1234') }
+ end
+ end
+
+ context 'when context is Build::Context::Global' do
+ it_behaves_like 'an exists rule with a context' do
+ let(:context) { Gitlab::Ci::Build::Context::Global.new(pipeline, yaml_variables: {}) }
+ end
+ end
+
+ context 'when context is Config::External::Context' do
+ it_behaves_like 'an exists rule with a context' do
+ let(:context) { Gitlab::Ci::Config::External::Context.new(project: project, sha: project.repository.tree.sha) }
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb
index b99048e2c18..0505b17ea91 100644
--- a/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/include/rules/rule_spec.rb
@@ -5,7 +5,7 @@ require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule do
let(:factory) do
Gitlab::Config::Entry::Factory.new(described_class)
- .value(config)
+ .value(config)
end
subject(:entry) { factory.create! }
@@ -25,6 +25,12 @@ RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule do
it { is_expected.to be_valid }
end
+ context 'when specifying an exists: clause' do
+ let(:config) { { exists: './this.md' } }
+
+ it { is_expected.to be_valid }
+ end
+
context 'using a list of multiple expressions' do
let(:config) { { if: ['$MY_VAR == "this"', '$YOUR_VAR == "that"'] } }
@@ -86,5 +92,13 @@ RSpec.describe Gitlab::Ci::Config::Entry::Include::Rules::Rule do
expect(subject).to eq(if: '$THIS || $THAT')
end
end
+
+ context 'when specifying an exists: clause' do
+ let(:config) { { exists: './test.md' } }
+
+ it 'returns the config' do
+ expect(subject).to eq(exists: './test.md')
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
index b872f6644a2..c9c28e2eb8b 100644
--- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb
@@ -33,6 +33,14 @@ RSpec.describe Gitlab::Ci::Config::Entry::Processable do
end
end
+ context 'when job name is more than 255' do
+ let(:entry) { node_class.new(config, name: ('a' * 256).to_sym) }
+
+ it 'shows a validation error' do
+ expect(entry.errors).to include "job name is too long (maximum is 255 characters)"
+ end
+ end
+
context 'when job name is empty' do
let(:entry) { node_class.new(config, name: ''.to_sym) }
diff --git a/spec/lib/gitlab/ci/config/extendable_spec.rb b/spec/lib/gitlab/ci/config/extendable_spec.rb
index 481f55d790e..2fc009569fc 100644
--- a/spec/lib/gitlab/ci/config/extendable_spec.rb
+++ b/spec/lib/gitlab/ci/config/extendable_spec.rb
@@ -73,6 +73,50 @@ RSpec.describe Gitlab::Ci::Config::Extendable do
end
end
+ context 'when the job tries to delete an extension key' do
+ let(:hash) do
+ {
+ something: {
+ script: 'deploy',
+ only: { variables: %w[$SOMETHING] }
+ },
+
+ test1: {
+ extends: 'something',
+ script: 'ls',
+ only: {}
+ },
+
+ test2: {
+ extends: 'something',
+ script: 'ls',
+ only: nil
+ }
+ }
+ end
+
+ it 'deletes the key if assigned to null' do
+ expect(subject.to_hash).to eq(
+ something: {
+ script: 'deploy',
+ only: { variables: %w[$SOMETHING] }
+ },
+ test1: {
+ extends: 'something',
+ script: 'ls',
+ only: {
+ variables: %w[$SOMETHING]
+ }
+ },
+ test2: {
+ extends: 'something',
+ script: 'ls',
+ only: nil
+ }
+ )
+ end
+ end
+
context 'when a hash uses recursive extensions' do
let(:hash) do
{
diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb
index c2f28253f54..2e9e6f95071 100644
--- a/spec/lib/gitlab/ci/config/external/processor_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb
@@ -406,7 +406,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
context 'when rules defined' do
context 'when a rule is invalid' do
let(:values) do
- { include: [{ local: 'builds.yml', rules: [{ exists: ['$MY_VAR'] }] }] }
+ { include: [{ local: 'builds.yml', rules: [{ changes: ['$MY_VAR'] }] }] }
end
it 'raises IncludeError' do
diff --git a/spec/lib/gitlab/ci/config/external/rules_spec.rb b/spec/lib/gitlab/ci/config/external/rules_spec.rb
index 9a5c29befa2..1e42cb30ae7 100644
--- a/spec/lib/gitlab/ci/config/external/rules_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/rules_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::External::Rules do
let(:rule_hashes) {}
@@ -32,6 +32,26 @@ RSpec.describe Gitlab::Ci::Config::External::Rules do
end
end
+ context 'when there is a rule with exists' do
+ let(:project) { create(:project, :repository) }
+ let(:context) { double(project: project, sha: project.repository.tree.sha, top_level_worktree_paths: ['test.md']) }
+ let(:rule_hashes) { [{ exists: 'Dockerfile' }] }
+
+ context 'when the file does not exist' do
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when the file exists' do
+ let(:context) { double(project: project, sha: project.repository.tree.sha, top_level_worktree_paths: ['Dockerfile']) }
+
+ before do
+ project.repository.create_file(project.owner, 'Dockerfile', "commit", message: 'test', branch_name: "master")
+ end
+
+ it { is_expected.to eq(true) }
+ end
+ end
+
context 'when there is a rule with if and when' do
let(:rule_hashes) { [{ if: '$MY_VAR == "hello"', when: 'on_success' }] }
@@ -41,12 +61,12 @@ RSpec.describe Gitlab::Ci::Config::External::Rules do
end
end
- context 'when there is a rule with exists' do
- let(:rule_hashes) { [{ exists: ['$MY_VAR'] }] }
+ context 'when there is a rule with changes' do
+ let(:rule_hashes) { [{ changes: ['$MY_VAR'] }] }
it 'raises an error' do
expect { result }.to raise_error(described_class::InvalidIncludeRulesError,
- 'invalid include rule: {:exists=>["$MY_VAR"]}')
+ 'invalid include rule: {:changes=>["$MY_VAR"]}')
end
end
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index 3ec4519748f..1b3e8a2ce4a 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Gitlab::Ci::Config do
end
let(:config) do
- described_class.new(yml, project: nil, sha: nil, user: nil)
+ described_class.new(yml, project: nil, pipeline: nil, sha: nil, user: nil)
end
context 'when config is valid' do
@@ -286,9 +286,12 @@ RSpec.describe Gitlab::Ci::Config do
end
context "when using 'include' directive" do
- let(:group) { create(:group) }
+ let_it_be(:group) { create(:group) }
+
let(:project) { create(:project, :repository, group: group) }
let(:main_project) { create(:project, :repository, :public, group: group) }
+ let(:pipeline) { build(:ci_pipeline, project: project) }
+
let(:remote_location) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:local_location) { 'spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml' }
@@ -327,7 +330,7 @@ RSpec.describe Gitlab::Ci::Config do
end
let(:config) do
- described_class.new(gitlab_ci_yml, project: project, sha: '12345', user: user)
+ described_class.new(gitlab_ci_yml, project: project, pipeline: pipeline, sha: '12345', user: user)
end
before do
@@ -594,7 +597,7 @@ RSpec.describe Gitlab::Ci::Config do
job1: {
script: ["echo 'hello from main file'"],
variables: {
- VARIABLE_DEFINED_IN_MAIN_FILE: 'some value'
+ VARIABLE_DEFINED_IN_MAIN_FILE: 'some value'
}
}
})
@@ -725,26 +728,91 @@ RSpec.describe Gitlab::Ci::Config do
end
context "when an 'include' has rules" do
+ context "when the rule is an if" do
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - local: #{local_location}
+ rules:
+ - if: $CI_PROJECT_ID == "#{project_id}"
+ image: ruby:2.7
+ HEREDOC
+ end
+
+ context 'when the rules condition is satisfied' do
+ let(:project_id) { project.id }
+
+ it 'includes the file' do
+ expect(config.to_hash).to include(local_location_hash)
+ end
+ end
+
+ context 'when the rules condition is satisfied' do
+ let(:project_id) { non_existing_record_id }
+
+ it 'does not include the file' do
+ expect(config.to_hash).not_to include(local_location_hash)
+ end
+ end
+ end
+
+ context "when the rule is an exists" do
+ let(:gitlab_ci_yml) do
+ <<~HEREDOC
+ include:
+ - local: #{local_location}
+ rules:
+ - exists: "#{filename}"
+ image: ruby:2.7
+ HEREDOC
+ end
+
+ before do
+ project.repository.create_file(
+ project.creator,
+ 'my_builds.yml',
+ local_file_content,
+ message: 'Add my_builds.yml',
+ branch_name: '12345'
+ )
+ end
+
+ context 'when the exists file does not exist' do
+ let(:filename) { 'not_a_real_file.md' }
+
+ it 'does not include the file' do
+ expect(config.to_hash).not_to include(local_location_hash)
+ end
+ end
+
+ context 'when the exists file does exist' do
+ let(:filename) { 'my_builds.yml' }
+
+ it 'does include the file' do
+ expect(config.to_hash).to include(local_location_hash)
+ end
+ end
+ end
+ end
+
+ context "when an 'include' has rules with a pipeline variable" do
let(:gitlab_ci_yml) do
<<~HEREDOC
include:
- local: #{local_location}
rules:
- - if: $CI_PROJECT_ID == "#{project_id}"
- image: ruby:2.7
+ - if: $CI_COMMIT_SHA == "#{project.commit.sha}"
HEREDOC
end
- context 'when the rules condition is satisfied' do
- let(:project_id) { project.id }
-
+ context 'when a pipeline is passed' do
it 'includes the file' do
expect(config.to_hash).to include(local_location_hash)
end
end
- context 'when the rules condition is satisfied' do
- let(:project_id) { non_existing_record_id }
+ context 'when a pipeline is not passed' do
+ let(:pipeline) { nil }
it 'does not include the file' do
expect(config.to_hash).not_to include(local_location_hash)
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
index 16517b39a45..cf21c98dbd5 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
@@ -83,7 +83,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
end
end
- it 'respects the defined payload schema' do
+ it 'respects the defined payload schema', :saas do
expect(::Gitlab::HTTP).to receive(:post) do |_url, params|
expect(params[:body]).to match_schema('/external_validation')
expect(params[:timeout]).to eq(described_class::DEFAULT_VALIDATION_REQUEST_TIMEOUT)
diff --git a/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb
index c52994fc6a2..5b0917c5c6f 100644
--- a/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/quota/deployments_spec.rb
@@ -3,9 +3,9 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Quota::Deployments do
- let_it_be(:namespace) { create(:namespace) }
- let_it_be(:default_plan, reload: true) { create(:default_plan) }
- let_it_be(:project, reload: true) { create(:project, :repository, namespace: namespace) }
+ let_it_be_with_refind(:namespace) { create(:namespace) }
+ let_it_be_with_reload(:default_plan) { create(:default_plan) }
+ let_it_be_with_reload(:project) { create(:project, :repository, namespace: namespace) }
let_it_be(:plan_limits) { create(:plan_limits, plan: default_plan) }
let(:pipeline) { build_stubbed(:ci_pipeline, project: project) }
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 3aa6b2e3c05..e2b64e65938 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
- let_it_be(:project) { create(:project, :repository) }
+ let_it_be_with_reload(:project) { create(:project, :repository) }
let_it_be(:head_sha) { project.repository.head_commit.id }
let(:pipeline) { build(:ci_empty_pipeline, project: project, sha: head_sha) }
@@ -13,7 +13,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
let(:previous_stages) { [] }
let(:current_stage) { double(seeds_names: [attributes[:name]]) }
- let(:seed_build) { described_class.new(seed_context, attributes, previous_stages, current_stage) }
+ let(:seed_build) { described_class.new(seed_context, attributes, previous_stages + [current_stage]) }
describe '#attributes' do
subject { seed_build.attributes }
@@ -393,12 +393,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
describe '#to_resource' do
subject { seed_build.to_resource }
- context 'when job is not a bridge' do
+ context 'when job is Ci::Build' do
it { is_expected.to be_a(::Ci::Build) }
it { is_expected.to be_valid }
shared_examples_for 'deployment job' do
it 'returns a job with deployment' do
+ expect { subject }.to change { Environment.count }.by(1)
+
expect(subject.deployment).not_to be_nil
expect(subject.deployment.deployable).to eq(subject)
expect(subject.deployment.environment.name).to eq(expected_environment_name)
@@ -413,6 +415,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
shared_examples_for 'ensures environment existence' do
it 'has environment' do
+ expect { subject }.to change { Environment.count }.by(1)
+
expect(subject).to be_has_environment
expect(subject.environment).to eq(environment_name)
expect(subject.metadata.expanded_environment_name).to eq(expected_environment_name)
@@ -422,6 +426,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
shared_examples_for 'ensures environment inexistence' do
it 'does not have environment' do
+ expect { subject }.not_to change { Environment.count }
+
expect(subject).not_to be_has_environment
expect(subject.environment).to be_nil
expect(subject.metadata&.expanded_environment_name).to be_nil
@@ -1212,14 +1218,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
]
end
- context 'when FF :variable_inside_variable is enabled' do
- before do
- stub_feature_flags(variable_inside_variable: [project])
- end
-
- it "does not have errors" do
- expect(subject.errors).to be_empty
- end
+ it "does not have errors" do
+ expect(subject.errors).to be_empty
end
end
@@ -1232,36 +1232,20 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
]
end
- context 'when FF :variable_inside_variable is disabled' do
- before do
- stub_feature_flags(variable_inside_variable: false)
- end
-
- it "does not have errors" do
- expect(subject.errors).to be_empty
- end
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ 'rspec: circular variable reference detected: ["A", "B", "C"]')
end
- context 'when FF :variable_inside_variable is enabled' do
- before do
- stub_feature_flags(variable_inside_variable: [project])
- end
+ context 'with job:rules:[if:]' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$C != null', when: 'always' }] } }
- it "returns an error" do
- expect(subject.errors).to contain_exactly(
- 'rspec: circular variable reference detected: ["A", "B", "C"]')
+ it "included? does not raise" do
+ expect { subject.included? }.not_to raise_error
end
- context 'with job:rules:[if:]' do
- let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$C != null', when: 'always' }] } }
-
- it "included? does not raise" do
- expect { subject.included? }.not_to raise_error
- end
-
- it "included? returns true" do
- expect(subject.included?).to eq(true)
- end
+ it "included? returns true" do
+ expect(subject.included?).to eq(true)
end
end
end
diff --git a/spec/lib/gitlab/ci/reports/security/report_spec.rb b/spec/lib/gitlab/ci/reports/security/report_spec.rb
index 5a85c3f19fc..a8b962ee970 100644
--- a/spec/lib/gitlab/ci/reports/security/report_spec.rb
+++ b/spec/lib/gitlab/ci/reports/security/report_spec.rb
@@ -221,4 +221,26 @@ RSpec.describe Gitlab::Ci::Reports::Security::Report do
end
end
end
+
+ describe '#has_signatures?' do
+ let(:finding) { create(:ci_reports_security_finding, signatures: signatures) }
+
+ subject { report.has_signatures? }
+
+ before do
+ report.add_finding(finding)
+ end
+
+ context 'when the findings of the report does not have signatures' do
+ let(:signatures) { [] }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when the findings of the report have signatures' do
+ let(:signatures) { [instance_double(Gitlab::Ci::Reports::Security::FindingSignature)] }
+
+ it { is_expected.to be_truthy }
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/reports/security/reports_spec.rb b/spec/lib/gitlab/ci/reports/security/reports_spec.rb
index 9b1e02f1418..79eee642552 100644
--- a/spec/lib/gitlab/ci/reports/security/reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/security/reports_spec.rb
@@ -54,11 +54,12 @@ RSpec.describe Gitlab::Ci::Reports::Security::Reports do
end
describe "#violates_default_policy_against?" do
- let(:high_severity_dast) { build(:ci_reports_security_finding, severity: 'high', report_type: :dast) }
+ let(:high_severity_dast) { build(:ci_reports_security_finding, severity: 'high', report_type: 'dast') }
let(:vulnerabilities_allowed) { 0 }
let(:severity_levels) { %w(critical high) }
+ let(:vulnerability_states) { %w(newly_detected)}
- subject { security_reports.violates_default_policy_against?(target_reports, vulnerabilities_allowed, severity_levels) }
+ subject { security_reports.violates_default_policy_against?(target_reports, vulnerabilities_allowed, severity_levels, vulnerability_states) }
before do
security_reports.get_report('sast', artifact).add_finding(high_severity_dast)
@@ -108,6 +109,22 @@ RSpec.describe Gitlab::Ci::Reports::Security::Reports do
it { is_expected.to be(false) }
end
+
+ context 'with related report_types' do
+ let(:report_types) { %w(dast sast) }
+
+ subject { security_reports.violates_default_policy_against?(target_reports, vulnerabilities_allowed, severity_levels, vulnerability_states, report_types) }
+
+ it { is_expected.to be(true) }
+ end
+
+ context 'with unrelated report_types' do
+ let(:report_types) { %w(dependency_scanning sast) }
+
+ subject { security_reports.violates_default_policy_against?(target_reports, vulnerabilities_allowed, severity_levels, vulnerability_states, report_types) }
+
+ it { is_expected.to be(false) }
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
index d377cf0c735..789f694b4b4 100644
--- a/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/Jobs/deploy_gitlab_ci_yaml_spec.rb
@@ -27,9 +27,9 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do
end
describe 'the created pipeline' do
- let(:project) { create(:project, :repository) }
- let(:user) { project.owner }
+ let_it_be(:project, refind: true) { create(:project, :repository) }
+ let(:user) { project.owner }
let(:default_branch) { 'master' }
let(:pipeline_ref) { default_branch }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
@@ -43,23 +43,23 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do
allow(project).to receive(:default_branch).and_return(default_branch)
end
- context 'with no cluster' do
+ context 'with no cluster or agent' do
it 'does not create any kubernetes deployment jobs' do
expect(build_names).to eq %w(placeholder)
end
end
context 'with only a disabled cluster' do
- let!(:cluster) { create(:cluster, :project, :provided_by_gcp, enabled: false, projects: [project]) }
+ before do
+ create(:cluster, :project, :provided_by_gcp, enabled: false, projects: [project])
+ end
it 'does not create any kubernetes deployment jobs' do
expect(build_names).to eq %w(placeholder)
end
end
- context 'with an active cluster' do
- let!(:cluster) { create(:cluster, :project, :provided_by_gcp, projects: [project]) }
-
+ shared_examples_for 'pipeline with deployment jobs' do
context 'on master' do
it 'by default' do
expect(build_names).to include('production')
@@ -218,5 +218,21 @@ RSpec.describe 'Jobs/Deploy.gitlab-ci.yml' do
end
end
end
+
+ context 'with an agent' do
+ before do
+ create(:cluster_agent, project: project)
+ end
+
+ it_behaves_like 'pipeline with deployment jobs'
+ end
+
+ context 'with a cluster' do
+ before do
+ create(:cluster, :project, :provided_by_gcp, projects: [project])
+ end
+
+ it_behaves_like 'pipeline with deployment jobs'
+ end
end
end
diff --git a/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb
new file mode 100644
index 00000000000..b9256ece78b
--- /dev/null
+++ b/spec/lib/gitlab/ci/templates/Jobs/sast_iac_gitlab_ci_yaml_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Jobs/SAST-IaC.latest.gitlab-ci.yml' do
+ subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Jobs/SAST-IaC.latest') }
+
+ describe 'the created pipeline' do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { project.owner }
+
+ let(:default_branch) { 'main' }
+ let(:pipeline_ref) { default_branch }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_ref) }
+ let(:pipeline) { service.execute!(:push).payload }
+ let(:build_names) { pipeline.builds.pluck(:name) }
+
+ before do
+ stub_ci_pipeline_yaml_file(template.content)
+ allow_next_instance_of(Ci::BuildScheduleWorker) do |instance|
+ allow(instance).to receive(:perform).and_return(true)
+ end
+ allow(project).to receive(:default_branch).and_return(default_branch)
+ end
+
+ context 'on feature branch' do
+ let(:pipeline_ref) { 'feature' }
+
+ it 'creates the kics-iac-sast job' do
+ expect(build_names).to contain_exactly('kics-iac-sast')
+ end
+ end
+
+ context 'on merge request' do
+ let(:service) { MergeRequests::CreatePipelineService.new(project: project, current_user: user) }
+ let(:merge_request) { create(:merge_request, :simple, source_project: project) }
+ let(:pipeline) { service.execute(merge_request).payload }
+
+ it 'has no jobs' do
+ expect(pipeline).to be_merge_request_event
+ expect(build_names).to be_empty
+ end
+ end
+
+ context 'SAST_DISABLED is set' do
+ before do
+ create(:ci_variable, key: 'SAST_DISABLED', value: 'true', project: project)
+ end
+
+ context 'on default branch' do
+ it 'has no jobs' do
+ expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError)
+ end
+ end
+
+ context 'on feature branch' do
+ let(:pipeline_ref) { 'feature' }
+
+ it 'has no jobs' do
+ expect { pipeline }.to raise_error(Ci::CreatePipelineService::CreateError)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
index 7602309627b..64ef6ecd7f8 100644
--- a/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/auto_devops_gitlab_ci_yaml_spec.rb
@@ -148,9 +148,7 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do
it_behaves_like 'no Kubernetes deployment job'
end
- context 'when the project has an active cluster' do
- let!(:cluster) { create(:cluster, :project, :provided_by_gcp, projects: [project]) }
-
+ shared_examples 'pipeline with Kubernetes jobs' do
describe 'deployment-related builds' do
context 'on default branch' do
it 'does not include rollout jobs besides production' do
@@ -233,6 +231,22 @@ RSpec.describe 'Auto-DevOps.gitlab-ci.yml' do
end
end
end
+
+ context 'when a cluster is attached' do
+ before do
+ create(:cluster, :project, :provided_by_gcp, projects: [project])
+ end
+
+ it_behaves_like 'pipeline with Kubernetes jobs'
+ end
+
+ context 'when project has an Agent is present' do
+ before do
+ create(:cluster_agent, project: project)
+ end
+
+ it_behaves_like 'pipeline with Kubernetes jobs'
+ end
end
describe 'buildpack detection' do
diff --git a/spec/lib/gitlab/ci/templates/kaniko_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/kaniko_gitlab_ci_yaml_spec.rb
new file mode 100644
index 00000000000..c7dbbea4622
--- /dev/null
+++ b/spec/lib/gitlab/ci/templates/kaniko_gitlab_ci_yaml_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Kaniko.gitlab-ci.yml' do
+ subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Kaniko') }
+
+ describe 'the created pipeline' do
+ let(:pipeline_branch) { 'master' }
+ let(:project) { create(:project, :custom_repo, files: { 'Dockerfile' => 'FROM alpine:latest' }) }
+ let(:user) { project.owner }
+ let(:service) { Ci::CreatePipelineService.new(project, user, ref: pipeline_branch ) }
+ let(:pipeline) { service.execute!(:push).payload }
+ let(:build_names) { pipeline.builds.pluck(:name) }
+
+ before do
+ stub_ci_pipeline_yaml_file(template.content)
+ allow(Ci::BuildScheduleWorker).to receive(:perform).and_return(true)
+ end
+
+ it 'creates "kaniko-build" job' do
+ expect(build_names).to include('kaniko-build')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb b/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb
index 3d1306e82a5..fd5d5d6af7f 100644
--- a/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb
+++ b/spec/lib/gitlab/ci/templates/terraform_latest_gitlab_ci_yaml_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe 'Terraform.latest.gitlab-ci.yml' do
context 'on master branch' do
it 'creates init, validate and build jobs', :aggregate_failures do
expect(pipeline.errors).to be_empty
- expect(build_names).to include('init', 'validate', 'build', 'deploy')
+ expect(build_names).to include('validate', 'build', 'deploy')
end
end
diff --git a/spec/lib/gitlab/ci/trace/archive_spec.rb b/spec/lib/gitlab/ci/trace/archive_spec.rb
index c9fc4e720c4..5e965f94347 100644
--- a/spec/lib/gitlab/ci/trace/archive_spec.rb
+++ b/spec/lib/gitlab/ci/trace/archive_spec.rb
@@ -3,99 +3,134 @@
require 'spec_helper'
RSpec.describe Gitlab::Ci::Trace::Archive do
- let_it_be(:job) { create(:ci_build, :success, :trace_live) }
- let_it_be_with_reload(:trace_metadata) { create(:ci_build_trace_metadata, build: job) }
- let_it_be(:src_checksum) do
- job.trace.read { |stream| Digest::MD5.hexdigest(stream.raw) }
- end
-
- let(:metrics) { spy('metrics') }
-
- describe '#execute' do
- subject { described_class.new(job, trace_metadata, metrics) }
-
- it 'computes and assigns checksum' do
- Gitlab::Ci::Trace::ChunkedIO.new(job) do |stream|
- expect { subject.execute!(stream) }.to change { Ci::JobArtifact.count }.by(1)
- end
-
- expect(trace_metadata.checksum).to eq(src_checksum)
- expect(trace_metadata.trace_artifact).to eq(job.job_artifacts_trace)
+ context 'with transactional fixtures' do
+ let_it_be(:job) { create(:ci_build, :success, :trace_live) }
+ let_it_be_with_reload(:trace_metadata) { create(:ci_build_trace_metadata, build: job) }
+ let_it_be(:src_checksum) do
+ job.trace.read { |stream| Digest::MD5.hexdigest(stream.raw) }
end
- context 'validating artifact checksum' do
- let(:trace) { 'abc' }
- let(:stream) { StringIO.new(trace, 'rb') }
- let(:src_checksum) { Digest::MD5.hexdigest(trace) }
+ let(:metrics) { spy('metrics') }
- context 'when the object store is disabled' do
- before do
- stub_artifacts_object_storage(enabled: false)
- end
-
- it 'skips validation' do
- subject.execute!(stream)
+ describe '#execute' do
+ subject { described_class.new(job, trace_metadata, metrics) }
- expect(trace_metadata.checksum).to eq(src_checksum)
- expect(trace_metadata.remote_checksum).to be_nil
- expect(metrics)
- .not_to have_received(:increment_error_counter)
- .with(type: :archive_invalid_checksum)
+ it 'computes and assigns checksum' do
+ Gitlab::Ci::Trace::ChunkedIO.new(job) do |stream|
+ expect { subject.execute!(stream) }.to change { Ci::JobArtifact.count }.by(1)
end
+
+ expect(trace_metadata.checksum).to eq(src_checksum)
+ expect(trace_metadata.trace_artifact).to eq(job.job_artifacts_trace)
end
- context 'with background_upload enabled' do
- before do
- stub_artifacts_object_storage(background_upload: true)
- end
+ context 'validating artifact checksum' do
+ let(:trace) { 'abc' }
+ let(:stream) { StringIO.new(trace, 'rb') }
+ let(:src_checksum) { Digest::MD5.hexdigest(trace) }
- it 'skips validation' do
- subject.execute!(stream)
+ context 'when the object store is disabled' do
+ before do
+ stub_artifacts_object_storage(enabled: false)
+ end
- expect(trace_metadata.checksum).to eq(src_checksum)
- expect(trace_metadata.remote_checksum).to be_nil
- expect(metrics)
- .not_to have_received(:increment_error_counter)
- .with(type: :archive_invalid_checksum)
+ it 'skips validation' do
+ subject.execute!(stream)
+ expect(trace_metadata.checksum).to eq(src_checksum)
+ expect(trace_metadata.remote_checksum).to be_nil
+ expect(metrics)
+ .not_to have_received(:increment_error_counter)
+ .with(error_reason: :archive_invalid_checksum)
+ end
end
- end
- context 'with direct_upload enabled' do
- before do
- stub_artifacts_object_storage(direct_upload: true)
- end
+ context 'with background_upload enabled' do
+ before do
+ stub_artifacts_object_storage(background_upload: true)
+ end
- it 'validates the archived trace' do
- subject.execute!(stream)
+ it 'skips validation' do
+ subject.execute!(stream)
- expect(trace_metadata.checksum).to eq(src_checksum)
- expect(trace_metadata.remote_checksum).to eq(src_checksum)
- expect(metrics)
- .not_to have_received(:increment_error_counter)
- .with(type: :archive_invalid_checksum)
+ expect(trace_metadata.checksum).to eq(src_checksum)
+ expect(trace_metadata.remote_checksum).to be_nil
+ expect(metrics)
+ .not_to have_received(:increment_error_counter)
+ .with(error_reason: :archive_invalid_checksum)
+ end
end
- context 'when the checksum does not match' do
- let(:invalid_remote_checksum) { SecureRandom.hex }
-
+ context 'with direct_upload enabled' do
before do
- expect(::Gitlab::Ci::Trace::RemoteChecksum)
- .to receive(:new)
- .with(an_instance_of(Ci::JobArtifact))
- .and_return(double(md5_checksum: invalid_remote_checksum))
+ stub_artifacts_object_storage(direct_upload: true)
end
it 'validates the archived trace' do
subject.execute!(stream)
expect(trace_metadata.checksum).to eq(src_checksum)
- expect(trace_metadata.remote_checksum).to eq(invalid_remote_checksum)
+ expect(trace_metadata.remote_checksum).to eq(src_checksum)
expect(metrics)
- .to have_received(:increment_error_counter)
- .with(type: :archive_invalid_checksum)
+ .not_to have_received(:increment_error_counter)
+ .with(error_reason: :archive_invalid_checksum)
+ end
+
+ context 'when the checksum does not match' do
+ let(:invalid_remote_checksum) { SecureRandom.hex }
+
+ before do
+ expect(::Gitlab::Ci::Trace::RemoteChecksum)
+ .to receive(:new)
+ .with(an_instance_of(Ci::JobArtifact))
+ .and_return(double(md5_checksum: invalid_remote_checksum))
+ end
+
+ it 'validates the archived trace' do
+ subject.execute!(stream)
+
+ expect(trace_metadata.checksum).to eq(src_checksum)
+ expect(trace_metadata.remote_checksum).to eq(invalid_remote_checksum)
+ expect(metrics)
+ .to have_received(:increment_error_counter)
+ .with(error_reason: :archive_invalid_checksum)
+ end
end
end
end
end
end
+
+ context 'without transactional fixtures', :delete do
+ let(:job) { create(:ci_build, :success, :trace_live) }
+ let(:trace_metadata) { create(:ci_build_trace_metadata, build: job) }
+ let(:stream) { StringIO.new('abc', 'rb') }
+
+ describe '#execute!' do
+ subject(:execute) do
+ ::Gitlab::Ci::Trace::Archive.new(job, trace_metadata).execute!(stream)
+ end
+
+ before do
+ stub_artifacts_object_storage(direct_upload: true)
+ end
+
+ it 'does not upload the trace inside a database transaction', :delete do
+ expect(Ci::ApplicationRecord.connection.transaction_open?).to be_falsey
+
+ allow_next_instance_of(Ci::JobArtifact) do |artifact|
+ artifact.job_id = job.id
+
+ expect(artifact)
+ .to receive(:store_file!)
+ .and_wrap_original do |store_method, *args|
+ expect(Ci::ApplicationRecord.connection.transaction_open?).to be_falsey
+
+ store_method.call(*args)
+ end
+ end
+
+ execute
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/trace/metrics_spec.rb b/spec/lib/gitlab/ci/trace/metrics_spec.rb
index 53e55a57973..733ffbbea22 100644
--- a/spec/lib/gitlab/ci/trace/metrics_spec.rb
+++ b/spec/lib/gitlab/ci/trace/metrics_spec.rb
@@ -17,23 +17,23 @@ RSpec.describe Gitlab::Ci::Trace::Metrics, :prometheus do
end
describe '#increment_error_counter' do
- context 'when the operation type is known' do
+ context 'when the error reason is known' do
it 'increments the counter' do
- subject.increment_error_counter(type: :chunks_invalid_size)
- subject.increment_error_counter(type: :chunks_invalid_checksum)
- subject.increment_error_counter(type: :archive_invalid_checksum)
+ subject.increment_error_counter(error_reason: :chunks_invalid_size)
+ subject.increment_error_counter(error_reason: :chunks_invalid_checksum)
+ subject.increment_error_counter(error_reason: :archive_invalid_checksum)
- expect(described_class.trace_errors_counter.get(type: :chunks_invalid_size)).to eq 1
- expect(described_class.trace_errors_counter.get(type: :chunks_invalid_checksum)).to eq 1
- expect(described_class.trace_errors_counter.get(type: :archive_invalid_checksum)).to eq 1
+ expect(described_class.trace_errors_counter.get(error_reason: :chunks_invalid_size)).to eq 1
+ expect(described_class.trace_errors_counter.get(error_reason: :chunks_invalid_checksum)).to eq 1
+ expect(described_class.trace_errors_counter.get(error_reason: :archive_invalid_checksum)).to eq 1
expect(described_class.trace_errors_counter.values.count).to eq 3
end
end
- context 'when the operation type is known' do
+ context 'when the error reason is unknown' do
it 'raises an exception' do
- expect { subject.increment_error_counter(type: :invalid_type) }
+ expect { subject.increment_error_counter(error_reason: :invalid_type) }
.to raise_error(ArgumentError)
end
end
diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb
index 1a31b2dad56..888ceb7ff9a 100644
--- a/spec/lib/gitlab/ci/trace_spec.rb
+++ b/spec/lib/gitlab/ci/trace_spec.rb
@@ -25,16 +25,6 @@ RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state, factory_defa
artifact1.file.migrate!(ObjectStorage::Store::REMOTE)
end
- it 'reloads the trace after is it migrated' do
- stub_const('Gitlab::HttpIO::BUFFER_SIZE', test_data.length)
-
- expect_next_instance_of(Gitlab::HttpIO) do |http_io|
- expect(http_io).to receive(:get_chunk).and_return(test_data, "")
- end
-
- expect(artifact2.job.trace.raw).to eq(test_data)
- end
-
it 'reloads the trace in case of a chunk error' do
chunk_error = described_class::ChunkedIO::FailedToGetChunkError
diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb
new file mode 100644
index 00000000000..10275f33484
--- /dev/null
+++ b/spec/lib/gitlab/ci/variables/builder_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Variables::Builder do
+ let(:builder) { described_class.new(pipeline) }
+ let(:pipeline) { create(:ci_pipeline) }
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ describe '#scoped_variables' do
+ let(:environment) { job.expanded_environment_name }
+ let(:dependencies) { true }
+
+ subject { builder.scoped_variables(job, environment: environment, dependencies: dependencies) }
+
+ it 'returns the expected variables' do
+ keys = %w[CI_JOB_NAME
+ CI_JOB_STAGE
+ CI_NODE_TOTAL
+ CI_BUILD_NAME
+ CI_BUILD_STAGE]
+
+ subject.map { |env| env[:key] }.tap do |names|
+ expect(names).to include(*keys)
+ end
+ end
+
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(ci_predefined_vars_in_builder: false)
+ end
+
+ it 'returns no variables' do
+ expect(subject.map { |env| env[:key] }).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb
index 7ba98380986..26c560565e0 100644
--- a/spec/lib/gitlab/ci/variables/collection_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection_spec.rb
@@ -358,302 +358,210 @@ RSpec.describe Gitlab::Ci::Variables::Collection do
end
describe '#sort_and_expand_all' do
- context 'when FF :variable_inside_variable is disabled' do
- let_it_be(:project_with_flag_disabled) { create(:project) }
- let_it_be(:project_with_flag_enabled) { create(:project) }
-
- before do
- stub_feature_flags(variable_inside_variable: [project_with_flag_enabled])
- end
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
- context 'table tests' do
- using RSpec::Parameterized::TableSyntax
-
- where do
- {
- "empty array": {
- variables: [],
- keep_undefined: false
- },
- "simple expansions": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' },
- { key: 'variable3', value: 'key$variable$variable2' }
- ],
- keep_undefined: false
- },
- "complex expansion": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'key${variable}' }
- ],
- keep_undefined: false
- },
- "out-of-order variable reference": {
- variables: [
- { key: 'variable2', value: 'key${variable}' },
- { key: 'variable', value: 'value' }
- ],
- keep_undefined: false
- },
- "complex expansions with raw variable": {
- variables: [
- { key: 'variable3', value: 'key_${variable}_${variable2}' },
- { key: 'variable', value: '$variable2', raw: true },
- { key: 'variable2', value: 'value2' }
- ],
- keep_undefined: false
- },
- "escaped characters in complex expansions are kept intact": {
- variables: [
- { key: 'variable3', value: 'key_${variable}_$${HOME}_%%HOME%%' },
- { key: 'variable', value: '$variable2' },
- { key: 'variable2', value: 'value2' }
- ],
- keep_undefined: false
- },
- "array with cyclic dependency": {
- variables: [
- { key: 'variable', value: '$variable2' },
- { key: 'variable2', value: '$variable3' },
- { key: 'variable3', value: 'key$variable$variable2' }
- ],
- keep_undefined: true
- }
+ where do
+ {
+ "empty array": {
+ variables: [],
+ keep_undefined: false,
+ result: []
+ },
+ "simple expansions": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key$variable$variable2' },
+ { key: 'variable4', value: 'key$variable$variable3' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'keyvalueresult' },
+ { key: 'variable4', value: 'keyvaluekeyvalueresult' }
+ ]
+ },
+ "complex expansion": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'key${variable}' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'keyvalue' }
+ ]
+ },
+ "unused variables": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result2' },
+ { key: 'variable3', value: 'result3' },
+ { key: 'variable4', value: 'key$variable$variable3' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result2' },
+ { key: 'variable3', value: 'result3' },
+ { key: 'variable4', value: 'keyvalueresult3' }
+ ]
+ },
+ "complex expansions": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key${variable}${variable2}' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'keyvalueresult' }
+ ]
+ },
+ "escaped characters in complex expansions keeping undefined are kept intact": {
+ variables: [
+ { key: 'variable3', value: 'key_${variable}_$${HOME}_%%HOME%%' },
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: 'value' }
+ ],
+ keep_undefined: true,
+ result: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'value' },
+ { key: 'variable3', value: 'key_value_$${HOME}_%%HOME%%' }
+ ]
+ },
+ "escaped characters in complex expansions discarding undefined are kept intact": {
+ variables: [
+ { key: 'variable2', value: 'key_${variable4}_$${HOME}_%%HOME%%' },
+ { key: 'variable', value: 'value_$${HOME}_%%HOME%%' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: 'value_$${HOME}_%%HOME%%' },
+ { key: 'variable2', value: 'key__$${HOME}_%%HOME%%' }
+ ]
+ },
+ "out-of-order expansion": {
+ variables: [
+ { key: 'variable3', value: 'key$variable2$variable' },
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable2', value: 'result' },
+ { key: 'variable', value: 'value' },
+ { key: 'variable3', value: 'keyresultvalue' }
+ ]
+ },
+ "out-of-order complex expansion": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key${variable2}${variable}' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'keyresultvalue' }
+ ]
+ },
+ "missing variable discarding original": {
+ variables: [
+ { key: 'variable2', value: 'key$variable' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable2', value: 'key' }
+ ]
+ },
+ "missing variable keeping original": {
+ variables: [
+ { key: 'variable2', value: 'key$variable' }
+ ],
+ keep_undefined: true,
+ result: [
+ { key: 'variable2', value: 'key$variable' }
+ ]
+ },
+ "complex expansions with missing variable keeping original": {
+ variables: [
+ { key: 'variable4', value: 'key${variable}${variable2}${variable3}' },
+ { key: 'variable', value: 'value' },
+ { key: 'variable3', value: 'value3' }
+ ],
+ keep_undefined: true,
+ result: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable3', value: 'value3' },
+ { key: 'variable4', value: 'keyvalue${variable2}value3' }
+ ]
+ },
+ "complex expansions with raw variable": {
+ variables: [
+ { key: 'variable3', value: 'key_${variable}_${variable2}' },
+ { key: 'variable', value: '$variable2', raw: true },
+ { key: 'variable2', value: 'value2' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: '$variable2', raw: true },
+ { key: 'variable2', value: 'value2' },
+ { key: 'variable3', value: 'key_$variable2_value2' }
+ ]
+ },
+ "variable value referencing password with special characters": {
+ variables: [
+ { key: 'VAR', value: '$PASSWORD' },
+ { key: 'PASSWORD', value: 'my_password$$_%%_$A' },
+ { key: 'A', value: 'value' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'VAR', value: 'my_password$$_%%_value' },
+ { key: 'PASSWORD', value: 'my_password$$_%%_value' },
+ { key: 'A', value: 'value' }
+ ]
+ },
+ "cyclic dependency causes original array to be returned": {
+ variables: [
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ],
+ keep_undefined: false,
+ result: [
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ]
}
- end
-
- with_them do
- let(:collection) { Gitlab::Ci::Variables::Collection.new(variables, keep_undefined: keep_undefined) }
-
- subject { collection.sort_and_expand_all(project_with_flag_disabled) }
-
- it 'returns Collection' do
- is_expected.to be_an_instance_of(Gitlab::Ci::Variables::Collection)
- end
-
- it 'does not expand variables' do
- var_hash = variables.pluck(:key, :value).to_h
- expect(subject.to_hash).to eq(var_hash)
- end
- end
+ }
end
- end
- context 'when FF :variable_inside_variable is enabled' do
- let_it_be(:project_with_flag_disabled) { create(:project) }
- let_it_be(:project_with_flag_enabled) { create(:project) }
+ with_them do
+ let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
- before do
- stub_feature_flags(variable_inside_variable: [project_with_flag_enabled])
- end
+ subject { collection.sort_and_expand_all(keep_undefined: keep_undefined) }
- context 'table tests' do
- using RSpec::Parameterized::TableSyntax
-
- where do
- {
- "empty array": {
- variables: [],
- keep_undefined: false,
- result: []
- },
- "simple expansions": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' },
- { key: 'variable3', value: 'key$variable$variable2' },
- { key: 'variable4', value: 'key$variable$variable3' }
- ],
- keep_undefined: false,
- result: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' },
- { key: 'variable3', value: 'keyvalueresult' },
- { key: 'variable4', value: 'keyvaluekeyvalueresult' }
- ]
- },
- "complex expansion": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'key${variable}' }
- ],
- keep_undefined: false,
- result: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'keyvalue' }
- ]
- },
- "unused variables": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result2' },
- { key: 'variable3', value: 'result3' },
- { key: 'variable4', value: 'key$variable$variable3' }
- ],
- keep_undefined: false,
- result: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result2' },
- { key: 'variable3', value: 'result3' },
- { key: 'variable4', value: 'keyvalueresult3' }
- ]
- },
- "complex expansions": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' },
- { key: 'variable3', value: 'key${variable}${variable2}' }
- ],
- keep_undefined: false,
- result: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' },
- { key: 'variable3', value: 'keyvalueresult' }
- ]
- },
- "escaped characters in complex expansions keeping undefined are kept intact": {
- variables: [
- { key: 'variable3', value: 'key_${variable}_$${HOME}_%%HOME%%' },
- { key: 'variable', value: '$variable2' },
- { key: 'variable2', value: 'value' }
- ],
- keep_undefined: true,
- result: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'value' },
- { key: 'variable3', value: 'key_value_$${HOME}_%%HOME%%' }
- ]
- },
- "escaped characters in complex expansions discarding undefined are kept intact": {
- variables: [
- { key: 'variable2', value: 'key_${variable4}_$${HOME}_%%HOME%%' },
- { key: 'variable', value: 'value_$${HOME}_%%HOME%%' }
- ],
- keep_undefined: false,
- result: [
- { key: 'variable', value: 'value_$${HOME}_%%HOME%%' },
- { key: 'variable2', value: 'key__$${HOME}_%%HOME%%' }
- ]
- },
- "out-of-order expansion": {
- variables: [
- { key: 'variable3', value: 'key$variable2$variable' },
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' }
- ],
- keep_undefined: false,
- result: [
- { key: 'variable2', value: 'result' },
- { key: 'variable', value: 'value' },
- { key: 'variable3', value: 'keyresultvalue' }
- ]
- },
- "out-of-order complex expansion": {
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' },
- { key: 'variable3', value: 'key${variable2}${variable}' }
- ],
- keep_undefined: false,
- result: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' },
- { key: 'variable3', value: 'keyresultvalue' }
- ]
- },
- "missing variable discarding original": {
- variables: [
- { key: 'variable2', value: 'key$variable' }
- ],
- keep_undefined: false,
- result: [
- { key: 'variable2', value: 'key' }
- ]
- },
- "missing variable keeping original": {
- variables: [
- { key: 'variable2', value: 'key$variable' }
- ],
- keep_undefined: true,
- result: [
- { key: 'variable2', value: 'key$variable' }
- ]
- },
- "complex expansions with missing variable keeping original": {
- variables: [
- { key: 'variable4', value: 'key${variable}${variable2}${variable3}' },
- { key: 'variable', value: 'value' },
- { key: 'variable3', value: 'value3' }
- ],
- keep_undefined: true,
- result: [
- { key: 'variable', value: 'value' },
- { key: 'variable3', value: 'value3' },
- { key: 'variable4', value: 'keyvalue${variable2}value3' }
- ]
- },
- "complex expansions with raw variable": {
- variables: [
- { key: 'variable3', value: 'key_${variable}_${variable2}' },
- { key: 'variable', value: '$variable2', raw: true },
- { key: 'variable2', value: 'value2' }
- ],
- keep_undefined: false,
- result: [
- { key: 'variable', value: '$variable2', raw: true },
- { key: 'variable2', value: 'value2' },
- { key: 'variable3', value: 'key_$variable2_value2' }
- ]
- },
- "variable value referencing password with special characters": {
- variables: [
- { key: 'VAR', value: '$PASSWORD' },
- { key: 'PASSWORD', value: 'my_password$$_%%_$A' },
- { key: 'A', value: 'value' }
- ],
- keep_undefined: false,
- result: [
- { key: 'VAR', value: 'my_password$$_%%_value' },
- { key: 'PASSWORD', value: 'my_password$$_%%_value' },
- { key: 'A', value: 'value' }
- ]
- },
- "cyclic dependency causes original array to be returned": {
- variables: [
- { key: 'variable', value: '$variable2' },
- { key: 'variable2', value: '$variable3' },
- { key: 'variable3', value: 'key$variable$variable2' }
- ],
- keep_undefined: false,
- result: [
- { key: 'variable', value: '$variable2' },
- { key: 'variable2', value: '$variable3' },
- { key: 'variable3', value: 'key$variable$variable2' }
- ]
- }
- }
+ it 'returns Collection' do
+ is_expected.to be_an_instance_of(Gitlab::Ci::Variables::Collection)
end
- with_them do
- let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
-
- subject { collection.sort_and_expand_all(project_with_flag_enabled, keep_undefined: keep_undefined) }
-
- it 'returns Collection' do
- is_expected.to be_an_instance_of(Gitlab::Ci::Variables::Collection)
- end
-
- it 'expands variables' do
- var_hash = result.to_h { |env| [env.fetch(:key), env.fetch(:value)] }
- .with_indifferent_access
- expect(subject.to_hash).to eq(var_hash)
- end
+ it 'expands variables' do
+ var_hash = result.to_h { |env| [env.fetch(:key), env.fetch(:value)] }
+ .with_indifferent_access
+ expect(subject.to_hash).to eq(var_hash)
+ end
- it 'preserves raw attribute' do
- expect(subject.pluck(:key, :raw).to_h).to eq(collection.pluck(:key, :raw).to_h)
- end
+ it 'preserves raw attribute' do
+ expect(subject.pluck(:key, :raw).to_h).to eq(collection.pluck(:key, :raw).to_h)
end
end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 1591c2e6b60..f00a801286d 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -1046,6 +1046,64 @@ module Gitlab
end
end
+ context 'when overriding `extends`' do
+ let(:config) do
+ <<~YAML
+ .base:
+ script: test
+ variables:
+ VAR1: base var 1
+
+ test1:
+ extends: .base
+ variables:
+ VAR1: test1 var 1
+ VAR2: test2 var 2
+
+ test2:
+ extends: .base
+ variables:
+ VAR2: test2 var 2
+
+ test3:
+ extends: .base
+ variables: {}
+
+ test4:
+ extends: .base
+ variables: null
+ YAML
+ end
+
+ it 'correctly extends jobs' do
+ expect(config_processor.builds[0]).to include(
+ name: 'test1',
+ options: { script: ['test'] },
+ job_variables: [{ key: 'VAR1', value: 'test1 var 1', public: true },
+ { key: 'VAR2', value: 'test2 var 2', public: true }]
+ )
+
+ expect(config_processor.builds[1]).to include(
+ name: 'test2',
+ options: { script: ['test'] },
+ job_variables: [{ key: 'VAR1', value: 'base var 1', public: true },
+ { key: 'VAR2', value: 'test2 var 2', public: true }]
+ )
+
+ expect(config_processor.builds[2]).to include(
+ name: 'test3',
+ options: { script: ['test'] },
+ job_variables: [{ key: 'VAR1', value: 'base var 1', public: true }]
+ )
+
+ expect(config_processor.builds[3]).to include(
+ name: 'test4',
+ options: { script: ['test'] },
+ job_variables: []
+ )
+ end
+ end
+
context 'when using recursive `extends`' do
let(:config) do
<<~YAML