From 733da6d6a015e8c951dcc02250cfe1fab87789c0 Mon Sep 17 00:00:00 2001 From: James Fargher Date: Wed, 10 Apr 2019 14:13:43 +1200 Subject: Instance level kubernetes clusters admin Instance level clusters were already mostly supported, this change adds admin area controllers for cluster CRUD --- .../pages/admin/clusters/destroy/index.js | 5 ++ .../javascripts/pages/admin/clusters/edit/index.js | 5 ++ .../javascripts/pages/admin/clusters/index.js | 21 +++++++ .../pages/admin/clusters/index/index.js | 6 ++ .../javascripts/pages/admin/clusters/show/index.js | 5 ++ .../admin/clusters/applications_controller.rb | 9 +++ app/controllers/admin/clusters_controller.rb | 17 ++++++ app/models/clusters/applications/runner.rb | 6 +- app/models/clusters/cluster.rb | 4 ++ app/models/clusters/instance.rb | 11 ++++ app/policies/clusters/cluster_policy.rb | 1 + app/policies/clusters/instance_policy.rb | 20 +++++++ app/presenters/clusters/cluster_presenter.rb | 4 ++ app/presenters/instance_clusterable_presenter.rb | 69 ++++++++++++++++++++++ app/services/clusters/build_service.rb | 2 + app/services/clusters/create_service.rb | 2 + app/views/layouts/nav/sidebar/_admin.html.haml | 12 ++++ 17 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/pages/admin/clusters/destroy/index.js create mode 100644 app/assets/javascripts/pages/admin/clusters/edit/index.js create mode 100644 app/assets/javascripts/pages/admin/clusters/index.js create mode 100644 app/assets/javascripts/pages/admin/clusters/index/index.js create mode 100644 app/assets/javascripts/pages/admin/clusters/show/index.js create mode 100644 app/controllers/admin/clusters/applications_controller.rb create mode 100644 app/controllers/admin/clusters_controller.rb create mode 100644 app/models/clusters/instance.rb create mode 100644 app/policies/clusters/instance_policy.rb create mode 100644 app/presenters/instance_clusterable_presenter.rb (limited to 'app') diff --git a/app/assets/javascripts/pages/admin/clusters/destroy/index.js b/app/assets/javascripts/pages/admin/clusters/destroy/index.js new file mode 100644 index 00000000000..8001d2dd1da --- /dev/null +++ b/app/assets/javascripts/pages/admin/clusters/destroy/index.js @@ -0,0 +1,5 @@ +import ClustersBundle from '~/clusters/clusters_bundle'; + +document.addEventListener('DOMContentLoaded', () => { + new ClustersBundle(); // eslint-disable-line no-new +}); diff --git a/app/assets/javascripts/pages/admin/clusters/edit/index.js b/app/assets/javascripts/pages/admin/clusters/edit/index.js new file mode 100644 index 00000000000..8001d2dd1da --- /dev/null +++ b/app/assets/javascripts/pages/admin/clusters/edit/index.js @@ -0,0 +1,5 @@ +import ClustersBundle from '~/clusters/clusters_bundle'; + +document.addEventListener('DOMContentLoaded', () => { + new ClustersBundle(); // eslint-disable-line no-new +}); diff --git a/app/assets/javascripts/pages/admin/clusters/index.js b/app/assets/javascripts/pages/admin/clusters/index.js new file mode 100644 index 00000000000..d0c9ae66c6a --- /dev/null +++ b/app/assets/javascripts/pages/admin/clusters/index.js @@ -0,0 +1,21 @@ +import PersistentUserCallout from '~/persistent_user_callout'; +import initGkeDropdowns from '~/projects/gke_cluster_dropdowns'; + +function initGcpSignupCallout() { + const callout = document.querySelector('.gcp-signup-offer'); + PersistentUserCallout.factory(callout); +} + +document.addEventListener('DOMContentLoaded', () => { + const { page } = document.body.dataset; + const newClusterViews = [ + 'admin:clusters:new', + 'admin:clusters:create_gcp', + 'admin:clusters:create_user', + ]; + + if (newClusterViews.indexOf(page) > -1) { + initGcpSignupCallout(); + initGkeDropdowns(); + } +}); diff --git a/app/assets/javascripts/pages/admin/clusters/index/index.js b/app/assets/javascripts/pages/admin/clusters/index/index.js new file mode 100644 index 00000000000..30d519d0e37 --- /dev/null +++ b/app/assets/javascripts/pages/admin/clusters/index/index.js @@ -0,0 +1,6 @@ +import PersistentUserCallout from '~/persistent_user_callout'; + +document.addEventListener('DOMContentLoaded', () => { + const callout = document.querySelector('.gcp-signup-offer'); + PersistentUserCallout.factory(callout); +}); diff --git a/app/assets/javascripts/pages/admin/clusters/show/index.js b/app/assets/javascripts/pages/admin/clusters/show/index.js new file mode 100644 index 00000000000..8001d2dd1da --- /dev/null +++ b/app/assets/javascripts/pages/admin/clusters/show/index.js @@ -0,0 +1,5 @@ +import ClustersBundle from '~/clusters/clusters_bundle'; + +document.addEventListener('DOMContentLoaded', () => { + new ClustersBundle(); // eslint-disable-line no-new +}); diff --git a/app/controllers/admin/clusters/applications_controller.rb b/app/controllers/admin/clusters/applications_controller.rb new file mode 100644 index 00000000000..3351d3ff825 --- /dev/null +++ b/app/controllers/admin/clusters/applications_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Admin::Clusters::ApplicationsController < Clusters::ApplicationsController + private + + def clusterable + @clusterable ||= InstanceClusterablePresenter.fabricate(Clusters::Instance.new, current_user: current_user) + end +end diff --git a/app/controllers/admin/clusters_controller.rb b/app/controllers/admin/clusters_controller.rb new file mode 100644 index 00000000000..777bdf5c981 --- /dev/null +++ b/app/controllers/admin/clusters_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class Admin::ClustersController < Clusters::ClustersController + prepend_before_action :check_instance_clusters_feature_flag! + + layout 'admin' + + private + + def clusterable + @clusterable ||= InstanceClusterablePresenter.fabricate(Clusters::Instance.new, current_user: current_user) + end + + def check_instance_clusters_feature_flag! + render_404 unless Feature.enabled?(:instance_clusters, default_enabled: true) + end +end diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index af648db3708..ceecd931bba 100644 --- a/app/models/clusters/applications/runner.rb +++ b/app/models/clusters/applications/runner.rb @@ -69,10 +69,12 @@ module Clusters } if cluster.group_type? - attributes.merge(groups: [group]) + attributes[:groups] = [group] elsif cluster.project_type? - attributes.merge(projects: [project]) + attributes[:projects] = [project] end + + attributes end def gitlab_url diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index d2b1adacbfb..7220159ac95 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -177,6 +177,10 @@ module Clusters end alias_method :group, :first_group + def instance + Instance.new if instance_type? + end + def kubeclient platform_kubernetes.kubeclient if kubernetes? end diff --git a/app/models/clusters/instance.rb b/app/models/clusters/instance.rb new file mode 100644 index 00000000000..fde83c5a8ad --- /dev/null +++ b/app/models/clusters/instance.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class Clusters::Instance + def clusters + Clusters::Cluster.instance_type + end + + def feature_available?(feature) + ::Feature.enabled?(feature, default_enabled: true) + end +end diff --git a/app/policies/clusters/cluster_policy.rb b/app/policies/clusters/cluster_policy.rb index d6d590687e2..316bd39f7a3 100644 --- a/app/policies/clusters/cluster_policy.rb +++ b/app/policies/clusters/cluster_policy.rb @@ -6,5 +6,6 @@ module Clusters delegate { cluster.group } delegate { cluster.project } + delegate { cluster.instance } end end diff --git a/app/policies/clusters/instance_policy.rb b/app/policies/clusters/instance_policy.rb new file mode 100644 index 00000000000..f72096e8fc6 --- /dev/null +++ b/app/policies/clusters/instance_policy.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Clusters + class InstancePolicy < BasePolicy + include ClusterableActions + + condition(:has_clusters, scope: :subject) { clusterable_has_clusters? } + condition(:can_have_multiple_clusters) { multiple_clusters_available? } + + rule { admin }.policy do + enable :read_cluster + enable :add_cluster + enable :create_cluster + enable :update_cluster + enable :admin_cluster + end + + rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster + end +end diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb index 81994bbce7d..33b217c8498 100644 --- a/app/presenters/clusters/cluster_presenter.rb +++ b/app/presenters/clusters/cluster_presenter.rb @@ -35,6 +35,8 @@ module Clusters s_("ClusterIntegration|Project cluster") elsif cluster.group_type? s_("ClusterIntegration|Group cluster") + elsif cluster.instance_type? + s_("ClusterIntegration|Instance cluster") end end @@ -43,6 +45,8 @@ module Clusters project_cluster_path(project, cluster) elsif cluster.group_type? group_cluster_path(group, cluster) + elsif cluster.instance_type? + admin_cluster_path(cluster) else raise NotImplementedError end diff --git a/app/presenters/instance_clusterable_presenter.rb b/app/presenters/instance_clusterable_presenter.rb new file mode 100644 index 00000000000..f8bbe5216f1 --- /dev/null +++ b/app/presenters/instance_clusterable_presenter.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +class InstanceClusterablePresenter < ClusterablePresenter + extend ::Gitlab::Utils::Override + include ActionView::Helpers::UrlHelper + + def self.fabricate(clusterable, **attributes) + attributes_with_presenter_class = attributes.merge(presenter_class: InstanceClusterablePresenter) + + Gitlab::View::Presenter::Factory + .new(clusterable, attributes_with_presenter_class) + .fabricate! + end + + override :index_path + def index_path + admin_clusters_path + end + + override :new_path + def new_path + new_admin_cluster_path + end + + override :cluster_status_cluster_path + def cluster_status_cluster_path(cluster, params = {}) + cluster_status_admin_cluster_path(cluster, params) + end + + override :install_applications_cluster_path + def install_applications_cluster_path(cluster, application) + install_applications_admin_cluster_path(cluster, application) + end + + override :update_applications_cluster_path + def update_applications_cluster_path(cluster, application) + update_applications_admin_cluster_path(cluster, application) + end + + override :cluster_path + def cluster_path(cluster, params = {}) + admin_cluster_path(cluster, params) + end + + override :create_user_clusters_path + def create_user_clusters_path + create_user_admin_clusters_path + end + + override :create_gcp_clusters_path + def create_gcp_clusters_path + create_gcp_admin_clusters_path + end + + override :empty_state_help_text + def empty_state_help_text + s_('ClusterIntegration|Adding an integration will share the cluster across all projects.') + end + + override :sidebar_text + def sidebar_text + s_('ClusterIntegration|Adding a Kubernetes cluster will automatically share the cluster across all projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster.') + end + + override :learn_more_link + def learn_more_link + link_to(s_('ClusterIntegration|Learn more about instance Kubernetes clusters'), help_page_path('user/instance/clusters/index'), target: '_blank', rel: 'noopener noreferrer') + end +end diff --git a/app/services/clusters/build_service.rb b/app/services/clusters/build_service.rb index 8de73831164..b1ac5549e30 100644 --- a/app/services/clusters/build_service.rb +++ b/app/services/clusters/build_service.rb @@ -12,6 +12,8 @@ module Clusters cluster.cluster_type = :project_type when ::Group cluster.cluster_type = :group_type + when Instance + cluster.cluster_type = :instance_type else raise NotImplementedError end diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb index 5a9da053780..886e484caaf 100644 --- a/app/services/clusters/create_service.rb +++ b/app/services/clusters/create_service.rb @@ -38,6 +38,8 @@ module Clusters { cluster_type: :project_type, projects: [clusterable] } when ::Group { cluster_type: :group_type, groups: [clusterable] } + when Instance + { cluster_type: :instance_type } else raise NotImplementedError end diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml index ece66d3180b..4e96b904ad1 100644 --- a/app/views/layouts/nav/sidebar/_admin.html.haml +++ b/app/views/layouts/nav/sidebar/_admin.html.haml @@ -132,6 +132,18 @@ = _('Abuse Reports') %span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(AbuseReport.count(:all)) + = nav_link(controller: :clusters) do + = link_to admin_clusters_path do + .nav-icon-container + = sprite_icon('cloud-gear') + %span.nav-item-name + = _('Kubernetes') + %ul.sidebar-sub-level-items.is-fly-out-only + = nav_link(controller: :clusters, html_options: { class: "fly-out-top-item" } ) do + = link_to admin_clusters_path do + %strong.fly-out-top-item-name + = _('Kubernetes') + - if akismet_enabled? = nav_link(controller: :spam_logs) do = link_to admin_spam_logs_path do -- cgit v1.2.3