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:
Diffstat (limited to 'spec/requests/api/terraform/state_spec.rb')
-rw-r--r--spec/requests/api/terraform/state_spec.rb321
1 files changed, 264 insertions, 57 deletions
diff --git a/spec/requests/api/terraform/state_spec.rb b/spec/requests/api/terraform/state_spec.rb
index 38b08b4e214..fd34345d814 100644
--- a/spec/requests/api/terraform/state_spec.rb
+++ b/spec/requests/api/terraform/state_spec.rb
@@ -2,20 +2,22 @@
require 'spec_helper'
-RSpec.describe API::Terraform::State, :snowplow do
+RSpec.describe API::Terraform::State, :snowplow, feature_category: :infrastructure_as_code do
include HttpBasicAuthHelpers
let_it_be(:project) { create(:project) }
let_it_be(:developer) { create(:user, developer_projects: [project]) }
let_it_be(:maintainer) { create(:user, maintainer_projects: [project]) }
- let!(:state) { create(:terraform_state, :with_version, project: project) }
-
let(:current_user) { maintainer }
let(:auth_header) { user_basic_auth_header(current_user) }
let(:project_id) { project.id }
- let(:state_name) { state.name }
+
+ let(:state_name) { "some-state" }
let(:state_path) { "/projects/#{project_id}/terraform/state/#{state_name}" }
+ let!(:state) do
+ create(:terraform_state, :with_version, project: project, name: URI.decode_www_form_component(state_name))
+ end
before do
stub_terraform_state_object_storage
@@ -91,15 +93,35 @@ RSpec.describe API::Terraform::State, :snowplow do
end
end
- context 'personal acceess token authentication' do
+ shared_examples 'can access terraform state' do
+ it 'returns terraform state of a project of given state name' do
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.body).to eq(state.reload.latest_file.read)
+ end
+ end
+
+ context 'personal access token authentication' do
context 'with maintainer permissions' do
let(:current_user) { maintainer }
- it 'returns terraform state belonging to a project of given state name' do
- request
+ where(given_state_name: %w[test-state test.state test%2Ffoo])
+ with_them do
+ it_behaves_like 'can access terraform state' do
+ let(:state_name) { given_state_name }
+ end
+ end
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.body).to eq(state.reload.latest_file.read)
+ context 'allow_dots_on_tf_state_names is disabled, and the state name contains a dot' do
+ let(:state_name) { 'state-name-with-dot' }
+ let(:state_path) { "/projects/#{project_id}/terraform/state/#{state_name}.tfstate" }
+
+ before do
+ stub_feature_flags(allow_dots_on_tf_state_names: false)
+ end
+
+ it_behaves_like 'can access terraform state'
end
context 'for a project that does not exist' do
@@ -112,18 +134,23 @@ RSpec.describe API::Terraform::State, :snowplow do
end
end
+ context 'with invalid state name' do
+ let(:state_name) { 'foo/bar' }
+
+ it 'returns a 404 error' do
+ request
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
it_behaves_like 'cannot access a state that is scheduled for deletion'
end
context 'with developer permissions' do
let(:current_user) { developer }
- it 'returns terraform state belonging to a project of given state name' do
- request
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.body).to eq(state.reload.latest_file.read)
- end
+ it_behaves_like 'can access terraform state'
end
end
@@ -133,12 +160,7 @@ RSpec.describe API::Terraform::State, :snowplow do
context 'with maintainer permissions' do
let(:job) { create(:ci_build, status: :running, project: project, user: maintainer) }
- it 'returns terraform state belonging to a project of given state name' do
- request
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.body).to eq(state.reload.latest_file.read)
- end
+ it_behaves_like 'can access terraform state'
it 'returns unauthorized if the the job is not running' do
job.update!(status: :failed)
@@ -161,12 +183,7 @@ RSpec.describe API::Terraform::State, :snowplow do
context 'with developer permissions' do
let(:job) { create(:ci_build, status: :running, project: project, user: developer) }
- it 'returns terraform state belonging to a project of given state name' do
- request
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.body).to eq(state.reload.latest_file.read)
- end
+ it_behaves_like 'can access terraform state'
end
end
end
@@ -182,11 +199,26 @@ RSpec.describe API::Terraform::State, :snowplow do
context 'with maintainer permissions' do
let(:current_user) { maintainer }
- it 'updates the state' do
- expect { request }.to change { Terraform::State.count }.by(0)
+ where(given_state_name: %w[test-state test.state test%2Ffoo])
+ with_them do
+ let(:state_name) { given_state_name }
- expect(response).to have_gitlab_http_status(:ok)
- expect(Gitlab::Json.parse(response.body)).to be_empty
+ it 'updates the state' do
+ expect { request }.to change { Terraform::State.count }.by(0)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(Gitlab::Json.parse(response.body)).to be_empty
+ end
+ end
+
+ context 'with invalid state name' do
+ let(:state_name) { 'foo/bar' }
+
+ it 'returns a 404 error' do
+ request
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
end
context 'when serial already exists' do
@@ -224,16 +256,39 @@ RSpec.describe API::Terraform::State, :snowplow do
end
context 'when there is no terraform state of a given name' do
- let(:state_name) { 'example2' }
+ let(:non_existing_state_name) { 'non-existing-state' }
+ let(:non_existing_state_path) { "/projects/#{project_id}/terraform/state/#{non_existing_state_name}" }
+
+ subject(:request) { post api(non_existing_state_path), headers: auth_header, as: :json, params: params }
context 'with maintainer permissions' do
let(:current_user) { maintainer }
- it 'creates a new state' do
- expect { request }.to change { Terraform::State.count }.by(1)
+ where(given_state_name: %w[test-state test.state test%2Ffoo])
+ with_them do
+ let(:state_name) { given_state_name }
- expect(response).to have_gitlab_http_status(:ok)
- expect(Gitlab::Json.parse(response.body)).to be_empty
+ it 'creates a new state' do
+ expect { request }.to change { Terraform::State.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(Gitlab::Json.parse(response.body)).to be_empty
+ end
+ end
+
+ context 'allow_dots_on_tf_state_names is disabled, and the state name contains a dot' do
+ let(:non_existing_state_name) { 'state-name-with-dot.tfstate' }
+
+ before do
+ stub_feature_flags(allow_dots_on_tf_state_names: false)
+ end
+
+ it 'strips characters after the dot' do
+ expect { request }.to change { Terraform::State.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(Terraform::State.last.name).to eq('state-name-with-dot')
+ end
end
end
@@ -269,6 +324,48 @@ RSpec.describe API::Terraform::State, :snowplow do
expect(state.reload_latest_version.build).to eq(job)
end
end
+
+ describe 'response depending on the max allowed state size' do
+ let(:current_user) { maintainer }
+
+ before do
+ stub_application_setting(max_terraform_state_size_bytes: max_allowed_state_size)
+
+ request
+ end
+
+ context 'when the max allowed state size is unlimited (set as 0)' do
+ let(:max_allowed_state_size) { 0 }
+
+ it 'returns a success response' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when the max allowed state size is greater than the request state size' do
+ let(:max_allowed_state_size) { params.to_json.size + 1 }
+
+ it 'returns a success response' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when the max allowed state size is equal to the request state size' do
+ let(:max_allowed_state_size) { params.to_json.size }
+
+ it 'returns a success response' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when the max allowed state size is less than the request state size' do
+ let(:max_allowed_state_size) { params.to_json.size - 1 }
+
+ it "returns a 'payload too large' response" do
+ expect(response).to have_gitlab_http_status(:payload_too_large)
+ end
+ end
+ end
end
describe 'DELETE /projects/:id/terraform/state/:name' do
@@ -276,11 +373,8 @@ RSpec.describe API::Terraform::State, :snowplow do
it_behaves_like 'endpoint with unique user tracking'
- context 'with maintainer permissions' do
- let(:current_user) { maintainer }
- let(:deletion_service) { instance_double(Terraform::States::TriggerDestroyService) }
-
- it 'schedules the state for deletion and returns empty body' do
+ shared_examples 'schedules the state for deletion' do
+ it 'returns empty body' do
expect(Terraform::States::TriggerDestroyService).to receive(:new).and_return(deletion_service)
expect(deletion_service).to receive(:execute).once
@@ -289,6 +383,40 @@ RSpec.describe API::Terraform::State, :snowplow do
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Json.parse(response.body)).to be_empty
end
+ end
+
+ context 'with maintainer permissions' do
+ let(:current_user) { maintainer }
+ let(:deletion_service) { instance_double(Terraform::States::TriggerDestroyService) }
+
+ where(given_state_name: %w[test-state test.state test%2Ffoo])
+ with_them do
+ let(:state_name) { given_state_name }
+
+ it_behaves_like 'schedules the state for deletion'
+ end
+
+ context 'allow_dots_on_tf_state_names is disabled, and the state name contains a dot' do
+ let(:state_name) { 'state-name-with-dot' }
+ let(:state_name_with_dot) { "#{state_name}.tfstate" }
+ let(:state_path) { "/projects/#{project_id}/terraform/state/#{state_name_with_dot}" }
+
+ before do
+ stub_feature_flags(allow_dots_on_tf_state_names: false)
+ end
+
+ it_behaves_like 'schedules the state for deletion'
+ end
+
+ context 'with invalid state name' do
+ let(:state_name) { 'foo/bar' }
+
+ it 'returns a 404 error' do
+ request
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
it_behaves_like 'cannot access a state that is scheduled for deletion'
end
@@ -304,7 +432,7 @@ RSpec.describe API::Terraform::State, :snowplow do
end
end
- describe 'PUT /projects/:id/terraform/state/:name/lock' do
+ describe 'POST /projects/:id/terraform/state/:name/lock' do
let(:params) do
{
ID: '123-456',
@@ -322,10 +450,14 @@ RSpec.describe API::Terraform::State, :snowplow do
it_behaves_like 'endpoint with unique user tracking'
it_behaves_like 'cannot access a state that is scheduled for deletion'
- it 'locks the terraform state' do
- request
+ context 'with invalid state name' do
+ let(:state_name) { 'foo/bar' }
- expect(response).to have_gitlab_http_status(:ok)
+ it 'returns a 404 error' do
+ request
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
end
context 'state is already locked' do
@@ -349,6 +481,47 @@ RSpec.describe API::Terraform::State, :snowplow do
expect(response).to have_gitlab_http_status(:forbidden)
end
end
+
+ where(given_state_name: %w[test-state test%2Ffoo])
+ with_them do
+ let(:state_name) { given_state_name }
+
+ it 'locks the terraform state' do
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'with a dot in the state name' do
+ let(:state_name) { 'test.state' }
+
+ context 'with allow_dots_on_tf_state_names ff enabled' do
+ before do
+ stub_feature_flags(allow_dots_on_tf_state_names: true)
+ end
+
+ let(:state_name) { 'test.state' }
+
+ it 'locks the terraform state' do
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'with allow_dots_on_tf_state_names ff disabled' do
+ before do
+ stub_feature_flags(allow_dots_on_tf_state_names: false)
+ end
+
+ it 'returns 404' do
+ request
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
end
describe 'DELETE /projects/:id/terraform/state/:name/lock' do
@@ -365,8 +538,9 @@ RSpec.describe API::Terraform::State, :snowplow do
end
before do
- state.lock_xid = '123-456'
+ state.lock_xid = '123.456'
state.save!
+ stub_feature_flags(allow_dots_on_tf_state_names: true)
end
subject(:request) { delete api("#{state_path}/lock"), headers: auth_header, params: params }
@@ -379,28 +553,61 @@ RSpec.describe API::Terraform::State, :snowplow do
let(:lock_id) { 'irrelevant to this test, just needs to be present' }
end
- context 'with the correct lock id' do
- let(:lock_id) { '123-456' }
+ where(given_state_name: %w[test-state test.state test%2Ffoo])
+ with_them do
+ let(:state_name) { given_state_name }
- it 'removes the terraform state lock' do
- request
+ context 'with the correct lock id' do
+ let(:lock_id) { '123.456' }
- expect(response).to have_gitlab_http_status(:ok)
+ it 'removes the terraform state lock' do
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'with allow_dots_on_tf_state_names ff disabled' do
+ before do
+ stub_feature_flags(allow_dots_on_tf_state_names: false)
+ end
+
+ context 'with dots in the state name' do
+ let(:lock_id) { '123.456' }
+ let(:state_name) { 'test.state' }
+
+ it 'returns 404' do
+ request
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'with no lock id (force-unlock)' do
+ let(:params) { {} }
+
+ it 'removes the terraform state lock' do
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
end
end
- context 'with no lock id (force-unlock)' do
- let(:params) { {} }
+ context 'with invalid state name' do
+ let(:lock_id) { '123.456' }
+ let(:state_name) { 'foo/bar' }
- it 'removes the terraform state lock' do
+ it 'returns a 404 error' do
request
- expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'with an incorrect lock id' do
- let(:lock_id) { '456-789' }
+ let(:lock_id) { '456.789' }
it 'returns an error' do
request
@@ -420,7 +627,7 @@ RSpec.describe API::Terraform::State, :snowplow do
end
context 'user does not have permission to unlock the state' do
- let(:lock_id) { '123-456' }
+ let(:lock_id) { '123.456' }
let(:current_user) { developer }
it 'returns an error' do