Commit a2f89f2b authored by Tiger's avatar Tiger Committed by Yannis Roussos

Associate Terraform state versions with the CI job that created them

https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45347
parent ad966830
......@@ -48,11 +48,11 @@ module Terraform
self.lock_xid.present?
end
def update_file!(data, version:)
def update_file!(data, version:, build:)
if versioning_enabled?
create_new_version!(data: data, version: version)
create_new_version!(data: data, version: version, build: build)
elsif latest_version.present?
migrate_legacy_version!(data: data, version: version)
migrate_legacy_version!(data: data, version: version, build: build)
else
self.file = data
save!
......@@ -81,18 +81,18 @@ module Terraform
# The code can be removed in the next major version (14.0), after
# which any states that haven't been migrated will need to be
# recreated: https://gitlab.com/gitlab-org/gitlab/-/issues/258960
def migrate_legacy_version!(data:, version:)
def migrate_legacy_version!(data:, version:, build:)
current_file = latest_version.file.read
current_version = parse_serial(current_file) || version - 1
update!(versioning_enabled: true)
reload_latest_version.update!(version: current_version, file: CarrierWaveStringFile.new(current_file))
create_new_version!(data: data, version: version)
create_new_version!(data: data, version: version, build: build)
end
def create_new_version!(data:, version:)
new_version = versions.build(version: version, created_by_user: locked_by_user)
def create_new_version!(data:, version:, build:)
new_version = versions.build(version: version, created_by_user: locked_by_user, build: build)
new_version.assign_attributes(file: data)
new_version.save!
end
......
......@@ -6,6 +6,7 @@ module Terraform
belongs_to :terraform_state, class_name: 'Terraform::State', optional: false
belongs_to :created_by_user, class_name: 'User', optional: true
belongs_to :build, class_name: 'Ci::Build', optional: true, foreign_key: :ci_build_id
scope :ordered_by_version_desc, -> { order(version: :desc) }
......
---
title: Associate Terraform state versions with the CI job that created them
merge_request: 45347
author:
type: added
# frozen_string_literal: true
class AddCiBuildIdToTerraformStateVersions < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'index_terraform_state_versions_on_ci_build_id'
disable_ddl_transaction!
def up
unless column_exists?(:terraform_state_versions, :ci_build_id)
add_column :terraform_state_versions, :ci_build_id, :bigint
end
add_concurrent_index :terraform_state_versions, :ci_build_id, name: INDEX_NAME
add_concurrent_foreign_key :terraform_state_versions, :ci_builds, column: :ci_build_id, on_delete: :nullify
end
def down
with_lock_retries do
remove_foreign_key_if_exists :terraform_state_versions, :ci_builds, column: :ci_build_id
end
remove_concurrent_index_by_name :terraform_state_versions, INDEX_NAME
remove_column :terraform_state_versions, :ci_build_id
end
end
cae3c41e344f15fa5a9cf71546a0bf209ead964fd3efefba39aba79afd60e0be
\ No newline at end of file
......@@ -16454,6 +16454,7 @@ CREATE TABLE terraform_state_versions (
verified_at timestamp with time zone,
verification_checksum bytea,
verification_failure text,
ci_build_id bigint,
CONSTRAINT check_0824bb7bbd CHECK ((char_length(file) <= 255)),
CONSTRAINT tf_state_versions_verification_failure_text_limit CHECK ((char_length(verification_failure) <= 255))
);
......@@ -21775,6 +21776,8 @@ CREATE INDEX index_term_agreements_on_term_id ON term_agreements USING btree (te
CREATE INDEX index_term_agreements_on_user_id ON term_agreements USING btree (user_id);
CREATE INDEX index_terraform_state_versions_on_ci_build_id ON terraform_state_versions USING btree (ci_build_id);
CREATE INDEX index_terraform_state_versions_on_created_by_user_id ON terraform_state_versions USING btree (created_by_user_id);
CREATE UNIQUE INDEX index_terraform_state_versions_on_state_id_and_version ON terraform_state_versions USING btree (terraform_state_id, version);
......@@ -22377,6 +22380,9 @@ ALTER TABLE ONLY clusters_applications_runners
ALTER TABLE ONLY design_management_designs_versions
ADD CONSTRAINT fk_03c671965c FOREIGN KEY (design_id) REFERENCES design_management_designs(id) ON DELETE CASCADE;
ALTER TABLE ONLY terraform_state_versions
ADD CONSTRAINT fk_04b91e4a9f FOREIGN KEY (ci_build_id) REFERENCES ci_builds(id) ON DELETE SET NULL;
ALTER TABLE ONLY ci_test_cases
ADD CONSTRAINT fk_0526c30ded FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
......
......@@ -52,7 +52,7 @@ module API
no_content! if data.empty?
remote_state_handler.handle_with_lock do |state|
state.update_file!(CarrierWaveStringFile.new(data), version: params[:serial])
state.update_file!(CarrierWaveStringFile.new(data), version: params[:serial], build: current_authenticated_job)
status :ok
end
end
......
......@@ -83,6 +83,8 @@ module Gitlab
return unless ::Gitlab::Auth::CI_JOB_USER == login
job = find_valid_running_job_by_token!(password)
@current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables
job.user
end
......
......@@ -607,6 +607,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
set_basic_auth_header(username, build.token)
is_expected.to eq user
expect(@current_authenticated_job).to eq build
end
it 'raises error with invalid token' do
......
......@@ -97,10 +97,11 @@ RSpec.describe Terraform::State do
end
describe '#update_file!' do
let(:version) { 3 }
let(:data) { Hash[terraform_version: '0.12.21'].to_json }
let_it_be(:build) { create(:ci_build) }
let_it_be(:version) { 3 }
let_it_be(:data) { Hash[terraform_version: '0.12.21'].to_json }
subject { terraform_state.update_file!(CarrierWaveStringFile.new(data), version: version) }
subject { terraform_state.update_file!(CarrierWaveStringFile.new(data), version: version, build: build) }
context 'versioning is enabled' do
let(:terraform_state) { create(:terraform_state) }
......@@ -109,6 +110,7 @@ RSpec.describe Terraform::State do
expect { subject }.to change { Terraform::StateVersion.count }
expect(terraform_state.latest_version.version).to eq(version)
expect(terraform_state.latest_version.build).to eq(build)
expect(terraform_state.latest_version.file.read).to eq(data)
end
end
......
......@@ -7,6 +7,7 @@ RSpec.describe Terraform::StateVersion do
it { is_expected.to belong_to(:terraform_state).required }
it { is_expected.to belong_to(:created_by_user).class_name('User').optional }
it { is_expected.to belong_to(:build).class_name('Ci::Build').optional }
describe 'scopes' do
describe '.ordered_by_version_desc' do
......
......@@ -113,7 +113,7 @@ RSpec.describe API::Terraform::State do
end
describe 'POST /projects/:id/terraform/state/:name' do
let(:params) { { 'instance': 'example-instance', 'serial': '1' } }
let(:params) { { 'instance': 'example-instance', 'serial': state.latest_version.version + 1 } }
subject(:request) { post api(state_path), headers: auth_header, as: :json, params: params }
......@@ -198,6 +198,18 @@ RSpec.describe API::Terraform::State do
end
end
end
context 'when using job token authentication' do
let(:job) { create(:ci_build, status: :running, project: project, user: maintainer) }
let(:auth_header) { job_basic_auth_header(job) }
it 'associates the job with the newly created state version' do
expect { request }.to change { state.versions.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
expect(state.reload_latest_version.build).to eq(job)
end
end
end
describe 'DELETE /projects/:id/terraform/state/:name' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment