Commit c55155b3 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'ce-to-ee-2017-12-05' into 'master'

CE upstream - Tuesday

Closes gitaly#737

See merge request gitlab-org/gitlab-ee!3633
parents 523723c9 b7513596
......@@ -25,6 +25,7 @@ class ListIssue {
this.isLoading = {
weight: false,
};
this.isLoading = {};
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
this.milestone_id = obj.milestone_id;
......
......@@ -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');
......
......@@ -3,3 +3,4 @@ import './polyfills';
import './jquery';
import './bootstrap';
import './vue';
import '../lib/utils/axios_utils';
......@@ -78,11 +78,13 @@
<div class="ci-job-component">
<a
v-tooltip
v-if="job.status.details_path"
v-if="job.status.has_details"
:href="job.status.details_path"
:title="tooltipText"
:class="cssClassJobName"
data-container="body">
data-container="body"
class="js-pipeline-graph-job-link"
>
<job-name-component
:name="job.name"
......@@ -95,7 +97,8 @@
v-tooltip
:title="tooltipText"
:class="cssClassJobName"
data-container="body">
data-container="body"
>
<job-name-component
:name="job.name"
......
......@@ -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 ');
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
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
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
......
......@@ -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)
......
......@@ -36,12 +36,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?
......@@ -175,6 +178,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
......@@ -140,7 +140,17 @@ class Namespace < ActiveRecord::Base
def find_fork_of(project)
return nil unless project.fork_network
project.fork_network.find_forks_in(projects).first
if RequestStore.active?
forks_in_namespace = RequestStore.fetch("namespaces:#{id}:forked_projects") do
Hash.new do |found_forks, project|
found_forks[project] = project.fork_network.find_forks_in(projects).first
end
end
forks_in_namespace[project]
else
project.fork_network.find_forks_in(projects).first
end
end
def lfs_enabled?
......
......@@ -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|
......
......@@ -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
= render 'devise/shared/tab_single', tab_title:'Change your password'
.login-box
.login-body
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'gl-show-field-errors' }) do |f|
= form_for(resource, as: resource_name, url: password_path(:user), html: { method: :put, class: 'gl-show-field-errors' }) do |f|
.devise-errors
= devise_error_messages!
= f.hidden_field :reset_password_token
......@@ -17,5 +17,5 @@
.clearfix.prepend-top-20
%p
%span.light Didn't receive a confirmation email?
= link_to "Request a new one", new_confirmation_path(resource_name)
= link_to "Request a new one", new_confirmation_path(:user)
= render 'devise/shared/sign_in_link'
......@@ -11,6 +11,6 @@
= f.check_box :remember_me, class: 'remember-me-checkbox'
%span Remember me
.pull-right.forgot-password
= link_to "Forgot your password?", new_password_path(resource_name)
= link_to "Forgot your password?", new_password_path(:user)
.submit-container.move-submit-down
= f.submit "Sign in", class: "btn btn-save"
<%- if controller_name != 'sessions' %>
<%= link_to "Sign in", new_session_path(resource_name), class: "btn" %><br />
<%= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: "btn" %><br />
<% end -%>
<%- if devise_mapping.registerable? && controller_name != 'registrations' && allow_signup? %>
<%= link_to "Sign up", new_registration_path(resource_name) %><br />
<%= link_to "Sign up", new_registration_path(:user) %><br />
<% end -%>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
<%= link_to "Forgot your password?", new_password_path(resource_name), class: "btn" %><br />
<%= link_to "Forgot your password?", new_password_path(:user), class: "btn" %><br />
<% end -%>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(:user) %><br />
<% end -%>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(:user) %><br />
<% end -%>
%p
%span.light
Already have login and password?
= link_to "Sign in", new_session_path(resource_name)
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes')
......@@ -32,4 +32,4 @@
%p
%span.light Didn't receive a confirmation email?
= succeed '.' do
= link_to "Request a new one", new_confirmation_path(resource_name)
= link_to "Request a new one", new_confirmation_path(:user)
......@@ -9,7 +9,7 @@
%p Try logging in using your username or email. If you have forgotten your password, try recovering it
= link_to "Sign in", new_session_path(:user), class: 'btn primary'
= link_to "Recover password", new_password_path(resource_name), class: 'btn secondary'
= link_to "Recover password", new_password_path(:user), class: 'btn secondary'
%hr
%p.light If none of the options work, try contacting a GitLab administrator.
......@@ -160,7 +160,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')
......@@ -168,7 +168,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') }
......@@ -197,18 +197,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
......
- 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
......
%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.')
%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))
= 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'
.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'
%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'
%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
......
.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'
......@@ -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
......
......@@ -3,8 +3,8 @@
.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'
= render 'form'
= render 'form'
......@@ -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'
......@@ -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'
= 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'
%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 }
= 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'
- 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'
---
title: Create a new form to add Existing Kubernetes Cluster
merge_request: 14805
author:
type: added
---
title: Confirming email with invalid token should no longer generate an error
merge_request: 15726
author:
type: fixed
---
title: Add axios to common file
merge_request:
author:
type: performance
---
title: Reduce requests for project forks on show page of projects that have forks
merge_request: 15663
author:
type: performance
---
title: Use custom user agent header in all GCP API requests.
merge_request: 15705
author:
type: changed
......@@ -218,10 +218,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
......
......@@ -152,12 +152,23 @@ CE and EE.
## Previewing the changes live
If you want to preview the doc changes of your merge request live, you can use
the manual `review-docs-deploy` job in your merge request.
the manual `review-docs-deploy` job in your merge request. You will need at
least Master permissions to be able to run it and is currently enabled for the
following projects:
- https://gitlab.com/gitlab-org/gitlab-ce
- https://gitlab.com/gitlab-org/gitlab-ee
NOTE: **Note:**
You will need to push a branch to those repositories, it doesn't work for forks.
TIP: **Tip:**
If your branch contains only documentation changes, you can use
[special branch names](#testing) to avoid long running pipelines.
In the mini pipeline graph, you should see an `>>` icon. Clicking on it will
reveal the `review-docs-deploy` job. Hit the play button for the job to start.
![Manual trigger a docs build](img/manual_build_docs.png)
This job will:
......
......@@ -2,7 +2,7 @@ module Gitlab
module Git
module Conflict
class File
attr_reader :content, :their_path, :our_path, :our_mode, :repository
attr_reader :content, :their_path, :our_path, :our_mode, :repository, :commit_oid
def initialize(repository, commit_oid, conflict, content)
@repository = repository
......
......@@ -75,7 +75,7 @@ module Gitlab
resolved_lines = file.resolve_lines(params[:sections])
new_file = resolved_lines.map { |line| line[:full_line] }.join("\n")
new_file << "\n" if file.our_blob.data.ends_with?("\n")
new_file << "\n" if file.our_blob.data.end_with?("\n")
elsif params[:content]
new_file = file.resolve_content(params[:content])
end
......
......@@ -19,6 +19,8 @@ module Gitlab
].freeze
SEARCH_CONTEXT_LINES = 3
GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git'.freeze
REBASE_WORKTREE_PREFIX = 'rebase'.freeze
SQUASH_WORKTREE_PREFIX = 'squash'.freeze
NoRepository = Class.new(StandardError)
InvalidBlobName = Class.new(StandardError)
......@@ -1071,13 +1073,8 @@ module Gitlab
raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ')
raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00")
command = [Gitlab.config.git.bin_path] + %w[update-ref --stdin -z]
input = "update #{ref_path}\x00#{ref}\x00\x00"
output, status = circuit_breaker.perform do
popen(command, path) { |stdin| stdin.write(input) }
end
raise GitError, output unless status.zero?
run_git!(%w[update-ref --stdin -z]) { |stdin| stdin.write(input) }
end
def fetch_ref(source_repository, source_ref:, target_ref:)
......@@ -1099,14 +1096,22 @@ module Gitlab
end
# Refactoring aid; allows us to copy code from app/models/repository.rb
def run_git(args, env: {}, nice: false)
def run_git(args, chdir: path, env: {}, nice: false, &block)
cmd = [Gitlab.config.git.bin_path, *args]
cmd.unshift("nice") if nice
circuit_breaker.perform do
popen(cmd, path, env)
popen(cmd, chdir, env, &block)
end
end
def run_git!(args, chdir: path, env: {}, nice: false, &block)
output, status = run_git(args, chdir: chdir, env: env, nice: nice, &block)
raise GitError, output unless status.zero?
output
end
# Refactoring aid; allows us to copy code from app/models/repository.rb
def run_git_with_timeout(args, timeout, env: {})
circuit_breaker.perform do
......@@ -1189,6 +1194,64 @@ module Gitlab
raise GitError.new("Could not fsck repository:\n#{output}") unless status.zero?
end
def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
rebase_path = worktree_path(REBASE_WORKTREE_PREFIX, rebase_id)
env = git_env_for_user(user)
with_worktree(rebase_path, branch, env: env) do
run_git!(
%W(pull --rebase #{remote_repository.path} #{remote_branch}),
chdir: rebase_path, env: env
)
rebase_sha = run_git!(%w(rev-parse HEAD), chdir: rebase_path, env: env).strip
Gitlab::Git::OperationService.new(user, self)
.update_branch(branch, rebase_sha, branch_sha)
rebase_sha
end
end
def rebase_in_progress?(rebase_id)
fresh_worktree?(worktree_path(REBASE_WORKTREE_PREFIX, rebase_id))
end
def squash(user, squash_id, branch:, start_sha:, end_sha:, author:, message:)
squash_path = worktree_path(SQUASH_WORKTREE_PREFIX, squash_id)
env = git_env_for_user(user).merge(
'GIT_AUTHOR_NAME' => author.name,
'GIT_AUTHOR_EMAIL' => author.email
)
diff_range = "#{start_sha}...#{end_sha}"
diff_files = run_git!(
%W(diff --name-only --diff-filter=a --binary #{diff_range})
).chomp
with_worktree(squash_path, branch, sparse_checkout_files: diff_files, env: env) do
# Apply diff of the `diff_range` to the worktree
diff = run_git!(%W(diff --binary #{diff_range}))
run_git!(%w(apply --index), chdir: squash_path, env: env) do |stdin|
stdin.write(diff)
end
# Commit the `diff_range` diff
run_git!(%W(commit --no-verify --message #{message}), chdir: squash_path, env: env)
# Return the squash sha. May print a warning for ambiguous refs, but
# we can ignore that with `--quiet` and just take the SHA, if present.
# HEAD here always refers to the current HEAD commit, even if there is
# another ref called HEAD.
run_git!(
%w(rev-parse --quiet --verify HEAD), chdir: squash_path, env: env
).chomp
end
end
def squash_in_progress?(squash_id)
fresh_worktree?(worktree_path(SQUASH_WORKTREE_PREFIX, squash_id))
end
def gitaly_repository
Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository)
end
......@@ -1225,6 +1288,57 @@ module Gitlab
private
def fresh_worktree?(path)
File.exist?(path) && !clean_stuck_worktree(path)
end
def with_worktree(worktree_path, branch, sparse_checkout_files: nil, env:)
base_args = %w(worktree add --detach)
# Note that we _don't_ want to test for `.present?` here: If the caller
# passes an non nil empty value it means it still wants sparse checkout
# but just isn't interested in any file, perhaps because it wants to
# checkout files in by a changeset but that changeset only adds files.
if sparse_checkout_files
# Create worktree without checking out
run_git!(base_args + ['--no-checkout', worktree_path], env: env)
worktree_git_path = run_git!(%w(rev-parse --git-dir), chdir: worktree_path)
configure_sparse_checkout(worktree_git_path, sparse_checkout_files)
# After sparse checkout configuration, checkout `branch` in worktree
run_git!(%W(checkout --detach #{branch}), chdir: worktree_path, env: env)
else
# Create worktree and checkout `branch` in it
run_git!(base_args + [worktree_path, branch], env: env)
end
yield
ensure
FileUtils.rm_rf(worktree_path) if File.exist?(worktree_path)
FileUtils.rm_rf(worktree_git_path) if worktree_git_path && File.exist?(worktree_git_path)
end
def clean_stuck_worktree(path)
return false unless File.mtime(path) < 15.minutes.ago
FileUtils.rm_rf(path)
true
end
# Adding a worktree means checking out the repository. For large repos,
# this can be very expensive, so set up sparse checkout for the worktree
# to only check out the files we're interested in.
def configure_sparse_checkout(worktree_git_path, files)
run_git!(%w(config core.sparseCheckout true))
return if files.empty?
worktree_info_path = File.join(worktree_git_path, 'info')
FileUtils.mkdir_p(worktree_info_path)
File.write(File.join(worktree_info_path, 'sparse-checkout'), files)
end
def rugged_fetch_source_branch(source_repository, source_branch, local_ref)
with_repo_branch_commit(source_repository, source_branch) do |commit|
if commit
......@@ -1236,6 +1350,24 @@ module Gitlab
end
end
def worktree_path(prefix, id)
id = id.to_s
raise ArgumentError, "worktree id can't be empty" unless id.present?
raise ArgumentError, "worktree id can't contain slashes " if id.include?("/")
File.join(path, 'gitlab-worktree', "#{prefix}-#{id}")
end
def git_env_for_user(user)
{
'GIT_COMMITTER_NAME' => user.name,
'GIT_COMMITTER_EMAIL' => user.email,
'GL_ID' => Gitlab::GlId.gl_id(user),
'GL_PROTOCOL' => Gitlab::Git::Hook::GL_PROTOCOL,
'GL_REPOSITORY' => gl_repository
}
end
# Gitaly note: JV: Trying to get rid of the 'filter' option so we can implement this with 'git'.
def branches_filter(filter: nil, sort_by: nil)
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37464
......
......@@ -44,7 +44,7 @@ module GoogleApi
service = Google::Apis::ContainerV1::ContainerService.new
service.authorization = access_token
service.get_zone_cluster(project_id, zone, cluster_id)
service.get_zone_cluster(project_id, zone, cluster_id, options: user_agent_header)
end
def projects_zones_clusters_create(project_id, zone, cluster_name, cluster_size, machine_type:)
......@@ -62,14 +62,14 @@ module GoogleApi
}
} )
service.create_cluster(project_id, zone, request_body)
service.create_cluster(project_id, zone, request_body, options: user_agent_header)
end
def projects_zones_operations(project_id, zone, operation_id)
service = Google::Apis::ContainerV1::ContainerService.new
service.authorization = access_token
service.get_zone_operation(project_id, zone, operation_id)
service.get_zone_operation(project_id, zone, operation_id, options: user_agent_header)
end
def parse_operation_id(self_link)
......@@ -82,6 +82,12 @@ module GoogleApi
def token_life_time(expires_at)
DateTime.strptime(expires_at, '%s').to_time.utc - Time.now.utc
end
def user_agent_header
Google::Apis::RequestOptions.new.tap do |options|
options.header = { 'User-Agent': "GitLab/#{Gitlab::VERSION.match('(\d+\.\d+)').captures.first} (GPN:GitLab;)" }
end
end
end
end
end
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
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
......@@ -284,6 +284,27 @@ describe ProjectsController do
expect(response).to redirect_to(namespace_project_path)
end
end
context 'when the project is forked and has a repository', :request_store do
let(:public_project) { create(:project, :public, :repository) }
let(:other_user) { create(:user) }
render_views
before do
# View the project as a user that does not have any rights
sign_in(other_user)
fork_project(public_project)
end
it 'does not increase the number of queries when the project is forked' do
expected_query = /#{public_project.fork_network.find_forks_in(other_user.namespace).to_sql}/
expect { get(:show, namespace_id: public_project.namespace, id: public_project) }
.not_to exceed_query_limit(1).for_query(expected_query)
end
end
end
describe "#update" do
......
......@@ -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
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
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
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
......@@ -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
......@@ -185,6 +185,36 @@ describe 'Pipeline', :js do
end
end
context 'when user does not have access to read jobs' do
before do
project.update(public_builds: false)
end
describe 'GET /:project/pipelines/:id' do
include_context 'pipeline builds'
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) }
before do
visit project_pipeline_path(project, pipeline)
end
it 'shows the pipeline graph' do
expect(page).to have_selector('.pipeline-visualization')
expect(page).to have_content('Build')
expect(page).to have_content('Test')
expect(page).to have_content('Deploy')
expect(page).to have_content('Retry')
expect(page).to have_content('Cancel running')
end
it 'should not link to job' do
expect(page).not_to have_selector('.js-pipeline-graph-job-link')
end
end
end
describe 'GET /:project/pipelines/:id/builds' do
include_context 'pipeline builds'
......
......@@ -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(
......
import Vue from 'vue';
import jobComponent from '~/pipelines/components/graph/job_component.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('pipeline graph job component', () => {
let JobComponent;
let component;
const mockJob = {
id: 4256,
......@@ -13,6 +15,7 @@ describe('pipeline graph job component', () => {
label: 'passed',
group: 'success',
details_path: '/root/ci-mock/builds/4256',
has_details: true,
action: {
icon: 'retry',
title: 'Retry',
......@@ -26,13 +29,13 @@ describe('pipeline graph job component', () => {
JobComponent = Vue.extend(jobComponent);
});
afterEach(() => {
component.$destroy();
});
describe('name with link', () => {
it('should render the job name and status with a link', (done) => {
const component = new JobComponent({
propsData: {
job: mockJob,
},
}).$mount();
component = mountComponent(JobComponent, { job: mockJob });
Vue.nextTick(() => {
const link = component.$el.querySelector('a');
......@@ -56,23 +59,23 @@ describe('pipeline graph job component', () => {
describe('name without link', () => {
it('it should render status and name', () => {
const component = new JobComponent({
propsData: {
job: {
id: 4256,
name: 'test',
status: {
icon: 'icon_status_success',
text: 'passed',
label: 'passed',
group: 'success',
details_path: '/root/ci-mock/builds/4256',
},
component = mountComponent(JobComponent, {
job: {
id: 4256,
name: 'test',
status: {
icon: 'icon_status_success',
text: 'passed',
label: 'passed',
group: 'success',
details_path: '/root/ci-mock/builds/4256',
has_details: false,
},
},
}).$mount();
});
expect(component.$el.querySelector('.js-status-icon-success')).toBeDefined();
expect(component.$el.querySelector('a')).toBeNull();
expect(
component.$el.querySelector('.ci-status-text').textContent.trim(),
......@@ -82,11 +85,7 @@ describe('pipeline graph job component', () => {
describe('action icon', () => {
it('it should render the action icon', () => {
const component = new JobComponent({
propsData: {
job: mockJob,
},
}).$mount();
component = mountComponent(JobComponent, { job: mockJob });
expect(component.$el.querySelector('a.ci-action-icon-container')).toBeDefined();
expect(component.$el.querySelector('i.ci-action-icon-wrapper')).toBeDefined();
......@@ -95,24 +94,20 @@ describe('pipeline graph job component', () => {
describe('dropdown', () => {
it('should render the dropdown action icon', () => {
const component = new JobComponent({
propsData: {
job: mockJob,
isDropdown: true,
},
}).$mount();
component = mountComponent(JobComponent, {
job: mockJob,
isDropdown: true,
});
expect(component.$el.querySelector('a.ci-action-icon-wrapper')).toBeDefined();
});
});
it('should render provided class name', () => {
const component = new JobComponent({
propsData: {
job: mockJob,
cssClassJobName: 'css-class-job-name',
},
}).$mount();
component = mountComponent(JobComponent, {
job: mockJob,
cssClassJobName: 'css-class-job-name',
});
expect(
component.$el.querySelector('a').classList.contains('css-class-job-name'),
......
......@@ -3,6 +3,7 @@ require 'spec_helper'
describe GoogleApi::CloudPlatform::Client do
let(:token) { 'token' }
let(:client) { described_class.new(token, nil) }
let(:user_agent_options) { client.instance_eval { user_agent_header } }
describe '.session_key_for_redirect_uri' do
let(:state) { 'random_string' }
......@@ -55,7 +56,8 @@ describe GoogleApi::CloudPlatform::Client do
before do
allow_any_instance_of(Google::Apis::ContainerV1::ContainerService)
.to receive(:get_zone_cluster).and_return(gke_cluster)
.to receive(:get_zone_cluster).with(any_args, options: user_agent_options)
.and_return(gke_cluster)
end
it { is_expected.to eq(gke_cluster) }
......@@ -74,7 +76,8 @@ describe GoogleApi::CloudPlatform::Client do
before do
allow_any_instance_of(Google::Apis::ContainerV1::ContainerService)
.to receive(:create_cluster).and_return(operation)
.to receive(:create_cluster).with(any_args, options: user_agent_options)
.and_return(operation)
end
it { is_expected.to eq(operation) }
......@@ -102,7 +105,8 @@ describe GoogleApi::CloudPlatform::Client do
before do
allow_any_instance_of(Google::Apis::ContainerV1::ContainerService)
.to receive(:get_zone_operation).and_return(operation)
.to receive(:get_zone_operation).with(any_args, options: user_agent_options)
.and_return(operation)
end
it { is_expected.to eq(operation) }
......@@ -125,4 +129,18 @@ describe GoogleApi::CloudPlatform::Client do
it { is_expected.to be_nil }
end
end
describe '#user_agent_header' do
subject { client.instance_eval { user_agent_header } }
it 'returns a RequestOptions object' do
expect(subject).to be_instance_of(Google::Apis::RequestOptions)
end
it 'has the correct GitLab version in User-Agent header' do
stub_const('Gitlab::VERSION', '10.3.0-pre')
expect(subject.header).to eq({ 'User-Agent': 'GitLab/10.3 (GPN:GitLab;)' })
end
end
end
......@@ -655,7 +655,7 @@ describe Namespace do
end
end
describe '#has_forks_of?' do
describe '#find_fork_of?' do
let(:project) { create(:project, :public) }
let!(:forked_project) { fork_project(project, namespace.owner, namespace: namespace) }
......@@ -674,6 +674,14 @@ describe Namespace do
expect(other_namespace.find_fork_of(project)).to eq(other_fork)
end
context 'with request store enabled', :request_store do
it 'only queries once' do
expect(project.fork_network).to receive(:find_forks_in).once.and_call_original
2.times { namespace.find_fork_of(project) }
end
end
end
describe '#root_ancestor' do
......
......@@ -41,7 +41,8 @@ RSpec::Matchers.define :exceed_query_limit do |expected|
supports_block_expectations
match do |block|
query_count(&block) > expected_count + threshold
@subject_block = block
actual_count > expected_count + threshold
end
failure_message_when_negated do |actual|
......@@ -55,6 +56,11 @@ RSpec::Matchers.define :exceed_query_limit do |expected|
self
end
def for_query(query)
@query = query
self
end
def threshold
@threshold.to_i
end
......@@ -68,12 +74,15 @@ RSpec::Matchers.define :exceed_query_limit do |expected|
end
def actual_count
@recorder.count
@actual_count ||= if @query
recorder.log.select { |recorded| recorded =~ @query }.size
else
recorder.count
end
end
def query_count(&block)
@recorder = ActiveRecord::QueryRecorder.new(&block)
@recorder.count
def recorder
@recorder ||= ActiveRecord::QueryRecorder.new(&@subject_block)
end
def count_queries(queries)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment