spec/lib/release_tools/public_release/gitlab_release_spec.rb (628 lines of code) (raw):

# frozen_string_literal: true require 'spec_helper' require 'release_tools/tasks' describe ReleaseTools::PublicRelease::GitlabRelease do describe '#initialize' do it 'converts CE versions to EE versions' do version = ReleaseTools::Version.new('42.0.0') release = described_class.new(version) expect(release.version).to eq('42.0.0-ee') end end describe '#execute' do let(:version) { ReleaseTools::Version.new('42.0.0-ee') } subject(:release) { described_class.new(version) } it 'runs the release' do ce_tag = double(:tag, name: 'v42.0.0') ee_tag = double(:tag, name: 'v42.0.0-ee') expect(release).to receive(:create_ce_target_branch) expect(release).to receive(:create_ee_target_branch) expect(release).to receive(:update_monthly_tag_metrics) expect(release).to receive(:update_managed_component_versions) expect(release).to receive(:compile_changelogs) expect(release).to receive(:update_versions) expect(release).to receive(:wait_for_ee_to_ce_sync) expect(release).to receive(:create_ce_commit_to_run_ci) expect(release).to receive(:create_ce_tag).and_return(ce_tag) expect(release).to receive(:create_ee_tag).and_return(ee_tag) expect(release).to receive(:start_new_minor_release) expect(release) .to receive(:add_release_data_for_tags) .with(ce_tag, ee_tag) expect(release) .to receive(:notify_slack) .with(ReleaseTools::Project::GitlabCe, version.to_ce) expect(release) .to receive(:notify_slack) .with(ReleaseTools::Project::GitlabEe, version) without_dry_run { release.execute } end context 'with dry-run mode' do it 'skips most of the tasks' do expect(release).to receive(:create_ce_target_branch) expect(release).to receive(:create_ee_target_branch) expect(release).not_to receive(:update_managed_component_versions) expect(release).not_to receive(:compile_changelogs) expect(release).not_to receive(:update_versions) expect(release).not_to receive(:wait_for_ee_to_ce_sync) expect(release).not_to receive(:create_ce_commit_to_run_ci) expect(release).not_to receive(:create_ce_tag) expect(release).not_to receive(:create_ee_tag) expect(release).not_to receive(:update_monthly_tag_metrics) expect(release).not_to receive(:start_new_minor_release) expect(release).not_to receive(:add_release_data_for_tags) expect(release).not_to receive(:notify_slack) expect(release).not_to receive(:notify_slack) release.execute end end end describe '#create_ce_target_branch' do let(:client) { class_spy(ReleaseTools::GitlabClient) } let(:version) { ReleaseTools::Version.new('42.1.0-ee') } subject(:release) { described_class.new(version, client: client) } it 'creates the CE target branch from the last stable branch' do expect(client) .to receive(:find_or_create_branch) .with('42-1-stable', '42-0-stable', release.ce_project_path) without_dry_run do release.create_ce_target_branch end end context 'with dry-run mode' do it 'skips api call' do expect(client).not_to receive(:find_or_create_branch) release.create_ce_target_branch end end end describe '#create_ee_target_branch' do let(:client) { class_spy(ReleaseTools::GitlabClient) } let(:version) { ReleaseTools::Version.new('42.1.0-ee') } it 'calls notify_stable_branch_creation' do release = described_class.new(version, client: client, commit: 'foo') expect(release).to receive(:notify_stable_branch_creation) without_dry_run do release.create_ee_target_branch end end context 'with dry run enabled' do it 'calls notify_stable_branch_creation' do release = described_class.new(version, client: client, commit: 'foo') expect(release).to receive(:notify_stable_branch_creation) release.create_ee_target_branch end end context 'when a custom commit is specified' do it 'creates the EE target branch from the custom commit' do release = described_class.new(version, client: client, commit: 'foo') expect(client) .to receive(:find_or_create_branch) .with('42-1-stable-ee', 'foo', release.project_path) without_dry_run do release.create_ee_target_branch end end end context "when a custom commit isn't specified" do it 'creates the EE target branch from the last production commit' do release = described_class.new(version, client: client) expect(release) .to receive(:last_production_commit) .and_return('123abc') expect(client) .to receive(:find_or_create_branch) .with('42-1-stable-ee', '123abc', release.project_path) without_dry_run do release.create_ee_target_branch end end end context 'with dry-run mode' do it 'skips api call' do release = described_class.new(version, client: client, commit: 'foo') expect(client).not_to receive(:find_or_create_branch) release.create_ee_target_branch end end end describe '#update_monthly_tag_metrics' do before do version = ReleaseTools::Version.new('16.10') allow(ReleaseTools::GitlabReleasesGemClient) .to receive(:version_for_date) .and_return(version) end context 'when tagging a patch release' do let(:client) { class_spy(ReleaseTools::GitlabClient) } let(:version) { ReleaseTools::Version.new('16.10.1-ee') } subject(:release) { described_class.new(version, client: client) } it 'does nothing' do expect(ReleaseTools::Metrics::MonthlyReleaseStatus).not_to receive(:new) without_dry_run do release.update_monthly_tag_metrics end end end context 'when tagging a minor release' do let(:client) { class_spy(ReleaseTools::GitlabClient) } let(:version) { ReleaseTools::Version.new('16.10.0') } subject(:release) { described_class.new(version, client: client) } it 'does nothing' do expect(ReleaseTools::Metrics::MonthlyReleaseStatus).not_to receive(:new) without_dry_run do release.update_monthly_tag_metrics end end end context 'when tagging a release candidate' do let(:client) { class_spy(ReleaseTools::GitlabClient) } let(:version) { ReleaseTools::Version.new('16.10.0-rc42') } subject(:release) { described_class.new(version, client: client) } it 'updates the monthly release status metric' do expect(ReleaseTools::Metrics::MonthlyReleaseStatus).to receive(:new).with(status: :tagged_rc) .and_return(instance_double(ReleaseTools::Metrics::MonthlyReleaseStatus, execute: true)) without_dry_run do release.update_monthly_tag_metrics end end end context 'when tagging an older release candidate' do let(:client) { class_spy(ReleaseTools::GitlabClient) } let(:version) { ReleaseTools::Version.new('16.9.0-rc42') } subject(:release) { described_class.new(version, client: client) } it 'does nothing' do expect(ReleaseTools::Metrics::MonthlyReleaseStatus).not_to receive(:new) without_dry_run do release.update_monthly_tag_metrics end end end end describe '#update_managed_component_versions' do before do enable_feature(:fetch_managed_component_version) end context 'when releasing an RC' do it 'does nothing' do version = ReleaseTools::Version.new('42.1.0-rc42-ee') release = described_class.new(version) expect(release).not_to receive(:commit_version_files) release.update_managed_component_versions end end context 'when releasing a non-RC release' do let(:version) { ReleaseTools::Version.new('42.1.0-ee') } let(:release) { described_class.new(version) } context 'with fetch_managed_component_version feature flag enabled' do it "updates managed version file with the version from the component" do expect(release.client) .to receive(:file_contents) .with('gitlab-org/gitaly', 'VERSION', '42-1-stable') .and_return('9.0.0') expect(release.client) .to receive(:file_contents) .with('gitlab-org/gitlab-pages', 'VERSION', '42-1-stable') .and_return('42.1.0') expect(release.logger).to receive(:warn).with( 'Managed component version does not match version being released', { project: 'gitlab-org/gitaly', component_version: '9.0.0', release_version: '42.1.0' } ) expect(release) .to receive(:commit_version_files) .with( '42-1-stable-ee', { 'GITALY_SERVER_VERSION' => '9.0.0', 'GITLAB_PAGES_VERSION' => '42.1.0' }, { message: "Update managed components version to 42.1.0", skip_ci: true } ) release.update_managed_component_versions end end context 'with fetch_managed_component_version feature flag disabled' do before do disable_feature(:fetch_managed_component_version) end it "updates managed version file with the version from the release" do expect(release.client) .not_to receive(:file_contents) expect(release) .to receive(:commit_version_files) .with( '42-1-stable-ee', { 'GITALY_SERVER_VERSION' => '42.1.0', 'GITLAB_PAGES_VERSION' => '42.1.0' }, { message: "Update managed components version to 42.1.0", skip_ci: true } ) release.update_managed_component_versions end end context 'with release_the_kas feature flag enabled' do it 'updates KAS with the version from the release' do enable_feature(:fetch_managed_component_version, :release_the_kas) expect(release.client) .not_to receive(:file_contents) .with('gitlab-org/cluster-integration/gitlab-agent', 'VERSION', '42-1-stable') expect(release.client) .to receive(:file_contents) .with('gitlab-org/gitaly', 'VERSION', '42-1-stable') .and_return('9.0.0') expect(release.client) .to receive(:file_contents) .with('gitlab-org/gitlab-pages', 'VERSION', '42-1-stable') .and_return('42.1.0') expect(release) .to receive(:commit_version_files) .with( '42-1-stable-ee', { 'GITALY_SERVER_VERSION' => '9.0.0', 'GITLAB_PAGES_VERSION' => '42.1.0', 'GITLAB_KAS_VERSION' => '42.1.0' }, { message: "Update managed components version to 42.1.0", skip_ci: true } ) release.update_managed_component_versions end end end end describe '#wait_for_ee_to_ce_sync' do before do pipeline = double(:pipeline, web_url: 'foo', id: 1) allow(ReleaseTools::GitlabOpsClient) .to receive(:create_pipeline) .with( ReleaseTools::Project::MergeTrain, { MERGE_FOSS: '1', SOURCE_PROJECT: 'gitlab-org/gitlab', SOURCE_BRANCH: '42-1-stable-ee', TARGET_PROJECT: 'gitlab-org/gitlab-foss', TARGET_BRANCH: '42-1-stable' } ) .and_return(pipeline) end it 'returns when EE has been synced to CE' do pipeline = double(:pipeline, status: 'success') version = ReleaseTools::Version.new('42.1.0-ee') release = described_class.new(version) expect(ReleaseTools::GitlabOpsClient) .to receive(:pipeline) .with(ReleaseTools::Project::MergeTrain, 1) .and_return(pipeline) expect { release.wait_for_ee_to_ce_sync }.not_to raise_error end it 'raises when the pipeline fails' do pipeline = double(:pipeline, status: 'failed') version = ReleaseTools::Version.new('42.1.0-ee') release = described_class.new(version) expect(ReleaseTools::GitlabOpsClient) .to receive(:pipeline) .with(ReleaseTools::Project::MergeTrain, 1) .and_return(pipeline) expect { release.wait_for_ee_to_ce_sync } .to raise_error(described_class::PipelineFailed) end it 'raises when the pipeline does not succeed in a timely manner' do pipeline = double(:pipeline, status: 'running') version = ReleaseTools::Version.new('42.1.0-ee') release = described_class.new(version) stub_const( 'ReleaseTools::PublicRelease::GitlabRelease::WAIT_SYNC_INTERVALS', Array.new(15, 0) ) expect(ReleaseTools::GitlabOpsClient) .to receive(:pipeline) .with(ReleaseTools::Project::MergeTrain, 1) .exactly(16) .times .and_return(pipeline) expect { release.wait_for_ee_to_ce_sync } .to raise_error(described_class::PipelineTooSlow) end it 'raises when the pipeline is waiting for resources' do pipeline = double(:pipeline, status: 'waiting_for_resource') version = ReleaseTools::Version.new('42.1.0-ee') release = described_class.new(version) stub_const( 'ReleaseTools::PublicRelease::GitlabRelease::WAIT_SYNC_INTERVALS', Array.new(15, 0) ) expect(ReleaseTools::GitlabOpsClient) .to receive(:pipeline) .with(ReleaseTools::Project::MergeTrain, 1) .exactly(16) .times .and_return(pipeline) expect { release.wait_for_ee_to_ce_sync } .to raise_error(described_class::PipelineTooSlow) end it 'retries the operation when the pipeline is still running' do version = ReleaseTools::Version.new('42.1.0-ee') release = described_class.new(version) stub_const( 'ReleaseTools::PublicRelease::GitlabRelease::WAIT_SYNC_INTERVALS', Array.new(15, 0) ) expect(ReleaseTools::GitlabOpsClient) .to receive(:pipeline) .with(ReleaseTools::Project::MergeTrain, 1) .twice .and_return( double(:pipeline, status: 'running'), double(:pipeline, status: 'success') ) expect { release.wait_for_ee_to_ce_sync }.not_to raise_error end end describe '#create_ce_commit_to_run_ci' do let(:release) { described_class.new(ReleaseTools::Version.new('42.1.0-ee')) } context 'when the last commit includes "ci skip"' do it 'creates a new empty commit' do commit = double(:commit, message: "Foo\n[ci skip]") expect(release.client) .to receive(:commit) .with('gitlab-org/gitlab-foss', { ref: '42-1-stable' }) .and_return(commit) expect(release.client) .to receive(:create_commit) .with( 'gitlab-org/gitlab-foss', '42-1-stable', an_instance_of(String), [ { action: 'update', file_path: 'VERSION', content: '42.1.0' } ] ) release.create_ce_commit_to_run_ci end end context "when the last commit doesn't include \"ci skip\"" do it "doesn't create a new commit" do commit = double(:commit, message: 'Bump version to X') expect(release.client) .to receive(:commit) .with('gitlab-org/gitlab-foss', { ref: '42-1-stable' }) .and_return(commit) expect(release.client).not_to receive(:create_commit) release.create_ce_commit_to_run_ci end end end describe '#compile_changelogs' do it 'compiles the changelog for a stable release' do client = class_spy(ReleaseTools::GitlabClient) version = ReleaseTools::Version.new('42.0.0-ee') release = described_class.new(version, client: client) compiler = instance_spy(ReleaseTools::ChangelogCompiler) expect(ReleaseTools::ChangelogCompiler) .to receive(:new) .with(release.project.canonical_or_security_path, { client: client }) .and_return(compiler) expect(compiler) .to receive(:compile) .with(version, { branch: '42-0-stable-ee' }) release.compile_changelogs end it 'does not compile the changelog for an RC' do client = class_spy(ReleaseTools::GitlabClient) version = ReleaseTools::Version.new('42.0.0-rc42-ee') release = described_class.new(version, client: client) expect(ReleaseTools::ChangelogCompiler).not_to receive(:new) release.compile_changelogs end end describe '#update_versions' do it 'updates the VERSION files for CE and EE' do version = ReleaseTools::Version.new('42.0.0-ee') release = described_class.new(version) expect(release) .to receive(:commit_version_files) .with( '42-0-stable-ee', { 'VERSION' => '42.0.0-ee' }, { skip_ci: false, skip_merge_train: true } ) expect(release) .to receive(:commit_version_files) .with( '42-0-stable', { 'VERSION' => '42.0.0' }, { project: release.ce_project_path, skip_ci: true } ) release.update_versions end end describe '#start_new_minor_release' do context 'when releasing a patch release' do it 'does nothing' do version = ReleaseTools::Version.new('42.1.1-ee') release = described_class.new(version) expect(release).not_to receive(:commit_version_files) release.start_new_minor_release end end context 'when releasing a new minor release' do it 'updates the VERSION files on the source branches' do version = ReleaseTools::Version.new('42.0.0-ee') release = described_class.new(version) allow(version).to receive_messages(to_upcoming_pre_release: '42.1.0-pre', to_ce: double(to_upcoming_pre_release: '42.1.0-pre')) expect(release) .to receive(:commit_version_files) .with('master', { 'VERSION' => '42.1.0-pre' }, { skip_ci: true }) expect(release) .to receive(:commit_version_files) .with( 'master', { 'VERSION' => '42.1.0-pre' }, { project: release.ce_project_path, skip_ci: true } ) release.start_new_minor_release end end end describe '#create_ce_tag' do let(:fake_metrics) { instance_spy(ReleaseTools::Metrics::Client) } let(:version) { ReleaseTools::Version.new('42.0.0-ee') } let(:release) { described_class.new(version) } before do allow(ReleaseTools::Metrics::Client).to receive(:new).and_return(fake_metrics) end context 'when the tag already exists' do it 'returns the tag' do tag = double(:tag) allow(release.client) .to receive(:tag) .with( release.ce_project_path, { tag: version.to_ce.tag } ) .and_return(tag) expect(release.client) .not_to receive(:create_tag) expect(fake_metrics).not_to receive(:inc) expect(release.create_ce_tag).to eq(tag) release.create_ce_tag end end context 'when the tag does not exist' do before do tag = double(:tag) allow(release.client) .to receive(:tag) .with( release.ce_project_path, { tag: version.to_ce.tag } ) .and_raise(gitlab_error(:NotFound)) allow(release.client).to receive(:create_tag).and_return(tag) allow(ReleaseTools::SharedStatus) .to receive(:critical_patch_release?) .and_return(false) end it 'creates the tag for CE' do expect(release.client) .to receive(:create_tag) .with( release.ce_project_path, version.to_ce.tag, '42-0-stable', 'Version v42.0.0' ) expect(ReleaseTools::Metrics::Client.new).to receive(:inc) release.create_ce_tag end end end describe '#create_ee_tag' do it 'creates the tag for EE' do version = ReleaseTools::Version.new('42.0.0-ee') release = described_class.new(version) expect(release.client) .to receive(:find_or_create_tag) .with( release.project_path, version.tag, '42-0-stable-ee', { message: 'Version v42.0.0-ee' } ) release.create_ee_tag end end describe '#add_release_data_for_tags' do it 'adds the release data for CE and EE' do version = ReleaseTools::Version.new('42.0.0-ee') release = described_class.new(version) ce_tag = double(:tag, name: 'foo', commit: double(:commit, id: 'a')) ee_tag = double(:tag, name: 'bar', commit: double(:commit, id: 'b')) expect(release.release_metadata).to receive(:add_release).with( { name: 'gitlab-ce', version: '42.0.0', sha: 'a', ref: 'foo', tag: true } ) expect(release.release_metadata).to receive(:add_release).with( { name: 'gitlab-ee', version: '42.0.0', sha: 'b', ref: 'bar', tag: true } ) release.add_release_data_for_tags(ce_tag, ee_tag) end end describe '#project' do it 'returns the project to release' do version = ReleaseTools::Version.new('42.0.0-ee') release = described_class.new(version) expect(release.project).to eq(ReleaseTools::Project::GitlabEe) end end describe '#ce_target_branch' do it 'returns the target branch for CE' do version = ReleaseTools::Version.new('42.0.0-ee') release = described_class.new(version) expect(release.ce_target_branch).to eq('42-0-stable') end end describe '#ce_project_path' do it 'returns the path to CE' do version = ReleaseTools::Version.new('42.0.0-ee') release = described_class.new(version) expect(release.ce_project_path) .to eq(ReleaseTools::Project::GitlabCe.canonical_or_security_path) end end describe '#source_for_target_branch' do let(:version) { ReleaseTools::Version.new('42.0.0-ee') } context 'when a custom commit is specified' do it 'returns the commit' do release = described_class.new(version, commit: 'foo') expect(release.source_for_target_branch).to eq('foo') end end context 'when no custom commit is specified' do it 'returns the SHA of the last deployment' do release = described_class.new(version) allow(release).to receive(:last_production_commit).and_return('foo') expect(release.source_for_target_branch).to eq('foo') end end end end