diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2017-12-05 12:00:02 +0000 |
---|---|---|
committer | Grzegorz Bizon <grzegorz@gitlab.com> | 2017-12-05 12:00:02 +0000 |
commit | 4b66bdfa1af8fbef5d2af94a62e2522806cc7250 (patch) | |
tree | b63b843eca0e92ac2938ad8a49a979c2b8ab52e9 | |
parent | 04a882d8d3bd68bee71f5b7073cb7a8ce0149852 (diff) | |
download | gitlab-ce-4b66bdfa1af8fbef5d2af94a62e2522806cc7250.tar.gz |
Second iteration of Move Kubernetes from service to Cluster page
38 files changed, 1160 insertions, 632 deletions
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index dc443475952..cdb5c430aa9 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -48,6 +48,7 @@ export default class Clusters { this.toggle = this.toggle.bind(this); this.installApplication = this.installApplication.bind(this); + this.showToken = this.showToken.bind(this); this.toggleButton = document.querySelector('.js-toggle-cluster'); this.toggleInput = document.querySelector('.js-toggle-input'); @@ -56,6 +57,8 @@ export default class Clusters { this.creatingContainer = document.querySelector('.js-cluster-creating'); this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason'); this.successApplicationContainer = document.querySelector('.js-cluster-application-notice'); + this.showTokenButton = document.querySelector('.js-show-cluster-token'); + this.tokenField = document.querySelector('.js-cluster-token'); initSettingsPanels(); this.initApplications(); @@ -97,11 +100,13 @@ export default class Clusters { addListeners() { this.toggleButton.addEventListener('click', this.toggle); + if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken); eventHub.$on('installApplication', this.installApplication); } removeListeners() { this.toggleButton.removeEventListener('click', this.toggle); + if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken); eventHub.$off('installApplication', this.installApplication); } @@ -149,6 +154,16 @@ export default class Clusters { this.toggleInput.setAttribute('value', this.toggleButton.classList.contains('checked').toString()); } + showToken() { + const type = this.tokenField.getAttribute('type'); + + if (type === 'password') { + this.tokenField.setAttribute('type', 'text'); + } else { + this.tokenField.setAttribute('type', 'password'); + } + } + hideAll() { this.errorContainer.classList.add('hidden'); this.successContainer.classList.add('hidden'); diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index e5b9e1f2de6..83e211d6086 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -8,3 +8,9 @@ // Wait for the Vue to kick-in and render the applications block min-height: 302px; } + +.clusters-dropdown-menu { + max-width: 100%; +} + +@include new-style-dropdown('.clusters-dropdown '); diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb new file mode 100644 index 00000000000..b64f7a2a6bd --- /dev/null +++ b/app/controllers/projects/clusters/gcp_controller.rb @@ -0,0 +1,75 @@ +class Projects::Clusters::GcpController < Projects::ApplicationController + before_action :authorize_read_cluster! + before_action :authorize_google_api, except: [:login] + before_action :authorize_create_cluster!, only: [:new, :create] + + def login + begin + state = generate_session_key_redirect(gcp_new_namespace_project_clusters_path.to_s) + + @authorize_url = GoogleApi::CloudPlatform::Client.new( + nil, callback_google_api_auth_url, + state: state).authorize_url + rescue GoogleApi::Auth::ConfigMissingError + # no-op + end + end + + def new + @cluster = ::Clusters::Cluster.new.tap do |cluster| + cluster.build_provider_gcp + end + end + + def create + @cluster = ::Clusters::CreateService + .new(project, current_user, create_params) + .execute(token_in_session) + + if @cluster.persisted? + redirect_to project_cluster_path(project, @cluster) + else + render :new + end + end + + private + + def create_params + params.require(:cluster).permit( + :enabled, + :name, + provider_gcp_attributes: [ + :gcp_project_id, + :zone, + :num_nodes, + :machine_type + ]).merge( + provider_type: :gcp, + platform_type: :kubernetes + ) + end + + def authorize_google_api + unless GoogleApi::CloudPlatform::Client.new(token_in_session, nil) + .validate_token(expires_at_in_session) + redirect_to action: 'login' + end + end + + def token_in_session + @token_in_session ||= + session[GoogleApi::CloudPlatform::Client.session_key_for_token] + end + + def expires_at_in_session + @expires_at_in_session ||= + session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] + end + + def generate_session_key_redirect(uri) + GoogleApi::CloudPlatform::Client.new_session_key_for_redirect_uri do |key| + session[key] = uri + end + end +end diff --git a/app/controllers/projects/clusters/user_controller.rb b/app/controllers/projects/clusters/user_controller.rb new file mode 100644 index 00000000000..d7678512073 --- /dev/null +++ b/app/controllers/projects/clusters/user_controller.rb @@ -0,0 +1,39 @@ +class Projects::Clusters::UserController < Projects::ApplicationController + before_action :authorize_read_cluster! + before_action :authorize_create_cluster!, only: [:new, :create] + + def new + @cluster = ::Clusters::Cluster.new.tap do |cluster| + cluster.build_platform_kubernetes + end + end + + def create + @cluster = ::Clusters::CreateService + .new(project, current_user, create_params) + .execute + + if @cluster.persisted? + redirect_to project_cluster_path(project, @cluster) + else + render :new + end + end + + private + + def create_params + params.require(:cluster).permit( + :enabled, + :name, + platform_kubernetes_attributes: [ + :namespace, + :api_url, + :token, + :ca_cert + ]).merge( + provider_type: :user, + platform_type: :kubernetes + ) + end +end diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb index 9a56c9de858..d18b6d4b78c 100644 --- a/app/controllers/projects/clusters_controller.rb +++ b/app/controllers/projects/clusters_controller.rb @@ -1,11 +1,12 @@ class Projects::ClustersController < Projects::ApplicationController - before_action :cluster, except: [:login, :index, :new, :new_gcp, :create] + before_action :cluster, except: [:index, :new] before_action :authorize_read_cluster! - before_action :authorize_create_cluster!, only: [:new, :new_gcp, :create] - before_action :authorize_google_api, only: [:new_gcp, :create] + before_action :authorize_create_cluster!, only: [:new] before_action :authorize_update_cluster!, only: [:update] before_action :authorize_admin_cluster!, only: [:destroy] + STATUS_POLLING_INTERVAL = 10_000 + def index if project.cluster redirect_to project_cluster_path(project, project.cluster) @@ -14,43 +15,13 @@ class Projects::ClustersController < Projects::ApplicationController end end - def login - begin - state = generate_session_key_redirect(providers_gcp_new_namespace_project_clusters_url.to_s) - - @authorize_url = GoogleApi::CloudPlatform::Client.new( - nil, callback_google_api_auth_url, - state: state).authorize_url - rescue GoogleApi::Auth::ConfigMissingError - # no-op - end - end - def new end - def new_gcp - @cluster = Clusters::Cluster.new.tap do |cluster| - cluster.build_provider_gcp - end - end - - def create - @cluster = Clusters::CreateService - .new(project, current_user, create_params) - .execute(token_in_session) - - if @cluster.persisted? - redirect_to project_cluster_path(project, @cluster) - else - render :new_gcp - end - end - def status respond_to do |format| format.json do - Gitlab::PollingInterval.set_header(response, interval: 10_000) + Gitlab::PollingInterval.set_header(response, interval: STATUS_POLLING_INTERVAL) render json: ClusterSerializer .new(project: @project, current_user: @current_user) @@ -88,46 +59,29 @@ class Projects::ClustersController < Projects::ApplicationController private def cluster - @cluster ||= project.cluster.present(current_user: current_user) - end - - def create_params - params.require(:cluster).permit( - :enabled, - :name, - :provider_type, - provider_gcp_attributes: [ - :gcp_project_id, - :zone, - :num_nodes, - :machine_type - ]) + @cluster ||= project.clusters.find(params[:id]) + .present(current_user: current_user) end def update_params - params.require(:cluster).permit(:enabled) - end - - def authorize_google_api - unless GoogleApi::CloudPlatform::Client.new(token_in_session, nil) - .validate_token(expires_at_in_session) - redirect_to action: 'login' - end - end - - def token_in_session - @token_in_session ||= - session[GoogleApi::CloudPlatform::Client.session_key_for_token] - end - - def expires_at_in_session - @expires_at_in_session ||= - session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] - end - - def generate_session_key_redirect(uri) - GoogleApi::CloudPlatform::Client.new_session_key_for_redirect_uri do |key| - session[key] = uri + if cluster.managed? + params.require(:cluster).permit( + :enabled, + platform_kubernetes_attributes: [ + :namespace + ] + ) + else + params.require(:cluster).permit( + :enabled, + :name, + platform_kubernetes_attributes: [ + :api_url, + :token, + :ca_cert, + :namespace + ] + ) end end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 6d7fb4b7dbf..45beced1427 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -17,7 +17,7 @@ module Clusters # we force autosave to happen when we save `Cluster` model has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true - has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes' + has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes', autosave: true has_one :application_helm, class_name: 'Clusters::Applications::Helm' has_one :application_ingress, class_name: 'Clusters::Applications::Ingress' @@ -70,6 +70,10 @@ module Clusters return platform_kubernetes if kubernetes? end + def managed? + !user? + end + def first_project return @first_project if defined?(@first_project) diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 7ab670cf1ef..9160a169452 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -34,12 +34,15 @@ module Clusters validates :api_url, url: true, presence: true validates :token, presence: true + validate :prevent_modification, on: :update + after_save :clear_reactive_cache! alias_attribute :ca_pem, :ca_cert delegate :project, to: :cluster, allow_nil: true delegate :enabled?, to: :cluster, allow_nil: true + delegate :managed?, to: :cluster, allow_nil: true alias_method :active?, :enabled? @@ -173,6 +176,17 @@ module Clusters def enforce_namespace_to_lower_case self.namespace = self.namespace&.downcase end + + def prevent_modification + return unless managed? + + if api_url_changed? || token_changed? || ca_pem_changed? + errors.add(:base, "cannot modify managed cluster") + return false + end + + true + end end end end diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb index 1d407739b21..7b697f6d807 100644 --- a/app/services/clusters/create_service.rb +++ b/app/services/clusters/create_service.rb @@ -2,7 +2,7 @@ module Clusters class CreateService < BaseService attr_reader :access_token - def execute(access_token) + def execute(access_token = nil) @access_token = access_token create_cluster.tap do |cluster| diff --git a/app/validators/cluster_name_validator.rb b/app/validators/cluster_name_validator.rb index 13ec342f399..e7d32550176 100644 --- a/app/validators/cluster_name_validator.rb +++ b/app/validators/cluster_name_validator.rb @@ -3,11 +3,7 @@ # Custom validator for ClusterName. class ClusterNameValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) - if record.user? - unless value.present? - record.errors.add(attribute, " has to be present") - end - elsif record.gcp? + if record.managed? if record.persisted? && record.name_changed? record.errors.add(attribute, " can not be changed because it's synchronized with provider") end @@ -19,6 +15,10 @@ class ClusterNameValidator < ActiveModel::EachValidator unless value =~ Gitlab::Regex.kubernetes_namespace_regex record.errors.add(attribute, Gitlab::Regex.kubernetes_namespace_regex_message) end + else + unless value.present? + record.errors.add(attribute, " has to be present") + end end end end diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 2ce960df13c..8b2d2a5c74d 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -146,7 +146,7 @@ = number_with_delimiter(@project.open_merge_requests_count) - if project_nav_tab? :pipelines - = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts, :clusters]) do + = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts, :clusters, :user, :gcp]) do = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines' do .nav-icon-container = sprite_icon('pipeline') @@ -154,7 +154,7 @@ CI / CD %ul.sidebar-sub-level-items - = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts], html_options: { class: "fly-out-top-item" } ) do + = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts, :clusters, :user, :gcp], html_options: { class: "fly-out-top-item" } ) do = link_to project_pipelines_path(@project) do %strong.fly-out-top-item-name #{ _('CI / CD') } @@ -183,18 +183,18 @@ %span Environments + - if project_nav_tab? :clusters + = nav_link(controller: [:clusters, :user, :gcp]) do + = link_to project_clusters_path(@project), title: 'Cluster', class: 'shortcuts-cluster' do + %span + Cluster + - if @project.feature_available?(:builds, current_user) && !@project.empty_repo? = nav_link(path: 'pipelines#charts') do = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do %span Charts - - if project_nav_tab? :clusters - = nav_link(controller: :clusters) do - = link_to project_clusters_path(@project), title: 'Cluster', class: 'shortcuts-cluster' do - %span - Cluster - - if project_nav_tab? :wiki = nav_link(controller: :wikis) do = link_to get_project_wiki_path(@project), class: 'shortcuts-wiki' do diff --git a/app/views/projects/clusters/_advanced_settings.html.haml b/app/views/projects/clusters/_advanced_settings.html.haml index 97532f1e2bd..2b3095eb94b 100644 --- a/app/views/projects/clusters/_advanced_settings.html.haml +++ b/app/views/projects/clusters/_advanced_settings.html.haml @@ -1,10 +1,11 @@ - if can?(current_user, :admin_cluster, @cluster) - .append-bottom-20 - %label.append-bottom-10 - = s_('ClusterIntegration|Google Container Engine') - %p - - link_gke = link_to(s_('ClusterIntegration|Google Container Engine'), @cluster.gke_cluster_url, target: '_blank', rel: 'noopener noreferrer') - = s_('ClusterIntegration|Manage your cluster by visiting %{link_gke}').html_safe % { link_gke: link_gke } + - if @cluster.managed? + .append-bottom-20 + %label.append-bottom-10 + = s_('ClusterIntegration|Google Container Engine') + %p + - link_gke = link_to(s_('ClusterIntegration|Google Container Engine'), @cluster.gke_cluster_url, target: '_blank', rel: 'noopener noreferrer') + = s_('ClusterIntegration|Manage your cluster by visiting %{link_gke}').html_safe % { link_gke: link_gke } .well.form-group %label.text-danger diff --git a/app/views/projects/clusters/_banner.html.haml b/app/views/projects/clusters/_banner.html.haml new file mode 100644 index 00000000000..a1cc66eac92 --- /dev/null +++ b/app/views/projects/clusters/_banner.html.haml @@ -0,0 +1,21 @@ +%h4= s_('ClusterIntegration|Enable cluster integration') +.settings-content + + .hidden.js-cluster-error.alert.alert-danger.alert-block.append-bottom-10{ role: 'alert' } + = s_('ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine') + %p.js-error-reason + + .hidden.js-cluster-creating.alert.alert-info.alert-block.append-bottom-10{ role: 'alert' } + = s_('ClusterIntegration|Cluster is being created on Google Container Engine...') + + .hidden.js-cluster-success.alert.alert-success.alert-block.append-bottom-10{ role: 'alert' } + = s_('ClusterIntegration|Cluster was successfully created on Google Container Engine. Refresh the page to see cluster\'s details') + + %p + - if @cluster.enabled? + - if can?(current_user, :update_cluster, @cluster) + = s_('ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab\'s connection to it.') + - else + = s_('ClusterIntegration|Cluster integration is enabled for this project.') + - else + = s_('ClusterIntegration|Cluster integration is disabled for this project.') diff --git a/app/views/projects/clusters/_dropdown.html.haml b/app/views/projects/clusters/_dropdown.html.haml new file mode 100644 index 00000000000..39188c7ca27 --- /dev/null +++ b/app/views/projects/clusters/_dropdown.html.haml @@ -0,0 +1,12 @@ +%h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up cluster integration') + +.dropdown.clusters-dropdown + %button.dropdown-menu-toggle.dropdown-menu-full-width{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false } + %span.dropdown-toggle-text + = dropdown_text + = icon('chevron-down') + %ul.dropdown-menu.clusters-dropdown-menu.dropdown-menu-full-width + %li + = link_to(s_('ClusterIntegration|Create cluster on Google Container Engine'), gcp_new_namespace_project_clusters_path(@project.namespace, @project)) + %li + = link_to(s_('ClusterIntegration|Add an existing cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project)) diff --git a/app/views/projects/clusters/_enabled.html.haml b/app/views/projects/clusters/_enabled.html.haml new file mode 100644 index 00000000000..f4d261df8f5 --- /dev/null +++ b/app/views/projects/clusters/_enabled.html.haml @@ -0,0 +1,16 @@ += form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| + = form_errors(@cluster) + .form-group.append-bottom-20 + %label.append-bottom-10 + = field.hidden_field :enabled, { class: 'js-toggle-input'} + + %button{ type: 'button', + class: "js-toggle-cluster project-feature-toggle #{'checked' unless !@cluster.enabled?} #{'disabled' unless can?(current_user, :update_cluster, @cluster)}", + 'aria-label': s_('ClusterIntegration|Toggle Cluster'), + disabled: !can?(current_user, :update_cluster, @cluster), + data: { 'enabled-text': 'Enabled', 'disabled-text': 'Disabled' } } + + - if can?(current_user, :update_cluster, @cluster) + .form-group + = field.submit _('Save'), class: 'btn btn-success' + diff --git a/app/views/projects/clusters/_form.html.haml b/app/views/projects/clusters/_form.html.haml deleted file mode 100644 index 1f8ae463d0f..00000000000 --- a/app/views/projects/clusters/_form.html.haml +++ /dev/null @@ -1,35 +0,0 @@ -.row - .col-sm-8.col-sm-offset-4 - %p - - link_to_help_page = link_to(s_('ClusterIntegration|help page'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') - = s_('ClusterIntegration|Read our %{link_to_help_page} on cluster integration.').html_safe % { link_to_help_page: link_to_help_page} - - = form_for @cluster, url: namespace_project_clusters_path(@project.namespace, @project, @cluster), as: :cluster do |field| - = field.hidden_field :provider_type, value: :gcp - = form_errors(@cluster) - .form-group - = field.label :name, s_('ClusterIntegration|Cluster name') - = field.text_field :name, class: 'form-control' - - = field.fields_for :provider_gcp, @cluster.provider_gcp do |provider_gcp_field| - .form-group - = provider_gcp_field.label :gcp_project_id, s_('ClusterIntegration|Google Cloud Platform project ID') - = link_to(s_('ClusterIntegration|See your projects'), 'https://console.cloud.google.com/home/dashboard', target: '_blank', rel: 'noopener noreferrer') - = provider_gcp_field.text_field :gcp_project_id, class: 'form-control' - - .form-group - = provider_gcp_field.label :zone, s_('ClusterIntegration|Zone') - = link_to(s_('ClusterIntegration|See zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer') - = provider_gcp_field.text_field :zone, class: 'form-control', placeholder: 'us-central1-a' - - .form-group - = provider_gcp_field.label :num_nodes, s_('ClusterIntegration|Number of nodes') - = provider_gcp_field.text_field :num_nodes, class: 'form-control', placeholder: '3' - - .form-group - = provider_gcp_field.label :machine_type, s_('ClusterIntegration|Machine type') - = link_to(s_('ClusterIntegration|See machine types'), 'https://cloud.google.com/compute/docs/machine-types', target: '_blank', rel: 'noopener noreferrer') - = provider_gcp_field.text_field :machine_type, class: 'form-control', placeholder: 'n1-standard-2' - - .form-group - = field.submit s_('ClusterIntegration|Create cluster'), class: 'btn btn-save' diff --git a/app/views/projects/clusters/gcp/_form.html.haml b/app/views/projects/clusters/gcp/_form.html.haml new file mode 100644 index 00000000000..0f6bae97571 --- /dev/null +++ b/app/views/projects/clusters/gcp/_form.html.haml @@ -0,0 +1,32 @@ +%p + - link_to_help_page = link_to(s_('ClusterIntegration|help page'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') + = s_('ClusterIntegration|Read our %{link_to_help_page} on cluster integration.').html_safe % { link_to_help_page: link_to_help_page} + += form_for @cluster, html: { class: 'prepend-top-20' }, url: gcp_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field| + = form_errors(@cluster) + .form-group + = field.label :name, s_('ClusterIntegration|Cluster name') + = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name') + + = field.fields_for :provider_gcp, @cluster.provider_gcp do |provider_gcp_field| + .form-group + = provider_gcp_field.label :gcp_project_id, s_('ClusterIntegration|Google Cloud Platform project ID') + = link_to(s_('ClusterIntegration|See your projects'), 'https://console.cloud.google.com/home/dashboard', target: '_blank', rel: 'noopener noreferrer') + = provider_gcp_field.text_field :gcp_project_id, class: 'form-control', placeholder: s_('ClusterIntegration|Project ID') + + .form-group + = provider_gcp_field.label :zone, s_('ClusterIntegration|Zone') + = link_to(s_('ClusterIntegration|See zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer') + = provider_gcp_field.text_field :zone, class: 'form-control', placeholder: 'us-central1-a' + + .form-group + = provider_gcp_field.label :num_nodes, s_('ClusterIntegration|Number of nodes') + = provider_gcp_field.text_field :num_nodes, class: 'form-control', placeholder: '3' + + .form-group + = provider_gcp_field.label :machine_type, s_('ClusterIntegration|Machine type') + = link_to(s_('ClusterIntegration|See machine types'), 'https://cloud.google.com/compute/docs/machine-types', target: '_blank', rel: 'noopener noreferrer') + = provider_gcp_field.text_field :machine_type, class: 'form-control', placeholder: 'n1-standard-4' + + .form-group + = field.submit s_('ClusterIntegration|Create cluster'), class: 'btn btn-success' diff --git a/app/views/projects/clusters/_header.html.haml b/app/views/projects/clusters/gcp/_header.html.haml index beb798e7154..cddb53c2688 100644 --- a/app/views/projects/clusters/_header.html.haml +++ b/app/views/projects/clusters/gcp/_header.html.haml @@ -1,5 +1,5 @@ -%h4.prepend-top-0 - = s_('ClusterIntegration|Create new cluster on Google Container Engine') +%h4.prepend-top-20 + = s_('ClusterIntegration|Enter the details for your cluster') %p = s_('ClusterIntegration|Please make sure that your Google account meets the following requirements:') %ul diff --git a/app/views/projects/clusters/gcp/_show.html.haml b/app/views/projects/clusters/gcp/_show.html.haml new file mode 100644 index 00000000000..3fa9f69708a --- /dev/null +++ b/app/views/projects/clusters/gcp/_show.html.haml @@ -0,0 +1,40 @@ +.form-group + %label.append-bottom-10{ for: 'cluster-name' } + = s_('ClusterIntegration|Cluster name') + .input-group + %input.form-control.cluster-name.js-select-on-focus{ value: @cluster.name, readonly: true } + %span.input-group-btn + = clipboard_button(text: @cluster.name, title: s_('ClusterIntegration|Copy cluster name'), class: 'btn-default') + += form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| + = form_errors(@cluster) + = field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field| + .form-group + = platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL') + .input-group + = platform_kubernetes_field.text_field :api_url, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|API URL'), readonly: true + %span.input-group-btn + = clipboard_button(text: @cluster.platform_kubernetes.api_url, title: s_('ClusterIntegration|Copy API URL'), class: 'btn-default') + + .form-group + = platform_kubernetes_field.label :ca_cert, s_('ClusterIntegration|CA Certificate') + .input-group + = platform_kubernetes_field.text_area :ca_cert, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)'), readonly: true + %span.input-group-addon.clipboard-addon + = clipboard_button(text: @cluster.platform_kubernetes.ca_cert, title: s_('ClusterIntegration|Copy CA Certificate'), class: 'btn-blank') + + .form-group + = platform_kubernetes_field.label :token, s_('ClusterIntegration|Token') + .input-group + = platform_kubernetes_field.text_field :token, class: 'form-control js-cluster-token js-select-on-focus', type: 'password', placeholder: s_('ClusterIntegration|Token'), readonly: true + %span.input-group-btn + %button.btn.btn-default.js-show-cluster-token{ type: 'button' } + = s_('ClusterIntegration|Show') + = clipboard_button(text: @cluster.platform_kubernetes.token, title: s_('ClusterIntegration|Copy Token'), class: 'btn-default') + + .form-group + = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)') + = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') + + .form-group + = field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success' diff --git a/app/views/projects/clusters/login.html.haml b/app/views/projects/clusters/gcp/login.html.haml index fde030b500b..790ba61fd86 100644 --- a/app/views/projects/clusters/login.html.haml +++ b/app/views/projects/clusters/gcp/login.html.haml @@ -3,8 +3,9 @@ .row.prepend-top-default .col-sm-4 - = render 'sidebar' + = render 'projects/clusters/sidebar' .col-sm-8 + = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create cluster on Google Container Engine') = render 'header' .row .col-sm-8.col-sm-offset-4.signin-with-google diff --git a/app/views/projects/clusters/gcp/new.html.haml b/app/views/projects/clusters/gcp/new.html.haml new file mode 100644 index 00000000000..9a79480c82f --- /dev/null +++ b/app/views/projects/clusters/gcp/new.html.haml @@ -0,0 +1,10 @@ +- breadcrumb_title "Cluster" +- page_title _("New Cluster") + +.row.prepend-top-default + .col-sm-4 + = render 'projects/clusters/sidebar' + .col-sm-8 + = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create cluster on Google Container Engine') + = render 'header' + = render 'form' diff --git a/app/views/projects/clusters/new.html.haml b/app/views/projects/clusters/new.html.haml index 665120c7e49..2e5bc34f64a 100644 --- a/app/views/projects/clusters/new.html.haml +++ b/app/views/projects/clusters/new.html.haml @@ -5,16 +5,9 @@ .col-sm-4 = render 'sidebar' .col-sm-8 - - if @project.deployment_platform&.active? - %h4.prepend-top-0= s_('ClusterIntegration|Cluster management') + %h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up cluster integration') - %p= s_('ClusterIntegration|A cluster has been set up on this project through the Kubernetes integration page') - = link_to s_('ClusterIntegration|Manage Kubernetes integration'), edit_project_service_path(@project, :kubernetes), class: 'btn append-bottom-20' - - - else - %h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up cluster integration') - - %p= s_('ClusterIntegration|Create a new cluster on Google Container Engine right from GitLab') - = link_to s_('ClusterIntegration|Create on GKE'), providers_gcp_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20' - %p= s_('ClusterIntegration|Enter the details for an existing Kubernetes cluster') - = link_to s_('ClusterIntegration|Add an existing cluster'), edit_project_service_path(@project, :kubernetes), class: 'btn append-bottom-20' + %p= s_('ClusterIntegration|Create a new cluster on Google Engine right from GitLab') + = link_to s_('ClusterIntegration|Create on GKE'), gcp_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20' + %p= s_('ClusterIntegration|Enter the details for an existing Kubernetes cluster') + = link_to s_('ClusterIntegration|Add an existing cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20' diff --git a/app/views/projects/clusters/new_gcp.html.haml b/app/views/projects/clusters/new_gcp.html.haml deleted file mode 100644 index 48e6b6ae8e8..00000000000 --- a/app/views/projects/clusters/new_gcp.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -- breadcrumb_title "Cluster" -- page_title _("New Cluster") - -.row.prepend-top-default - .col-sm-4 - = render 'sidebar' - .col-sm-8 - = render 'header' - -= render 'form' diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml index b7671f5e3c4..d23efe4d9aa 100644 --- a/app/views/projects/clusters/show.html.haml +++ b/app/views/projects/clusters/show.html.haml @@ -13,52 +13,16 @@ cluster_status_reason: @cluster.status_reason, help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications') } } - .js-cluster-application-notice .flash-container %section.settings.no-animate.expanded - %h4= s_('ClusterIntegration|Enable cluster integration') - .settings-content - - .hidden.js-cluster-error.alert.alert-danger.alert-block.append-bottom-10{ role: 'alert' } - = s_('ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine') - %p.js-error-reason - - .hidden.js-cluster-creating.alert.alert-info.alert-block.append-bottom-10{ role: 'alert' } - = s_('ClusterIntegration|Cluster is being created on Google Container Engine...') - - .hidden.js-cluster-success.alert.alert-success.alert-block.append-bottom-10{ role: 'alert' } - = s_('ClusterIntegration|Cluster was successfully created on Google Container Engine') - - %p - - if @cluster.enabled? - - if can?(current_user, :update_cluster, @cluster) - = s_('ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab\'s connection to it.') - - else - = s_('ClusterIntegration|Cluster integration is enabled for this project.') - - else - = s_('ClusterIntegration|Cluster integration is disabled for this project.') - - = form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| - = form_errors(@cluster) - .form-group.append-bottom-20 - %label.append-bottom-10 - = field.hidden_field :enabled, { class: 'js-toggle-input'} - - %button{ type: 'button', - class: "js-toggle-cluster project-feature-toggle #{'checked' unless !@cluster.enabled?} #{'disabled' unless can?(current_user, :update_cluster, @cluster)}", - 'aria-label': s_('ClusterIntegration|Toggle Cluster'), - disabled: !can?(current_user, :update_cluster, @cluster), - data: { 'enabled-text': 'Enabled', 'disabled-text': 'Disabled' } } - - - if can?(current_user, :update_cluster, @cluster) - .form-group - = field.submit _('Save'), class: 'btn btn-success' + = render 'banner' + = render 'enabled' .cluster-applications-table#js-cluster-applications - %section.settings#js-cluster-details + %section.settings#js-cluster-details{ class: ('expanded' if expanded) } .settings-header %h4= s_('ClusterIntegration|Cluster details') %button.btn.js-settings-toggle @@ -66,20 +30,16 @@ %p= s_('ClusterIntegration|See and edit the details for your cluster') .settings-content - - .form_group.append-bottom-20 - %label.append-bottom-10{ for: 'cluster-name' } - = s_('ClusterIntegration|Cluster name') - .input-group - %input.form-control.cluster-name{ value: @cluster.name, disabled: true } - %span.input-group-addon.clipboard-addon - = clipboard_button(text: @cluster.name, title: s_('ClusterIntegration|Copy cluster name')) + - if @cluster.managed? + = render 'projects/clusters/gcp/show' + - else + = render 'projects/clusters/user/show' %section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) } .settings-header %h4= _('Advanced settings') %button.btn.js-settings-toggle = expanded ? 'Collapse' : 'Expand' - %p= s_('ClusterIntegration|Manage Cluster integration on your GitLab project') + %p= s_('ClusterIntegration|Manage cluster integration on your GitLab project') .settings-content = render 'advanced_settings' diff --git a/app/views/projects/clusters/user/_form.html.haml b/app/views/projects/clusters/user/_form.html.haml new file mode 100644 index 00000000000..4a9bd5186c6 --- /dev/null +++ b/app/views/projects/clusters/user/_form.html.haml @@ -0,0 +1,25 @@ += form_for @cluster, url: user_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field| + = form_errors(@cluster) + .form-group + = field.label :name, s_('ClusterIntegration|Cluster name') + = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name') + + = field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field| + .form-group + = platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL') + = platform_kubernetes_field.text_field :api_url, class: 'form-control', placeholder: s_('ClusterIntegration|API URL') + + .form-group + = platform_kubernetes_field.label :ca_cert, s_('ClusterIntegration|CA Certificate') + = platform_kubernetes_field.text_area :ca_cert, class: 'form-control', placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)') + + .form-group + = platform_kubernetes_field.label :token, s_('ClusterIntegration|Token') + = platform_kubernetes_field.text_field :token, class: 'form-control', placeholder: s_('ClusterIntegration|Service token'), autocomplete: 'off' + + .form-group + = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)') + = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') + + .form-group + = field.submit s_('ClusterIntegration|Add cluster'), class: 'btn btn-success' diff --git a/app/views/projects/clusters/user/_header.html.haml b/app/views/projects/clusters/user/_header.html.haml new file mode 100644 index 00000000000..06ac210a06d --- /dev/null +++ b/app/views/projects/clusters/user/_header.html.haml @@ -0,0 +1,5 @@ +%h4.prepend-top-20 + = s_('ClusterIntegration|Enter the details for your cluster') +%p + - link_to_help_page = link_to(s_('ClusterIntegration|documentation'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') + = s_('ClusterIntegration|Please enter access information for your cluster. If you need help, you can read our %{link_to_help_page} on clusters').html_safe % { link_to_help_page: link_to_help_page } diff --git a/app/views/projects/clusters/user/_show.html.haml b/app/views/projects/clusters/user/_show.html.haml new file mode 100644 index 00000000000..5931e0b7f17 --- /dev/null +++ b/app/views/projects/clusters/user/_show.html.haml @@ -0,0 +1,29 @@ += form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| + = form_errors(@cluster) + .form-group + = field.label :name, s_('ClusterIntegration|Cluster name') + = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name') + + = field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field| + .form-group + = platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL') + = platform_kubernetes_field.text_field :api_url, class: 'form-control', placeholder: s_('ClusterIntegration|API URL') + + .form-group + = platform_kubernetes_field.label :ca_cert, s_('ClusterIntegration|CA Certificate') + = platform_kubernetes_field.text_area :ca_cert, class: 'form-control', placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)') + + .form-group + = platform_kubernetes_field.label :token, s_('ClusterIntegration|Token') + .input-group + = platform_kubernetes_field.text_field :token, class: 'form-control js-cluster-token', type: 'password', placeholder: s_('ClusterIntegration|Token'), autocomplete: 'off' + %span.input-group-addon.clipboard-addon + %button.js-show-cluster-token.btn-blank{ type: 'button' } + = s_('ClusterIntegration|Show') + + .form-group + = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)') + = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') + + .form-group + = field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success' diff --git a/app/views/projects/clusters/user/new.html.haml b/app/views/projects/clusters/user/new.html.haml new file mode 100644 index 00000000000..68f38f83453 --- /dev/null +++ b/app/views/projects/clusters/user/new.html.haml @@ -0,0 +1,11 @@ +- breadcrumb_title "Cluster" +- page_title _("New Cluster") + +.row.prepend-top-default + .col-sm-4 + = render 'projects/clusters/sidebar' + .col-sm-8 + = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Add an existing cluster') + = render 'header' + .prepend-top-20 + = render 'form' diff --git a/changelogs/unreleased/35616-move-k8-to-cluster-page.yml b/changelogs/unreleased/35616-move-k8-to-cluster-page.yml new file mode 100644 index 00000000000..032a39608ce --- /dev/null +++ b/changelogs/unreleased/35616-move-k8-to-cluster-page.yml @@ -0,0 +1,5 @@ +--- +title: Create a new form to add Existing Kubernetes Cluster +merge_request: 14805 +author: +type: added diff --git a/config/routes/project.rb b/config/routes/project.rb index bdafaba3ab3..45474034822 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -183,10 +183,16 @@ constraints(ProjectUrlConstrainer.new) do end end - resources :clusters, except: [:edit] do + resources :clusters, except: [:edit, :create] do collection do - get :login - get '/providers/gcp/new', action: :new_gcp + scope :providers do + get '/user/new', to: 'clusters/user#new' + post '/user', to: 'clusters/user#create' + + get '/gcp/new', to: 'clusters/gcp#new' + get '/gcp/login', to: 'clusters/gcp#login' + post '/gcp', to: 'clusters/gcp#create' + end end member do diff --git a/spec/controllers/projects/clusters/gcp_controller_spec.rb b/spec/controllers/projects/clusters/gcp_controller_spec.rb new file mode 100644 index 00000000000..bb5ef7bb365 --- /dev/null +++ b/spec/controllers/projects/clusters/gcp_controller_spec.rb @@ -0,0 +1,185 @@ +require 'spec_helper' + +describe Projects::Clusters::GcpController do + include AccessMatchersForController + include GoogleApi::CloudPlatformHelpers + + set(:project) { create(:project) } + + describe 'GET login' do + describe 'functionality' do + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + end + + 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 + 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(gcp_new_project_clusters_path(project)) + end + 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 + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_denied_for(:developer).of(project) } + it { expect { go }.to be_denied_for(:reporter).of(project) } + it { expect { go }.to be_denied_for(:guest).of(project) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + + def go + get :login, namespace_id: project.namespace, project_id: project + end + end + + describe 'GET new' do + describe 'functionality' do + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + end + + context 'when access token is valid' do + before do + stub_google_api_validate_token + end + + it 'has new object' do + go + + expect(assigns(:cluster)).to be_an_instance_of(Clusters::Cluster) + end + end + + context 'when access token is expired' do + before do + stub_google_api_expired_token + end + + it { expect(go).to redirect_to(gcp_login_project_clusters_path(project)) } + end + + context 'when access token is not stored in session' do + it { expect(go).to redirect_to(gcp_login_project_clusters_path(project)) } + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_denied_for(:developer).of(project) } + it { expect { go }.to be_denied_for(:reporter).of(project) } + it { expect { go }.to be_denied_for(:guest).of(project) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + + def go + get :new, namespace_id: project.namespace, project_id: project + end + end + + describe 'POST create' do + let(:params) do + { + cluster: { + name: 'new-cluster', + provider_gcp_attributes: { + gcp_project_id: '111' + } + } + } + end + + describe 'functionality' do + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + end + + context 'when access token is valid' do + before do + stub_google_api_validate_token + end + + context 'when creates a cluster on gke' do + 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 } + expect(response).to redirect_to(project_cluster_path(project, project.cluster)) + expect(project.cluster).to be_gcp + expect(project.cluster).to be_kubernetes + end + end + end + + context 'when access token is expired' do + before do + stub_google_api_expired_token + end + + it 'redirects to login page' do + expect(go).to redirect_to(gcp_login_project_clusters_path(project)) + end + end + + context 'when access token is not stored in session' do + it 'redirects to login page' do + expect(go).to redirect_to(gcp_login_project_clusters_path(project)) + end + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_denied_for(:developer).of(project) } + it { expect { go }.to be_denied_for(:reporter).of(project) } + it { expect { go }.to be_denied_for(:guest).of(project) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + + def go + post :create, params.merge(namespace_id: project.namespace, project_id: project) + end + end +end diff --git a/spec/controllers/projects/clusters/user_controller_spec.rb b/spec/controllers/projects/clusters/user_controller_spec.rb new file mode 100644 index 00000000000..22005e0dc66 --- /dev/null +++ b/spec/controllers/projects/clusters/user_controller_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe Projects::Clusters::UserController do + include AccessMatchersForController + + set(:project) { create(:project) } + + describe 'GET new' do + describe 'functionality' do + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + end + + it 'has new object' do + go + + expect(assigns(:cluster)).to be_an_instance_of(Clusters::Cluster) + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_denied_for(:developer).of(project) } + it { expect { go }.to be_denied_for(:reporter).of(project) } + it { expect { go }.to be_denied_for(:guest).of(project) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + + def go + get :new, namespace_id: project.namespace, project_id: project + end + end + + describe 'POST create' do + let(:params) do + { + cluster: { + name: 'new-cluster', + platform_kubernetes_attributes: { + api_url: 'http://my-url', + token: 'test', + namespace: 'aaa' + } + } + } + end + + describe 'functionality' do + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + end + + 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 } + expect(response).to redirect_to(project_cluster_path(project, project.cluster)) + end + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_denied_for(:developer).of(project) } + it { expect { go }.to be_denied_for(:reporter).of(project) } + it { expect { go }.to be_denied_for(:guest).of(project) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + + def go + post :create, params.merge(namespace_id: project.namespace, project_id: project) + end + end +end diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index ca2bcb2b5ae..66e67652dad 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -4,6 +4,8 @@ describe Projects::ClustersController do include AccessMatchersForController include GoogleApi::CloudPlatformHelpers + set(:project) { create(:project) } + describe 'GET index' do describe 'functionality' do let(:user) { create(:user) } @@ -14,22 +16,18 @@ describe Projects::ClustersController do end context 'when project has a cluster' do - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } + let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } it { expect(go).to redirect_to(project_cluster_path(project, project.cluster)) } end context 'when project does not have a cluster' do - let(:project) { create(:project) } - it { expect(go).to redirect_to(new_project_cluster_path(project)) } end end describe 'security' do - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } + let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } it { expect { go }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:owner).of(project) } @@ -46,198 +44,8 @@ describe Projects::ClustersController do end end - describe 'GET login' do - let(:project) { create(:project) } - - describe 'functionality' do - let(:user) { create(:user) } - - before do - project.add_master(user) - sign_in(user) - end - - context 'when omniauth has been configured' do - let(:key) { 'secere-key' } - - let(:session_key_for_redirect_uri) do - GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(key) - end - - before do - 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(providers_gcp_new_project_clusters_url(project)) - end - 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 - end - - describe 'security' do - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_allowed_for(:owner).of(project) } - it { expect { go }.to be_allowed_for(:master).of(project) } - it { expect { go }.to be_denied_for(:developer).of(project) } - it { expect { go }.to be_denied_for(:reporter).of(project) } - it { expect { go }.to be_denied_for(:guest).of(project) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } - end - - def go - get :login, namespace_id: project.namespace, project_id: project - end - end - - shared_examples 'requires to login' do - it 'redirects to create a cluster' do - subject - - expect(response).to redirect_to(login_project_clusters_path(project)) - end - end - - describe 'GET new_gcp' do - let(:project) { create(:project) } - - describe 'functionality' do - let(:user) { create(:user) } - - before do - project.add_master(user) - sign_in(user) - end - - context 'when access token is valid' do - before do - stub_google_api_validate_token - end - - it 'has new object' do - go - - expect(assigns(:cluster)).to be_an_instance_of(Clusters::Cluster) - end - end - - context 'when access token is expired' do - before do - stub_google_api_expired_token - end - - it { expect(go).to redirect_to(login_project_clusters_path(project)) } - end - - context 'when access token is not stored in session' do - it { expect(go).to redirect_to(login_project_clusters_path(project)) } - end - end - - describe 'security' do - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_allowed_for(:owner).of(project) } - it { expect { go }.to be_allowed_for(:master).of(project) } - it { expect { go }.to be_denied_for(:developer).of(project) } - it { expect { go }.to be_denied_for(:reporter).of(project) } - it { expect { go }.to be_denied_for(:guest).of(project) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } - end - - def go - get :new_gcp, namespace_id: project.namespace, project_id: project - end - end - - describe 'POST create' do - let(:project) { create(:project) } - - let(:params) do - { - cluster: { - name: 'new-cluster', - provider_type: :gcp, - provider_gcp_attributes: { - gcp_project_id: '111' - } - } - } - end - - describe 'functionality' do - let(:user) { create(:user) } - - before do - project.add_master(user) - sign_in(user) - end - - context 'when access token is valid' do - before do - stub_google_api_validate_token - end - - context 'when creates a cluster on gke' do - it 'creates a new cluster' do - expect(ClusterProvisionWorker).to receive(:perform_async) - expect { go }.to change { Clusters::Cluster.count } - expect(response).to redirect_to(project_cluster_path(project, project.cluster)) - end - end - end - - context 'when access token is expired' do - before do - stub_google_api_expired_token - end - - it 'redirects to login page' do - expect(go).to redirect_to(login_project_clusters_path(project)) - end - end - - context 'when access token is not stored in session' do - it 'redirects to login page' do - expect(go).to redirect_to(login_project_clusters_path(project)) - end - end - end - - describe 'security' do - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_allowed_for(:owner).of(project) } - it { expect { go }.to be_allowed_for(:master).of(project) } - it { expect { go }.to be_denied_for(:developer).of(project) } - it { expect { go }.to be_denied_for(:reporter).of(project) } - it { expect { go }.to be_denied_for(:guest).of(project) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } - end - - def go - post :create, params.merge(namespace_id: project.namespace, project_id: project) - end - end - describe 'GET status' do - let(:cluster) { create(:cluster, :project, :providing_by_gcp) } - let(:project) { cluster.project } + let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) } describe 'functionality' do let(:user) { create(:user) } @@ -275,8 +83,7 @@ describe Projects::ClustersController do end describe 'GET show' do - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } + let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } describe 'functionality' do let(:user) { create(:user) } @@ -313,10 +120,8 @@ describe Projects::ClustersController do end describe 'PUT update' do - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } - - describe 'functionality' do + context 'when cluster is provided by GCP' do + let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } let(:user) { create(:user) } before do @@ -324,10 +129,16 @@ describe Projects::ClustersController do sign_in(user) end - context 'when update enabled' do + context 'when changing parameters' do let(:params) do { - cluster: { enabled: false } + cluster: { + enabled: false, + name: 'my-new-cluster-name', + platform_kubernetes_attributes: { + namespace: 'my-namespace' + } + } } end @@ -340,8 +151,14 @@ describe Projects::ClustersController do expect(cluster.enabled).to be_falsey end + it "does not change cluster name" do + go + + expect(cluster.name).to eq('test-cluster') + end + context 'when cluster is being created' do - let(:cluster) { create(:cluster, :project, :providing_by_gcp) } + let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) } it "rejects changes" do go @@ -354,11 +171,46 @@ describe Projects::ClustersController do end end + context 'when cluster is provided by user' do + let(:cluster) { create(:cluster, :provided_by_user, projects: [project]) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + end + + context 'when changing parameters' do + let(:params) do + { + cluster: { + enabled: false, + name: 'my-new-cluster-name', + platform_kubernetes_attributes: { + namespace: 'my-namespace' + } + } + } + end + + it "updates and redirects back to show page" do + go + + cluster.reload + expect(response).to redirect_to(project_cluster_path(project, project.cluster)) + expect(flash[:notice]).to eq('Cluster was successfully updated.') + expect(cluster.enabled).to be_falsey + expect(cluster.name).to eq('my-new-cluster-name') + expect(cluster.platform_kubernetes.namespace).to eq('my-namespace') + end + end + end + describe 'security' do + set(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } + let(:params) do - { - cluster: { enabled: false } - } + { cluster: { enabled: false } } end it { expect { go }.to be_allowed_for(:admin) } @@ -378,10 +230,7 @@ describe Projects::ClustersController do end end - describe 'delete update' do - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } - + describe 'DELETE destroy' do describe 'functionality' do let(:user) { create(:user) } @@ -390,31 +239,37 @@ describe Projects::ClustersController do sign_in(user) end - it "destroys and redirects back to clusters list" do - 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) + context 'when cluster is provided by GCP' do + context 'when cluster is created' do + let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } - expect(response).to redirect_to(project_clusters_path(project)) - expect(flash[:notice]).to eq('Cluster integration was successfully removed.') - end + it "destroys and redirects back to clusters list" do + 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) - context 'when cluster is being created' do - let(:cluster) { create(:cluster, :project, :providing_by_gcp) } + expect(response).to redirect_to(project_clusters_path(project)) + expect(flash[:notice]).to eq('Cluster integration was successfully removed.') + end + end - it "destroys and redirects back to clusters list" do - expect { go } - .to change { Clusters::Cluster.count }.by(-1) - .and change { Clusters::Providers::Gcp.count }.by(-1) + context 'when cluster is being created' do + let!(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) } - expect(response).to redirect_to(project_clusters_path(project)) - expect(flash[:notice]).to eq('Cluster integration was successfully removed.') + it "destroys and redirects back to clusters list" do + expect { go } + .to change { Clusters::Cluster.count }.by(-1) + .and change { Clusters::Providers::Gcp.count }.by(-1) + + expect(response).to redirect_to(project_clusters_path(project)) + expect(flash[:notice]).to eq('Cluster integration was successfully removed.') + end end end - context 'when provider is user' do - let(:cluster) { create(:cluster, :project, :provided_by_user) } + context 'when cluster is provided by user' do + let!(:cluster) { create(:cluster, :provided_by_user, projects: [project]) } it "destroys and redirects back to clusters list" do expect { go } @@ -429,6 +284,8 @@ describe Projects::ClustersController do end describe 'security' do + set(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } + it { expect { go }.to be_allowed_for(:admin) } it { expect { go }.to be_allowed_for(:owner).of(project) } it { expect { go }.to be_allowed_for(:master).of(project) } diff --git a/spec/factories/clusters/cluster.rb b/spec/factories/clusters/clusters.rb index c4261178f2d..81866845a20 100644 --- a/spec/factories/clusters/cluster.rb +++ b/spec/factories/clusters/clusters.rb @@ -13,27 +13,20 @@ FactoryGirl.define do provider_type :user platform_type :kubernetes - platform_kubernetes do - create(:cluster_platform_kubernetes, :configured) - end + platform_kubernetes factory: [:cluster_platform_kubernetes, :configured] end trait :provided_by_gcp do provider_type :gcp platform_type :kubernetes - before(:create) do |cluster, evaluator| - cluster.platform_kubernetes = build(:cluster_platform_kubernetes, :configured) - cluster.provider_gcp = build(:cluster_provider_gcp, :created) - end + provider_gcp factory: [:cluster_provider_gcp, :created] + platform_kubernetes factory: [:cluster_platform_kubernetes, :configured] end trait :providing_by_gcp do provider_type :gcp - - provider_gcp do - create(:cluster_provider_gcp, :creating) - end + provider_gcp factory: [:cluster_provider_gcp, :creating] end end end diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb new file mode 100644 index 00000000000..b34cd061ec6 --- /dev/null +++ b/spec/features/projects/clusters/applications_spec.rb @@ -0,0 +1,107 @@ +require 'spec_helper' + +feature 'Clusters Applications', :js do + include GoogleApi::CloudPlatformHelpers + + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + end + + describe 'Installing applications' do + before do + visit project_cluster_path(project, cluster) + end + + context 'when cluster is being created' do + let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project])} + + scenario 'user is unable to install applications' do + page.within('.js-cluster-application-row-helm') do + expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') + expect(page.find(:css, '.js-cluster-application-install-button').text).to eq('Install') + end + end + end + + context 'when cluster is created' do + let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project])} + + scenario 'user can install applications' do + page.within('.js-cluster-application-row-helm') do + expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to be_nil + expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Install') + end + end + + context 'when user installs Helm' do + before do + allow(ClusterInstallAppWorker).to receive(:perform_async).and_return(nil) + + page.within('.js-cluster-application-row-helm') do + page.find(:css, '.js-cluster-application-install-button').click + end + end + + it 'he sees status transition' do + page.within('.js-cluster-application-row-helm') do + # FE sends request and gets the response, then the buttons is "Install" + expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') + expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Install') + + Clusters::Cluster.last.application_helm.make_installing! + + # FE starts polling and update the buttons to "Installing" + expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') + expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installing') + + Clusters::Cluster.last.application_helm.make_installed! + + expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') + expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installed') + end + + expect(page).to have_content('Helm Tiller was successfully installed on your cluster') + end + end + + context 'when user installs Ingress' do + context 'when user installs application: Ingress' do + before do + allow(ClusterInstallAppWorker).to receive(:perform_async).and_return(nil) + + create(:cluster_applications_helm, :installed, cluster: cluster) + + page.within('.js-cluster-application-row-ingress') do + page.find(:css, '.js-cluster-application-install-button').click + end + end + + it 'he sees status transition' do + page.within('.js-cluster-application-row-ingress') do + # FE sends request and gets the response, then the buttons is "Install" + expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') + expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Install') + + Clusters::Cluster.last.application_ingress.make_installing! + + # FE starts polling and update the buttons to "Installing" + expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') + expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installing') + + Clusters::Cluster.last.application_ingress.make_installed! + + expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') + expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installed') + end + + expect(page).to have_content('Ingress was successfully installed on your cluster') + end + end + end + end + end +end diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb new file mode 100644 index 00000000000..8a0da669147 --- /dev/null +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -0,0 +1,136 @@ +require 'spec_helper' + +feature 'Gcp Cluster', :js do + include GoogleApi::CloudPlatformHelpers + + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + project.add_master(user) + gitlab_sign_in(user) + allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 } + end + + context 'when user has signed with Google' do + before do + allow_any_instance_of(Projects::Clusters::GcpController) + .to receive(:token_in_session).and_return('token') + allow_any_instance_of(Projects::Clusters::GcpController) + .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s) + end + + context 'when user does not have a cluster and visits cluster index page' do + before do + visit project_clusters_path(project) + + click_link 'Create on GKE' + end + + context 'when user filled form with valid parameters' do + before do + 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) + + fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123' + fill_in 'cluster_name', with: 'dev-cluster' + click_button 'Create cluster' + end + + it 'user sees a cluster details page and creation status' do + expect(page).to have_content('Cluster is being created on Google Container Engine...') + + Clusters::Cluster.last.provider.make_created! + + expect(page).to have_content('Cluster was successfully created on Google Container Engine') + end + + it 'user sees a error if something worng during creation' do + expect(page).to have_content('Cluster is being created on Google Container Engine...') + + Clusters::Cluster.last.provider.make_errored!('Something wrong!') + + expect(page).to have_content('Something wrong!') + end + end + + context 'when user filled form with invalid parameters' do + before do + click_button 'Create cluster' + end + + it 'user sees a validation error' do + expect(page).to have_css('#error_explanation') + end + end + end + + context 'when user does have a cluster and visits cluster page' do + let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } + + before do + visit project_cluster_path(project, cluster) + end + + it 'user sees a cluster details page' do + expect(page).to have_button('Save') + expect(page.find(:css, '.cluster-name').value).to eq(cluster.name) + end + + context 'when user disables the cluster' do + before do + page.find(:css, '.js-toggle-cluster').click + click_button 'Save' + end + + it 'user sees the successful message' do + expect(page).to have_content('Cluster was successfully updated.') + end + end + + context 'when user changes cluster parameters' do + before do + fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace' + click_button 'Save changes' + end + + it 'user sees the successful message' do + expect(page).to have_content('Cluster was successfully updated.') + expect(cluster.reload.platform_kubernetes.namespace).to eq('my-namespace') + end + end + + context 'when user destroy the cluster' do + before do + page.accept_confirm do + click_link 'Remove integration' + end + end + + it 'user sees creation form with the successful message' do + expect(page).to have_content('Cluster integration was successfully removed.') + expect(page).to have_link('Create on GKE') + end + end + end + end + + context 'when user has not signed with Google' do + before do + visit project_clusters_path(project) + + click_link 'Create on GKE' + end + + it 'user sees a login page' do + expect(page).to have_css('.signin-with-google') + end + end +end diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb new file mode 100644 index 00000000000..e97ba88f2f4 --- /dev/null +++ b/spec/features/projects/clusters/user_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +feature 'User Cluster', :js do + include GoogleApi::CloudPlatformHelpers + + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + project.add_master(user) + gitlab_sign_in(user) + allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 } + end + + context 'when user does not have a cluster and visits cluster index page' do + before do + visit project_clusters_path(project) + + click_link 'Add an existing cluster' + end + + context 'when user filled form with valid parameters' do + before do + fill_in 'cluster_name', with: 'dev-cluster' + fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'http://example.com' + fill_in 'cluster_platform_kubernetes_attributes_token', with: 'my-token' + click_button 'Add cluster' + end + + it 'user sees a cluster details page' do + expect(page).to have_content('Enable cluster integration') + expect(page.find_field('cluster[name]').value).to eq('dev-cluster') + expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value) + .to have_content('http://example.com') + expect(page.find_field('cluster[platform_kubernetes_attributes][token]').value) + .to have_content('my-token') + end + end + + context 'when user filled form with invalid parameters' do + before do + click_button 'Add cluster' + end + + it 'user sees a validation error' do + expect(page).to have_css('#error_explanation') + end + end + end + + context 'when user does have a cluster and visits cluster page' do + let(:cluster) { create(:cluster, :provided_by_user, projects: [project]) } + + before do + visit project_cluster_path(project, cluster) + end + + it 'user sees a cluster details page' do + expect(page).to have_button('Save') + end + + context 'when user disables the cluster' do + before do + page.find(:css, '.js-toggle-cluster').click + fill_in 'cluster_name', with: 'dev-cluster' + click_button 'Save' + end + + it 'user sees the successful message' do + expect(page).to have_content('Cluster was successfully updated.') + end + end + + context 'when user changes cluster parameters' do + before do + fill_in 'cluster_name', with: 'my-dev-cluster' + fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace' + click_button 'Save changes' + end + + it 'user sees the successful message' do + expect(page).to have_content('Cluster was successfully updated.') + expect(cluster.reload.name).to eq('my-dev-cluster') + expect(cluster.reload.platform_kubernetes.namespace).to eq('my-namespace') + end + end + + context 'when user destroy the cluster' do + before do + page.accept_confirm do + click_link 'Remove integration' + end + end + + it 'user sees creation form with the successful message' do + expect(page).to have_content('Cluster integration was successfully removed.') + expect(page).to have_link('Add an existing cluster') + end + end + end +end diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb index 197e6df4997..4243c4fd266 100644 --- a/spec/features/projects/clusters_spec.rb +++ b/spec/features/projects/clusters_spec.rb @@ -3,204 +3,23 @@ require 'spec_helper' feature 'Clusters', :js do include GoogleApi::CloudPlatformHelpers - let!(:project) { create(:project, :repository) } - let!(:user) { create(:user) } + let(:project) { create(:project) } + let(:user) { create(:user) } before do project.add_master(user) gitlab_sign_in(user) end - context 'when user has signed in Google' do - before do - allow_any_instance_of(Projects::ClustersController) - .to receive(:token_in_session).and_return('token') - allow_any_instance_of(Projects::ClustersController) - .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s) - end - - context 'when user does not have a cluster and visits cluster index page' do - before do - visit project_clusters_path(project) - - click_link 'Create on GKE' - end - - it 'user sees a new page' do - expect(page).to have_button('Create cluster') - end - - context 'when user filled form with valid parameters' do - before do - double.tap do |dbl| - allow(dbl).to receive(:status).and_return('RUNNING') - allow(dbl).to receive(:self_link) - .and_return('projects/gcp-project-12345/zones/us-central1-a/operations/ope-123') - allow_any_instance_of(GoogleApi::CloudPlatform::Client) - .to receive(:projects_zones_clusters_create).and_return(dbl) - end - - allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil) - - fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123' - fill_in 'cluster_name', with: 'dev-cluster' - click_button 'Create cluster' - end - - it 'user sees a cluster details page and creation status' do - expect(page).to have_content('Cluster is being created on Google Container Engine...') - - # Application Installation buttons - page.within('.js-cluster-application-row-helm') do - expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') - expect(page.find(:css, '.js-cluster-application-install-button').text).to eq('Install') - end - - Clusters::Cluster.last.provider.make_created! - - expect(page).to have_content('Cluster was successfully created on Google Container Engine') - end - - it 'user sees a error if something worng during creation' do - expect(page).to have_content('Cluster is being created on Google Container Engine...') - - Clusters::Cluster.last.provider.make_errored!('Something wrong!') - - expect(page).to have_content('Something wrong!') - end - end - - context 'when user filled form with invalid parameters' do - before do - click_button 'Create cluster' - end - - it 'user sees a validation error' do - expect(page).to have_css('#error_explanation') - end - end - end - - context 'when user has a cluster and visits cluster index page' do - let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } - - before do - visit project_clusters_path(project) - end - - it 'user sees an cluster details page' do - expect(page).to have_button('Save') - expect(page.find(:css, '.cluster-name').value).to eq(cluster.name) - - # Application Installation buttons - page.within('.js-cluster-application-row-helm') do - expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to be_nil - expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Install') - end - end - - context 'when user installs application: Helm Tiller' do - before do - allow(ClusterInstallAppWorker).to receive(:perform_async).and_return(nil) - - page.within('.js-cluster-application-row-helm') do - page.find(:css, '.js-cluster-application-install-button').click - end - end - - it 'user sees status transition' do - page.within('.js-cluster-application-row-helm') do - # FE sends request and gets the response, then the buttons is "Install" - expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') - expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Install') - - Clusters::Cluster.last.application_helm.make_installing! - - # FE starts polling and update the buttons to "Installing" - expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') - expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installing') - - Clusters::Cluster.last.application_helm.make_installed! - - expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') - expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installed') - end - - expect(page).to have_content('Helm Tiller was successfully installed on your cluster') - end - end - - context 'when user installs application: Ingress' do - before do - allow(ClusterInstallAppWorker).to receive(:perform_async).and_return(nil) - # Helm Tiller needs to be installed before you can install Ingress - create(:cluster_applications_helm, :installed, cluster: cluster) - - visit project_clusters_path(project) - - page.within('.js-cluster-application-row-ingress') do - page.find(:css, '.js-cluster-application-install-button').click - end - end - - it 'user sees status transition' do - page.within('.js-cluster-application-row-ingress') do - # FE sends request and gets the response, then the buttons is "Install" - expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') - expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Install') - - Clusters::Cluster.last.application_ingress.make_installing! - - # FE starts polling and update the buttons to "Installing" - expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') - expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installing') - - Clusters::Cluster.last.application_ingress.make_installed! - - expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') - expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installed') - end - - expect(page).to have_content('Ingress was successfully installed on your cluster') - end - end - - context 'when user disables the cluster' do - before do - page.find(:css, '.js-toggle-cluster').click - click_button 'Save' - end - - it 'user sees the succeccful message' do - expect(page).to have_content('Cluster was successfully updated.') - end - end - - context 'when user destory the cluster' do - before do - page.accept_confirm do - click_link 'Remove integration' - end - end - - it 'user sees creation form with the succeccful message' do - expect(page).to have_content('Cluster integration was successfully removed.') - expect(page).to have_link('Create on GKE') - end - end - end - end - - context 'when user has not signed in Google' do + context 'when user does not have a cluster and visits cluster index page' do before do visit project_clusters_path(project) click_link 'Create on GKE' end - it 'user sees a login page' do - expect(page).to have_css('.signin-with-google') + it 'user sees a new page' do + expect(page).to have_button('Create cluster') end end end diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js index 027e8001053..6d6e71cc215 100644 --- a/spec/javascripts/clusters/clusters_bundle_spec.js +++ b/spec/javascripts/clusters/clusters_bundle_spec.js @@ -36,6 +36,20 @@ describe('Clusters', () => { }); }); + describe('showToken', () => { + it('should update tye field type', () => { + cluster.showTokenButton.click(); + expect( + cluster.tokenField.getAttribute('type'), + ).toEqual('text'); + + cluster.showTokenButton.click(); + expect( + cluster.tokenField.getAttribute('type'), + ).toEqual('password'); + }); + }); + describe('checkForNewInstalls', () => { const INITIAL_APP_MAP = { helm: { status: null, title: 'Helm Tiller' }, @@ -113,7 +127,7 @@ describe('Clusters', () => { }); describe('when cluster is created', () => { - it('should show the success container', () => { + it('should show the success container and fresh the page', () => { cluster.updateContainer(null, 'created'); expect( |