# frozen_string_literal: true require 'spec_helper' describe API::ProjectClusters do include KubernetesHelpers let(:current_user) { create(:user) } let(:non_member) { create(:user) } let(:project) { create(:project, :repository) } before do project.add_maintainer(current_user) end describe 'GET /projects/:id/clusters' do let!(:extra_cluster) { create(:cluster, :provided_by_gcp, :project) } let!(:clusters) do create_list(:cluster, 5, :provided_by_gcp, :project, :production_environment, projects: [project]) end context 'non-authorized user' do it 'responds with 404' do get api("/projects/#{project.id}/clusters", non_member) expect(response).to have_gitlab_http_status(404) end end context 'authorized user' do before do get api("/projects/#{project.id}/clusters", current_user) end it 'responds with 200' do expect(response).to have_gitlab_http_status(200) end it 'includes pagination headers' do expect(response).to include_pagination_headers end it 'onlies include authorized clusters' do cluster_ids = json_response.map { |cluster| cluster['id'] } expect(cluster_ids).to match_array(clusters.pluck(:id)) expect(cluster_ids).not_to include(extra_cluster.id) end end end describe 'GET /projects/:id/clusters/:cluster_id' do let(:cluster_id) { cluster.id } let(:platform_kubernetes) do create(:cluster_platform_kubernetes, :configured, namespace: 'project-namespace') end let(:cluster) do create(:cluster, :project, :provided_by_gcp, :with_domain, platform_kubernetes: platform_kubernetes, user: current_user, projects: [project]) end context 'non-authorized user' do it 'responds with 404' do get api("/projects/#{project.id}/clusters/#{cluster_id}", non_member) expect(response).to have_gitlab_http_status(404) end end context 'authorized user' do before do get api("/projects/#{project.id}/clusters/#{cluster_id}", current_user) end it 'returns specific cluster' do expect(json_response['id']).to eq(cluster.id) end it 'returns cluster information' do expect(json_response['provider_type']).to eq('gcp') expect(json_response['platform_type']).to eq('kubernetes') expect(json_response['environment_scope']).to eq('*') expect(json_response['cluster_type']).to eq('project_type') expect(json_response['domain']).to eq('example.com') end it 'returns project information' do cluster_project = json_response['project'] expect(cluster_project['id']).to eq(project.id) expect(cluster_project['name']).to eq(project.name) expect(cluster_project['path']).to eq(project.path) end it 'returns kubernetes platform information' do platform = json_response['platform_kubernetes'] expect(platform['api_url']).to eq('https://kubernetes.example.com') expect(platform['namespace']).to eq('project-namespace') expect(platform['ca_cert']).to be_present end it 'returns user information' do user = json_response['user'] expect(user['id']).to eq(current_user.id) expect(user['username']).to eq(current_user.username) end it 'returns GCP provider information' do gcp_provider = json_response['provider_gcp'] expect(gcp_provider['cluster_id']).to eq(cluster.id) expect(gcp_provider['status_name']).to eq('created') expect(gcp_provider['gcp_project_id']).to eq('test-gcp-project') expect(gcp_provider['zone']).to eq('us-central1-a') expect(gcp_provider['machine_type']).to eq('n1-standard-2') expect(gcp_provider['num_nodes']).to eq(3) expect(gcp_provider['endpoint']).to eq('111.111.111.111') end context 'when cluster has no provider' do let(:cluster) do create(:cluster, :project, :provided_by_user, projects: [project]) end it 'does not include GCP provider info' do expect(json_response['provider_gcp']).not_to be_present end end context 'with non-existing cluster' do let(:cluster_id) { 123 } it 'returns 404' do expect(response).to have_gitlab_http_status(404) end end end end shared_context 'kubernetes calls stubbed' do before do stub_kubeclient_discover(api_url) stub_kubeclient_get_namespace(api_url, namespace: namespace) stub_kubeclient_get_service_account(api_url, "#{namespace}-service-account", namespace: namespace) stub_kubeclient_put_service_account(api_url, "#{namespace}-service-account", namespace: namespace) stub_kubeclient_get_secret( api_url, { metadata_name: "#{namespace}-token", token: Base64.encode64('sample-token'), namespace: namespace } ) stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace) stub_kubeclient_get_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace) stub_kubeclient_put_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace) end end describe 'POST /projects/:id/clusters/user' do include_context 'kubernetes calls stubbed' let(:api_url) { 'https://kubernetes.example.com' } let(:namespace) { project.path } let(:authorization_type) { 'rbac' } let(:platform_kubernetes_attributes) do { api_url: api_url, token: 'sample-token', namespace: namespace, authorization_type: authorization_type } end let(:cluster_params) do { name: 'test-cluster', domain: 'domain.example.com', managed: false, platform_kubernetes_attributes: platform_kubernetes_attributes } end context 'non-authorized user' do it 'responds with 404' do post api("/projects/#{project.id}/clusters/user", non_member), params: cluster_params expect(response).to have_gitlab_http_status(404) end end context 'authorized user' do before do post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params end context 'with valid params' do it 'responds with 201' do expect(response).to have_gitlab_http_status(201) end it 'creates a new Cluster::Cluster' do cluster_result = Clusters::Cluster.find(json_response["id"]) platform_kubernetes = cluster_result.platform expect(cluster_result).to be_user expect(cluster_result).to be_kubernetes expect(cluster_result.project).to eq(project) expect(cluster_result.name).to eq('test-cluster') expect(cluster_result.domain).to eq('domain.example.com') expect(cluster_result.managed).to be_falsy expect(platform_kubernetes.rbac?).to be_truthy expect(platform_kubernetes.api_url).to eq(api_url) expect(platform_kubernetes.namespace).to eq(namespace) expect(platform_kubernetes.token).to eq('sample-token') end end context 'when user does not indicate authorization type' do let(:platform_kubernetes_attributes) do { api_url: api_url, token: 'sample-token', namespace: namespace } end it 'defaults to RBAC' do cluster_result = Clusters::Cluster.find(json_response['id']) expect(cluster_result.platform_kubernetes.rbac?).to be_truthy end end context 'when user sets authorization type as ABAC' do let(:authorization_type) { 'abac' } it 'creates an ABAC cluster' do cluster_result = Clusters::Cluster.find(json_response['id']) expect(cluster_result.platform.abac?).to be_truthy end end context 'with invalid params' do let(:namespace) { 'invalid_namespace' } it 'responds with 400' do expect(response).to have_gitlab_http_status(400) end it 'does not create a new Clusters::Cluster' do expect(project.reload.clusters).to be_empty end it 'returns validation errors' do expect(json_response['message']['platform_kubernetes.namespace'].first).to be_present end end end context 'when user tries to add multiple clusters' do before do create(:cluster, :provided_by_gcp, :project, projects: [project]) post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params end it 'responds with 403' do expect(response).to have_gitlab_http_status(403) end it 'returns an appropriate message' do expect(json_response['message']).to include('Instance does not support multiple Kubernetes clusters') end end end describe 'PUT /projects/:id/clusters/:cluster_id' do include_context 'kubernetes calls stubbed' let(:api_url) { 'https://kubernetes.example.com' } let(:namespace) { 'new-namespace' } let(:platform_kubernetes_attributes) { { namespace: namespace } } let(:update_params) do { domain: 'new-domain.com', platform_kubernetes_attributes: platform_kubernetes_attributes } end let!(:kubernetes_namespace) do create(:cluster_kubernetes_namespace, cluster: cluster, project: project) end let(:cluster) do create(:cluster, :project, :provided_by_gcp, projects: [project]) end context 'non-authorized user' do it 'responds with 404' do put api("/projects/#{project.id}/clusters/#{cluster.id}", non_member), params: update_params expect(response).to have_gitlab_http_status(404) end end context 'authorized user' do before do put api("/projects/#{project.id}/clusters/#{cluster.id}", current_user), params: update_params cluster.reload end context 'with valid params' do it 'responds with 200' do expect(response).to have_gitlab_http_status(200) end it 'updates cluster attributes' do expect(cluster.domain).to eq('new-domain.com') expect(cluster.platform_kubernetes.namespace).to eq('new-namespace') end end context 'with invalid params' do let(:namespace) { 'invalid_namespace' } it 'responds with 400' do expect(response).to have_gitlab_http_status(400) end it 'does not update cluster attributes' do expect(cluster.domain).not_to eq('new_domain.com') expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace') expect(cluster.kubernetes_namespace_for(project)).not_to eq('invalid_namespace') end it 'returns validation errors' do expect(json_response['message']['platform_kubernetes.namespace'].first).to match('can contain only lowercase letters') end end context 'with a GCP cluster' do context 'when user tries to change GCP specific fields' do let(:platform_kubernetes_attributes) do { api_url: 'https://new-api-url.com', token: 'new-sample-token' } end it 'responds with 400' do expect(response).to have_gitlab_http_status(400) end it 'returns validation error' do expect(json_response['message']['platform_kubernetes.base'].first).to eq('Cannot modify managed Kubernetes cluster') end end context 'when user tries to change namespace' do let(:namespace) { 'new-namespace' } it 'responds with 200' do expect(response).to have_gitlab_http_status(200) end end end context 'with an user cluster' do let(:api_url) { 'https://new-api-url.com' } let(:cluster) do create(:cluster, :project, :provided_by_user, projects: [project]) end let(:platform_kubernetes_attributes) do { api_url: api_url, namespace: 'new-namespace', token: 'new-sample-token' } end let(:update_params) do { name: 'new-name', platform_kubernetes_attributes: platform_kubernetes_attributes } end it 'responds with 200' do expect(response).to have_gitlab_http_status(200) end it 'updates platform kubernetes attributes' do platform_kubernetes = cluster.platform_kubernetes expect(cluster.name).to eq('new-name') expect(platform_kubernetes.namespace).to eq('new-namespace') expect(platform_kubernetes.api_url).to eq('https://new-api-url.com') expect(platform_kubernetes.token).to eq('new-sample-token') end end context 'with a cluster that does not belong to user' do let(:cluster) { create(:cluster, :project, :provided_by_user) } it 'responds with 404' do expect(response).to have_gitlab_http_status(404) end end end end describe 'DELETE /projects/:id/clusters/:cluster_id' do let(:cluster_params) { { cluster_id: cluster.id } } let(:cluster) do create(:cluster, :project, :provided_by_gcp, projects: [project]) end context 'non-authorized user' do it 'responds with 404' do delete api("/projects/#{project.id}/clusters/#{cluster.id}", non_member), params: cluster_params expect(response).to have_gitlab_http_status(404) end end context 'authorized user' do before do delete api("/projects/#{project.id}/clusters/#{cluster.id}", current_user), params: cluster_params end it 'responds with 204' do expect(response).to have_gitlab_http_status(204) end it 'deletes the cluster' do expect(Clusters::Cluster.exists?(id: cluster.id)).to be_falsy end context 'with a cluster that does not belong to user' do let(:cluster) { create(:cluster, :project, :provided_by_user) } it 'responds with 404' do expect(response).to have_gitlab_http_status(404) end end end end end