clusters_controller_spec.rb 19.4 KB
Newer Older
1 2 3 4 5 6 7 8
# frozen_string_literal: true

require 'spec_helper'

describe Groups::ClustersController do
  include AccessMatchersForController
  include GoogleApi::CloudPlatformHelpers

9
  let_it_be(:group) { create(:group) }
10 11 12 13 14 15 16 17 18 19

  let(:user) { create(:user) }

  before do
    group.add_maintainer(user)
    sign_in(user)
  end

  describe 'GET index' do
    def go(params = {})
blackst0ne's avatar
blackst0ne committed
20
      get :index, params: params.reverse_merge(group_id: group)
21 22
    end

23 24 25
    describe 'functionality' do
      context 'when group has one or more clusters' do
        let(:group) { create(:group) }
26

27 28 29
        let!(:enabled_cluster) do
          create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group])
        end
30

31 32 33
        let!(:disabled_cluster) do
          create(:cluster, :disabled, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
        end
34

35 36
        it 'lists available clusters' do
          go
37

38 39 40 41
          expect(response).to have_gitlab_http_status(:ok)
          expect(response).to render_template(:index)
          expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster])
        end
42

43 44
        context 'when page is specified' do
          let(:last_page) { group.clusters.page.total_pages }
45

46 47 48
          before do
            allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
            create_list(:cluster, 2, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
49 50
          end

51 52
          it 'redirects to the page' do
            go(page: last_page)
53 54

            expect(response).to have_gitlab_http_status(:ok)
55
            expect(assigns(:clusters).current_page).to eq(last_page)
56 57
          end
        end
58
      end
59

60 61
      context 'when group does not have a cluster' do
        let(:group) { create(:group) }
62

63 64
        it 'returns an empty state page' do
          go
65

66 67 68
          expect(response).to have_gitlab_http_status(:ok)
          expect(response).to render_template(:index, partial: :empty_state)
          expect(assigns(:clusters)).to eq([])
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
        end
      end
    end

    describe 'security' do
      let(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) }

      it { expect { go }.to be_allowed_for(:admin) }
      it { expect { go }.to be_allowed_for(:owner).of(group) }
      it { expect { go }.to be_allowed_for(:maintainer).of(group) }
      it { expect { go }.to be_denied_for(:developer).of(group) }
      it { expect { go }.to be_denied_for(:reporter).of(group) }
      it { expect { go }.to be_denied_for(:guest).of(group) }
      it { expect { go }.to be_denied_for(:user) }
      it { expect { go }.to be_denied_for(:external) }
    end
  end

  describe 'GET new' do
88 89
    def go(provider: 'gke')
      get :new, params: { group_id: group, provider: provider }
90 91 92 93 94 95 96 97 98 99
    end

    describe 'functionality for new cluster' do
      context 'when omniauth has been configured' do
        let(:key) { 'secret-key' }
        let(:session_key_for_redirect_uri) do
          GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(key)
        end

        before do
100
          stub_feature_flags(create_eks_clusters: false)
101 102 103 104 105 106 107 108 109
          allow(SecureRandom).to receive(:hex).and_return(key)
        end

        it 'has authorize_url' do
          go

          expect(assigns(:authorize_url)).to include(key)
          expect(session[session_key_for_redirect_uri]).to eq(new_group_cluster_path(group))
        end
110 111 112 113 114 115 116 117 118 119 120 121 122 123

        context 'when create_eks_clusters feature flag is enabled' do
          before do
            stub_feature_flags(create_eks_clusters: true)
          end

          context 'when selected provider is gke and no valid gcp token exists' do
            it 'redirects to gcp authorize_url' do
              go

              expect(response).to redirect_to(assigns(:authorize_url))
            end
          end
        end
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
      end

      context 'when omniauth has not configured' do
        before do
          stub_omniauth_setting(providers: [])
        end

        it 'does not have authorize_url' do
          go

          expect(assigns(:authorize_url)).to be_nil
        end
      end

      context 'when access token is valid' do
        before do
          stub_google_api_validate_token
        end

        it 'has new object' do
          go

146
          expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
        end
      end

      context 'when access token is expired' do
        before do
          stub_google_api_expired_token
        end

        it { expect(@valid_gcp_token).to be_falsey }
      end

      context 'when access token is not stored in session' do
        it { expect(@valid_gcp_token).to be_falsey }
      end
    end

    describe 'functionality for existing cluster' do
      it 'has new object' do
        go

167
        expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter)
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
      end
    end

    describe 'security' do
      it { expect { go }.to be_allowed_for(:admin) }
      it { expect { go }.to be_allowed_for(:owner).of(group) }
      it { expect { go }.to be_allowed_for(:maintainer).of(group) }
      it { expect { go }.to be_denied_for(:developer).of(group) }
      it { expect { go }.to be_denied_for(:reporter).of(group) }
      it { expect { go }.to be_denied_for(:guest).of(group) }
      it { expect { go }.to be_denied_for(:user) }
      it { expect { go }.to be_denied_for(:external) }
    end
  end

  describe 'POST create for new cluster' do
    let(:legacy_abac_param) { 'true' }
    let(:params) do
      {
        cluster: {
          name: 'new-cluster',
189
          managed: '1',
190 191 192 193 194 195 196 197 198
          provider_gcp_attributes: {
            gcp_project_id: 'gcp-project-12345',
            legacy_abac: legacy_abac_param
          }
        }
      }
    end

    def go
blackst0ne's avatar
blackst0ne committed
199
      post :create_gcp, params: params.merge(group_id: group)
200 201 202 203 204 205 206 207 208 209 210 211
    end

    describe 'functionality' do
      context 'when access token is valid' do
        before do
          stub_google_api_validate_token
        end

        it 'creates a new cluster' do
          expect(ClusterProvisionWorker).to receive(:perform_async)
          expect { go }.to change { Clusters::Cluster.count }
            .and change { Clusters::Providers::Gcp.count }
212 213 214 215 216 217 218

          cluster = group.clusters.first

          expect(response).to redirect_to(group_cluster_path(group, cluster))
          expect(cluster).to be_gcp
          expect(cluster).to be_kubernetes
          expect(cluster.provider_gcp).to be_legacy_abac
219
          expect(cluster).to be_managed
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
        end

        context 'when legacy_abac param is false' do
          let(:legacy_abac_param) { 'false' }

          it 'creates a new cluster with legacy_abac_disabled' do
            expect(ClusterProvisionWorker).to receive(:perform_async)
            expect { go }.to change { Clusters::Cluster.count }
              .and change { Clusters::Providers::Gcp.count }
            expect(group.clusters.first.provider_gcp).not_to be_legacy_abac
          end
        end
      end

      context 'when access token is expired' do
        before do
          stub_google_api_expired_token
        end

        it { expect(@valid_gcp_token).to be_falsey }
      end

      context 'when access token is not stored in session' do
        it { expect(@valid_gcp_token).to be_falsey }
      end
    end

    describe 'security' do
      before do
        allow_any_instance_of(described_class)
          .to receive(:token_in_session).and_return('token')
        allow_any_instance_of(described_class)
          .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
        allow_any_instance_of(GoogleApi::CloudPlatform::Client)
          .to receive(:projects_zones_clusters_create) do
          OpenStruct.new(
            self_link: 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123',
            status: 'RUNNING'
          )
        end

        allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil)
      end

      it { expect { go }.to be_allowed_for(:admin) }
      it { expect { go }.to be_allowed_for(:owner).of(group) }
      it { expect { go }.to be_allowed_for(:maintainer).of(group) }
      it { expect { go }.to be_denied_for(:developer).of(group) }
      it { expect { go }.to be_denied_for(:reporter).of(group) }
      it { expect { go }.to be_denied_for(:guest).of(group) }
      it { expect { go }.to be_denied_for(:user) }
      it { expect { go }.to be_denied_for(:external) }
    end
  end

  describe 'POST create for existing cluster' do
    let(:params) do
      {
        cluster: {
          name: 'new-cluster',
280
          managed: '1',
281 282
          platform_kubernetes_attributes: {
            api_url: 'http://my-url',
283
            token: 'test'
284 285 286 287 288 289
          }
        }
      }
    end

    def go
blackst0ne's avatar
blackst0ne committed
290
      post :create_user, params: params.merge(group_id: group)
291 292 293 294 295 296 297 298 299 300
    end

    describe 'functionality' do
      context 'when creates a cluster' do
        it 'creates a new cluster' do
          expect(ClusterProvisionWorker).to receive(:perform_async)

          expect { go }.to change { Clusters::Cluster.count }
            .and change { Clusters::Platforms::Kubernetes.count }

301
          cluster = group.clusters.first
302

303 304 305
          expect(response).to redirect_to(group_cluster_path(group, cluster))
          expect(cluster).to be_user
          expect(cluster).to be_kubernetes
306
          expect(cluster).to be_managed
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
        end
      end

      context 'when creates a RBAC-enabled cluster' do
        let(:params) do
          {
            cluster: {
              name: 'new-cluster',
              platform_kubernetes_attributes: {
                api_url: 'http://my-url',
                token: 'test',
                authorization_type: 'rbac'
              }
            }
          }
        end

        it 'creates a new cluster' do
          expect(ClusterProvisionWorker).to receive(:perform_async)

          expect { go }.to change { Clusters::Cluster.count }
            .and change { Clusters::Platforms::Kubernetes.count }

330
          cluster = group.clusters.first
331

332 333 334 335
          expect(response).to redirect_to(group_cluster_path(group, cluster))
          expect(cluster).to be_user
          expect(cluster).to be_kubernetes
          expect(cluster).to be_platform_kubernetes_rbac
336 337
        end
      end
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360

      context 'when creates a user-managed cluster' do
        let(:params) do
          {
            cluster: {
              name: 'new-cluster',
              managed: '0',
              platform_kubernetes_attributes: {
                api_url: 'http://my-url',
                token: 'test',
                authorization_type: 'rbac'
              }
            }
          }
        end

        it 'creates a new user-managed cluster' do
          go

          cluster = group.clusters.first
          expect(cluster.managed?).to be_falsy
        end
      end
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
    end

    describe 'security' do
      it { expect { go }.to be_allowed_for(:admin) }
      it { expect { go }.to be_allowed_for(:owner).of(group) }
      it { expect { go }.to be_allowed_for(:maintainer).of(group) }
      it { expect { go }.to be_denied_for(:developer).of(group) }
      it { expect { go }.to be_denied_for(:reporter).of(group) }
      it { expect { go }.to be_denied_for(:guest).of(group) }
      it { expect { go }.to be_denied_for(:user) }
      it { expect { go }.to be_denied_for(:external) }
    end
  end

  describe 'GET cluster_status' do
    let(:cluster) { create(:cluster, :providing_by_gcp, cluster_type: :group_type, groups: [group]) }

    def go
      get :cluster_status,
blackst0ne's avatar
blackst0ne committed
380 381 382 383
        params: {
          group_id: group.to_param,
          id: cluster
        },
384 385 386 387
        format: :json
    end

    describe 'functionality' do
388
      it 'responds with matching schema' do
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
        go

        expect(response).to have_gitlab_http_status(:ok)
        expect(response).to match_response_schema('cluster_status')
      end

      it 'invokes schedule_status_update on each application' do
        expect_any_instance_of(Clusters::Applications::Ingress).to receive(:schedule_status_update)

        go
      end
    end

    describe 'security' do
      it { expect { go }.to be_allowed_for(:admin) }
      it { expect { go }.to be_allowed_for(:owner).of(group) }
      it { expect { go }.to be_allowed_for(:maintainer).of(group) }
      it { expect { go }.to be_denied_for(:developer).of(group) }
      it { expect { go }.to be_denied_for(:reporter).of(group) }
      it { expect { go }.to be_denied_for(:guest).of(group) }
      it { expect { go }.to be_denied_for(:user) }
      it { expect { go }.to be_denied_for(:external) }
    end
  end

  describe 'GET show' do
    let(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) }

    def go
      get :show,
blackst0ne's avatar
blackst0ne committed
419 420 421 422
        params: {
          group_id: group,
          id: cluster
        }
423 424 425
    end

    describe 'functionality' do
426
      it 'renders view' do
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
        go

        expect(response).to have_gitlab_http_status(:ok)
        expect(assigns(:cluster)).to eq(cluster)
      end
    end

    describe 'security' do
      it { expect { go }.to be_allowed_for(:admin) }
      it { expect { go }.to be_allowed_for(:owner).of(group) }
      it { expect { go }.to be_allowed_for(:maintainer).of(group) }
      it { expect { go }.to be_denied_for(:developer).of(group) }
      it { expect { go }.to be_denied_for(:reporter).of(group) }
      it { expect { go }.to be_denied_for(:guest).of(group) }
      it { expect { go }.to be_denied_for(:user) }
      it { expect { go }.to be_denied_for(:external) }
    end
  end

  describe 'PUT update' do
    def go(format: :html)
blackst0ne's avatar
blackst0ne committed
448
      put :update, params: params.merge(
449 450 451 452 453 454 455
        group_id: group.to_param,
        id: cluster,
        format: format
      )
    end

    let(:cluster) { create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group]) }
456
    let(:domain) { 'test-domain.com' }
457 458 459 460 461

    let(:params) do
      {
        cluster: {
          enabled: false,
462
          name: 'my-new-cluster-name',
463
          managed: false,
464
          base_domain: domain
465 466 467 468
        }
      }
    end

469
    it 'updates and redirects back to show page' do
470 471 472 473 474 475 476
      go

      cluster.reload
      expect(response).to redirect_to(group_cluster_path(group, cluster))
      expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
      expect(cluster.enabled).to be_falsey
      expect(cluster.name).to eq('my-new-cluster-name')
477
      expect(cluster).not_to be_managed
478 479 480 481
      expect(cluster.domain).to eq('test-domain.com')
    end

    context 'when domain is invalid' do
482
      let(:domain) { 'http://not-a-valid-domain' }
483

484
      it 'does not update cluster attributes' do
485 486 487 488 489 490 491
        go

        cluster.reload
        expect(response).to render_template(:show)
        expect(cluster.name).not_to eq('my-new-cluster-name')
        expect(cluster.domain).not_to eq('test-domain.com')
      end
492 493 494 495 496 497 498 499 500
    end

    context 'when format is json' do
      context 'when changing parameters' do
        context 'when valid parameters are used' do
          let(:params) do
            {
              cluster: {
                enabled: false,
501
                name: 'my-new-cluster-name',
502
                managed: false,
503
                domain: domain
504 505 506 507
              }
            }
          end

508
          it 'updates and redirects back to show page' do
509 510 511 512 513 514
            go(format: :json)

            cluster.reload
            expect(response).to have_http_status(:no_content)
            expect(cluster.enabled).to be_falsey
            expect(cluster.name).to eq('my-new-cluster-name')
515
            expect(cluster).not_to be_managed
516 517 518 519 520 521 522 523
          end
        end

        context 'when invalid parameters are used' do
          let(:params) do
            {
              cluster: {
                enabled: false,
524
                name: ''
525 526 527 528
              }
            }
          end

529
          it 'rejects changes' do
530 531 532 533 534 535 536 537 538
            go(format: :json)

            expect(response).to have_http_status(:bad_request)
          end
        end
      end
    end

    describe 'security' do
539
      let_it_be(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) }
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556

      it { expect { go }.to be_allowed_for(:admin) }
      it { expect { go }.to be_allowed_for(:owner).of(group) }
      it { expect { go }.to be_allowed_for(:maintainer).of(group) }
      it { expect { go }.to be_denied_for(:developer).of(group) }
      it { expect { go }.to be_denied_for(:reporter).of(group) }
      it { expect { go }.to be_denied_for(:guest).of(group) }
      it { expect { go }.to be_denied_for(:user) }
      it { expect { go }.to be_denied_for(:external) }
    end
  end

  describe 'DELETE destroy' do
    let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group]) }

    def go
      delete :destroy,
blackst0ne's avatar
blackst0ne committed
557 558 559 560
        params: {
          group_id: group,
          id: cluster
        }
561 562 563 564 565
    end

    describe 'functionality' do
      context 'when cluster is provided by GCP' do
        context 'when cluster is created' do
566
          it 'destroys and redirects back to clusters list' do
567 568 569 570 571 572 573 574 575 576 577 578 579
            expect { go }
              .to change { Clusters::Cluster.count }.by(-1)
              .and change { Clusters::Platforms::Kubernetes.count }.by(-1)
              .and change { Clusters::Providers::Gcp.count }.by(-1)

            expect(response).to redirect_to(group_clusters_path(group))
            expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
          end
        end

        context 'when cluster is being created' do
          let!(:cluster) { create(:cluster, :providing_by_gcp, :production_environment, cluster_type: :group_type, groups: [group]) }

580
          it 'destroys and redirects back to clusters list' do
581 582 583 584 585 586 587 588 589 590 591 592 593
            expect { go }
              .to change { Clusters::Cluster.count }.by(-1)
              .and change { Clusters::Providers::Gcp.count }.by(-1)

            expect(response).to redirect_to(group_clusters_path(group))
            expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
          end
        end
      end

      context 'when cluster is provided by user' do
        let!(:cluster) { create(:cluster, :provided_by_user, :production_environment, cluster_type: :group_type, groups: [group]) }

594
        it 'destroys and redirects back to clusters list' do
595 596 597 598 599 600 601 602 603 604 605 606
          expect { go }
            .to change { Clusters::Cluster.count }.by(-1)
            .and change { Clusters::Platforms::Kubernetes.count }.by(-1)
            .and change { Clusters::Providers::Gcp.count }.by(0)

          expect(response).to redirect_to(group_clusters_path(group))
          expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.')
        end
      end
    end

    describe 'security' do
607
      let_it_be(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group]) }
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625

      it { expect { go }.to be_allowed_for(:admin) }
      it { expect { go }.to be_allowed_for(:owner).of(group) }
      it { expect { go }.to be_allowed_for(:maintainer).of(group) }
      it { expect { go }.to be_denied_for(:developer).of(group) }
      it { expect { go }.to be_denied_for(:reporter).of(group) }
      it { expect { go }.to be_denied_for(:guest).of(group) }
      it { expect { go }.to be_denied_for(:user) }
      it { expect { go }.to be_denied_for(:external) }
    end
  end

  context 'no group_id param' do
    it 'does not respond to any action without group_id param' do
      expect { get :index }.to raise_error(ActionController::UrlGenerationError)
    end
  end
end