Commit 91d0d91f authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'origin/master' into ce-to-ee-2017-11-22

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parents 423dde58 80e00cb2
...@@ -48,13 +48,32 @@ ...@@ -48,13 +48,32 @@
created() { created() {
eventHub.$on('toggleFolder', this.toggleFolder); eventHub.$on('toggleFolder', this.toggleFolder);
<<<<<<< HEAD
=======
eventHub.$on('toggleDeployBoard', this.toggleDeployBoard);
>>>>>>> origin/master
}, },
beforeDestroy() { beforeDestroy() {
eventHub.$off('toggleFolder'); eventHub.$off('toggleFolder');
<<<<<<< HEAD
}, },
methods: { methods: {
=======
eventHub.$off('toggleDeployBoard');
},
methods: {
/**
* Toggles the visibility of the deploy boards of the clicked environment.
* @param {Object} model
*/
toggleDeployBoard(model) {
this.store.toggleDeployBoard(model.id);
},
>>>>>>> origin/master
toggleFolder(folder) { toggleFolder(folder) {
this.store.toggleFolder(folder); this.store.toggleFolder(folder);
......
...@@ -21,7 +21,10 @@ import tabs from '../../vue_shared/components/navigation_tabs.vue'; ...@@ -21,7 +21,10 @@ import tabs from '../../vue_shared/components/navigation_tabs.vue';
import container from '../components/container.vue'; import container from '../components/container.vue';
export default { export default {
<<<<<<< HEAD
=======
>>>>>>> origin/master
components: { components: {
environmentTable, environmentTable,
container, container,
...@@ -58,7 +61,10 @@ export default { ...@@ -58,7 +61,10 @@ export default {
} }
}); });
}, },
<<<<<<< HEAD
=======
>>>>>>> origin/master
/** /**
* Handles URL and query parameter changes. * Handles URL and query parameter changes.
* When the user uses the pagination or the tabs, * When the user uses the pagination or the tabs,
......
...@@ -44,12 +44,21 @@ export default class EnvironmentsStore { ...@@ -44,12 +44,21 @@ export default class EnvironmentsStore {
storeEnvironments(environments = []) { storeEnvironments(environments = []) {
const filteredEnvironments = environments.map((env) => { const filteredEnvironments = environments.map((env) => {
const oldEnvironmentState = this.state.environments const oldEnvironmentState = this.state.environments
<<<<<<< HEAD
.find((element) => { .find((element) => {
if (env.latest) { if (env.latest) {
return element.id === env.latest.id; return element.id === env.latest.id;
} }
return element.id === env.id; return element.id === env.id;
}) || {}; }) || {};
=======
.find((element) => {
if (env.latest) {
return element.id === env.latest.id;
}
return element.id === env.id;
}) || {};
>>>>>>> origin/master
let filtered = {}; let filtered = {};
......
...@@ -171,7 +171,10 @@ ...@@ -171,7 +171,10 @@
*/ */
updateContent(parameters) { updateContent(parameters) {
this.updateInternalState(parameters); this.updateInternalState(parameters);
<<<<<<< HEAD
=======
>>>>>>> origin/master
// fetch new data // fetch new data
return this.service.getPipelines(this.requestData) return this.service.getPipelines(this.requestData)
.then((response) => { .then((response) => {
......
import renderMath from './render_math'; import renderMath from './render_math';
<<<<<<< HEAD
import renderMermaid from './render_mermaid'; import renderMermaid from './render_mermaid';
=======
>>>>>>> origin/master
// Render Gitlab flavoured Markdown // Render Gitlab flavoured Markdown
// //
...@@ -8,7 +11,10 @@ import renderMermaid from './render_mermaid'; ...@@ -8,7 +11,10 @@ import renderMermaid from './render_mermaid';
$.fn.renderGFM = function renderGFM() { $.fn.renderGFM = function renderGFM() {
this.find('.js-syntax-highlight').syntaxHighlight(); this.find('.js-syntax-highlight').syntaxHighlight();
renderMath(this.find('.js-render-math')); renderMath(this.find('.js-render-math'));
<<<<<<< HEAD
renderMermaid(this.find('.js-render-mermaid')); renderMermaid(this.find('.js-render-mermaid'));
=======
>>>>>>> origin/master
return this; return this;
}; };
......
...@@ -204,7 +204,12 @@ class ApplicationController < ActionController::Base ...@@ -204,7 +204,12 @@ class ApplicationController < ActionController::Base
end end
def check_password_expiration def check_password_expiration
<<<<<<< HEAD
return if session[:impersonator_id] || !current_user&.allow_password_authentication? return if session[:impersonator_id] || !current_user&.allow_password_authentication?
=======
return if session[:impersonator_id]
return unless current_user&.allow_password_authentication?
>>>>>>> origin/master
password_expires_at = current_user&.password_expires_at password_expires_at = current_user&.password_expires_at
......
...@@ -213,6 +213,10 @@ class Namespace < ActiveRecord::Base ...@@ -213,6 +213,10 @@ class Namespace < ActiveRecord::Base
parent.present? parent.present?
end end
def root_ancestor
ancestors.reorder(nil).find_by(parent_id: nil)
end
def subgroup? def subgroup?
has_parent? has_parent?
end end
......
...@@ -500,6 +500,14 @@ class Project < ActiveRecord::Base ...@@ -500,6 +500,14 @@ class Project < ActiveRecord::Base
.base_and_ancestors(upto: top) .base_and_ancestors(upto: top)
end end
def root_namespace
if namespace.has_parent?
namespace.root_ancestor
else
namespace
end
end
def lfs_enabled? def lfs_enabled?
return namespace.lfs_enabled? if self[:lfs_enabled].nil? return namespace.lfs_enabled? if self[:lfs_enabled].nil?
......
...@@ -20,7 +20,8 @@ ...@@ -20,7 +20,8 @@
= render 'groups/group_admin_settings', f: f = render 'groups/group_admin_settings', f: f
= render 'namespaces/shared_runners_minutes_setting', f: f - if @group.shared_runner_minutes_supported?
= render 'namespaces/shared_runners_minutes_setting', f: f
- if @group.new_record? - if @group.new_record?
.form-group .form-group
......
...@@ -10,3 +10,4 @@ ...@@ -10,3 +10,4 @@
"can-create-deployment" => can?(current_user, :create_deployment, @project).to_s, "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
"can-read-environment" => can?(current_user, :read_environment, @project).to_s, "can-read-environment" => can?(current_user, :read_environment, @project).to_s,
"css-class" => container_class } } "css-class" => container_class } }
---
title: Account shared runner minutes to top-level namespace
merge_request:
author:
type: fixed
---
title: EE Protected Branches API access levels include user_id/group_id where relevant
merge_request: 3535
author:
type: changed
This diff is collapsed.
...@@ -571,8 +571,7 @@ or a failover promotes a different **Master** node. ...@@ -571,8 +571,7 @@ or a failover promotes a different **Master** node.
In `/etc/gitlab/gitlab.rb`: In `/etc/gitlab/gitlab.rb`:
```ruby ```ruby
redis_master_role['enable'] = true roles ['redis_sentinel_role', 'redis_master_role']
redis_sentinel_role['enable'] = true
redis['bind'] = '10.0.0.1' redis['bind'] = '10.0.0.1'
redis['port'] = 6379 redis['port'] = 6379
redis['password'] = 'redis-password-goes-here' redis['password'] = 'redis-password-goes-here'
...@@ -594,8 +593,7 @@ sentinel['quorum'] = 2 ...@@ -594,8 +593,7 @@ sentinel['quorum'] = 2
In `/etc/gitlab/gitlab.rb`: In `/etc/gitlab/gitlab.rb`:
```ruby ```ruby
redis_slave_role['enable'] = true roles ['redis_sentinel_role', 'redis_slave_role']
redis_sentinel_role['enable'] = true
redis['bind'] = '10.0.0.2' redis['bind'] = '10.0.0.2'
redis['port'] = 6379 redis['port'] = 6379
redis['password'] = 'redis-password-goes-here' redis['password'] = 'redis-password-goes-here'
...@@ -617,8 +615,7 @@ sentinel['quorum'] = 2 ...@@ -617,8 +615,7 @@ sentinel['quorum'] = 2
In `/etc/gitlab/gitlab.rb`: In `/etc/gitlab/gitlab.rb`:
```ruby ```ruby
redis_slave_role['enable'] = true roles ['redis_sentinel_role', 'redis_slave_role']
redis_sentinel_role['enable'] = true
redis['bind'] = '10.0.0.3' redis['bind'] = '10.0.0.3'
redis['port'] = 6379 redis['port'] = 6379
redis['password'] = 'redis-password-goes-here' redis['password'] = 'redis-password-goes-here'
......
...@@ -36,13 +36,17 @@ Example response: ...@@ -36,13 +36,17 @@ Example response:
"push_access_levels": [ "push_access_levels": [
{ {
"access_level": 40, "access_level": 40,
"user_id": null,
"group_id": null,
"access_level_description": "Masters" "access_level_description": "Masters"
} }
], ],
"merge_access_levels": [ "merge_access_levels": [
{ {
"access_level": 40, "access_level": null,
"access_level_description": "Masters" "user_id": null,
"group_id": 1234,
"access_level_description": "Example Merge Group"
} }
] ]
}, },
...@@ -75,13 +79,17 @@ Example response: ...@@ -75,13 +79,17 @@ Example response:
"push_access_levels": [ "push_access_levels": [
{ {
"access_level": 40, "access_level": 40,
"user_id": null,
"group_id": null,
"access_level_description": "Masters" "access_level_description": "Masters"
} }
], ],
"merge_access_levels": [ "merge_access_levels": [
{ {
"access_level": 40, "access_level": null,
"access_level_description": "Masters" "user_id": null,
"group_id": 1234,
"access_level_description": "Example Merge Group"
} }
] ]
} }
...@@ -115,12 +123,16 @@ Example response: ...@@ -115,12 +123,16 @@ Example response:
"push_access_levels": [ "push_access_levels": [
{ {
"access_level": 30, "access_level": 30,
"user_id": null,
"group_id": null,
"access_level_description": "Developers + Masters" "access_level_description": "Developers + Masters"
} }
], ],
"merge_access_levels": [ "merge_access_levels": [
{ {
"access_level": 30, "access_level": 30,
"user_id": null,
"group_id": null,
"access_level_description": "Developers + Masters" "access_level_description": "Developers + Masters"
} }
] ]
......
...@@ -223,9 +223,6 @@ will not be able to perform all necessary configuration steps. Refer to ...@@ -223,9 +223,6 @@ will not be able to perform all necessary configuration steps. Refer to
match your database replication requirements. Consult the [PostgreSQL - Replication documentation](https://www.postgresql.org/docs/9.6/static/runtime-config-replication.html) match your database replication requirements. Consult the [PostgreSQL - Replication documentation](https://www.postgresql.org/docs/9.6/static/runtime-config-replication.html)
for more information. for more information.
1. Check to make sure your firewall rules are set so that the secondary nodes
can access port `5432` on the primary node.
1. Save the file and [reconfigure GitLab][] for the database listen changes to 1. Save the file and [reconfigure GitLab][] for the database listen changes to
take effect. take effect.
...@@ -319,18 +316,23 @@ primary before the database is replicated. ...@@ -319,18 +316,23 @@ primary before the database is replicated.
1. Test that the remote connection to the primary server works. 1. Test that the remote connection to the primary server works.
``` ```
# Certificate and key currently used by GitLab # Certificate and key currently used by GitLab, and connecting by FQDN
sudo -u gitlab-psql /opt/gitlab/embedded/bin/psql -h primary.geo.example.com -U gitlab_replicator -d "dbname=gitlabhq_production sslmode=verify-ca" -W sudo -u gitlab-psql /opt/gitlab/embedded/bin/psql -h primary.geo.example.com -U gitlab_replicator -d "dbname=gitlabhq_production sslmode=verify-full" -W
# Self-signed certificate and key # Self-signed certificate and key, or connecting by IP address
sudo -u gitlab-psql /opt/gitlab/embedded/bin/psql -h 1.2.3.4 -U gitlab_replicator -d "dbname=gitlabhq_production sslmode=verify-full" -W sudo -u gitlab-psql /opt/gitlab/embedded/bin/psql -h 1.2.3.4 -U gitlab_replicator -d "dbname=gitlabhq_production sslmode=verify-ca" -W
``` ```
When prompted enter the password you set in the first step for the When prompted enter the password you set in the first step for the
`gitlab_replicator` user. If all worked correctly, you should see the `gitlab_replicator` user. If all worked correctly, you should see the
database prompt. database prompt.
A failure to connect here indicates that the TLS or networking configuration
is incorrect. Ensure that you've used the correct certificates and IP
addresses / FQDNs throughout. If you have a firewall, ensure that the
secondary is permitted to access the primary on port 5432.
1. Exit the PostgreSQL console: 1. Exit the PostgreSQL console:
``` ```
...@@ -391,10 +393,10 @@ data before running `pg_basebackup`. ...@@ -391,10 +393,10 @@ data before running `pg_basebackup`.
1. Execute the command below to start a backup/restore and begin the replication: 1. Execute the command below to start a backup/restore and begin the replication:
``` ```
# Certificate and key currently used by GitLab # Certificate and key currently used by GitLab, and connecting by FQDN
gitlab-ctl replicate-geo-database --host=primary.geo.example.com --slot-name=secondary_example gitlab-ctl replicate-geo-database --host=primary.geo.example.com --slot-name=secondary_example
# Self-signed certificate and key # Self-signed certificate and key, or connecting by IP
gitlab-ctl replicate-geo-database --host=1.2.3.4 --slot-name=secondary_example --sslmode=verify-ca gitlab-ctl replicate-geo-database --host=1.2.3.4 --slot-name=secondary_example --sslmode=verify-ca
``` ```
......
...@@ -270,22 +270,24 @@ primary before the database is replicated. ...@@ -270,22 +270,24 @@ primary before the database is replicated.
1. Test that the remote connection to the primary server works: 1. Test that the remote connection to the primary server works:
If you're using a CA-issued certificate and connecting by FQDN:
``` ```
# Certificate and key currently used by GitLab, and connecting by FQDN
sudo -u postgres psql -h primary.geo.example.com -U gitlab_replicator -d "dbname=gitlabhq_production sslmode=verify-ca" -W sudo -u postgres psql -h primary.geo.example.com -U gitlab_replicator -d "dbname=gitlabhq_production sslmode=verify-ca" -W
```
If you're using a self-signed certificate or connecting by IP address:
``` # Self-signed certificate and key, or connecting by IP address
sudo -u postgres psql -h 1.2.3.4 -U gitlab_replicator -d "dbname=gitlabhq_production sslmode=verify-full" -W sudo -u postgres psql -h 1.2.3.4 -U gitlab_replicator -d "dbname=gitlabhq_production sslmode=verify-ca" -W
``` ```
When prompted enter the password you set in the first step for the When prompted enter the password you set in the first step for the
`gitlab_replicator` user. If all worked correctly, you should see the `gitlab_replicator` user. If all worked correctly, you should see the
database prompt. database prompt.
A failure to connect here indicates that the TLS or networking configuration
is incorrect. Ensure that you've used the correct certificates and IP
addresses / FQDNs throughout. If you have a firewall, ensure that the
secondary is permitted to access the primary on port 5432.
1. Exit the PostgreSQL console: 1. Exit the PostgreSQL console:
``` ```
......
class Groups::PipelineQuotaController < Groups::ApplicationController class Groups::PipelineQuotaController < Groups::ApplicationController
before_action :authorize_admin_group! before_action :authorize_admin_group!
before_action :validate_shared_runner_minutes_support!
layout 'group_settings' layout 'group_settings'
def index def index
@projects = @group.projects.with_shared_runners_limit_enabled.page(params[:page]) @projects = all_projects.with_shared_runners_limit_enabled.page(params[:page])
end
private
def all_projects
if Feature.enabled?(:shared_runner_minutes_on_root_namespace)
@group.all_projects
else
@group.projects
end
end
def validate_shared_runner_minutes_support!
render_404 unless @group.shared_runner_minutes_supported?
end end
end end
...@@ -34,6 +34,7 @@ module EE ...@@ -34,6 +34,7 @@ module EE
to: :namespace_statistics, allow_nil: true to: :namespace_statistics, allow_nil: true
validate :validate_plan_name validate :validate_plan_name
validate :validate_shared_runner_minutes_support
end end
module ClassMethods module ClassMethods
...@@ -42,10 +43,6 @@ module EE ...@@ -42,10 +43,6 @@ module EE
end end
end end
def root_ancestor
ancestors.reorder(nil).find_by(parent_id: nil)
end
def move_dir def move_dir
raise NotImplementedError unless defined?(super) raise NotImplementedError unless defined?(super)
...@@ -96,13 +93,22 @@ module EE ...@@ -96,13 +93,22 @@ module EE
actual_plan&.name || FREE_PLAN actual_plan&.name || FREE_PLAN
end end
def shared_runner_minutes_supported?
if has_parent?
!Feature.enabled?(:shared_runner_minutes_on_root_namespace)
else
true
end
end
def actual_shared_runners_minutes_limit def actual_shared_runners_minutes_limit
shared_runners_minutes_limit || shared_runners_minutes_limit ||
current_application_settings.shared_runners_minutes current_application_settings.shared_runners_minutes
end end
def shared_runners_minutes_limit_enabled? def shared_runners_minutes_limit_enabled?
shared_runners_enabled? && shared_runner_minutes_supported? &&
shared_runners_enabled? &&
actual_shared_runners_minutes_limit.nonzero? actual_shared_runners_minutes_limit.nonzero?
end end
...@@ -111,6 +117,14 @@ module EE ...@@ -111,6 +117,14 @@ module EE
shared_runners_minutes.to_i >= actual_shared_runners_minutes_limit shared_runners_minutes.to_i >= actual_shared_runners_minutes_limit
end end
def shared_runners_enabled?
if Feature.enabled?(:shared_runner_minutes_on_root_namespace)
all_projects.with_shared_runners.any?
else
projects.with_shared_runners.any?
end
end
# These helper methods are required to not break the Namespace API. # These helper methods are required to not break the Namespace API.
def plan=(plan_name) def plan=(plan_name)
if plan_name.is_a?(String) if plan_name.is_a?(String)
...@@ -140,6 +154,14 @@ module EE ...@@ -140,6 +154,14 @@ module EE
end end
end end
def validate_shared_runner_minutes_support
return if shared_runner_minutes_supported?
if shared_runners_minutes_limit_changed?
errors.add(:shared_runners_minutes_limit, 'is not supported for this namespace')
end
end
def load_feature_available(feature) def load_feature_available(feature)
globally_available = License.feature_available?(feature) globally_available = License.feature_available?(feature)
......
...@@ -54,7 +54,7 @@ module EE ...@@ -54,7 +54,7 @@ module EE
to: :statistics, allow_nil: true to: :statistics, allow_nil: true
delegate :actual_shared_runners_minutes_limit, delegate :actual_shared_runners_minutes_limit,
:shared_runners_minutes_used?, to: :namespace :shared_runners_minutes_used?, to: :shared_runners_limit_namespace
validates :repository_size_limit, validates :repository_size_limit,
numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true } numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true }
...@@ -82,6 +82,14 @@ module EE ...@@ -82,6 +82,14 @@ module EE
end end
end end
def shared_runners_limit_namespace
if Feature.enabled?(:shared_runner_minutes_on_root_namespace)
root_namespace
else
namespace
end
end
def mirror def mirror
super && feature_available?(:repository_mirrors) super && feature_available?(:repository_mirrors)
end end
...@@ -190,11 +198,12 @@ module EE ...@@ -190,11 +198,12 @@ module EE
end end
def shared_runners_available? def shared_runners_available?
super && !namespace.shared_runners_minutes_used? super && !shared_runners_limit_namespace.shared_runners_minutes_used?
end end
def shared_runners_minutes_limit_enabled? def shared_runners_minutes_limit_enabled?
!public? && shared_runners_enabled? && namespace.shared_runners_minutes_limit_enabled? !public? && shared_runners_enabled? &&
shared_runners_limit_namespace.shared_runners_minutes_limit_enabled?
end end
def feature_available?(feature, user = nil) def feature_available?(feature, user = nil)
......
...@@ -17,8 +17,7 @@ module EE ...@@ -17,8 +17,7 @@ module EE
end end
def builds_check_limit def builds_check_limit
::Namespace.reorder(nil) all_namespaces
.where('namespaces.id = projects.namespace_id')
.joins('LEFT JOIN namespace_statistics ON namespace_statistics.namespace_id = namespaces.id') .joins('LEFT JOIN namespace_statistics ON namespace_statistics.namespace_id = namespaces.id')
.where('COALESCE(namespaces.shared_runners_minutes_limit, ?, 0) = 0 OR ' \ .where('COALESCE(namespaces.shared_runners_minutes_limit, ?, 0) = 0 OR ' \
'COALESCE(namespace_statistics.shared_runners_seconds, 0) < COALESCE(namespaces.shared_runners_minutes_limit, ?, 0) * 60', 'COALESCE(namespace_statistics.shared_runners_seconds, 0) < COALESCE(namespaces.shared_runners_minutes_limit, ?, 0) * 60',
...@@ -26,6 +25,16 @@ module EE ...@@ -26,6 +25,16 @@ module EE
.select('1') .select('1')
end end
def all_namespaces
namespaces = ::Namespace.reorder(nil).where('namespaces.id = projects.namespace_id')
if Feature.enabled?(:shared_runner_minutes_on_root_namespace)
namespaces = ::Gitlab::GroupHierarchy.new(namespaces).roots
end
namespaces
end
def application_shared_runners_minutes def application_shared_runners_minutes
current_application_settings.shared_runners_minutes current_application_settings.shared_runners_minutes
end end
......
...@@ -18,10 +18,10 @@ class UpdateBuildMinutesService < BaseService ...@@ -18,10 +18,10 @@ class UpdateBuildMinutesService < BaseService
end end
def project_statistics def project_statistics
project.statistics || project.create_statistics(namespace: namespace) project.statistics || project.create_statistics(namespace: project.namespace)
end end
def namespace def namespace
project.namespace project.shared_runners_limit_namespace
end end
end end
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
%td %td
.avatar-container.s20.hidden-xs .avatar-container.s20.hidden-xs
= project_icon(project, alt: '', class: 'avatar project-avatar s20') = project_icon(project, alt: '', class: 'avatar project-avatar s20')
%strong= link_to project.name, project %strong= link_to project.full_name, project
%td %td
= project.shared_runners_minutes = project.shared_runners_minutes
- if projects.blank? - if projects.blank?
......
- if project.namespace.shared_runners_minutes_used? - if project.shared_runners_limit_namespace.shared_runners_minutes_used?
- quota_used = project.namespace.shared_runners_minutes - quota_used = project.shared_runners_limit_namespace.shared_runners_minutes
- quota_limit = project.namespace.actual_shared_runners_minutes_limit - quota_limit = project.shared_runners_limit_namespace.actual_shared_runners_minutes_limit
.bs-callout.bs-callout-warning .bs-callout.bs-callout-warning
%p %p
You have used all your shared Runners pipeline minutes. You have used all your shared Runners pipeline minutes.
......
- project = local_assigns.fetch(:project, nil) - project = local_assigns.fetch(:project, nil)
- namespace = local_assigns.fetch(:namespace, project && project.namespace) - namespace = local_assigns.fetch(:namespace, project && project.shared_runners_limit_namespace)
- scope = (project || namespace).full_path - scope = (project || namespace).full_path
- has_limit = (project || namespace).shared_runners_minutes_limit_enabled? - has_limit = (project || namespace).shared_runners_minutes_limit_enabled?
- can_see_status = project.nil? || can?(current_user, :create_pipeline, project) - can_see_status = project.nil? || can?(current_user, :create_pipeline, project)
......
...@@ -341,6 +341,12 @@ module API ...@@ -341,6 +341,12 @@ module API
class ProtectedRefAccess < Grape::Entity class ProtectedRefAccess < Grape::Entity
expose :access_level expose :access_level
## EE-only
expose :user_id
expose :group_id
## EE-only
expose :access_level_description do |protected_ref_access| expose :access_level_description do |protected_ref_access|
protected_ref_access.humanize protected_ref_access.humanize
end end
......
...@@ -1246,11 +1246,19 @@ module Gitlab ...@@ -1246,11 +1246,19 @@ module Gitlab
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/695 # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/695
def git_merged_branch_names(branch_names = []) def git_merged_branch_names(branch_names = [])
root_sha = find_branch(root_ref).target root_sha = find_branch(root_ref).target
<<<<<<< HEAD
git_arguments = git_arguments =
%W[branch --merged #{root_sha} %W[branch --merged #{root_sha}
--format=%(refname:short)\ %(objectname)] + branch_names --format=%(refname:short)\ %(objectname)] + branch_names
=======
git_arguments =
%W[branch --merged #{root_sha}
--format=%(refname:short)\ %(objectname)] + branch_names
>>>>>>> origin/master
lines = run_git(git_arguments).first.lines lines = run_git(git_arguments).first.lines
lines.each_with_object([]) do |line, branches| lines.each_with_object([]) do |line, branches|
......
...@@ -33,6 +33,10 @@ module Gitlab ...@@ -33,6 +33,10 @@ module Gitlab
base_and_ancestors(upto: upto).where.not(id: ancestors_base.select(:id)) base_and_ancestors(upto: upto).where.not(id: ancestors_base.select(:id))
end end
def roots
base_and_ancestors.where(namespaces: { parent_id: nil })
end
# Returns a relation that includes the ancestors_base set of groups # Returns a relation that includes the ancestors_base set of groups
# and all their ancestors (recursively). # and all their ancestors (recursively).
# #
......
...@@ -95,8 +95,56 @@ feature 'Groups > Pipeline Quota' do ...@@ -95,8 +95,56 @@ feature 'Groups > Pipeline Quota' do
end end
page.within('.pipeline-project-metrics') do page.within('.pipeline-project-metrics') do
expect(page).to have_content(project.name) expect(page).to have_content(project.full_name)
expect(page).not_to have_content(other_project.name) expect(page).not_to have_content(other_project.full_name)
end
end
end
context 'with shared_runner_minutes_on_root_namespace disabled' do
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: false)
end
context 'when accessing group with subgroups' do
let(:group) { create(:group, :with_used_build_minutes_limit) }
let!(:subgroup) { create(:group, parent: group) }
let!(:subproject) { create(:project, namespace: subgroup, shared_runners_enabled: true) }
it 'does not show project of subgroup' do
visit_pipeline_quota_page
expect(page).to have_content(project.full_name)
expect(page).not_to have_content(subproject.full_name)
end
end
end
context 'with shared_runner_minutes_on_root_namespace enabled', :nested_groups do
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: true)
end
context 'when accessing subgroup' do
let(:root_ancestor) { create(:group) }
let(:group) { create(:group, parent: root_ancestor) }
it 'does not show subproject' do
visit_pipeline_quota_page
expect(page).to have_http_status(:not_found)
end
end
context 'when accesing root group' do
let!(:subgroup) { create(:group, parent: group) }
let!(:subproject) { create(:project, namespace: subgroup, shared_runners_enabled: true) }
it 'does show projects of subgroup' do
visit_pipeline_quota_page
expect(page).to have_content(project.full_name)
expect(page).to have_content(subproject.full_name)
end end
end end
end end
......
...@@ -63,6 +63,34 @@ describe Namespace do ...@@ -63,6 +63,34 @@ describe Namespace do
end end
end end
end end
describe '#validate_shared_runner_minutes_support' do
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: true)
end
context 'when changing :shared_runners_minutes_limit' do
before do
namespace.shared_runners_minutes_limit = 100
end
context 'when group is subgroup' do
set(:root_ancestor) { create(:group) }
let(:namespace) { create(:namespace, parent: root_ancestor) }
it 'is invalid' do
expect(namespace).not_to be_valid
expect(namespace.errors[:shared_runners_minutes_limit]).to include('is not supported for this namespace')
end
end
context 'when group is root' do
it 'is valid' do
expect(namespace).to be_valid
end
end
end
end
end end
describe '#move_dir' do describe '#move_dir' do
...@@ -295,6 +323,42 @@ describe Namespace do ...@@ -295,6 +323,42 @@ describe Namespace do
end end
end end
describe '#shared_runner_minutes_supported?' do
subject { namespace.shared_runner_minutes_supported? }
context 'when is subgroup' do
before do
namespace.parent = build(:group)
end
context 'when shared_runner_minutes_on_root_namespace is disabled' do
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: false)
end
it 'returns true' do
is_expected.to eq(true)
end
end
context 'when shared_runner_minutes_on_root_namespace is enabled', :nested_groups do
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: true)
end
it 'returns false' do
is_expected.to eq(false)
end
end
end
context 'when is root' do
it 'returns true' do
is_expected.to eq(true)
end
end
end
describe '#shared_runners_minutes_limit_enabled?' do describe '#shared_runners_minutes_limit_enabled?' do
subject { namespace.shared_runners_minutes_limit_enabled? } subject { namespace.shared_runners_minutes_limit_enabled? }
...@@ -315,6 +379,15 @@ describe Namespace do ...@@ -315,6 +379,15 @@ describe Namespace do
end end
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
context 'when is subgroup', :nested_groups do
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: true)
namespace.parent = build(:group)
end
it { is_expected.to be_falsey }
end
end end
end end
...@@ -323,6 +396,49 @@ describe Namespace do ...@@ -323,6 +396,49 @@ describe Namespace do
end end
end end
describe '#shared_runners_enabled?' do
subject { namespace.shared_runners_enabled? }
context 'subgroup with shared runners enabled project' do
let(:subgroup) { create(:group, parent: namespace) }
let!(:subproject) { create(:project, namespace: subgroup, shared_runners_enabled: true) }
context 'when shared_runner_minutes_on_root_namespace is disabled' do
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: false)
end
it "returns false" do
is_expected.to eq(false)
end
end
context 'when shared_runner_minutes_on_root_namespace is enabled', :nested_groups do
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: true)
end
it "returns true" do
is_expected.to eq(true)
end
end
end
context 'group with shared runners enabled project' do
let!(:project) { create(:project, namespace: namespace, shared_runners_enabled: true) }
it "returns true" do
is_expected.to eq(true)
end
end
context 'group without projects' do
it "returns false" do
is_expected.to eq(false)
end
end
end
describe '#shared_runners_minutes_used?' do describe '#shared_runners_minutes_used?' do
subject { namespace.shared_runners_minutes_used? } subject { namespace.shared_runners_minutes_used? }
...@@ -363,19 +479,6 @@ describe Namespace do ...@@ -363,19 +479,6 @@ describe Namespace do
end end
end end
describe '#root_ancestor' do
it 'returns the top most ancestor', :nested_groups do
root_group = create(:group)
nested_group = create(:group, parent: root_group)
deep_nested_group = create(:group, parent: nested_group)
very_deep_nested_group = create(:group, parent: deep_nested_group)
expect(nested_group.root_ancestor).to eq(root_group)
expect(deep_nested_group.root_ancestor).to eq(root_group)
expect(very_deep_nested_group.root_ancestor).to eq(root_group)
end
end
describe '#actual_plan' do describe '#actual_plan' do
context 'when namespace has a plan associated' do context 'when namespace has a plan associated' do
before do before do
......
...@@ -8,9 +8,9 @@ describe Project do ...@@ -8,9 +8,9 @@ describe Project do
it { is_expected.to delegate_method(:shared_runners_seconds).to(:statistics) } it { is_expected.to delegate_method(:shared_runners_seconds).to(:statistics) }
it { is_expected.to delegate_method(:shared_runners_seconds_last_reset).to(:statistics) } it { is_expected.to delegate_method(:shared_runners_seconds_last_reset).to(:statistics) }
it { is_expected.to delegate_method(:actual_shared_runners_minutes_limit).to(:namespace) } it { is_expected.to delegate_method(:actual_shared_runners_minutes_limit).to(:shared_runners_limit_namespace) }
it { is_expected.to delegate_method(:shared_runners_minutes_limit_enabled?).to(:namespace) } it { is_expected.to delegate_method(:shared_runners_minutes_limit_enabled?).to(:shared_runners_limit_namespace) }
it { is_expected.to delegate_method(:shared_runners_minutes_used?).to(:namespace) } it { is_expected.to delegate_method(:shared_runners_minutes_used?).to(:shared_runners_limit_namespace) }
it { is_expected.to have_one(:mirror_data).class_name('ProjectMirrorData') } it { is_expected.to have_one(:mirror_data).class_name('ProjectMirrorData') }
it { is_expected.to have_many(:path_locks) } it { is_expected.to have_many(:path_locks) }
...@@ -532,6 +532,34 @@ describe Project do ...@@ -532,6 +532,34 @@ describe Project do
end end
end end
describe '#shared_runners_limit_namespace' do
set(:root_ancestor) { create(:group) }
set(:group) { create(:group, parent: root_ancestor) }
let(:project) { create(:project, namespace: group) }
subject { project.shared_runners_limit_namespace }
context 'when shared_runner_minutes_on_root_namespace is disabled' do
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: false)
end
it 'returns parent namespace' do
is_expected.to eq(group)
end
end
context 'when shared_runner_minutes_on_root_namespace is enabled', :nested_groups do
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: true)
end
it 'returns root namespace' do
is_expected.to eq(root_ancestor)
end
end
end
describe '#shared_runners_minutes_limit_enabled?' do describe '#shared_runners_minutes_limit_enabled?' do
let(:project) { create(:project) } let(:project) { create(:project) }
......
...@@ -62,6 +62,66 @@ module Ci ...@@ -62,6 +62,66 @@ module Ci
end end
end end
end end
context 'when group is subgroup' do
let!(:root_ancestor) { create(:group) }
let!(:group) { create(:group, parent: root_ancestor) }
let!(:project) { create :project, shared_runners_enabled: true, group: group }
let(:build) { execute(shared_runner) }
context 'when shared_runner_minutes_on_root_namespace is disabled' do
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: false)
end
it "does return a build" do
expect(build).not_to be_nil
end
context 'when we are over limit on subnamespace' do
before do
group.create_namespace_statistics(
shared_runners_seconds: 6001)
end
it "does not return a build" do
expect(build).to be_nil
end
end
end
context 'when shared_runner_minutes_on_root_namespace is enabled', :nested_groups do
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: true)
end
it "does return a build" do
expect(build).not_to be_nil
end
context 'when we are over limit on subnamespace' do
before do
group.create_namespace_statistics(
shared_runners_seconds: 6001)
end
it "limit is ignored and build is returned" do
expect(build).not_to be_nil
end
end
context 'when we are over limit on root namespace' do
before do
root_ancestor.create_namespace_statistics(
shared_runners_seconds: 6001)
end
it "does not return a build" do
expect(build).to be_nil
end
end
end
end
end end
def execute(runner) def execute(runner)
......
require 'spec_helper'
describe 'admin/groups/_form' do
set(:admin) { create(:admin) }
before do
assign(:group, group)
allow(view).to receive(:can?) { true }
allow(view).to receive(:current_user) { admin }
allow(view).to receive(:visibility_level) { group.visibility_level }
end
describe 'when :shared_runner_minutes_on_root_namespace is disabled' do
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: false)
end
context 'when sub group is used' do
let(:root_ancestor) { create(:group) }
let(:group) { build(:group, parent: root_ancestor) }
it 'renders shared_runners_minutes_setting' do
render
expect(rendered).to render_template('namespaces/_shared_runners_minutes_setting')
end
end
end
describe 'when :shared_runner_minutes_on_root_namespace is enabled', :nested_groups do
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: true)
end
context 'when sub group is used' do
let(:root_ancestor) { create(:group) }
let(:group) { build(:group, parent: root_ancestor) }
it 'does not render shared_runners_minutes_setting' do
render
expect(rendered).not_to render_template('namespaces/_shared_runners_minutes_setting')
end
end
context 'when root group is used' do
let(:group) { build(:group) }
it 'does not render shared_runners_minutes_setting' do
render
expect(rendered).to render_template('namespaces/_shared_runners_minutes_setting')
end
end
end
end
<<<<<<< HEAD
=======
>>>>>>> origin/master
import Vue from 'vue'; import Vue from 'vue';
import emptyState from '~/environments/components/empty_state.vue'; import emptyState from '~/environments/components/empty_state.vue';
import mountComponent from '../helpers/vue_mount_component_helper'; import mountComponent from '../helpers/vue_mount_component_helper';
......
...@@ -128,7 +128,10 @@ describe('Environment', () => { ...@@ -128,7 +128,10 @@ describe('Environment', () => {
setTimeout(() => { setTimeout(() => {
spyOn(component, 'updateContent'); spyOn(component, 'updateContent');
component.$el.querySelector('.js-environments-tab-stopped').click(); component.$el.querySelector('.js-environments-tab-stopped').click();
<<<<<<< HEAD:spec/javascripts/environments/environments_app_spec.js
=======
>>>>>>> origin/master:spec/javascripts/environments/environments_app_spec.js
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' }); expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
done(); done();
}); });
......
...@@ -95,7 +95,11 @@ describe('Environments Folder View', () => { ...@@ -95,7 +95,11 @@ describe('Environments Folder View', () => {
it('should render parent folder name', (done) => { it('should render parent folder name', (done) => {
setTimeout(() => { setTimeout(() => {
expect( expect(
<<<<<<< HEAD
component.$el.querySelector('.js-folder-name').textContent.trim(), component.$el.querySelector('.js-folder-name').textContent.trim(),
=======
component.$el.querySelector('.js-folder-name').textContent,
>>>>>>> origin/master
).toContain('Environments / review'); ).toContain('Environments / review');
done(); done();
}, 0); }, 0);
...@@ -107,6 +111,7 @@ describe('Environments Folder View', () => { ...@@ -107,6 +111,7 @@ describe('Environments Folder View', () => {
expect( expect(
component.$el.querySelectorAll('.gl-pagination'), component.$el.querySelectorAll('.gl-pagination'),
).not.toBeNull(); ).not.toBeNull();
<<<<<<< HEAD
done(); done();
}, 0); }, 0);
}); });
...@@ -117,15 +122,33 @@ describe('Environments Folder View', () => { ...@@ -117,15 +122,33 @@ describe('Environments Folder View', () => {
component.$el.querySelector('.gl-pagination .js-last-button a').click(); component.$el.querySelector('.gl-pagination .js-last-button a').click();
expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '10' }); expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '10' });
=======
>>>>>>> origin/master
done(); done();
}, 0); }, 0);
}); });
<<<<<<< HEAD
it('should make an API request when using tabs', (done) => { it('should make an API request when using tabs', (done) => {
setTimeout(() => { setTimeout(() => {
spyOn(component, 'updateContent'); spyOn(component, 'updateContent');
component.$el.querySelector('.js-environments-tab-stopped').click(); component.$el.querySelector('.js-environments-tab-stopped').click();
=======
it('should make an API request when changing page', (done) => {
spyOn(component, 'updateContent');
setTimeout(() => {
component.$el.querySelector('.gl-pagination .js-last-button a').click();
expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '10' });
done();
}, 0);
});
it('should make an API request when using tabs', (done) => {
setTimeout(() => {
spyOn(component, 'updateContent');
component.$el.querySelector('.js-environments-tab-stopped').click();
>>>>>>> origin/master
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' }); expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' });
done(); done();
}); });
......
...@@ -187,6 +187,7 @@ describe('Pipelines', () => { ...@@ -187,6 +187,7 @@ describe('Pipelines', () => {
store: new Store(), store: new Store(),
}); });
component.updateContent({ scope: 'finished', page: '4' }); component.updateContent({ scope: 'finished', page: '4' });
<<<<<<< HEAD
expect(component.page).toEqual('4'); expect(component.page).toEqual('4');
expect(component.scope).toEqual('finished'); expect(component.scope).toEqual('finished');
...@@ -213,6 +214,36 @@ describe('Pipelines', () => { ...@@ -213,6 +214,36 @@ describe('Pipelines', () => {
component = mountComponent(PipelinesComponent, { component = mountComponent(PipelinesComponent, {
store: new Store(), store: new Store(),
}); });
=======
expect(component.page).toEqual('4');
expect(component.scope).toEqual('finished');
expect(component.requestData.scope).toEqual('finished');
expect(component.requestData.page).toEqual('4');
});
});
describe('onChangeTab', () => {
it('should set page to 1', () => {
component = mountComponent(PipelinesComponent, {
store: new Store(),
});
spyOn(component, 'updateContent');
component.onChangeTab('running');
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'running', page: '1' });
});
});
describe('onChangePage', () => {
it('should update page and keep scope', () => {
component = mountComponent(PipelinesComponent, {
store: new Store(),
});
>>>>>>> origin/master
spyOn(component, 'updateContent'); spyOn(component, 'updateContent');
component.onChangePage(4); component.onChangePage(4);
......
...@@ -83,6 +83,20 @@ describe Gitlab::GroupHierarchy, :postgresql do ...@@ -83,6 +83,20 @@ describe Gitlab::GroupHierarchy, :postgresql do
end end
end end
describe '#root' do
it 'includes only the roots' do
relation = described_class.new(Group.where(id: child2)).roots
expect(relation).to contain_exactly(parent)
end
it 'when quering parent it includes parent' do
relation = described_class.new(Group.where(id: parent)).roots
expect(relation).to contain_exactly(parent)
end
end
describe '#all_groups' do describe '#all_groups' do
let(:relation) do let(:relation) do
described_class.new(Group.where(id: child1.id)).all_groups described_class.new(Group.where(id: child1.id)).all_groups
......
...@@ -675,4 +675,17 @@ describe Namespace do ...@@ -675,4 +675,17 @@ describe Namespace do
expect(other_namespace.find_fork_of(project)).to eq(other_fork) expect(other_namespace.find_fork_of(project)).to eq(other_fork)
end end
end end
describe '#root_ancestor' do
it 'returns the top most ancestor', :nested_groups do
root_group = create(:group)
nested_group = create(:group, parent: root_group)
deep_nested_group = create(:group, parent: nested_group)
very_deep_nested_group = create(:group, parent: deep_nested_group)
expect(nested_group.root_ancestor).to eq(root_group)
expect(deep_nested_group.root_ancestor).to eq(root_group)
expect(very_deep_nested_group.root_ancestor).to eq(root_group)
end
end
end end
...@@ -3474,4 +3474,27 @@ describe Project do ...@@ -3474,4 +3474,27 @@ describe Project do
expect(project.wiki_repository_exists?).to eq(false) expect(project.wiki_repository_exists?).to eq(false)
end end
end end
describe '#root_namespace' do
let(:project) { build(:project, namespace: parent) }
subject { project.root_namespace }
context 'when namespace has parent group' do
let(:root_ancestor) { create(:group) }
let(:parent) { build(:group, parent: root_ancestor) }
it 'returns root ancestor' do
is_expected.to eq(root_ancestor)
end
end
context 'when namespace is root ancestor' do
let(:parent) { build(:group) }
it 'returns current namespace' do
is_expected.to eq(parent)
end
end
end
end end
...@@ -66,6 +66,27 @@ describe API::ProtectedBranches do ...@@ -66,6 +66,27 @@ describe API::ProtectedBranches do
let(:message) { '404 Not found' } let(:message) { '404 Not found' }
end end
end end
context 'with per user/group access levels' do
let(:push_user) { create(:user) }
let(:merge_group) { create(:group) }
before do
protected_branch.push_access_levels.create!(user: push_user)
protected_branch.merge_access_levels.create!(group: merge_group)
end
it 'returns access level details' do
get api(route, user)
push_user_ids = json_response['push_access_levels'].map {|level| level['user_id']}
merge_group_ids = json_response['merge_access_levels'].map {|level| level['group_id']}
expect(response).to have_gitlab_http_status(200)
expect(push_user_ids).to include(push_user.id)
expect(merge_group_ids).to include(merge_group.id)
end
end
end end
context 'when authenticated as a master' do context 'when authenticated as a master' do
......
...@@ -42,6 +42,40 @@ describe UpdateBuildMinutesService do ...@@ -42,6 +42,40 @@ describe UpdateBuildMinutesService do
.to eq(100 + build.duration.to_i) .to eq(100 + build.duration.to_i)
end end
end end
context 'when namespace is subgroup' do
let(:root_ancestor) { create(:group, shared_runners_minutes_limit: 100) }
context 'when shared_runner_minutes_on_root_namespace is disabled' do
let(:namespace) { create(:namespace, parent: root_ancestor, shared_runners_minutes_limit: 100) }
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: false)
end
it 'creates a statistics in current namespace' do
subject
expect(namespace.namespace_statistics.reload.shared_runners_seconds)
.to eq(build.duration.to_i)
end
end
context 'when shared_runner_minutes_on_root_namespace is enabled', :nested_groups do
let(:namespace) { create(:namespace, parent: root_ancestor) }
before do
stub_feature_flags(shared_runner_minutes_on_root_namespace: true)
end
it 'creates a statistics in root namespace' do
subject
expect(root_ancestor.namespace_statistics.reload.shared_runners_seconds)
.to eq(build.duration.to_i)
end
end
end
end end
context 'for specific runner' do context 'for specific runner' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment