Commit 5b3c096c authored by Thong Kuah's avatar Thong Kuah

Convert clusters to use a top-level controller

In preparation so that we can create both cluster attached to project
and cluster attached to group.

- Move ClustersController to top level

- Move Clusters::ApplicationsController to top-level too

- Creates a Clusters::BaseController to share common functions

- Do not rely on @project ivar. Anything could set the ivar.

- Fix Vue page components due to new data-page value

Because of the controller change we have gone from
`projects:clusters:new` to `clusters:new`, so we need to update the file
location of the page components. There is somewhere a function that will
convert data-page to a file location.

On that note, projects/clusters/gcp/new/, translate to
Projects::Clusters::Gcp#new doesn't exist so replace that with
clusters/create_gcp/ and clusters/create_user/
parent 5a953741
import initDismissableCallout from '~/dismissable_callout';
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns'; import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initDismissableCallout('.gcp-signup-offer');
initGkeDropdowns(); initGkeDropdowns();
}); });
import initDismissableCallout from '~/dismissable_callout';
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
document.addEventListener('DOMContentLoaded', () => {
initDismissableCallout('.gcp-signup-offer');
initGkeDropdowns();
});
import initDismissableCallout from '~/dismissable_callout';
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
document.addEventListener('DOMContentLoaded', () => {
initDismissableCallout('.gcp-signup-offer');
initGkeDropdowns();
});
import initDismissableCallout from '~/dismissable_callout';
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
import Project from './project'; import Project from './project';
import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation'; import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const { page } = document.body.dataset;
const newClusterViews = [
'projects:clusters:new',
'projects:clusters:create_gcp',
'projects:clusters:create_user',
];
if (newClusterViews.indexOf(page) > -1) {
initDismissableCallout('.gcp-signup-offer');
initGkeDropdowns();
}
new Project(); // eslint-disable-line no-new new Project(); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new
}); });
# frozen_string_literal: true # frozen_string_literal: true
class Projects::Clusters::ApplicationsController < Projects::ApplicationController class Clusters::ApplicationsController < Clusters::BaseController
before_action :cluster before_action :cluster
before_action :authorize_read_cluster!
before_action :authorize_create_cluster!, only: [:create] before_action :authorize_create_cluster!, only: [:create]
def create def create
......
# frozen_string_literal: true
class Clusters::BaseController < ApplicationController
include RoutableActions
skip_before_action :authenticate_user!
before_action :require_project_id
before_action :project, if: :project_type?
before_action :repository, if: :project_type?
before_action :authorize_read_cluster!
private
# We can extend to `#group_type?` in the future
def require_project_id
not_found unless project_type?
end
def project
@project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]))
end
def repository
@repository ||= project.repository
end
def authorize_read_cluster!
access_denied! unless can?(current_user, :read_cluster, clusterable)
end
def authorize_create_cluster!
access_denied! unless can?(current_user, :create_cluster, clusterable)
end
def clusterable
project if project_type?
end
def project_type?
params[:project_id].present?
end
end
# frozen_string_literal: true # frozen_string_literal: true
class Projects::ClustersController < Projects::ApplicationController class ClustersController < Clusters::BaseController
before_action :cluster, except: [:index, :new, :create_gcp, :create_user] before_action :cluster, except: [:index, :new, :create_gcp, :create_user]
before_action :authorize_read_cluster!
before_action :generate_gcp_authorize_url, only: [:new] before_action :generate_gcp_authorize_url, only: [:new]
before_action :validate_gcp_token, only: [:new] before_action :validate_gcp_token, only: [:new]
before_action :gcp_cluster, only: [:new] before_action :gcp_cluster, only: [:new]
...@@ -11,6 +10,9 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -11,6 +10,9 @@ class Projects::ClustersController < Projects::ApplicationController
before_action :authorize_update_cluster!, only: [:update] before_action :authorize_update_cluster!, only: [:update]
before_action :authorize_admin_cluster!, only: [:destroy] before_action :authorize_admin_cluster!, only: [:destroy]
before_action :update_applications_status, only: [:status] before_action :update_applications_status, only: [:status]
layout :determine_layout
helper_method :token_in_session helper_method :token_in_session
STATUS_POLLING_INTERVAL = 10_000 STATUS_POLLING_INTERVAL = 10_000
...@@ -29,7 +31,7 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -29,7 +31,7 @@ class Projects::ClustersController < Projects::ApplicationController
Gitlab::PollingInterval.set_header(response, interval: STATUS_POLLING_INTERVAL) Gitlab::PollingInterval.set_header(response, interval: STATUS_POLLING_INTERVAL)
render json: ClusterSerializer render json: ClusterSerializer
.new(project: @project, current_user: @current_user) .new(project: project, current_user: @current_user)
.represent_status(@cluster) .represent_status(@cluster)
end end
end end
...@@ -105,6 +107,12 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -105,6 +107,12 @@ class Projects::ClustersController < Projects::ApplicationController
private private
def determine_layout
if project_type?
'project'
end
end
def cluster def cluster
@cluster ||= project.clusters.find(params[:id]) @cluster ||= project.clusters.find(params[:id])
.present(current_user: current_user) .present(current_user: current_user)
...@@ -169,7 +177,7 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -169,7 +177,7 @@ class Projects::ClustersController < Projects::ApplicationController
end end
def generate_gcp_authorize_url def generate_gcp_authorize_url
state = generate_session_key_redirect(new_project_cluster_path(@project).to_s) state = generate_session_key_redirect(new_project_cluster_path(project).to_s)
@authorize_url = GoogleApi::CloudPlatform::Client.new( @authorize_url = GoogleApi::CloudPlatform::Client.new(
nil, callback_google_api_auth_url, nil, callback_google_api_auth_url,
......
...@@ -10,7 +10,7 @@ module ClustersHelper ...@@ -10,7 +10,7 @@ module ClustersHelper
return unless show_gcp_signup_offer? return unless show_gcp_signup_offer?
content_tag :section, class: 'no-animate expanded' do content_tag :section, class: 'no-animate expanded' do
render 'projects/clusters/gcp_signup_offer_banner' render 'clusters/gcp_signup_offer_banner'
end end
end end
end end
...@@ -19,9 +19,9 @@ ...@@ -19,9 +19,9 @@
.tab-content.gitlab-tab-content .tab-content.gitlab-tab-content
.tab-pane{ id: 'create-gcp-cluster-pane', class: active_when(active_tab == 'gcp'), role: 'tabpanel' } .tab-pane{ id: 'create-gcp-cluster-pane', class: active_when(active_tab == 'gcp'), role: 'tabpanel' }
= render 'projects/clusters/gcp/header' = render 'clusters/gcp/header'
- if @valid_gcp_token - if @valid_gcp_token
= render 'projects/clusters/gcp/form' = render 'clusters/gcp/form'
- elsif @authorize_url - elsif @authorize_url
.signin-with-google .signin-with-google
= link_to(image_tag('auth_buttons/signin_with_google.png', width: '191px'), @authorize_url) = link_to(image_tag('auth_buttons/signin_with_google.png', width: '191px'), @authorize_url)
...@@ -32,5 +32,5 @@ ...@@ -32,5 +32,5 @@
= s_('Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_to_documentation: link } = s_('Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_to_documentation: link }
.tab-pane{ id: 'add-user-cluster-pane', class: active_when(active_tab == 'user'), role: 'tabpanel' } .tab-pane{ id: 'add-user-cluster-pane', class: active_when(active_tab == 'user'), role: 'tabpanel' }
= render 'projects/clusters/user/header' = render 'clusters/user/header'
= render 'projects/clusters/user/form' = render 'clusters/user/form'
...@@ -38,9 +38,9 @@ ...@@ -38,9 +38,9 @@
%p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster') %p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster')
.settings-content .settings-content
- if @cluster.managed? - if @cluster.managed?
= render 'projects/clusters/gcp/show' = render 'clusters/gcp/show'
- else - else
= render 'projects/clusters/user/show' = render 'clusters/user/show'
%section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) } %section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) }
.settings-header .settings-header
......
---
title: Change to top level controller for clusters so that we can use it for project
clusters (now) and group clusters (later)
merge_request: 22438
author:
type: other
...@@ -84,6 +84,23 @@ Rails.application.routes.draw do ...@@ -84,6 +84,23 @@ Rails.application.routes.draw do
draw :instance_statistics draw :instance_statistics
end end
concern :clusterable do
resources :clusters, except: [:edit, :create], controller: '/clusters' do
collection do
post :create_gcp
post :create_user
end
member do
get :status, format: :json
scope :applications do
post '/:application', to: '/clusters/applications#create', as: :install_applications
end
end
end
end
draw :api draw :api
draw :sidekiq draw :sidekiq
draw :help draw :help
......
...@@ -206,20 +206,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -206,20 +206,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end end
end end
resources :clusters, except: [:edit, :create] do concerns :clusterable
collection do
post :create_gcp
post :create_user
end
member do
get :status, format: :json
scope :applications do
post '/:application', to: 'clusters/applications#create', as: :install_applications
end
end
end
resources :environments, except: [:destroy] do resources :environments, except: [:destroy] do
member do member do
......
...@@ -4,7 +4,7 @@ module QA ...@@ -4,7 +4,7 @@ module QA
module Operations module Operations
module Kubernetes module Kubernetes
class Add < Page::Base class Add < Page::Base
view 'app/views/projects/clusters/new.html.haml' do view 'app/views/clusters/new.html.haml' do
element :add_existing_cluster_button, "Add existing cluster" # rubocop:disable QA/ElementWithPattern element :add_existing_cluster_button, "Add existing cluster" # rubocop:disable QA/ElementWithPattern
end end
......
...@@ -4,7 +4,7 @@ module QA ...@@ -4,7 +4,7 @@ module QA
module Operations module Operations
module Kubernetes module Kubernetes
class AddExisting < Page::Base class AddExisting < Page::Base
view 'app/views/projects/clusters/user/_form.html.haml' do view 'app/views/clusters/user/_form.html.haml' do
element :cluster_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern element :cluster_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern
element :api_url, 'text_field :api_url' # rubocop:disable QA/ElementWithPattern element :api_url, 'text_field :api_url' # rubocop:disable QA/ElementWithPattern
element :ca_certificate, 'text_area :ca_cert' # rubocop:disable QA/ElementWithPattern element :ca_certificate, 'text_area :ca_cert' # rubocop:disable QA/ElementWithPattern
......
...@@ -4,7 +4,7 @@ module QA ...@@ -4,7 +4,7 @@ module QA
module Operations module Operations
module Kubernetes module Kubernetes
class Index < Page::Base class Index < Page::Base
view 'app/views/projects/clusters/_empty_state.html.haml' do view 'app/views/clusters/_empty_state.html.haml' do
element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add Kubernetes cluster')" # rubocop:disable QA/ElementWithPattern element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add Kubernetes cluster')" # rubocop:disable QA/ElementWithPattern
end end
......
# frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe Projects::Clusters::ApplicationsController do describe Clusters::ApplicationsController do
include AccessMatchersForController include AccessMatchersForController
def current_application def current_application
......
# frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe Projects::ClustersController do describe ClustersController do
include AccessMatchersForController include AccessMatchersForController
include GoogleApi::CloudPlatformHelpers include GoogleApi::CloudPlatformHelpers
...@@ -218,9 +220,9 @@ describe Projects::ClustersController do ...@@ -218,9 +220,9 @@ describe Projects::ClustersController do
describe 'security' do describe 'security' do
before do before do
allow_any_instance_of(described_class) allow_any_instance_of(described_class)
.to receive(:token_in_session).and_return('token') .to receive(:token_in_session).and_return('token')
allow_any_instance_of(described_class) allow_any_instance_of(described_class)
.to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s) .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
allow_any_instance_of(GoogleApi::CloudPlatform::Client) allow_any_instance_of(GoogleApi::CloudPlatform::Client)
.to receive(:projects_zones_clusters_create) do .to receive(:projects_zones_clusters_create) do
OpenStruct.new( OpenStruct.new(
...@@ -322,10 +324,11 @@ describe Projects::ClustersController do ...@@ -322,10 +324,11 @@ describe Projects::ClustersController do
let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) } let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
def go def go
get :status, namespace_id: project.namespace, get :status,
project_id: project, namespace_id: project.namespace,
id: cluster, project_id: project,
format: :json id: cluster,
format: :json
end end
describe 'functionality' do describe 'functionality' do
...@@ -359,9 +362,10 @@ describe Projects::ClustersController do ...@@ -359,9 +362,10 @@ describe Projects::ClustersController do
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
def go def go
get :show, namespace_id: project.namespace, get :show,
project_id: project, namespace_id: project.namespace,
id: cluster project_id: project,
id: cluster
end end
describe 'functionality' do describe 'functionality' do
...@@ -530,9 +534,10 @@ describe Projects::ClustersController do ...@@ -530,9 +534,10 @@ describe Projects::ClustersController do
let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) } let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
def go def go
delete :destroy, namespace_id: project.namespace, delete :destroy,
project_id: project, namespace_id: project.namespace,
id: cluster project_id: project,
id: cluster
end end
describe 'functionality' do describe 'functionality' do
...@@ -591,4 +596,10 @@ describe Projects::ClustersController do ...@@ -591,4 +596,10 @@ describe Projects::ClustersController do
it { expect { go }.to be_denied_for(:external) } it { expect { go }.to be_denied_for(:external) }
end end
end end
context 'no project_id param' do
it 'does not respond to any action without project_id param' do
expect { get :index }.to raise_error(ActionController::UrlGenerationError)
end
end
end end
...@@ -9,16 +9,16 @@ describe 'Gcp Cluster', :js do ...@@ -9,16 +9,16 @@ describe 'Gcp Cluster', :js do
before do before do
project.add_maintainer(user) project.add_maintainer(user)
gitlab_sign_in(user) gitlab_sign_in(user)
allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 } allow(ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
end end
context 'when user has signed with Google' do context 'when user has signed with Google' do
let(:project_id) { 'test-project-1234' } let(:project_id) { 'test-project-1234' }
before do before do
allow_any_instance_of(Projects::ClustersController) allow_any_instance_of(ClustersController)
.to receive(:token_in_session).and_return('token') .to receive(:token_in_session).and_return('token')
allow_any_instance_of(Projects::ClustersController) allow_any_instance_of(ClustersController)
.to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s) .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
end end
......
...@@ -9,7 +9,7 @@ describe 'User Cluster', :js do ...@@ -9,7 +9,7 @@ describe 'User Cluster', :js do
before do before do
project.add_maintainer(user) project.add_maintainer(user)
gitlab_sign_in(user) gitlab_sign_in(user)
allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 } allow(ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
end end
context 'when user does not have a cluster and visits cluster index page' do context 'when user does not have a cluster and visits cluster index page' 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