diff options
Diffstat (limited to 'spec/models/ci/build_spec.rb')
-rw-r--r-- | spec/models/ci/build_spec.rb | 384 |
1 files changed, 129 insertions, 255 deletions
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index e3e78acb7e5..51cd6efb85f 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1882,20 +1882,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def it { is_expected.to eq('review/x') } end - context 'when FF `ci_remove_legacy_predefined_variables` is disabled' do - before do - stub_feature_flags(ci_remove_legacy_predefined_variables: false) - end - - context 'when using persisted variables' do - let(:build) do - create(:ci_build, environment: 'review/x$CI_BUILD_ID', pipeline: pipeline) - end - - it { is_expected.to eq('review/x') } - end - end - context 'when environment name uses a nested variable' do let(:yaml_variables) do [ @@ -2664,16 +2650,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def it { is_expected.not_to be_playable } end - - context 'when build is waiting for deployment approval' do - subject { build_stubbed(:ci_build, :manual, environment: 'production', pipeline: pipeline) } - - before do - create(:deployment, :blocked, deployable: subject) - end - - it { is_expected.not_to be_playable } - end end describe 'project settings' do @@ -2954,97 +2930,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def ] end - # Remove this definition when FF `ci_remove_legacy_predefined_variables` is removed - let(:predefined_with_legacy_variables) do - [ - { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true, masked: false }, - { key: 'CI_PIPELINE_URL', value: project.web_url + "/-/pipelines/#{pipeline.id}", public: true, masked: false }, - { key: 'CI_JOB_ID', value: build.id.to_s, public: true, masked: false }, - { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true, masked: false }, - { key: 'CI_JOB_TOKEN', value: 'my-token', public: false, masked: true }, - { key: 'CI_JOB_STARTED_AT', value: build.started_at&.iso8601, public: true, masked: false }, - { key: 'CI_BUILD_ID', value: build.id.to_s, public: true, masked: false }, - { key: 'CI_BUILD_TOKEN', value: 'my-token', public: false, masked: true }, - { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true, masked: false }, - { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false, masked: true }, - { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false, masked: false }, - { key: 'CI_DEPENDENCY_PROXY_USER', value: 'gitlab-ci-token', public: true, masked: false }, - { key: 'CI_DEPENDENCY_PROXY_PASSWORD', value: 'my-token', public: false, masked: true }, - { key: 'CI_JOB_JWT', value: 'ci.job.jwt', public: false, masked: true }, - { key: 'CI_JOB_JWT_V1', value: 'ci.job.jwt', public: false, masked: true }, - { key: 'CI_JOB_JWT_V2', value: 'ci.job.jwtv2', public: false, masked: true }, - { key: 'CI_JOB_NAME', value: 'test', public: true, masked: false }, - { key: 'CI_JOB_NAME_SLUG', value: 'test', public: true, masked: false }, - { key: 'CI_JOB_STAGE', value: 'test', public: true, masked: false }, - { key: 'CI_NODE_TOTAL', value: '1', public: true, masked: false }, - { key: 'CI_BUILD_NAME', value: 'test', public: true, masked: false }, - { key: 'CI_BUILD_STAGE', value: 'test', public: true, masked: false }, - { key: 'CI', value: 'true', public: true, masked: false }, - { key: 'GITLAB_CI', value: 'true', public: true, masked: false }, - { key: 'CI_SERVER_URL', value: Gitlab.config.gitlab.url, public: true, masked: false }, - { key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host, public: true, masked: false }, - { key: 'CI_SERVER_PORT', value: Gitlab.config.gitlab.port.to_s, public: true, masked: false }, - { key: 'CI_SERVER_PROTOCOL', value: Gitlab.config.gitlab.protocol, public: true, masked: false }, - { key: 'CI_SERVER_SHELL_SSH_HOST', value: Gitlab.config.gitlab_shell.ssh_host.to_s, public: true, masked: false }, - { key: 'CI_SERVER_SHELL_SSH_PORT', value: Gitlab.config.gitlab_shell.ssh_port.to_s, public: true, masked: false }, - { key: 'CI_SERVER_NAME', value: 'GitLab', public: true, masked: false }, - { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true, masked: false }, - { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true, masked: false }, - { key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s, public: true, masked: false }, - { key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s, public: true, masked: false }, - { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true, masked: false }, - { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true, masked: false }, - { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true, masked: false }, - { key: 'CI_PROJECT_NAME', value: project.path, public: true, masked: false }, - { key: 'CI_PROJECT_TITLE', value: project.title, public: true, masked: false }, - { key: 'CI_PROJECT_DESCRIPTION', value: project.description, public: true, masked: false }, - { key: 'CI_PROJECT_PATH', value: project.full_path, public: true, masked: false }, - { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true, masked: false }, - { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true, masked: false }, - { key: 'CI_PROJECT_NAMESPACE_ID', value: project.namespace.id.to_s, public: true, masked: false }, - { key: 'CI_PROJECT_ROOT_NAMESPACE', value: project.namespace.root_ancestor.path, public: true, masked: false }, - { key: 'CI_PROJECT_URL', value: project.web_url, public: true, masked: false }, - { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false }, - { key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: project.repository_languages.map(&:name).join(',').downcase, public: true, masked: false }, - { key: 'CI_PROJECT_CLASSIFICATION_LABEL', value: project.external_authorization_classification_label, public: true, masked: false }, - { key: 'CI_DEFAULT_BRANCH', value: project.default_branch, public: true, masked: false }, - { key: 'CI_CONFIG_PATH', value: project.ci_config_path_or_default, public: true, masked: false }, - { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false }, - { key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false }, - { key: 'CI_DEPENDENCY_PROXY_SERVER', value: Gitlab.host_with_port, public: true, masked: false }, - { key: 'CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX', - value: "#{Gitlab.host_with_port}/#{project.namespace.root_ancestor.path.downcase}#{DependencyProxy::URL_SUFFIX}", - public: true, - masked: false }, - { key: 'CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX', - value: "#{Gitlab.host_with_port}/#{project.namespace.full_path.downcase}#{DependencyProxy::URL_SUFFIX}", - public: true, - masked: false }, - { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false }, - { key: 'CI_API_GRAPHQL_URL', value: 'http://localhost/api/graphql', public: true, masked: false }, - { key: 'CI_TEMPLATE_REGISTRY_HOST', value: template_registry_host, public: true, masked: false }, - { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true, masked: false }, - { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false }, - { key: 'CI_PIPELINE_CREATED_AT', value: pipeline.created_at.iso8601, public: true, masked: false }, - { key: 'CI_COMMIT_SHA', value: build.sha, public: true, masked: false }, - { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true, masked: false }, - { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true, masked: false }, - { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true, masked: false }, - { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true, masked: false }, - { key: 'CI_COMMIT_BRANCH', value: build.ref, public: true, masked: false }, - { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true, masked: false }, - { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false }, - { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false }, - { key: 'CI_COMMIT_REF_PROTECTED', value: (!!pipeline.protected_ref?).to_s, public: true, masked: false }, - { key: 'CI_COMMIT_TIMESTAMP', value: pipeline.git_commit_timestamp, public: true, masked: false }, - { key: 'CI_COMMIT_AUTHOR', value: pipeline.git_author_full_text, public: true, masked: false }, - { key: 'CI_BUILD_REF', value: build.sha, public: true, masked: false }, - { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true, masked: false }, - { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true, masked: false }, - { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true, masked: false } - ] - end - before do allow(Gitlab::Ci::Jwt).to receive(:for_build).and_return('ci.job.jwt') allow(Gitlab::Ci::JwtV2).to receive(:for_build).and_return('ci.job.jwtv2') @@ -3055,14 +2940,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def it { is_expected.to be_instance_of(Gitlab::Ci::Variables::Collection) } it { expect(subject.to_runner_variables).to eq(predefined_variables) } - context 'when FF `ci_remove_legacy_predefined_variables` is disabled' do - before do - stub_feature_flags(ci_remove_legacy_predefined_variables: false) - end - - it { expect(subject.to_runner_variables).to eq(predefined_with_legacy_variables) } - end - it 'excludes variables that require an environment or user' do environment_based_variables_collection = subject.filter do |variable| %w[ @@ -3204,80 +3081,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def end end end - - context 'when FF `ci_remove_legacy_predefined_variables` is disabled' do - before do - stub_feature_flags(ci_remove_legacy_predefined_variables: false) - end - - context 'when build has environment and user-provided variables' do - let(:expected_variables) do - predefined_with_legacy_variables.map { |variable| variable.fetch(:key) } + - %w[YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG - CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_TIER CI_ENVIRONMENT_URL] - end - - before do - create(:environment, project: build.project, name: 'staging') - - build.yaml_variables = [{ key: 'YAML_VARIABLE', value: 'var', public: true }] - build.environment = 'staging' - - # CI_ENVIRONMENT_NAME is set in predefined_variables when job environment is provided - predefined_with_legacy_variables.insert(20, { key: 'CI_ENVIRONMENT_NAME', value: 'staging', public: true, masked: false }) - end - - it 'matches explicit variables ordering' do - received_variables = subject.map { |variable| variable[:key] } - - expect(received_variables).to eq expected_variables - end - - describe 'CI_ENVIRONMENT_ACTION' do - let(:enviroment_action_variable) { subject.find { |variable| variable[:key] == 'CI_ENVIRONMENT_ACTION' } } - - shared_examples 'defaults value' do - it 'value matches start' do - expect(enviroment_action_variable[:value]).to eq('start') - end - end - - it_behaves_like 'defaults value' - - context 'when options is set' do - before do - build.update!(options: options) - end - - context 'when options is empty' do - let(:options) { {} } - - it_behaves_like 'defaults value' - end - - context 'when options is nil' do - let(:options) { nil } - - it_behaves_like 'defaults value' - end - - context 'when options environment is specified' do - let(:options) { { environment: {} } } - - it_behaves_like 'defaults value' - end - - context 'when options environment action specified' do - let(:options) { { environment: { action: 'stop' } } } - - it 'matches the specified action' do - expect(enviroment_action_variable[:value]).to eq('stop') - end - end - end - end - end - end end context 'when the build has ID tokens' do @@ -3880,7 +3683,9 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def end context 'for the apple_app_store integration' do - let_it_be(:apple_app_store_integration) { create(:apple_app_store_integration) } + before do + allow(build.pipeline).to receive(:protected_ref?).and_return(pipeline_protected_ref) + end let(:apple_app_store_variables) do [ @@ -3891,39 +3696,70 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def ] end - context 'when the apple_app_store exists' do - context 'when a build is protected' do - before do - allow(build.pipeline).to receive(:protected_ref?).and_return(true) - build.project.update!(apple_app_store_integration: apple_app_store_integration) + shared_examples 'does not include the apple_app_store variables' do + specify do + expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_ISSUER_ID' }).to be_nil + expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY' }).to be_nil + expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY_ID' }).to be_nil + expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64' }).to be_nil + end + end + + shared_examples 'includes apple_app_store variables' do + specify do + expect(subject).to include(*apple_app_store_variables) + end + end + + context 'when an Apple App Store integration exists' do + let_it_be(:apple_app_store_integration) do + create(:apple_app_store_integration, project: project) + end + + context 'when app_store_protected_refs is true' do + context 'when a build is protected' do + let(:pipeline_protected_ref) { true } + + include_examples 'includes apple_app_store variables' end - it 'includes apple_app_store variables' do - is_expected.to include(*apple_app_store_variables) + context 'when a build is not protected' do + let(:pipeline_protected_ref) { false } + + include_examples 'does not include the apple_app_store variables' end end - context 'when a build is not protected' do + context 'when app_store_protected_refs is false' do before do - allow(build.pipeline).to receive(:protected_ref?).and_return(false) - build.project.update!(apple_app_store_integration: apple_app_store_integration) + apple_app_store_integration.update!(app_store_protected_refs: false) end - it 'does not include the apple_app_store variables' do - expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_ISSUER_ID' }).to be_nil - expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY' }).to be_nil - expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY_ID' }).to be_nil - expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64' }).to be_nil + context 'when a build is protected' do + let(:pipeline_protected_ref) { true } + + include_examples 'includes apple_app_store variables' + end + + context 'when a build is not protected' do + let(:pipeline_protected_ref) { false } + + include_examples 'includes apple_app_store variables' end end end - context 'when the apple_app_store integration does not exist' do - it 'does not include apple_app_store variables' do - expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_ISSUER_ID' }).to be_nil - expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY' }).to be_nil - expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY_ID' }).to be_nil - expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64' }).to be_nil + context 'when an Apple App Store integration does not exist' do + context 'when a build is protected' do + let(:pipeline_protected_ref) { true } + + include_examples 'does not include the apple_app_store variables' + end + + context 'when a build is not protected' do + let(:pipeline_protected_ref) { false } + + include_examples 'does not include the apple_app_store variables' end end end @@ -4020,6 +3856,80 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def end end end + + context 'when ID tokens are defined with variables' do + let(:ci_server_url) { Gitlab.config.gitlab.url } + + let(:ci_server_host) { Gitlab.config.gitlab.host } + + before do + rsa_key = OpenSSL::PKey::RSA.generate(3072).to_s + stub_application_setting(ci_jwt_signing_key: rsa_key) + build.metadata.update!(id_tokens: { + 'ID_TOKEN_1' => { aud: '$CI_SERVER_URL' }, + 'ID_TOKEN_2' => { aud: 'https://$CI_SERVER_HOST' }, + 'ID_TOKEN_3' => { aud: ['developers', '$CI_SERVER_URL', 'https://$CI_SERVER_HOST'] } + }) + build.runner = build_stubbed(:ci_runner) + end + + subject(:runner_vars) { build.variables.to_runner_variables } + + it 'includes the ID token variables with expanded aud values' do + expect(runner_vars).to include( + a_hash_including(key: 'ID_TOKEN_1', public: false, masked: true), + a_hash_including(key: 'ID_TOKEN_2', public: false, masked: true), + a_hash_including(key: 'ID_TOKEN_3', public: false, masked: true) + ) + + id_token_var_1 = runner_vars.find { |var| var[:key] == 'ID_TOKEN_1' } + id_token_var_2 = runner_vars.find { |var| var[:key] == 'ID_TOKEN_2' } + id_token_var_3 = runner_vars.find { |var| var[:key] == 'ID_TOKEN_3' } + id_token_1 = JWT.decode(id_token_var_1[:value], nil, false).first + id_token_2 = JWT.decode(id_token_var_2[:value], nil, false).first + id_token_3 = JWT.decode(id_token_var_3[:value], nil, false).first + expect(id_token_1['aud']).to eq(ci_server_url) + expect(id_token_2['aud']).to eq("https://#{ci_server_host}") + expect(id_token_3['aud']).to match_array(['developers', ci_server_url, "https://#{ci_server_host}"]) + end + end + + context 'when ID tokens are defined with variables of an environment' do + let!(:envprod) do + create(:environment, project: build.project, name: 'production') + end + + let!(:varprod) do + create(:ci_variable, project: build.project, key: 'ENVIRONMENT_SCOPED_VAR', value: 'https://prod', environment_scope: 'prod*') + end + + before do + build.update!(environment: 'production') + rsa_key = OpenSSL::PKey::RSA.generate(3072).to_s + stub_application_setting(ci_jwt_signing_key: rsa_key) + build.metadata.update!(id_tokens: { + 'ID_TOKEN_1' => { aud: '$ENVIRONMENT_SCOPED_VAR' }, + 'ID_TOKEN_2' => { aud: ['$CI_ENVIRONMENT_NAME', '$ENVIRONMENT_SCOPED_VAR'] } + }) + build.runner = build_stubbed(:ci_runner) + end + + subject(:runner_vars) { build.variables.to_runner_variables } + + it 'includes the ID token variables with expanded aud values' do + expect(runner_vars).to include( + a_hash_including(key: 'ID_TOKEN_1', public: false, masked: true), + a_hash_including(key: 'ID_TOKEN_2', public: false, masked: true) + ) + + id_token_var_1 = runner_vars.find { |var| var[:key] == 'ID_TOKEN_1' } + id_token_var_2 = runner_vars.find { |var| var[:key] == 'ID_TOKEN_2' } + id_token_1 = JWT.decode(id_token_var_1[:value], nil, false).first + id_token_2 = JWT.decode(id_token_var_2[:value], nil, false).first + expect(id_token_1['aud']).to eq('https://prod') + expect(id_token_2['aud']).to match_array(['production', 'https://prod']) + end + end end describe '#scoped_variables' do @@ -4091,30 +4001,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def expect(names).not_to include(*keys) end end - - context 'when FF `ci_remove_legacy_predefined_variables` is disabled' do - before do - stub_feature_flags(ci_remove_legacy_predefined_variables: false) - end - - it 'does not return prohibited variables' do - keys = %w[CI_JOB_ID - CI_JOB_URL - CI_JOB_TOKEN - CI_BUILD_ID - CI_BUILD_TOKEN - CI_REGISTRY_USER - CI_REGISTRY_PASSWORD - CI_REPOSITORY_URL - CI_ENVIRONMENT_URL - CI_DEPLOY_USER - CI_DEPLOY_PASSWORD] - - build.scoped_variables.map { |env| env[:key] }.tap do |names| - expect(names).not_to include(*keys) - end - end - end end context 'with dependency variables' do @@ -4253,18 +4139,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def end end - describe 'when the build is waiting for deployment approval' do - let(:build) { create(:ci_build, :manual, environment: 'production', pipeline: pipeline) } - - before do - create(:deployment, :blocked, deployable: build) - end - - it 'does not allow the build to be enqueued' do - expect { build.enqueue! }.to raise_error(StateMachines::InvalidTransition) - end - end - describe 'state transition: any => [:pending]' do let(:build) { create(:ci_build, :created, pipeline: pipeline) } |