Commit 28060caa authored by Marin Jankovski's avatar Marin Jankovski

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

parents 7d38df30 6f96ccaa
...@@ -624,7 +624,7 @@ Style/PredicateName: ...@@ -624,7 +624,7 @@ Style/PredicateName:
# branches, and conditions. # branches, and conditions.
Metrics/AbcSize: Metrics/AbcSize:
Enabled: true Enabled: true
Max: 56.96 Max: 55.25
# This cop checks if the length of a block exceeds some maximum value. # This cop checks if the length of a block exceeds some maximum value.
Metrics/BlockLength: Metrics/BlockLength:
...@@ -665,7 +665,7 @@ Metrics/ParameterLists: ...@@ -665,7 +665,7 @@ Metrics/ParameterLists:
# A complexity metric geared towards measuring complexity for a human reader. # A complexity metric geared towards measuring complexity for a human reader.
Metrics/PerceivedComplexity: Metrics/PerceivedComplexity:
Enabled: true Enabled: true
Max: 18 Max: 17
# Lint ######################################################################## # Lint ########################################################################
......
...@@ -330,7 +330,7 @@ GEM ...@@ -330,7 +330,7 @@ GEM
multi_json (~> 1.10) multi_json (~> 1.10)
retriable (~> 1.4) retriable (~> 1.4)
signet (~> 0.6) signet (~> 0.6)
google-protobuf (3.3.0) google-protobuf (3.4.0.2)
googleauth (0.5.1) googleauth (0.5.1)
faraday (~> 0.9) faraday (~> 0.9)
jwt (~> 1.4) jwt (~> 1.4)
......
...@@ -12,3 +12,4 @@ import 'core-js/fn/symbol'; ...@@ -12,3 +12,4 @@ import 'core-js/fn/symbol';
// Browser polyfills // Browser polyfills
import './polyfills/custom_event'; import './polyfills/custom_event';
import './polyfills/element'; import './polyfills/element';
import './polyfills/nodelist';
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = function forEach(callback, thisArg = window) {
for (let i = 0; i < this.length; i += 1) {
callback.call(thisArg, this[i], i, this);
}
};
}
...@@ -74,6 +74,7 @@ import PerformanceBar from './performance_bar'; ...@@ -74,6 +74,7 @@ import PerformanceBar from './performance_bar';
import initNotes from './init_notes'; import initNotes from './init_notes';
import initLegacyFilters from './init_legacy_filters'; import initLegacyFilters from './init_legacy_filters';
import initIssuableSidebar from './init_issuable_sidebar'; import initIssuableSidebar from './init_issuable_sidebar';
import initProjectVisibilitySelector from './project_visibility';
import GpgBadges from './gpg_badges'; import GpgBadges from './gpg_badges';
import UserFeatureHelper from './helpers/user_feature_helper'; import UserFeatureHelper from './helpers/user_feature_helper';
import initChangesDropdown from './init_changes_dropdown'; import initChangesDropdown from './init_changes_dropdown';
...@@ -575,6 +576,7 @@ import initChangesDropdown from './init_changes_dropdown'; ...@@ -575,6 +576,7 @@ import initChangesDropdown from './init_changes_dropdown';
break; break;
case 'new': case 'new':
new ProjectNew(); new ProjectNew();
initProjectVisibilitySelector();
break; break;
case 'show': case 'show':
new Star(); new Star();
......
...@@ -2,19 +2,20 @@ import _ from 'underscore'; ...@@ -2,19 +2,20 @@ import _ from 'underscore';
(() => { (() => {
/* /*
* TODO: Make these methods more configurable (e.g. parseSeconds timePeriodContstraints, * TODO: Make these methods more configurable (e.g. stringifyTime condensed or
* stringifyTime condensed or non-condensed, abbreviateTimelengths) * non-condensed, abbreviateTimelengths)
* */ * */
const utils = window.gl.utils = gl.utils || {}; const utils = window.gl.utils = gl.utils || {};
const prettyTime = utils.prettyTime = { const prettyTime = utils.prettyTime = {
/* /*
* Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # } * Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # }
* Seconds can be negative or positive, zero or non-zero. * Seconds can be negative or positive, zero or non-zero. Can be configured for any day
* or week length.
*/ */
parseSeconds(seconds) { parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) {
const DAYS_PER_WEEK = 5; const DAYS_PER_WEEK = daysPerWeek;
const HOURS_PER_DAY = 8; const HOURS_PER_DAY = hoursPerDay;
const MINUTES_PER_HOUR = 60; const MINUTES_PER_HOUR = 60;
const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR; const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR;
const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR; const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR;
......
...@@ -14,7 +14,14 @@ export default class ProjectSelectComboButton { ...@@ -14,7 +14,14 @@ export default class ProjectSelectComboButton {
bindEvents() { bindEvents() {
this.projectSelectInput.siblings('.new-project-item-select-button') this.projectSelectInput.siblings('.new-project-item-select-button')
.on('click', this.openDropdown); .on('click', e => this.openDropdown(e));
this.newItemBtn.on('click', (e) => {
if (!this.getProjectFromLocalStorage()) {
e.preventDefault();
this.openDropdown(e);
}
});
this.projectSelectInput.on('change', () => this.selectProject()); this.projectSelectInput.on('change', () => this.selectProject());
} }
...@@ -28,8 +35,9 @@ export default class ProjectSelectComboButton { ...@@ -28,8 +35,9 @@ export default class ProjectSelectComboButton {
} }
} }
openDropdown() { // eslint-disable-next-line class-methods-use-this
$(this).siblings('.project-item-select').select2('open'); openDropdown(event) {
$(event.currentTarget).siblings('.project-item-select').select2('open');
} }
selectProject() { selectProject() {
...@@ -56,10 +64,8 @@ export default class ProjectSelectComboButton { ...@@ -56,10 +64,8 @@ export default class ProjectSelectComboButton {
if (project) { if (project) {
this.newItemBtn.attr('href', project.url); this.newItemBtn.attr('href', project.url);
this.newItemBtn.text(`${this.formattedText.defaultTextPrefix} in ${project.name}`); this.newItemBtn.text(`${this.formattedText.defaultTextPrefix} in ${project.name}`);
this.newItemBtn.enable();
} else { } else {
this.newItemBtn.text(`Select project to create ${this.formattedText.presetTextSuffix}`); this.newItemBtn.text(`Select project to create ${this.formattedText.presetTextSuffix}`);
this.newItemBtn.disable();
} }
} }
......
function setVisibilityOptions(namespaceSelector) {
if (!namespaceSelector || !('selectedIndex' in namespaceSelector)) {
return;
}
const selectedNamespace = namespaceSelector.options[namespaceSelector.selectedIndex];
const { name, visibility, visibilityLevel, showPath, editPath } = selectedNamespace.dataset;
document.querySelectorAll('.visibility-level-setting .radio').forEach((option) => {
const optionInput = option.querySelector('input[type=radio]');
const optionValue = optionInput ? optionInput.value : 0;
const optionTitle = option.querySelector('.option-title');
const optionName = optionTitle ? optionTitle.innerText.toLowerCase() : '';
// don't change anything if the option is restricted by admin
if (!option.classList.contains('restricted')) {
if (visibilityLevel < optionValue) {
option.classList.add('disabled');
optionInput.disabled = true;
const reason = option.querySelector('.option-disabled-reason');
if (reason) {
reason.innerHTML =
`This project cannot be ${optionName} because the visibility of
<a href="${showPath}">${name}</a> is ${visibility}. To make this project
${optionName}, you must first <a href="${editPath}">change the visibility</a>
of the parent group.`;
}
} else {
option.classList.remove('disabled');
optionInput.disabled = false;
}
}
});
}
export default function initProjectVisibilitySelector() {
const namespaceSelector = document.querySelector('select.js-select-namespace');
if (namespaceSelector) {
$('.select2.js-select-namespace').on('change', () => setVisibilityOptions(namespaceSelector));
setVisibilityOptions(namespaceSelector);
}
}
<script> <script>
import commitIconSvg from 'icons/_icon_commit.svg'; import commitIconSvg from 'icons/_icon_commit.svg';
import userAvatarLink from './user_avatar/user_avatar_link.vue'; import userAvatarLink from './user_avatar/user_avatar_link.vue';
import tooltip from '../directives/tooltip';
export default { export default {
props: { props: {
...@@ -100,17 +101,22 @@ ...@@ -100,17 +101,22 @@
this.author.username ? `${this.author.username}'s avatar` : null; this.author.username ? `${this.author.username}'s avatar` : null;
}, },
}, },
data() { directives: {
return { commitIconSvg }; tooltip,
}, },
components: { components: {
userAvatarLink, userAvatarLink,
}, },
created() {
this.commitIconSvg = commitIconSvg;
},
}; };
</script> </script>
<template> <template>
<div class="branch-commit"> <div class="branch-commit">
<div v-if="hasCommitRef" class="icon-container hidden-xs"> <div
v-if="hasCommitRef"
class="icon-container hidden-xs">
<i <i
v-if="tag" v-if="tag"
class="fa fa-tag" class="fa fa-tag"
...@@ -126,7 +132,10 @@ ...@@ -126,7 +132,10 @@
<a <a
v-if="hasCommitRef" v-if="hasCommitRef"
class="ref-name hidden-xs" class="ref-name hidden-xs"
:href="commitRef.ref_url"> :href="commitRef.ref_url"
v-tooltip
data-container="body"
:title="commitRef.name">
{{commitRef.name}} {{commitRef.name}}
</a> </a>
...@@ -153,7 +162,8 @@ ...@@ -153,7 +162,8 @@
:img-alt="userImageAltDescription" :img-alt="userImageAltDescription"
:tooltip-text="author.username" :tooltip-text="author.username"
/> />
<a class="commit-row-message" <a
class="commit-row-message"
:href="commitUrl"> :href="commitUrl">
{{title}} {{title}}
</a> </a>
......
...@@ -299,28 +299,6 @@ ...@@ -299,28 +299,6 @@
} }
} }
.project-visibility-level-holder {
.radio {
margin-bottom: 10px;
i {
margin: 2px 0;
font-size: 20px;
}
.option-title {
font-weight: $gl-font-weight-normal;
display: inline-block;
color: $gl-text-color;
}
.option-descr {
margin-left: 29px;
color: $project-option-descr-color;
}
}
}
.save-project-loader { .save-project-loader {
margin-top: 50px; margin-top: 50px;
margin-bottom: 50px; margin-bottom: 50px;
......
...@@ -143,6 +143,47 @@ ...@@ -143,6 +143,47 @@
} }
} }
.visibility-level-setting {
.radio {
margin-bottom: 10px;
i.fa {
margin: 2px 0;
font-size: 20px;
}
.option-title {
font-weight: $gl-font-weight-normal;
display: inline-block;
color: $gl-text-color;
}
.option-description,
.option-disabled-reason {
margin-left: 29px;
color: $project-option-descr-color;
}
.option-disabled-reason {
display: none;
}
&.disabled {
i.fa {
opacity: 0.5;
}
.option-description {
display: none;
}
.option-disabled-reason {
display: block;
}
}
}
}
.prometheus-metrics-monitoring { .prometheus-metrics-monitoring {
.panel { .panel {
.panel-toggle { .panel-toggle {
......
...@@ -117,11 +117,14 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -117,11 +117,14 @@ class Admin::UsersController < Admin::ApplicationController
user_params_with_pass = user_params.dup user_params_with_pass = user_params.dup
if params[:user][:password].present? if params[:user][:password].present?
user_params_with_pass.merge!( password_params = {
password: params[:user][:password], password: params[:user][:password],
password_confirmation: params[:user][:password_confirmation], password_confirmation: params[:user][:password_confirmation]
password_expires_at: Time.now }
)
password_params[:password_expires_at] = Time.now unless changing_own_password?
user_params_with_pass.merge!(password_params)
end end
respond_to do |format| respond_to do |format|
...@@ -167,6 +170,10 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -167,6 +170,10 @@ class Admin::UsersController < Admin::ApplicationController
protected protected
def changing_own_password?
user == current_user
end
def user def user
@user ||= User.find_by!(username: params[:id]) @user ||= User.find_by!(username: params[:id])
end end
......
module RequiresWhitelistedMonitoringClient module RequiresWhitelistedMonitoringClient
extend ActiveSupport::Concern extend ActiveSupport::Concern
include Gitlab::CurrentSettings
included do included do
before_action :validate_ip_whitelisted_or_valid_token! before_action :validate_ip_whitelisted_or_valid_token!
end end
......
...@@ -94,6 +94,6 @@ class Projects::ApplicationController < ApplicationController ...@@ -94,6 +94,6 @@ class Projects::ApplicationController < ApplicationController
end end
def require_pages_enabled! def require_pages_enabled!
not_found unless Gitlab.config.pages.enabled not_found unless @project.pages_available?
end end
end end
...@@ -20,7 +20,10 @@ class ProjectsController < Projects::ApplicationController ...@@ -20,7 +20,10 @@ class ProjectsController < Projects::ApplicationController
end end
def new def new
@project = Project.new namespace = Namespace.find_by(id: params[:namespace_id]) if params[:namespace_id]
return access_denied! if namespace && !can?(current_user, :create_projects, namespace)
@project = Project.new(namespace_id: namespace&.id)
end end
def edit def edit
......
...@@ -202,7 +202,7 @@ module ApplicationHelper ...@@ -202,7 +202,7 @@ module ApplicationHelper
end end
def support_url def support_url
current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/' Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/'
end end
def page_filter_path(options = {}) def page_filter_path(options = {})
......
module ApplicationSettingsHelper module ApplicationSettingsHelper
extend self extend self
include Gitlab::CurrentSettings
delegate :gravatar_enabled?, delegate :gravatar_enabled?,
:signup_enabled?, :signup_enabled?,
:password_authentication_enabled?, :password_authentication_enabled?,
......
module AuthHelper module AuthHelper
include Gitlab::CurrentSettings
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq).freeze PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq).freeze
FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze
......
...@@ -4,7 +4,8 @@ module NamespacesHelper ...@@ -4,7 +4,8 @@ module NamespacesHelper
end end
def namespaces_options(selected = :current_user, display_path: false, extra_group: nil) def namespaces_options(selected = :current_user, display_path: false, extra_group: nil)
groups = current_user.owned_groups + current_user.masters_groups groups = current_user.owned_groups + current_user.masters_groups
users = [current_user.namespace]
unless extra_group.nil? || extra_group.is_a?(Group) unless extra_group.nil? || extra_group.is_a?(Group)
extra_group = Group.find(extra_group) if Namespace.find(extra_group).kind == 'group' extra_group = Group.find(extra_group) if Namespace.find(extra_group).kind == 'group'
...@@ -14,22 +15,9 @@ module NamespacesHelper ...@@ -14,22 +15,9 @@ module NamespacesHelper
groups |= [extra_group] groups |= [extra_group]
end end
users = [current_user.namespace]
data_attr_group = { 'data-options-parent' => 'groups' }
data_attr_users = { 'data-options-parent' => 'users' }
group_opts = [
"Groups", groups.sort_by(&:human_name).map { |g| [display_path ? g.full_path : g.human_name, g.id, data_attr_group] }
]
users_opts = [
"Users", users.sort_by(&:human_name).map { |u| [display_path ? u.path : u.human_name, u.id, data_attr_users] }
]
options = [] options = []
options << group_opts options << options_for_group(groups, display_path: display_path, type: 'group')
options << users_opts options << options_for_group(users, display_path: display_path, type: 'user')
if selected == :current_user && current_user.namespace if selected == :current_user && current_user.namespace
selected = current_user.namespace.id selected = current_user.namespace.id
...@@ -45,4 +33,23 @@ module NamespacesHelper ...@@ -45,4 +33,23 @@ module NamespacesHelper
avatar_icon(namespace.owner.email, size) avatar_icon(namespace.owner.email, size)
end end
end end
private
def options_for_group(namespaces, display_path:, type:)
group_label = type.pluralize
elements = namespaces.sort_by(&:human_name).map! do |n|
[display_path ? n.full_path : n.human_name, n.id,
data: {
options_parent: group_label,
visibility_level: n.visibility_level_value,
visibility: n.visibility,
name: n.name,
show_path: (type == 'group') ? group_path(n) : user_path(n),
edit_path: (type == 'group') ? edit_group_path(n) : nil
}]
end
[group_label.camelize, elements]
end
end end
module ProjectsHelper module ProjectsHelper
include Gitlab::CurrentSettings
def link_to_project(project) def link_to_project(project)
link_to [project.namespace.becomes(Namespace), project], title: h(project.name) do link_to [project.namespace.becomes(Namespace), project], title: h(project.name) do
title = content_tag(:span, project.name, class: 'project-name') title = content_tag(:span, project.name, class: 'project-name')
......
...@@ -63,6 +63,68 @@ module VisibilityLevelHelper ...@@ -63,6 +63,68 @@ module VisibilityLevelHelper
end end
end end
def restricted_visibility_level_description(level)
level_name = Gitlab::VisibilityLevel.level_name(level)
"#{level_name.capitalize} visibility has been restricted by the administrator."
end
def disallowed_visibility_level_description(level, form_model)
case form_model
when Project
disallowed_project_visibility_level_description(level, form_model)
when Group
disallowed_group_visibility_level_description(level, form_model)
end
end
# Note: these messages closely mirror the form validation strings found in the project
# model and any changes or additons to these may also need to be made there.
def disallowed_project_visibility_level_description(level, project)
level_name = Gitlab::VisibilityLevel.level_name(level).downcase
reasons = []
instructions = ''
unless project.visibility_level_allowed_as_fork?(level)
reasons << "the fork source project has lower visibility"
end
unless project.visibility_level_allowed_by_group?(level)
errors = visibility_level_errors_for_group(project.group, level_name)
reasons << errors[:reason]
instructions << errors[:instruction]
end
reasons = reasons.any? ? ' because ' + reasons.to_sentence : ''
"This project cannot be #{level_name}#{reasons}.#{instructions}".html_safe
end
# Note: these messages closely mirror the form validation strings found in the group
# model and any changes or additons to these may also need to be made there.
def disallowed_group_visibility_level_description(level, group)
level_name = Gitlab::VisibilityLevel.level_name(level).downcase
reasons = []
instructions = ''
unless group.visibility_level_allowed_by_projects?(level)
reasons << "it contains projects with higher visibility"
end
unless group.visibility_level_allowed_by_sub_groups?(level)
reasons << "it contains sub-groups with higher visibility"
end
unless group.visibility_level_allowed_by_parent?(level)
errors = visibility_level_errors_for_group(group.parent, level_name)
reasons << errors[:reason]
instructions << errors[:instruction]
end
reasons = reasons.any? ? ' because ' + reasons.to_sentence : ''
"This group cannot be #{level_name}#{reasons}.#{instructions}".html_safe
end
def visibility_icon_description(form_model) def visibility_icon_description(form_model)
case form_model case form_model
when Project when Project
...@@ -95,7 +157,18 @@ module VisibilityLevelHelper ...@@ -95,7 +157,18 @@ module VisibilityLevelHelper
:default_group_visibility, :default_group_visibility,
to: :current_application_settings to: :current_application_settings
def skip_level?(form_model, level) def disallowed_visibility_level?(form_model, level)
form_model.is_a?(Project) && !form_model.visibility_level_allowed?(level) return false unless form_model.respond_to?(:visibility_level_allowed?)
!form_model.visibility_level_allowed?(level)
end
private
def visibility_level_errors_for_group(group, level_name)
group_name = link_to group.name, group_path(group)
change_visiblity = link_to 'change the visibility', edit_group_path(group)
{ reason: "the visibility of #{group_name} is #{group.visibility}",
instruction: " To make this group #{level_name}, you must first #{change_visiblity} of the parent group." }
end end
end end
class BaseMailer < ActionMailer::Base class BaseMailer < ActionMailer::Base
include Gitlab::CurrentSettings
around_action :render_with_default_locale around_action :render_with_default_locale
helper ApplicationHelper helper ApplicationHelper
helper MarkupHelper helper MarkupHelper
attr_accessor :current_user attr_accessor :current_user
helper_method :current_user, :can? helper_method :current_user, :can?, :current_application_settings
default from: proc { default_sender_address.format } default from: proc { default_sender_address.format }
default reply_to: proc { default_reply_to_address.format } default reply_to: proc { default_reply_to_address.format }
......
...@@ -387,7 +387,9 @@ module Ci ...@@ -387,7 +387,9 @@ module Ci
[ [
{ key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
{ key: 'GITLAB_USER_EMAIL', value: user.email, public: true } { key: 'GITLAB_USER_EMAIL', value: user.email, public: true },
{ key: 'GITLAB_USER_LOGIN', value: user.username, public: true },
{ key: 'GITLAB_USER_NAME', value: user.name, public: true }
] ]
end end
......
...@@ -28,7 +28,7 @@ module Spammable ...@@ -28,7 +28,7 @@ module Spammable
def submittable_as_spam? def submittable_as_spam?
if user_agent_detail if user_agent_detail
user_agent_detail.submittable? && current_application_settings.akismet_enabled user_agent_detail.submittable? && Gitlab::CurrentSettings.current_application_settings.akismet_enabled
else else
false false
end end
......
...@@ -26,6 +26,8 @@ class Group < Namespace ...@@ -26,6 +26,8 @@ class Group < Namespace
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :visibility_level_allowed_by_projects validate :visibility_level_allowed_by_projects
validate :visibility_level_allowed_by_sub_groups
validate :visibility_level_allowed_by_parent
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
...@@ -102,15 +104,24 @@ class Group < Namespace ...@@ -102,15 +104,24 @@ class Group < Namespace
full_name full_name
end end
def visibility_level_allowed_by_projects def visibility_level_allowed_by_parent?(level = self.visibility_level)
allowed_by_projects = self.projects.where('visibility_level > ?', self.visibility_level).none? return true unless parent_id && parent_id.nonzero?
unless allowed_by_projects level <= parent.visibility_level
level_name = Gitlab::VisibilityLevel.level_name(visibility_level).downcase end
self.errors.add(:visibility_level, "#{level_name} is not allowed since there are projects with higher visibility.")
end def visibility_level_allowed_by_projects?(level = self.visibility_level)
!projects.where('visibility_level > ?', level).exists?
end
allowed_by_projects def visibility_level_allowed_by_sub_groups?(level = self.visibility_level)
!children.where('visibility_level > ?', level).exists?
end
def visibility_level_allowed?(level = self.visibility_level)
visibility_level_allowed_by_parent?(level) &&
visibility_level_allowed_by_projects?(level) &&
visibility_level_allowed_by_sub_groups?(level)
end end
def avatar_url(**args) def avatar_url(**args)
...@@ -275,11 +286,29 @@ class Group < Namespace ...@@ -275,11 +286,29 @@ class Group < Namespace
list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten
end end
protected private
def update_two_factor_requirement def update_two_factor_requirement
return unless require_two_factor_authentication_changed? || two_factor_grace_period_changed? return unless require_two_factor_authentication_changed? || two_factor_grace_period_changed?
users.find_each(&:update_two_factor_requirement) users.find_each(&:update_two_factor_requirement)
end end
def visibility_level_allowed_by_parent
return if visibility_level_allowed_by_parent?
errors.add(:visibility_level, "#{visibility} is not allowed since the parent group has a #{parent.visibility} visibility.")
end
def visibility_level_allowed_by_projects
return if visibility_level_allowed_by_projects?
errors.add(:visibility_level, "#{visibility} is not allowed since this group contains projects with higher visibility.")
end
def visibility_level_allowed_by_sub_groups
return if visibility_level_allowed_by_sub_groups?
errors.add(:visibility_level, "#{visibility} is not allowed since there are sub-groups with higher visibility.")
end
end end
...@@ -269,7 +269,13 @@ class Issue < ActiveRecord::Base ...@@ -269,7 +269,13 @@ class Issue < ActiveRecord::Base
end end
end end
def update_project_counter_caches?
state_changed? || confidential_changed?
end
def update_project_counter_caches def update_project_counter_caches
return unless update_project_counter_caches?
Projects::OpenIssuesCountService.new(project).refresh_cache Projects::OpenIssuesCountService.new(project).refresh_cache
end end
......
...@@ -942,7 +942,13 @@ class MergeRequest < ActiveRecord::Base ...@@ -942,7 +942,13 @@ class MergeRequest < ActiveRecord::Base
true true
end end
def update_project_counter_caches?
state_changed?
end
def update_project_counter_caches def update_project_counter_caches
return unless update_project_counter_caches?
Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache
end end
......
...@@ -195,6 +195,10 @@ class Namespace < ActiveRecord::Base ...@@ -195,6 +195,10 @@ class Namespace < ActiveRecord::Base
parent.present? parent.present?
end end
def subgroup?
has_parent?
end
def soft_delete_without_removing_associations def soft_delete_without_removing_associations
# We can't use paranoia's `#destroy` since this will hard-delete projects. # We can't use paranoia's `#destroy` since this will hard-delete projects.
# Project uses `pending_delete` instead of the acts_as_paranoia gem. # Project uses `pending_delete` instead of the acts_as_paranoia gem.
......
...@@ -19,6 +19,7 @@ class Project < ActiveRecord::Base ...@@ -19,6 +19,7 @@ class Project < ActiveRecord::Base
include Routable include Routable
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
extend Gitlab::CurrentSettings
BoardLimitExceeded = Class.new(StandardError) BoardLimitExceeded = Class.new(StandardError)
...@@ -1246,6 +1247,10 @@ class Project < ActiveRecord::Base ...@@ -1246,6 +1247,10 @@ class Project < ActiveRecord::Base
File.join(pages_path, 'public') File.join(pages_path, 'public')
end end
def pages_available?
Gitlab.config.pages.enabled && !namespace.subgroup?
end
def remove_private_deploy_keys def remove_private_deploy_keys
exclude_keys_linked_to_other_projects = <<-SQL exclude_keys_linked_to_other_projects = <<-SQL
NOT EXISTS ( NOT EXISTS (
......
...@@ -2,6 +2,8 @@ class ProtectedBranch < ActiveRecord::Base ...@@ -2,6 +2,8 @@ class ProtectedBranch < ActiveRecord::Base
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include ProtectedRef include ProtectedRef
extend Gitlab::CurrentSettings
protected_ref_access_levels :merge, :push protected_ref_access_levels :merge, :push
# Check if branch name is marked as protected in the system # Check if branch name is marked as protected in the system
......
...@@ -1044,7 +1044,7 @@ class Repository ...@@ -1044,7 +1044,7 @@ class Repository
end end
def fetch_remote(remote, forced: false, no_tags: false) def fetch_remote(remote, forced: false, no_tags: false)
gitlab_shell.fetch_remote(repository_storage_path, disk_path, remote, forced: forced, no_tags: no_tags) gitlab_shell.fetch_remote(raw_repository, remote, forced: forced, no_tags: no_tags)
end end
def fetch_ref(source_path, source_ref, target_ref) def fetch_ref(source_path, source_ref, target_ref)
......
...@@ -10,6 +10,8 @@ class Snippet < ActiveRecord::Base ...@@ -10,6 +10,8 @@ class Snippet < ActiveRecord::Base
include Spammable include Spammable
include Editable include Editable
extend Gitlab::CurrentSettings
cache_markdown_field :title, pipeline: :single_line cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :description cache_markdown_field :description
cache_markdown_field :content cache_markdown_field :content
......
...@@ -2,6 +2,7 @@ require 'carrierwave/orm/activerecord' ...@@ -2,6 +2,7 @@ require 'carrierwave/orm/activerecord'
class User < ActiveRecord::Base class User < ActiveRecord::Base
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
extend Gitlab::CurrentSettings
include Gitlab::ConfigHelper include Gitlab::ConfigHelper
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
......
require_dependency 'declarative_policy' require_dependency 'declarative_policy'
class BasePolicy < DeclarativePolicy::Base class BasePolicy < DeclarativePolicy::Base
include Gitlab::CurrentSettings
desc "User is an instance admin" desc "User is an instance admin"
with_options scope: :user, score: 0 with_options scope: :user, score: 0
condition(:admin) { @user&.admin? } condition(:admin) { @user&.admin? }
...@@ -15,6 +13,6 @@ class BasePolicy < DeclarativePolicy::Base ...@@ -15,6 +13,6 @@ class BasePolicy < DeclarativePolicy::Base
desc "The application is restricted from public visibility" desc "The application is restricted from public visibility"
condition(:restricted_public_level, scope: :global) do condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC) Gitlab::CurrentSettings.current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end end
end end
class AkismetService class AkismetService
include Gitlab::CurrentSettings
attr_accessor :owner, :text, :options attr_accessor :owner, :text, :options
def initialize(owner, text, options = {}) def initialize(owner, text, options = {})
......
module Auth module Auth
class ContainerRegistryAuthenticationService < BaseService class ContainerRegistryAuthenticationService < BaseService
include Gitlab::CurrentSettings extend Gitlab::CurrentSettings
AUDIENCE = 'container_registry'.freeze AUDIENCE = 'container_registry'.freeze
......
module Projects module Projects
class UpdatePagesService < BaseService class UpdatePagesService < BaseService
include Gitlab::CurrentSettings
BLOCK_SIZE = 32.kilobytes BLOCK_SIZE = 32.kilobytes
MAX_SIZE = 1.terabyte MAX_SIZE = 1.terabyte
SITE_PATH = 'public/'.freeze SITE_PATH = 'public/'.freeze
......
class UploadService class UploadService
include Gitlab::CurrentSettings
def initialize(model, file, uploader_class = FileUploader) def initialize(model, file, uploader_class = FileUploader)
@model, @file, @uploader_class = model, file, uploader_class @model, @file, @uploader_class = model, file, uploader_class
end end
......
module Users module Users
class BuildService < BaseService class BuildService < BaseService
include Gitlab::CurrentSettings
def initialize(current_user, params = {}) def initialize(current_user, params = {})
@current_user = current_user @current_user = current_user
@params = params.dup @params = params.dup
......
...@@ -7,15 +7,15 @@ ...@@ -7,15 +7,15 @@
= f.label :default_branch_protection, class: 'control-label col-sm-2' = f.label :default_branch_protection, class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control' = f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control'
.form-group.project-visibility-level-holder .form-group.visibility-level-setting
= f.label :default_project_visibility, class: 'control-label col-sm-2' = f.label :default_project_visibility, class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project.new) = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project.new)
.form-group.project-visibility-level-holder .form-group.visibility-level-setting
= f.label :default_snippet_visibility, class: 'control-label col-sm-2' = f.label :default_snippet_visibility, class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new) = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new)
.form-group.project-visibility-level-holder .form-group.visibility-level-setting
= f.label :default_group_visibility, class: 'control-label col-sm-2' = f.label :default_group_visibility, class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= render('shared/visibility_radios', model_method: :default_group_visibility, form: f, selected_level: @application_setting.default_group_visibility, form_model: Group.new) = render('shared/visibility_radios', model_method: :default_group_visibility, form: f, selected_level: @application_setting.default_group_visibility, form_model: Group.new)
......
...@@ -208,7 +208,7 @@ ...@@ -208,7 +208,7 @@
= link_to project_settings_ci_cd_path(@project), title: 'CI / CD' do = link_to project_settings_ci_cd_path(@project), title: 'CI / CD' do
%span %span
CI / CD CI / CD
- if Gitlab.config.pages.enabled - if @project.pages_available?
= nav_link(controller: :pages) do = nav_link(controller: :pages) do
= link_to project_pages_path(@project), title: 'Pages' do = link_to project_pages_path(@project), title: 'Pages' do
%span %span
......
...@@ -112,7 +112,7 @@ ...@@ -112,7 +112,7 @@
%span.light (optional) %span.light (optional)
= f.text_area :description, placeholder: 'Description format', class: "form-control", rows: 3, maxlength: 250 = f.text_area :description, placeholder: 'Description format', class: "form-control", rows: 3, maxlength: 250
.form-group.project-visibility-level-holder .form-group.visibility-level-setting
= f.label :visibility_level, class: 'label-light' do = f.label :visibility_level, class: 'label-light' do
Visibility Level Visibility Level
= link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' } = link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' }
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
= link_to project_settings_ci_cd_path(@project), title: 'Pipelines' do = link_to project_settings_ci_cd_path(@project), title: 'Pipelines' do
%span %span
Pipelines Pipelines
- if Gitlab.config.pages.enabled - if @project.pages_available?
= nav_link(controller: :pages) do = nav_link(controller: :pages) do
= link_to project_pages_path(@project), title: 'Pages' do = link_to project_pages_path(@project), title: 'Pages' do
%span %span
......
- with_label = local_assigns.fetch(:with_label, true) - with_label = local_assigns.fetch(:with_label, true)
.form-group.project-visibility-level-holder .form-group.visibility-level-setting
- if with_label - if with_label
= f.label :visibility_level, class: 'control-label' do = f.label :visibility_level, class: 'control-label' do
Visibility Level Visibility Level
......
- Gitlab::VisibilityLevel.values.each do |level| - Gitlab::VisibilityLevel.values.each do |level|
- next if skip_level?(form_model, level) - disallowed = disallowed_visibility_level?(form_model, level)
.radio - restricted = restricted_visibility_levels.include?(level)
- restricted = restricted_visibility_levels.include?(level) - disabled = disallowed || restricted
.radio{ class: [('disabled' if disabled), ('restricted' if restricted)] }
= form.label "#{model_method}_#{level}" do = form.label "#{model_method}_#{level}" do
= form.radio_button model_method, level, checked: (selected_level == level), disabled: restricted = form.radio_button model_method, level, checked: (selected_level == level), disabled: disabled
= visibility_level_icon(level) = visibility_level_icon(level)
.option-title .option-title
= visibility_level_label(level) = visibility_level_label(level)
.option-descr .option-description
= visibility_level_description(level, form_model) = visibility_level_description(level, form_model)
- unless restricted_visibility_levels.empty? .option-disabled-reason
%div - if restricted
%span.info = restricted_visibility_level_description(level)
Some visibility level settings have been restricted by the administrator. - elsif disallowed
= disallowed_visibility_level_description(level, form_model)
---
title: Add CI/CD job predefined variables with user name and login
merge_request: 13824
author:
type: added
---
title: Decrease ABC threshold to 55.25
merge_request: 13904
author: Maxim Rydkin
type: other
---
title: Changes the password change workflow for admins.
merge_request: 13901
author:
type: fixed
---
title: Ensure correct visibility level options shown on all Project, Group, and Snippets
forms
merge_request: 13442
author:
type: fixed
---
title: Fix the /projects/:id/repository/files/:file_path/raw endpoint to handle dots in the file_path
merge_request: 13512
author: mahcsig
type: fixed
---
title: Adds tooltip to the branch name and improves performance
merge_request:
author:
type: fixed
---
title: Removes disabled state from dashboard project button
merge_request:
author:
type: fixed
---
title: Upgrade brace-expansion NPM package due to security issue
merge_request: 13665
author: Markus Koller
type: security
---
title: Filter additional secrets from Rails logs
merge_request:
author:
type: security
---
title: Only update the sidebar count caches when needed
merge_request:
author:
type: other
---
title: Remove pages settings when not available
merge_request:
author:
type: changed
...@@ -51,31 +51,24 @@ module Gitlab ...@@ -51,31 +51,24 @@ module Gitlab
# Configure sensitive parameters which will be filtered from the log file. # Configure sensitive parameters which will be filtered from the log file.
# #
# Parameters filtered: # Parameters filtered:
# - Password (:password, :password_confirmation) # - Any parameter ending with `_token`
# - Private tokens # - Any parameter containing `password`
# - Any parameter containing `secret`
# - Two-factor tokens (:otp_attempt) # - Two-factor tokens (:otp_attempt)
# - Repo/Project Import URLs (:import_url) # - Repo/Project Import URLs (:import_url)
# - Build variables (:variables) # - Build variables (:variables)
# - GitLab Pages SSL cert/key info (:certificate, :encrypted_key) # - GitLab Pages SSL cert/key info (:certificate, :encrypted_key)
# - Webhook URLs (:hook) # - Webhook URLs (:hook)
# - GitLab-shell secret token (:secret_token)
# - Sentry DSN (:sentry_dsn) # - Sentry DSN (:sentry_dsn)
# - Deploy keys (:key) # - Deploy keys (:key)
config.filter_parameters += [/_token$/, /password/, /secret/]
config.filter_parameters += %i( config.filter_parameters += %i(
authentication_token
certificate certificate
encrypted_key encrypted_key
hook hook
import_url import_url
incoming_email_token
rss_token
key key
otp_attempt otp_attempt
password
password_confirmation
private_token
runners_token
secret_token
sentry_dsn sentry_dsn
variables variables
) )
......
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
require 'gitlab/current_settings' require 'gitlab/current_settings'
include Gitlab::CurrentSettings
if Rails.env.production? if Rails.env.production?
# allow it to fail: it may do so when create_from_defaults is executed before migrations are actually done # allow it to fail: it may do so when create_from_defaults is executed before migrations are actually done
begin begin
sentry_enabled = current_application_settings.sentry_enabled sentry_enabled = Gitlab::CurrentSettings.current_application_settings.sentry_enabled
rescue rescue
sentry_enabled = false sentry_enabled = false
end end
if sentry_enabled if sentry_enabled
Raven.configure do |config| Raven.configure do |config|
config.dsn = current_application_settings.sentry_dsn config.dsn = Gitlab::CurrentSettings.current_application_settings.sentry_dsn
config.release = Gitlab::REVISION config.release = Gitlab::REVISION
# Sanitize fields based on those sanitized from Rails. # Sanitize fields based on those sanitized from Rails.
......
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
require 'gitlab/current_settings' require 'gitlab/current_settings'
include Gitlab::CurrentSettings
# allow it to fail: it may do so when create_from_defaults is executed before migrations are actually done # allow it to fail: it may do so when create_from_defaults is executed before migrations are actually done
begin begin
Settings.gitlab['session_expire_delay'] = current_application_settings.session_expire_delay || 10080 Settings.gitlab['session_expire_delay'] = Gitlab::CurrentSettings.current_application_settings.session_expire_delay || 10080
rescue rescue
Settings.gitlab['session_expire_delay'] ||= 10080 Settings.gitlab['session_expire_delay'] ||= 10080
end end
......
...@@ -26,6 +26,7 @@ Build, test, and deploy the software you develop with [GitLab CI/CD](../ci/READM ...@@ -26,6 +26,7 @@ Build, test, and deploy the software you develop with [GitLab CI/CD](../ci/READM
| Article title | Category | Publishing date | | Article title | Category | Publishing date |
| :------------ | :------: | --------------: | | :------------ | :------: | --------------: |
| [How to test and deploy Laravel/PHP applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md) | Tutorial | 2017-08-31 |
| [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md) | Tutorial | 2017-08-15 | | [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md) | Tutorial | 2017-08-15 |
| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017-07-13 | | [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017-07-13 |
| [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/) | Concepts | 2017-07-11 | | [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/) | Concepts | 2017-07-11 |
......
# Test and deploy Laravel applications with GitLab CI/CD and Envoy
> **[Article Type](../../development/writing_documentation.md#types-of-technical-articles):** tutorial ||
> **Level:** intermediary ||
> **Author:** [Mehran Rasulian](https://gitlab.com/mehranrasulian) ||
> **Publication date:** 2017-08-31
## Introduction
GitLab features our applications with Continuous Integration, and it is possible to easily deploy the new code changes to the production server whenever we want.
In this tutorial, we'll show you how to initialize a [Laravel](http://laravel.com/) application and setup our [Envoy](https://laravel.com/docs/envoy) tasks, then we'll jump into see how to test and deploy it with [GitLab CI/CD](../../ci/README.md) via [Continuous Delivery](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/).
We assume you have a basic experience with Laravel, Linux servers,
and you know how to use GitLab.
Laravel is a high quality web framework written in PHP.
It has a great community with a [fantastic documentation](https://laravel.com/docs).
Aside from the usual routing, controllers, requests, responses, views, and (blade) templates, out of the box Laravel provides plenty of additional services such as cache, events, localization, authentication and many others.
We will use [Envoy](https://laravel.com/docs/master/envoy) as an SSH task runner based on PHP.
It uses a clean, minimal [Blade syntax](https://laravel.com/docs/blade) to setup tasks that can run on remote servers, such as, cloning your project from the repository, installing the Composer dependencies, and running [Artisan commands](https://laravel.com/docs/artisan).
## Initialize our Laravel app on GitLab
We assume [you have installed a new laravel project](https://laravel.com/docs/installation#installation), so let's start with a unit test, and initialize Git for the project.
### Unit Test
Every new installation of Laravel (currently 5.4) comes with two type of tests, 'Feature' and 'Unit', placed in the tests directory.
Here's a unit test from `test/Unit/ExampleTest.php`:
```php
<?php
namespace Tests\Unit;
...
class ExampleTest extends TestCase
{
public function testBasicTest()
{
$this->assertTrue(true);
}
}
```
This test is as simple as asserting that the given value is true.
Laravel uses `PHPUnit` for tests by default.
If we run `vendor/bin/phpunit` we should see the green output:
```bash
vendor/bin/phpunit
OK (1 test, 1 assertions)
```
This test will be used later for continuously testing our app with GitLab CI/CD.
### Push to GitLab
Since we have our app up and running locally, it's time to push the codebase to our remote repository.
Let's create [a new project](../../gitlab-basics/create-project.md) in GitLab named `laravel-sample`.
After that, follow the command line instructions displayed on the project's homepage to initiate the repository on our machine and push the first commit.
```bash
cd laravel-sample
git init
git remote add origin git@gitlab.example.com:<USERNAME>/laravel-sample.git
git add .
git commit -m 'Initial Commit'
git push -u origin master
```
## Configure the production server
Before we begin setting up Envoy and GitLab CI/CD, let's quickly make sure the production server is ready for deployment.
We have installed LEMP stack which stands for Linux, Nginx, MySQL and PHP on our Ubuntu 16.04.
### Create a new user
Let's now create a new user that will be used to deploy our website and give it
the needed permissions using [Linux ACL](https://serversforhackers.com/video/linux-acls):
```bash
# Create user deployer
sudo adduser deployer
# Give the read-write-execute permissions to deployer user for directory /var/www
sudo setfacl -R -m u:deployer:rwx /var/www
```
If you don't have ACL installed on your Ubuntu server, use this command to install it:
```bash
sudo apt install acl
```
### Add SSH key
Let's suppose we want to deploy our app to the production server from a private repository on GitLab. First, we need to [generate a new SSH key pair **with no passphrase**](../../ssh/README.md) for the deployer user.
After that, we need to copy the private key, which will be used to connect to our server as the deployer user with SSH, to be able to automate our deployment process:
```bash
# As the deployer user on server
#
# Copy the content of public key to authorized_keys
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
# Copy the private key text block
cat ~/.ssh/id_rsa
```
Now, let's add it to your GitLab project as a [secret variable](../../ci/variables/README.md#secret-variables).
Secret variables are user-defined variables and are stored out of `.gitlab-ci.yml`, for security purposes.
They can be added per project by navigating to the project's **Settings** > **CI/CD**.
![secret variables page](img/secret_variables_page.png)
To the field **KEY**, add the name `SSH_PRIVATE_KEY`, and to the **VALUE** field, paste the private key you've copied earlier.
We'll use this variable in the `.gitlab-ci.yml` later, to easily connect to our remote server as the deployer user without entering its password.
We also need to add the public key to **Project** > **Settings** > **Repository** as [Deploy Keys](../../ssh/README.md/#deploy-keys), which gives us the ability to access our repository from the server through [SSH protocol](../../gitlab-basics/command-line-commands.md/#start-working-on-your-project).
```bash
# As the deployer user on the server
#
# Copy the public key
cat ~/.ssh/id_rsa.pub
```
![deploy keys page](img/deploy_keys_page.png)
To the field **Title**, add any name you want, and paste the public key into the **Key** field.
Now, let's clone our repository on the server just to make sure the `deployer` user has access to the repository.
```bash
# As the deployer user on server
#
git clone git@gitlab.example.com:<USERNAME>/laravel-sample.git
```
>**Note:**
Answer **yes** if asked `Are you sure you want to continue connecting (yes/no)?`.
It adds GitLab.com to the known hosts.
### Configuring Nginx
Now, let's make sure our web server configuration points to the `current/public` rather than `public`.
Open the default Nginx server block configuration file by typing:
```bash
sudo nano /etc/nginx/sites-available/default
```
The configuration should be like this.
```
server {
root /var/www/app/current/public;
server_name example.com;
# Rest of the configuration
}
```
>**Note:**
You may replace the app's name in `/var/www/app/current/public` with the folder name of your application.
## Setting up Envoy
So we have our Laravel app ready for production.
The next thing is to use Envoy to perform the deploy.
To use Envoy, we should first install it on our local machine [using the given instructions by Laravel](https://laravel.com/docs/envoy/#introduction).
### How Envoy works
The pros of Envoy is that it doesn't require Blade engine, it just uses Blade syntax to define tasks.
To start, we create an `Envoy.blade.php` in the root of our app with a simple task to test Envoy.
```php
@servers(['web' => 'remote_username@remote_host'])
@task('list', [on => 'web'])
ls -l
@endtask
```
As you may expect, we have an array within `@servers` directive at the top of the file, which contains a key named `web` with a value of the server's address (e.g. `deployer@192.168.1.1`).
Then within our `@task` directive we define the bash commands that should be run on the server when the task is executed.
On the local machine use the `run` command to run Envoy tasks.
```bash
envoy run list
```
It should execute the `list` task we defined earlier, which connects to the server and lists directory contents.
Envoy is not a dependency of Laravel, therefore you can use it for any PHP application.
### Zero downtime deployment
Every time we deploy to the production server, Envoy downloads the latest release of our app from GitLab repository and replace it with preview's release.
Envoy does this without any [downtime](https://en.wikipedia.org/wiki/Downtime),
so we don't have to worry during the deployment while someone might be reviewing the site.
Our deployment plan is to clone the latest release from GitLab repository, install the Composer dependencies and finally, activate the new release.
#### @setup directive
The first step of our deployment process is to define a set of variables within [@setup](https://laravel.com/docs/envoy/#setup) directive.
You may change the `app` to your application's name:
```php
...
@setup
$repository = 'git@gitlab.example.com:<USERNAME>/laravel-sample.git';
$releases_dir = '/var/www/app/releases';
$app_dir = '/var/www/app';
$release = date('YmdHis');
$new_release_dir = $releases_dir .'/'. $release;
@endsetup
...
```
- `$repository` is the address of our repository
- `$releases_dir` directory is where we deploy the app
- `$app_dir` is the actual location of the app that is live on the server
- `$release` contains a date, so every time that we deploy a new release of our app, we get a new folder with the current date as name
- `$new_release_dir` is the full path of the new release which is used just to make the tasks cleaner
#### @story directive
The [@story](https://laravel.com/docs/envoy/#stories) directive allows us define a list of tasks that can be run as a single task.
Here we have three tasks called `clone_repository`, `run_composer`, `update_symlinks`. These variables are usable to making our task's codes more cleaner:
```php
...
@story('deploy')
clone_repository
run_composer
update_symlinks
@endstory
...
```
Let's create these three tasks one by one.
#### Clone the repository
The first task will create the `releases` directory (if it doesn't exist), and then clone the `master` branch of the repository (by default) into the new release directory, given by the `$new_release_dir` variable.
The `releases` directory will hold all our deployments:
```php
...
@task('clone_repository')
echo 'Cloning repository'
[ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
git clone --depth 1 {{ $repository }} {{ $new_release_dir }}
@endtask
...
```
While our project grows, its Git history will be very very long over time.
Since we are creating a directory per release, it might not be necessary to have the history of the project downloaded for each release.
The `--depth 1` option is a great solution which saves systems time and disk space as well.
#### Installing dependencies with Composer
As you may know, this task just navigates to the new release directory and runs Composer to install the application dependencies:
```php
...
@task('run_composer')
echo "Starting deployment ({{ $release }})"
cd {{ $new_release_dir }}
composer install --prefer-dist --no-scripts -q -o
@endtask
...
```
#### Activate new release
Next thing to do after preparing the requirements of our new release, is to remove the storage directory from it and to create two symbolic links to point the application's `storage` directory and `.env` file to the new release.
Then, we need to create another symbolic link to the new release with the name of `current` placed in the app directory.
The `current` symbolic link always points to the latest release of our app:
```php
...
@task('update_symlinks')
echo "Linking storage directory"
rm -rf {{ $new_release_dir }}/storage
ln -nfs {{ $app_dir }}/storage {{ $new_release_dir }}/storage
echo 'Linking .env file'
ln -nfs {{ $app_dir }}/.env {{ $new_release_dir }}/.env
echo 'Linking current release'
ln -nfs {{ $new_release_dir }} {{ $app_dir }}/current
@endtask
```
As you see, we use `-nfs` as an option for `ln` command, which says that the `storage`, `.env` and `current` no longer points to the preview's release and will point them to the new release by force (`f` from `-nfs` means force), which is the case when we are doing multiple deployments.
### Full script
The script is ready, but make sure to change the `deployer@192.168.1.1` to your server and also change `/var/www/app` with the directory you want to deploy your app.
At the end, our `Envoy.blade.php` file will look like this:
```php
@servers(['web' => 'deployer@192.168.1.1'])
@setup
$repository = 'git@gitlab.example.com:<USERNAME>/laravel-sample.git';
$releases_dir = '/var/www/app/releases';
$app_dir = '/var/www/app';
$release = date('YmdHis');
$new_release_dir = $releases_dir .'/'. $release;
@endsetup
@story('deploy')
clone_repository
run_composer
update_symlinks
@endstory
@task('clone_repository')
echo 'Cloning repository'
[ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
git clone --depth 1 {{ $repository }} {{ $new_release_dir }}
@endtask
@task('run_composer')
echo "Starting deployment ({{ $release }})"
cd {{ $new_release_dir }}
composer install --prefer-dist --no-scripts -q -o
@endtask
@task('update_symlinks')
echo "Linking storage directory"
rm -rf {{ $new_release_dir }}/storage
ln -nfs {{ $app_dir }}/storage {{ $new_release_dir }}/storage
echo 'Linking .env file'
ln -nfs {{ $app_dir }}/.env {{ $new_release_dir }}/.env
echo 'Linking current release'
ln -nfs {{ $new_release_dir }} {{ $app_dir }}/current
@endtask
```
One more thing we should do before any deployment is to manually copy our application `storage` folder to the `/var/www/app` directory on the server for the first time.
You might want to create another Envoy task to do that for you.
We also create the `.env` file in the same path to setup our production environment variables for Laravel.
These are persistent data and will be shared to every new release.
Now, we would need to deploy our app by running `envoy run deploy`, but it won't be necessary since GitLab can handle that for us with CI's [environments](../../ci/environments.md), which will be described [later](#setting-up-gitlab-ci-cd) in this tutorial.
Now it's time to commit [Envoy.blade.php](https://gitlab.com/mehranrasulian/laravel-sample/blob/master/Envoy.blade.php) and push it to the `master` branch.
To keep things simple, we commit directly to `master`, without using [feature-branches](../../workflow/gitlab_flow.md/#github-flow-as-a-simpler-alternative) since collaboration is beyond the scope of this tutorial.
In a real world project, teams may use [Issue Tracker](../../user/project/issues/index.md) and [Merge Requests](../../user/project/merge_requests/index.md) to move their code across branches:
```bash
git add Envoy.blade.php
git commit -m 'Add Envoy'
git push origin master
```
## Continuous Integration with GitLab
We have our app ready on GitLab, and we also can deploy it manually.
But let's take a step forward to do it automatically with [Continuous Delivery](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#continuous-delivery) method.
We need to check every commit with a set of automated tests to become aware of issues at the earliest, and then, we can deploy to the target environment if we are happy with the result of the tests.
[GitLab CI/CD](../../ci/README.md) allows us to use [Docker](https://docker.com/) engine to handle the process of testing and deploying our app.
In the case you're not familiar with Docker, refer to [How to Automate Docker Deployments](http://paislee.io/how-to-automate-docker-deployments/).
To be able to build, test, and deploy our app with GitLab CI/CD, we need to prepare our work environment.
To do that, we'll use a Docker image which has the minimum requirements that a Laravel app needs to run.
[There are other ways](../../ci/examples/php.md/#test-php-projects-using-the-docker-executor) to do that as well, but they may lead our builds run slowly, which is not what we want when there are faster options to use.
With Docker images our builds run incredibly faster!
### Create a Container Image
Let's create a [Dockerfile](https://gitlab.com/mehranrasulian/laravel-sample/blob/master/Dockerfile) in the root directory of our app with the following content:
```bash
# Set the base image for subsequent instructions
FROM php:7.1
# Update packages
RUN apt-get update
# Install PHP and composer dependencies
RUN apt-get install -qq git curl libmcrypt-dev libjpeg-dev libpng-dev libfreetype6-dev libbz2-dev
# Clear out the local repository of retrieved package files
RUN apt-get clean
# Install needed extensions
# Here you can install any other extension that you need during the test and deployment process
RUN docker-php-ext-install mcrypt pdo_mysql zip
# Install Composer
RUN curl --silent --show-error https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Install Laravel Envoy
RUN composer global require "laravel/envoy=~1.0"
```
We added the [official PHP 7.1 Docker image](https://hub.docker.com/r/_/php/), which consist of a minimum installation of Debian Jessie with PHP pre-installed, and works perfectly for our use case.
We used `docker-php-ext-install` (provided by the official PHP Docker image) to install the PHP extensions we need.
#### Setting Up GitLab Container Registry
Now that we have our `Dockerfile` let's build and push it to our [GitLab Container Registry](../../user/project/container_registry.md).
> The registry is the place to store and tag images for later use. Developers may want to maintain their own registry for private, company images, or for throw-away images used only in testing. Using GitLab Container Registry means you don't need to set up and administer yet another service or use a public registry.
On your GitLab project repository navigate to the **Registry** tab.
![container registry page empty image](img/container_registry_page_empty_image.png)
You may need to [enable Container Registry](../../user/project/container_registry.md#enable-the-container-registry-for-your-project) to your project to see this tab. You'll find it under your project's **Settings > General > Sharing and permissions**.
![container registry checkbox](img/container_registry_checkbox.png)
To start using Container Registry on our machine, we first need to login to the GitLab registry using our GitLab username and password:
```bash
docker login registry.gitlab.com
```
Then we can build and push our image to GitLab:
```bash
docker build -t registry.gitlab.com/<USERNAME>/laravel-sample .
docker push registry.gitlab.com/<USERNAME>/laravel-sample
```
>**Note:**
To run the above commands, we first need to have [Docker](https://docs.docker.com/engine/installation/) installed on our machine.
Congratulations! You just pushed the first Docker image to the GitLab Registry, and if you refresh the page you should be able to see it:
![container registry page with image](img/container_registry_page_with_image.jpg)
>**Note:**
You can also [use GitLab CI/CD](https://about.gitlab.com/2016/05/23/gitlab-container-registry/#use-with-gitlab-ci) to build and push your Docker images, rather than doing that on your machine.
We'll use this image further down in the `.gitlab-ci.yml` configuration file to handle the process of testing and deploying our app.
Let's commit the `Dockerfile` file.
```bash
git add Dockerfile
git commit -m 'Add Dockerfile'
git push origin master
```
### Setting up GitLab CI/CD
In order to build and test our app with GitLab CI/CD, we need a file called `.gitlab-ci.yml` in our repository's root. It is similar to Circle CI and Travis CI, but built-in GitLab.
Our `.gitlab-ci.yml` file will look like this:
```yaml
image: registry.gitlab.com/<USERNAME>/laravel-sample:latest
services:
- mysql:5.7
variables:
MYSQL_DATABASE: homestead
MYSQL_ROOT_PASSWORD: secret
DB_HOST: mysql
DB_USERNAME: root
stages:
- test
- deploy
unit_test:
stage: test
script:
- composer install
- cp .env.example .env
- php artisan key:generate
- php artisan migrate
- vendor/bin/phpunit
deploy_production:
stage: deploy
script:
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- ssh-add <(echo "$SSH_PRIVATE_KEY")
- mkdir -p ~/.ssh
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
- ~/.composer/vendor/bin/envoy run deploy
environment:
name: production
url: http://192.168.1.1
when: manual
only:
- master
```
That's a lot to take in, isn't it? Let's run through it step by step.
#### Image and Services
[GitLab Runners](../../ci/runners/README.md) run the script defined by `.gitlab-ci.yml`.
The `image` keyword tells the Runners which image to use.
The `services` keyword defines additional images [that are linked to the main image](../../ci/docker/using_docker_images.md/#what-is-a-service).
Here we use the container image we created before as our main image and also use MySQL 5.7 as a service.
```yaml
image: registry.gitlab.com/<USERNAME>/laravel-sample:latest
services:
- mysql:5.7
...
```
>**Note:**
If you wish to test your app with different PHP versions and [database management systems](../../ci/services/README.md), you can define different `image` and `services` keywords for each test job.
#### Variables
GitLab CI/CD allows us to use [environment variables](../../ci/yaml/README.md#variables) in our jobs.
We defined MySQL as our database management system, which comes with a superuser root created by default.
So we should adjust the configuration of MySQL instance by defining `MYSQL_DATABASE` variable as our database name and `MYSQL_ROOT_PASSWORD` variable as the password of `root`.
Find out more about MySQL variables at the [official MySQL Docker Image](https://hub.docker.com/r/_/mysql/).
Also set the variables `DB_HOST` to `mysql` and `DB_USERNAME` to `root`, which are Laravel specific variables.
We define `DB_HOST` as `mysql` instead of `127.0.0.1`, as we use MySQL Docker image as a service which [is linked to the main Docker image](../../ci/docker/using_docker_images.md/#how-services-are-linked-to-the-build).
```yaml
...
variables:
MYSQL_DATABASE: homestead
MYSQL_ROOT_PASSWORD: secret
DB_HOST: mysql
DB_USERNAME: root
...
```
#### Unit Test as the first job
We defined the required shell scripts as an array of the [script](../../ci/yaml/README.md#script) variable to be executed when running `unit_test` job.
These scripts are some Artisan commands to prepare the Laravel, and, at the end of the script, we'll run the tests by `PHPUnit`.
```yaml
...
unit_test:
script:
# Install app dependencies
- composer install
# Setup .env
- cp .env.example .env
# Generate an environment key
- php artisan key:generate
# Run migrations
- php artisan migrate
# Run tests
- vendor/bin/phpunit
...
```
#### Deploy to production
The job `deploy_production` will deploy the app to the production server.
To deploy our app with Envoy, we had to set up the `$SSH_PRIVATE_KEY` variable as an [SSH private key](../../ci/ssh_keys/README.md/#ssh-keys-when-using-the-docker-executor).
If the SSH keys have added successfully, we can run Envoy.
As mentioned before, GitLab supports [Continuous Delivery](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#continuous-delivery) methods as well.
The [environment](../../ci/yaml/README.md#environment) keyword tells GitLab that this job deploys to the `production` environment.
The `url` keyword is used to generate a link to our application on the GitLab Environments page.
The `only` keyword tells GitLab CI that the job should be executed only when the pipeline is building the `master` branch.
Lastly, `when: manual` is used to turn the job from running automatically to a manual action.
```yaml
...
deploy_production:
script:
# Add the private SSH key to the build environment
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- ssh-add <(echo "$SSH_PRIVATE_KEY")
- mkdir -p ~/.ssh
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
# Run Envoy
- ~/.composer/vendor/bin/envoy run deploy
environment:
name: production
url: http://192.168.1.1
when: manual
only:
- master
```
You may also want to add another job for [staging environment](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments), to final test your application before deploying to production.
### Turn on GitLab CI/CD
We have prepared everything we need to test and deploy our app with GitLab CI/CD.
To do that, commit and push `.gitlab-ci.yml` to the `master` branch. It will trigger a pipeline, which you can watch live under your project's **Pipelines**.
![pipelines page](img/pipelines_page.png)
Here we see our **Test** and **Deploy** stages.
The **Test** stage has the `unit_test` build running.
click on it to see the Runner's output.
![pipeline page](img/pipeline_page.png)
After our code passed through the pipeline successfully, we can deploy to our production server by clicking the **play** button on the right side.
![pipelines page deploy button](img/pipelines_page_deploy_button.png)
Once the deploy pipeline passed successfully, navigate to **Pipelines > Environments**.
![environments page](img/environments_page.png)
If something doesn't work as expected, you can roll back to the latest working version of your app.
![environment page](img/environment_page.png)
By clicking on the external link icon specified on the right side, GitLab opens the production website.
Our deployment successfully was done and we can see the application is live.
![laravel welcome page](img/laravel_welcome_page.png)
In the case that you're interested to know how is the application directory structure on the production server after deployment, here are three directories named `current`, `releases` and `storage`.
As you know, the `current` directory is a symbolic link that points to the latest release.
The `.env` file consists of our Laravel environment variables.
![production server app directory](img/production_server_app_directory.png)
If you navigate to the `current` directory, you should see the application's content.
As you see, the `.env` is pointing to the `/var/www/app/.env` file and also `storage` is pointing to the `/var/www/app/storage/` directory.
![production server current directory](img/production_server_current_directory.png)
## Conclusion
We configured GitLab CI to perform automated tests and used the method of [Continuous Delivery](https://continuousdelivery.com/) to deploy to production a Laravel application with Envoy, directly from the codebase.
Envoy also was a great match to help us deploy the application without writing our custom bash script and doing Linux magics.
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
> **Article [Type](../../development/writing_documentation.md#types-of-technical-articles):** tutorial || > **Article [Type](../../development/writing_documentation.md#types-of-technical-articles):** tutorial ||
> **Level:** intermediary || > **Level:** intermediary ||
> **Author:** [Crt Mori](https://gitlab.com/Letme) || > **Author:** [Crt Mori](https://gitlab.com/Letme) ||
> **Publication date:** 2017/08/17 > **Publication date:** 2017-08-17
## Introduction ## Introduction
......
...@@ -112,6 +112,7 @@ Here is an collection of tutorials and guides on setting up your CI pipeline. ...@@ -112,6 +112,7 @@ Here is an collection of tutorials and guides on setting up your CI pipeline.
- [Run PHP Composer & NPM scripts then deploy them to a staging server](examples/deployment/composer-npm-deploy.md) - [Run PHP Composer & NPM scripts then deploy them to a staging server](examples/deployment/composer-npm-deploy.md)
- [Analyze code quality with the Code Climate CLI](examples/code_climate.md) - [Analyze code quality with the Code Climate CLI](examples/code_climate.md)
- **Articles** - **Articles**
- [How to test and deploy Laravel/PHP applications with GitLab CI/CD and Envoy](../articles/laravel_with_gitlab_and_envoy/index.md)
- [How to deploy Maven projects to Artifactory with GitLab CI/CD](../articles/artifactory_and_gitlab/index.md) - [How to deploy Maven projects to Artifactory with GitLab CI/CD](../articles/artifactory_and_gitlab/index.md)
- [Automated Debian packaging](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/) - [Automated Debian packaging](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
- [Spring boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/) - [Spring boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
......
...@@ -42,7 +42,7 @@ It is also good practice to check the server's own public key to make sure you ...@@ -42,7 +42,7 @@ It is also good practice to check the server's own public key to make sure you
are not being targeted by a man-in-the-middle attack. To do this, add another are not being targeted by a man-in-the-middle attack. To do this, add another
variable named `SSH_SERVER_HOSTKEYS`. To find out the hostkeys of your server, run variable named `SSH_SERVER_HOSTKEYS`. To find out the hostkeys of your server, run
the `ssh-keyscan YOUR_SERVER` command from a trusted network (ideally, from the the `ssh-keyscan YOUR_SERVER` command from a trusted network (ideally, from the
server itself), and paste its output into the `SSH_SERVER_HOSTKEY` variable. If server itself), and paste its output into the `SSH_SERVER_HOSTKEYS` variable. If
you need to connect to multiple servers, concatenate all the server public keys you need to connect to multiple servers, concatenate all the server public keys
that you collected into the **Value** of the variable. There must be one key per that you collected into the **Value** of the variable. There must be one key per
line. line.
......
...@@ -78,6 +78,8 @@ future GitLab releases.** ...@@ -78,6 +78,8 @@ future GitLab releases.**
| **GITLAB_CI** | all | all | Mark that job is executed in GitLab CI environment | | **GITLAB_CI** | all | all | Mark that job is executed in GitLab CI environment |
| **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the job | | **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the job |
| **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the job | | **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the job |
| **GITLAB_USER_LOGIN** | 10.0 | all | The login username of the user who started the job |
| **GITLAB_USER_NAME** | 10.0 | all | The real name of the user who started the job |
| **RESTORE_CACHE_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to restore the cache running a job | | **RESTORE_CACHE_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to restore the cache running a job |
## 9.0 Renaming ## 9.0 Renaming
......
# GitLab Helm Chart # GitLab Helm Chart
> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these. > **Note:**
* GitLab is working on a [cloud native set of Charts](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) which will replace these.
> Officially supported cloud providers are Google Container Service and Azure Container Service. * Officially supported cloud providers are Google Container Service and Azure Container Service.
The `gitlab` Helm chart deploys GitLab into your Kubernetes cluster. The `gitlab` Helm chart deploys GitLab into your Kubernetes cluster.
...@@ -22,9 +22,7 @@ This chart includes the following: ...@@ -22,9 +22,7 @@ This chart includes the following:
- [Persistent Volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) provisioner support in the underlying infrastructure - [Persistent Volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) provisioner support in the underlying infrastructure
- The ability to point a DNS entry or URL at your GitLab install - The ability to point a DNS entry or URL at your GitLab install
- The `kubectl` CLI installed locally and authenticated for the cluster - The `kubectl` CLI installed locally and authenticated for the cluster
- The Helm Client installed locally - The [Helm client](https://github.com/kubernetes/helm/blob/master/docs/quickstart.md) installed locally on your machine
- The Helm Server (Tiller) already installed and running in the cluster, by running `helm init`
- The GitLab Helm Repo [added to your Helm Client](index.md#add-the-gitlab-helm-repository)
## Configuring GitLab ## Configuring GitLab
...@@ -428,7 +426,7 @@ ingress: ...@@ -428,7 +426,7 @@ ingress:
## Installing GitLab using the Helm Chart ## Installing GitLab using the Helm Chart
> You may see a temporary error message `SchedulerPredicates failed due to PersistentVolumeClaim is not bound` while storage provisions. Once the storage provisions, the pods will automatically restart. This may take a couple minutes depending on your cloud provider. If the error persists, please review the [prerequisites](#prerequisites) to ensure you have enough RAM, CPU, and storage. > You may see a temporary error message `SchedulerPredicates failed due to PersistentVolumeClaim is not bound` while storage provisions. Once the storage provisions, the pods will automatically restart. This may take a couple minutes depending on your cloud provider. If the error persists, please review the [prerequisites](#prerequisites) to ensure you have enough RAM, CPU, and storage.
Ensure the GitLab repo has been added and re-initialize Helm: Add the GitLab Helm repository and initialize Helm:
```bash ```bash
helm repo add gitlab https://charts.gitlab.io helm repo add gitlab https://charts.gitlab.io
......
# GitLab-Omnibus Helm Chart # GitLab-Omnibus Helm Chart
> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these. > **Note:**
* This Helm chart is in beta, while [additional features](https://gitlab.com/charts/charts.gitlab.io/issues/68) are being worked on.
> Officially supported cloud providers are Google Container Service and Azure Container Service. * GitLab is working on a [cloud native set of Charts](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md) which will eventually replace these.
* Officially supported cloud providers are Google Container Service and Azure Container Service.
This work is based partially on: https://github.com/lwolf/kubernetes-gitlab/. GitLab would like to thank Sergey Nuzhdin for his work. This work is based partially on: https://github.com/lwolf/kubernetes-gitlab/. GitLab would like to thank Sergey Nuzhdin for his work.
...@@ -29,53 +30,51 @@ Terms: ...@@ -29,53 +30,51 @@ Terms:
## Prerequisites ## Prerequisites
- _At least_ 4 GB of RAM available on your cluster, in chunks of 1 GB. 41GB of storage and 2 CPU are also required. - _At least_ 4 GB of RAM available on your cluster. 41GB of storage and 2 CPU are also required.
- Kubernetes 1.4+ with Beta APIs enabled - Kubernetes 1.4+ with Beta APIs enabled
- [Persistent Volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) provisioner support in the underlying infrastructure - [Persistent Volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) provisioner support in the underlying infrastructure
- An [external IP address](#networking-prerequisites)
- A [wildcard DNS entry](#networking-prerequisites), which resolves to the external IP address - A [wildcard DNS entry](#networking-prerequisites), which resolves to the external IP address
- The `kubectl` CLI installed locally and authenticated for the cluster - The `kubectl` CLI installed locally and authenticated for the cluster
- The Helm Client installed locally - The [Helm client](https://github.com/kubernetes/helm/blob/master/docs/quickstart.md) installed locally on your machine
- The Helm Server (Tiller) already installed and running in the cluster, by running `helm init`
- The GitLab Helm Repo [added to your Helm Client](index.md#add-the-gitlab-helm-repository)
### Networking Prerequisites ### Networking Prerequisites
This chart configures a GitLab server and Kubernetes cluster which can support dynamic [Review Apps](https://docs.gitlab.com/ee/ci/review_apps/index.html), as well as services like the integrated [Container Registry](https://docs.gitlab.com/ee/user/project/container_registry.html) and [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/). This chart configures a GitLab server and Kubernetes cluster which can support dynamic [Review Apps](https://docs.gitlab.com/ee/ci/review_apps/index.html), as well as services like the integrated [Container Registry](https://docs.gitlab.com/ee/user/project/container_registry.html) and [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/).
To support the GitLab services and dynamic environments, a wildcard DNS entry is required which resolves to the external Load Balancer IP. To support the GitLab services and dynamic environments, a wildcard DNS entry is required which resolves to the [Load Balancer](#load-balancer-ip) or [External IP](#external-ip). Configuration of the DNS entry will depend upon the DNS service being used.
#### External IP (Recommended)
To provision an external IP on GCP and Azure, simply request a new address from the Networking section. Ensure that the region matches the region your container cluster is created in. Note, it is important that the IP is not assigned at this point in time. It will be automatically assigned once the Helm chart is installed, and assigned to the Load Balancer. To provision an external IP on GCP and Azure, simply request a new address from the Networking section. Ensure that the region matches the region your container cluster is created in. Note, it is important that the IP is not assigned at this point in time. It will be automatically assigned once the Helm chart is installed, and assigned to the Load Balancer.
Now that an external IP address has been allocated, ensure that the wildcard DNS entry you would like to use resolves to this IP. Please consult the documentation for your DNS service for more information on creating DNS records. Now that an external IP address has been allocated, ensure that the wildcard DNS entry you would like to use resolves to this IP. Please consult the documentation for your DNS service for more information on creating DNS records.
Finally, set the `baseIP` setting to this IP address when [deploying GitLab](#configuring-and-installing-gitlab).
#### Load Balancer IP
If you do not specify a `baseIP`, an ephemeral IP will be assigned to the Load Balancer or Ingress. You can retrieve this IP by running the following command *after* deploying GitLab:
`kubectl get svc -w --namespace nginx-ingress nginx`
The IP address will be displayed in the `EXTERNAL-IP` field, and should be used to configure the Wildcard DNS entry. For more information on creating a wildcard DNS entry, consult the documentation for the DNS server you are using.
For production deployments of GitLab, we strongly recommend using an [External IP](#external-ip).
## Configuring and Installing GitLab ## Configuring and Installing GitLab
For most installations, only two parameters are required: For most installations, only two parameters are required:
- `baseIP`: the desired [external IP address](#networking-prerequisites)
- `baseDomain`: the [base domain](#networking-prerequisites) with the wildcard host entry resolving to the `baseIP`. For example, `mycompany.io`. - `baseDomain`: the [base domain](#networking-prerequisites) with the wildcard host entry resolving to the `baseIP`. For example, `mycompany.io`.
- `legoEmail`: Email address to use when requesting new SSL certificates from Let's Encrypt
Other common configuration options: Other common configuration options:
- `baseIP`: the desired [external IP address](#networking-prerequisites)
- `gitlab`: Choose the [desired edition](https://about.gitlab.com/products), either `ee` or `ce`. `ce` is the default. - `gitlab`: Choose the [desired edition](https://about.gitlab.com/products), either `ee` or `ce`. `ce` is the default.
- `gitlabEELicense`: For Enterprise Edition, the [license](https://docs.gitlab.com/ee/user/admin_area/license.html) can be installed directly via the Chart - `gitlabEELicense`: For Enterprise Edition, the [license](https://docs.gitlab.com/ee/user/admin_area/license.html) can be installed directly via the Chart
- `provider`: Optimizes the deployment for a cloud provider. The default is `gke` for GCP, with `acs` also supported for Azure. - `provider`: Optimizes the deployment for a cloud provider. The default is `gke` for GCP, with `acs` also supported for Azure.
- `legoEmail`: Email address to use when requesting new SSL certificates from Let's Encrypt
For additional configuration options, consult the [values.yaml](https://gitlab.com/charts/charts.gitlab.io/blob/master/charts/gitlab-omnibus/values.yaml). For additional configuration options, consult the [values.yaml](https://gitlab.com/charts/charts.gitlab.io/blob/master/charts/gitlab-omnibus/values.yaml).
These settings can either be passed directly on the command line:
```bash
helm install --name gitlab --set baseDomain=gitlab.io,baseIP=1.1.1.1,gitlab=ee,gitlabEELicense=$LICENSE,legoEmail=email@gitlab.com gitlab/gitlab-omnibus
```
or within a YAML file:
```bash
helm install --name gitlab -f values.yaml gitlab/gitlab-omnibus
```
> **Note:**
If you are using a machine type with support for less than 4 attached disks, like an Azure trial, you should disable dedicated storage for [Postgres and Redis](#persistent-storage).
### Choosing a different GitLab release version ### Choosing a different GitLab release version
The version of GitLab installed is based on the `gitlab` setting (see [section](#choosing-gitlab-edition) above), and The version of GitLab installed is based on the `gitlab` setting (see [section](#choosing-gitlab-edition) above), and
...@@ -95,6 +94,8 @@ There is no guarantee that other release versions of GitLab, other than what are ...@@ -95,6 +94,8 @@ There is no guarantee that other release versions of GitLab, other than what are
used by default in the chart, will be supported by a chart install. used by default in the chart, will be supported by a chart install.
### Persistent storage ### Persistent storage
> **Note:**
If you are using a machine type with support for less than 4 attached disks, like an Azure trial, you should disable dedicated storage for [Postgres and Redis](#persistent-storage).
By default, persistent storage is enabled for GitLab and the charts it depends By default, persistent storage is enabled for GitLab and the charts it depends
on (Redis and PostgreSQL). on (Redis and PostgreSQL).
...@@ -124,9 +125,10 @@ Ingress routing and SSL are automatically configured within this Chart. An NGINX ...@@ -124,9 +125,10 @@ Ingress routing and SSL are automatically configured within this Chart. An NGINX
Let's Encrypt limits a single TLD to five certificate requests within a single week. This means that common DNS wildcard services like [xip.io](http://xip.io) and [nip.io](http://nip.io) are unlikely to work. Let's Encrypt limits a single TLD to five certificate requests within a single week. This means that common DNS wildcard services like [xip.io](http://xip.io) and [nip.io](http://nip.io) are unlikely to work.
## Installing GitLab using the Helm Chart ## Installing GitLab using the Helm Chart
> You may see a temporary error message `SchedulerPredicates failed due to PersistentVolumeClaim is not bound` while storage provisions. Once the storage provisions, the pods will automatically restart. This may take a couple minutes depending on your cloud provider. If the error persists, please review the [prerequisites](#prerequisites) to ensure you have enough RAM, CPU, and storage. > **Note:**
You may see a temporary error message `SchedulerPredicates failed due to PersistentVolumeClaim is not bound` while storage provisions. Once the storage provisions, the pods will automatically start. This may take a couple minutes depending on your cloud provider. If the error persists, please review the [prerequisites](#prerequisites) to ensure you have enough RAM, CPU, and storage.
Ensure the GitLab repo has been added and re-initialize Helm: Add the GitLab Helm repository and initialize Helm:
```bash ```bash
helm repo add gitlab https://charts.gitlab.io helm repo add gitlab https://charts.gitlab.io
......
# GitLab Runner Helm Chart # GitLab Runner Helm Chart
> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these. > **Note:**
Officially supported cloud providers are Google Container Service and Azure Container Service.
> Officially supported cloud providers are Google Container Service and Azure Container Service.
The `gitlab-runner` Helm chart deploys a GitLab Runner instance into your The `gitlab-runner` Helm chart deploys a GitLab Runner instance into your
Kubernetes cluster. Kubernetes cluster.
...@@ -17,9 +16,7 @@ This chart configures the Runner to: ...@@ -17,9 +16,7 @@ This chart configures the Runner to:
- Your GitLab Server's API is reachable from the cluster - Your GitLab Server's API is reachable from the cluster
- Kubernetes 1.4+ with Beta APIs enabled - Kubernetes 1.4+ with Beta APIs enabled
- The `kubectl` CLI installed locally and authenticated for the cluster - The `kubectl` CLI installed locally and authenticated for the cluster
- The Helm Client installed locally - The [Helm client](https://github.com/kubernetes/helm/blob/master/docs/quickstart.md) installed locally on your machine
- The Helm Server (Tiller) already installed and running in the cluster, by running `helm init`
- The GitLab Helm Repo added to your Helm Client. See [Adding GitLab Helm Repo](index.md#add-the-gitlab-helm-repository)
## Configuring GitLab Runner using the Helm Chart ## Configuring GitLab Runner using the Helm Chart
...@@ -36,6 +33,8 @@ In order for GitLab Runner to function, your config file **must** specify the fo ...@@ -36,6 +33,8 @@ In order for GitLab Runner to function, your config file **must** specify the fo
- `runnerRegistrationToken` - The Registration Token for adding new Runners to the GitLab Server. This must be - `runnerRegistrationToken` - The Registration Token for adding new Runners to the GitLab Server. This must be
retrieved from your GitLab Instance. See the [GitLab Runner Documentation](../../ci/runners/README.md#creating-and-registering-a-runner) for more information. retrieved from your GitLab Instance. See the [GitLab Runner Documentation](../../ci/runners/README.md#creating-and-registering-a-runner) for more information.
Unless you need to specify additional configuration, you are [ready to install](#installing-gitlab-runner-using-the-helm-chart).
### Other configuration ### Other configuration
The rest of the configuration is [documented in the `values.yaml`](https://gitlab.com/charts/charts.gitlab.io/blob/master/charts/gitlab-runner/values.yaml) in the chart repository. The rest of the configuration is [documented in the `values.yaml`](https://gitlab.com/charts/charts.gitlab.io/blob/master/charts/gitlab-runner/values.yaml) in the chart repository.
...@@ -115,6 +114,17 @@ runners: ...@@ -115,6 +114,17 @@ runners:
``` ```
### Controlling maximum Runner concurrency
A single GitLab Runner deployed on Kubernetes is able to execute multiple jobs in parallel by automatically starting additional Runner pods. The [`concurrent` setting](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section) controls the maximum number of pods allowed at a single time, and defaults to `10`.
```yaml
## Configure the maximum number of concurrent jobs
## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
##
concurrent: 10
```
### Running Docker-in-Docker containers with GitLab Runners ### Running Docker-in-Docker containers with GitLab Runners
See [Running Privileged Containers for the Runners](#running-privileged-containers-for-the-runners) for how to enable it, See [Running Privileged Containers for the Runners](#running-privileged-containers-for-the-runners) for how to enable it,
...@@ -190,7 +200,7 @@ certsSecretName: <SECRET NAME> ...@@ -190,7 +200,7 @@ certsSecretName: <SECRET NAME>
## Installing GitLab Runner using the Helm Chart ## Installing GitLab Runner using the Helm Chart
Ensure the GitLab repo has been added and re-initialize Helm: Add the GitLab Helm repository and initialize Helm:
```bash ```bash
helm repo add gitlab https://charts.gitlab.io helm repo add gitlab https://charts.gitlab.io
......
# Installing GitLab on Kubernetes # Installing GitLab on Kubernetes
> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these.
> Officially supported cloud providers are Google Container Service and Azure Container Service. > Officially supported cloud providers are Google Container Service and Azure Container Service.
The easiest method to deploy GitLab in [Kubernetes](https://kubernetes.io/) is The easiest method to deploy GitLab in [Kubernetes](https://kubernetes.io/) is
to take advantage of the official GitLab Helm charts. [Helm] is a package to take advantage of GitLab's Helm charts. [Helm] is a package
management tool for Kubernetes, allowing apps to be easily managed via their management tool for Kubernetes, allowing apps to be easily managed via their
Charts. A [Chart] is a detailed description of the application including how it Charts. A [Chart] is a detailed description of the application including how it
should be deployed, upgraded, and configured. should be deployed, upgraded, and configured.
The GitLab Helm repository is located at https://charts.gitlab.io. GitLab provides [official Helm Charts](#official-gitlab-helm-charts-recommended) which is the recommended way to run GitLab with Kubernetes.
You can report any issues related to GitLab's Helm Charts at
There are also two other sets of charts:
* Our [upcoming cloud native Charts](#upcoming-cloud-native-helm-charts), which are in development but will eventually replace the current official charts.
* [Community contributed charts](#community-contributed-helm-charts). These charts should be considered deprecated, in favor of the official charts.
## Official GitLab Helm Charts (Recommended)
These charts utilize our [GitLab Omnibus Docker images](https://docs.gitlab.com/omnibus/docker/README.html). You can report any issues and feedback related to these charts at
https://gitlab.com/charts/charts.gitlab.io/issues. https://gitlab.com/charts/charts.gitlab.io/issues.
Contributions and improvements are also very welcome.
## Prerequisites ### Deploying GitLab on Kubernetes (Recommended)
> *Note*: This chart will eventually be replaced by the [cloud native charts](#upcoming-cloud-native-helm-charts), which are presently in development.
The best way to deploy GitLab on Kubernetes is to use the [gitlab-omnibus](gitlab_omnibus.md) chart. It includes everything needed to run GitLab, including: a [Runner](https://docs.gitlab.com/runner/), [Container Registry](https://docs.gitlab.com/ee/user/project/container_registry.html#gitlab-container-registry), [automatic SSL](https://github.com/kubernetes/charts/tree/master/stable/kube-lego), and an [Ingress](https://github.com/kubernetes/ingress/tree/master/controllers/nginx). This chart is in beta while [additional features](https://gitlab.com/charts/charts.gitlab.io/issues/68) are being completed.
To use the charts, the Helm tool must be installed and initialized. The best ### Deploying just the GitLab Runner
place to start is by reviewing the [Helm Quick Start Guide][helm-quick].
## Add the GitLab Helm repository To deploy just the GitLab Runner, utilize the [gitlab-runner](gitlab_runner_chart.md) chart. It offers a quick way to configure and deploy the Runner on Kubernetes, regardless of where your GitLab server may be running.
Once Helm has been installed, the GitLab chart repository must be added: ### Advanced deployment of GitLab (Not recommended)
> *Note*: This chart will eventually be replaced by the [cloud native charts](#upcoming-cloud-native-helm-charts), which are presently in development.
```bash If advanced configuration of GitLab is required, the beta [gitlab](gitlab_chart.md) chart can be used which deploys the GitLab service along with optional Postgres and Redis. It offers extensive configuration, but requires deep knowledge of Kubernetes and Helm to use.
helm repo add gitlab https://charts.gitlab.io
```
After adding the repository, Helm must be re-initialized: ## Upcoming Cloud Native Helm Charts
```bash GitLab is working towards a building a [cloud native deployment method](https://gitlab.com/charts/helm.gitlab.io/blob/master/README.md). A key part of this effort is to isolate each service into it's [own Docker container and Helm chart](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420), rather than utilizing the all-in-one container image of the [current charts](#official-gitlab-helm-charts-recommended).
helm init
```
## Using the GitLab Helm Charts By offering individual containers and charts, we will be able to provide a number of benefits:
* Easier horizontal scaling of each service
* Smaller more efficient images
* Potential for rolling updates and canaries within a service
* and plenty more.
GitLab makes available three Helm Charts. This is a large project and will be worked on over the span of multiple releases. For the most up to date status and release information, please see our [tracking issue](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/2420).
- [gitlab-omnibus](gitlab_omnibus.md): **Recommended** and the easiest way to get started. Includes everything needed to run GitLab, including: a [Runner](https://docs.gitlab.com/runner/), [Container Registry](https://docs.gitlab.com/ee/user/project/container_registry.html#gitlab-container-registry), [automatic SSL](https://github.com/kubernetes/charts/tree/master/stable/kube-lego), and an [Ingress](https://github.com/kubernetes/ingress/tree/master/controllers/nginx). ## Community Contributed Helm Charts
- [gitlab](gitlab_chart.md): Just the GitLab service, with optional Postgres and Redis.
- [gitlab-runner](gitlab_runner_chart.md): GitLab Runner, to process CI jobs.
We are also working on a new set of [cloud native Charts](https://gitlab.com/charts/helm.gitlab.io) which will eventually replace these. The community has also [contributed GitLab charts](https://github.com/kubernetes/charts/tree/master/stable/gitlab-ce) to the [Helm Stable Repository](https://github.com/kubernetes/charts#repository-structure). These charts should be considered [deprecated](https://github.com/kubernetes/charts/issues/1138) in favor of the [official Charts](#official-gitlab-helm-charts-recommended).
[chart]: https://github.com/kubernetes/charts [chart]: https://github.com/kubernetes/charts
[helm-quick]: https://github.com/kubernetes/helm/blob/master/docs/quickstart.md
[helm]: https://github.com/kubernetes/helm/blob/master/README.md [helm]: https://github.com/kubernetes/helm/blob/master/README.md
module API module API
class Files < Grape::API class Files < Grape::API
FILE_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(file_path: API::NO_SLASH_URL_PART_REGEX)
# Prevents returning plain/text responses for files with .txt extension # Prevents returning plain/text responses for files with .txt extension
after_validation { content_type "application/json" } after_validation { content_type "application/json" }
...@@ -58,13 +60,13 @@ module API ...@@ -58,13 +60,13 @@ module API
params do params do
requires :id, type: String, desc: 'The project ID' requires :id, type: String, desc: 'The project ID'
end end
resource :projects, requirements: { id: %r{[^/]+} } do resource :projects, requirements: FILE_ENDPOINT_REQUIREMENTS do
desc 'Get raw file contents from the repository' desc 'Get raw file contents from the repository'
params do params do
requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
requires :ref, type: String, desc: 'The name of branch, tag commit' requires :ref, type: String, desc: 'The name of branch, tag commit'
end end
get ":id/repository/files/:file_path/raw" do get ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS do
assign_file_vars! assign_file_vars!
send_git_blob @repo, @blob send_git_blob @repo, @blob
...@@ -75,7 +77,7 @@ module API ...@@ -75,7 +77,7 @@ module API
requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb' requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
requires :ref, type: String, desc: 'The name of branch, tag or commit' requires :ref, type: String, desc: 'The name of branch, tag or commit'
end end
get ":id/repository/files/:file_path", requirements: { file_path: /.+/ } do get ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do
assign_file_vars! assign_file_vars!
{ {
...@@ -95,7 +97,7 @@ module API ...@@ -95,7 +97,7 @@ module API
params do params do
use :extended_file_params use :extended_file_params
end end
post ":id/repository/files/:file_path", requirements: { file_path: /.+/ } do post ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do
authorize! :push_code, user_project authorize! :push_code, user_project
file_params = declared_params(include_missing: false) file_params = declared_params(include_missing: false)
...@@ -113,7 +115,7 @@ module API ...@@ -113,7 +115,7 @@ module API
params do params do
use :extended_file_params use :extended_file_params
end end
put ":id/repository/files/:file_path", requirements: { file_path: /.+/ } do put ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do
authorize! :push_code, user_project authorize! :push_code, user_project
file_params = declared_params(include_missing: false) file_params = declared_params(include_missing: false)
...@@ -137,7 +139,7 @@ module API ...@@ -137,7 +139,7 @@ module API
params do params do
use :simple_file_params use :simple_file_params
end end
delete ":id/repository/files/:file_path", requirements: { file_path: /.+/ } do delete ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do
authorize! :push_code, user_project authorize! :push_code, user_project
file_params = declared_params(include_missing: false) file_params = declared_params(include_missing: false)
......
...@@ -42,6 +42,10 @@ module API ...@@ -42,6 +42,10 @@ module API
::Users::ActivityService.new(actor, 'Git SSH').execute if commands.include?(params[:action]) ::Users::ActivityService.new(actor, 'Git SSH').execute if commands.include?(params[:action])
end end
def merge_request_urls
::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
end
private private
def set_project def set_project
......
module API module API
module Helpers module Helpers
module Runner module Runner
include Gitlab::CurrentSettings
JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'.freeze JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'.freeze
JOB_TOKEN_PARAM = :token JOB_TOKEN_PARAM = :token
UPDATE_RUNNER_EVERY = 10 * 60 UPDATE_RUNNER_EVERY = 10 * 60
......
...@@ -68,7 +68,7 @@ module API ...@@ -68,7 +68,7 @@ module API
end end
get "/merge_request_urls" do get "/merge_request_urls" do
::MergeRequests::GetUrlsService.new(project).execute(params[:changes]) merge_request_urls
end end
# #
...@@ -155,6 +155,21 @@ module API ...@@ -155,6 +155,21 @@ module API
# render_api_error!(e, 500) # render_api_error!(e, 500)
# end # end
end end
post '/post_receive' do
status 200
PostReceive.perform_async(params[:gl_repository], params[:identifier],
params[:changes])
broadcast_message = BroadcastMessage.current&.last&.message
reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease
{
merge_request_urls: merge_request_urls,
broadcast_message: broadcast_message,
reference_counter_decreased: reference_counter_decreased
}
end
end end
end end
end end
# Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails # Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails
class EmailTemplateInterceptor class EmailTemplateInterceptor
include Gitlab::CurrentSettings extend Gitlab::CurrentSettings
def self.delivering_email(message) def self.delivering_email(message)
# Remove HTML part if HTML emails are disabled. # Remove HTML part if HTML emails are disabled.
......
...@@ -226,49 +226,51 @@ module Github ...@@ -226,49 +226,51 @@ module Github
while url while url
response = Github::Client.new(options).get(url, state: :all, sort: :created, direction: :asc) response = Github::Client.new(options).get(url, state: :all, sort: :created, direction: :asc)
response.body.each do |raw| response.body.each { |raw| populate_issue(raw) }
representation = Github::Representation::Issue.new(raw, options)
begin url = response.rels[:next]
# Every pull request is an issue, but not every issue end
# is a pull request. For this reason, "shared" actions end
# for both features, like manipulating assignees, labels
# and milestones, are provided within the Issues API. def populate_issue(raw)
if representation.pull_request? representation = Github::Representation::Issue.new(raw, options)
next unless representation.has_labels?
begin
merge_request = MergeRequest.find_by!(target_project_id: project.id, iid: representation.iid) # Every pull request is an issue, but not every issue
merge_request.update_attribute(:label_ids, label_ids(representation.labels)) # is a pull request. For this reason, "shared" actions
else # for both features, like manipulating assignees, labels
next if Issue.where(iid: representation.iid, project_id: project.id).exists? # and milestones, are provided within the Issues API.
if representation.pull_request?
author_id = user_id(representation.author, project.creator_id) return unless representation.has_labels?
issue = Issue.new
issue.iid = representation.iid merge_request = MergeRequest.find_by!(target_project_id: project.id, iid: representation.iid)
issue.project_id = project.id merge_request.update_attribute(:label_ids, label_ids(representation.labels))
issue.title = representation.title else
issue.description = format_description(representation.description, representation.author) return if Issue.where(iid: representation.iid, project_id: project.id).exists?
issue.state = representation.state
issue.label_ids = label_ids(representation.labels) author_id = user_id(representation.author, project.creator_id)
issue.milestone_id = milestone_id(representation.milestone) issue = Issue.new
issue.author_id = author_id issue.iid = representation.iid
issue.assignee_ids = [user_id(representation.assignee)] issue.project_id = project.id
issue.created_at = representation.created_at issue.title = representation.title
issue.updated_at = representation.updated_at issue.description = format_description(representation.description, representation.author)
issue.save!(validate: false) issue.state = representation.state
issue.label_ids = label_ids(representation.labels)
# Fetch comments issue.milestone_id = milestone_id(representation.milestone)
if representation.has_comments? issue.author_id = author_id
comments_url = "/repos/#{repo}/issues/#{issue.iid}/comments" issue.assignee_ids = [user_id(representation.assignee)]
fetch_comments(issue, :comment, comments_url) issue.created_at = representation.created_at
end issue.updated_at = representation.updated_at
end issue.save!(validate: false)
rescue => e
error(:issue, representation.url, e.message) # Fetch comments
if representation.has_comments?
comments_url = "/repos/#{repo}/issues/#{issue.iid}/comments"
fetch_comments(issue, :comment, comments_url)
end end
end end
rescue => e
url = response.rels[:next] error(:issue, representation.url, e.message)
end end
end end
......
...@@ -6,6 +6,8 @@ module Gitlab ...@@ -6,6 +6,8 @@ module Gitlab
# Parser/renderer for the AsciiDoc format that uses Asciidoctor and filters # Parser/renderer for the AsciiDoc format that uses Asciidoctor and filters
# the resulting HTML through HTML pipeline filters. # the resulting HTML through HTML pipeline filters.
module Asciidoc module Asciidoc
extend Gitlab::CurrentSettings
DEFAULT_ADOC_ATTRS = [ DEFAULT_ADOC_ATTRS = [
'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab', 'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab',
'env-gitlab', 'source-highlighter=html-pipeline', 'icons=font' 'env-gitlab', 'source-highlighter=html-pipeline', 'icons=font'
......
...@@ -19,6 +19,8 @@ module Gitlab ...@@ -19,6 +19,8 @@ module Gitlab
OPTIONAL_SCOPES = (AVAILABLE_SCOPES + OPENID_SCOPES - DEFAULT_SCOPES).freeze OPTIONAL_SCOPES = (AVAILABLE_SCOPES + OPENID_SCOPES - DEFAULT_SCOPES).freeze
class << self class << self
include Gitlab::CurrentSettings
def find_for_git_client(login, password, project:, ip:) def find_for_git_client(login, password, project:, ip:)
raise "Must provide an IP for rate limiting" if ip.nil? raise "Must provide an IP for rate limiting" if ip.nil?
......
module Gitlab module Gitlab
module CurrentSettings module CurrentSettings
extend self
def current_application_settings def current_application_settings
if RequestStore.active? if RequestStore.active?
RequestStore.fetch(:current_application_settings) { ensure_application_settings! } RequestStore.fetch(:current_application_settings) { ensure_application_settings! }
......
...@@ -47,6 +47,9 @@ module Gitlab ...@@ -47,6 +47,9 @@ module Gitlab
# Directory name of repo # Directory name of repo
attr_reader :name attr_reader :name
# Relative path of repo
attr_reader :relative_path
# Rugged repo object # Rugged repo object
attr_reader :rugged attr_reader :rugged
......
...@@ -37,6 +37,22 @@ module Gitlab ...@@ -37,6 +37,22 @@ module Gitlab
request = Gitaly::ApplyGitattributesRequest.new(repository: @gitaly_repo, revision: revision) request = Gitaly::ApplyGitattributesRequest.new(repository: @gitaly_repo, revision: revision)
GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request) GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request)
end end
def fetch_remote(remote, ssh_auth: nil, forced: false, no_tags: false)
request = Gitaly::FetchRemoteRequest.new(repository: @gitaly_repo, remote: remote, force: forced, no_tags: no_tags)
if ssh_auth&.ssh_import?
if ssh_auth.ssh_key_auth? && ssh_auth.ssh_private_key.present?
request.ssh_key = ssh_auth.ssh_private_key
end
if ssh_auth.ssh_known_hosts.present?
request.known_hosts = ssh_auth.ssh_known_hosts
end
end
GitalyClient.call(@storage, :repository_service, :fetch_remote, request)
end
end end
end end
end end
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
module Gitlab module Gitlab
module GonHelper module GonHelper
include WebpackHelper include WebpackHelper
include Gitlab::CurrentSettings
def add_gon_variables def add_gon_variables
gon.api_version = 'v4' gon.api_version = 'v4'
......
module Gitlab module Gitlab
module Metrics module Metrics
module InfluxDb module InfluxDb
extend Gitlab::CurrentSettings include Gitlab::CurrentSettings
extend self extend self
MUTEX = Mutex.new MUTEX = Mutex.new
......
module Gitlab module Gitlab
module PerformanceBar module PerformanceBar
include Gitlab::CurrentSettings extend Gitlab::CurrentSettings
ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids:v2'.freeze ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids:v2'.freeze
EXPIRY_TIME = 5.minutes EXPIRY_TIME = 5.minutes
......
module Gitlab module Gitlab
class PollingInterval class PollingInterval
include Gitlab::CurrentSettings extend Gitlab::CurrentSettings
HEADER_NAME = 'Poll-Interval'.freeze HEADER_NAME = 'Poll-Interval'.freeze
......
module Gitlab module Gitlab
module ProtocolAccess module ProtocolAccess
extend Gitlab::CurrentSettings
def self.allowed?(protocol) def self.allowed?(protocol)
if protocol == 'web' if protocol == 'web'
true true
......
module Gitlab module Gitlab
module Recaptcha module Recaptcha
extend Gitlab::CurrentSettings
def self.load_configurations! def self.load_configurations!
if current_application_settings.recaptcha_enabled if current_application_settings.recaptcha_enabled
::Recaptcha.configure do |config| ::Recaptcha.configure do |config|
......
module Gitlab
class ReferenceCounter
REFERENCE_EXPIRE_TIME = 600
attr_reader :gl_repository, :key
def initialize(gl_repository)
@gl_repository = gl_repository
@key = "git-receive-pack-reference-counter:#{gl_repository}"
end
def value
Gitlab::Redis::SharedState.with { |redis| (redis.get(key) || 0).to_i }
end
def increase
redis_cmd do |redis|
redis.incr(key)
redis.expire(key, REFERENCE_EXPIRE_TIME)
end
end
def decrease
redis_cmd do |redis|
current_value = redis.decr(key)
if current_value < 0
Rails.logger.warn("Reference counter for #{gl_repository} decreased" \
" when its value was less than 1. Reseting the counter.")
redis.del(key)
end
end
end
private
def redis_cmd
Gitlab::Redis::SharedState.with { |redis| yield(redis) }
true
rescue => e
Rails.logger.warn("GitLab: An unexpected error occurred in writing to Redis: #{e}")
false
end
end
end
module Gitlab module Gitlab
module Sentry module Sentry
extend Gitlab::CurrentSettings
def self.enabled? def self.enabled?
Rails.env.production? && current_application_settings.sentry_enabled? Rails.env.production? && current_application_settings.sentry_enabled?
end end
......
...@@ -98,33 +98,24 @@ module Gitlab ...@@ -98,33 +98,24 @@ module Gitlab
# Fetch remote for repository # Fetch remote for repository
# #
# name - project path with namespace # repository - an instance of Git::Repository
# remote - remote name # remote - remote name
# forced - should we use --force flag? # forced - should we use --force flag?
# no_tags - should we use --no-tags flag? # no_tags - should we use --no-tags flag?
# #
# Ex. # Ex.
# fetch_remote("gitlab/gitlab-ci", "upstream") # fetch_remote(my_repo, "upstream")
# #
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387
def fetch_remote(storage, name, remote, ssh_auth: nil, forced: false, no_tags: false) def fetch_remote(repository, remote, ssh_auth: nil, forced: false, no_tags: false)
args = [gitlab_shell_projects_path, 'fetch-remote', storage, "#{name}.git", remote, "#{Gitlab.config.gitlab_shell.git_timeout}"] gitaly_migrate(:fetch_remote) do |is_enabled|
args << '--force' if forced if is_enabled
args << '--no-tags' if no_tags repository.gitaly_repository_client.fetch_remote(remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags)
else
vars = {} storage_path = Gitlab.config.repositories.storages[repository.storage]["path"]
local_fetch_remote(storage_path, repository.relative_path, remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags)
if ssh_auth&.ssh_import?
if ssh_auth.ssh_key_auth? && ssh_auth.ssh_private_key.present?
vars['GITLAB_SHELL_SSH_KEY'] = ssh_auth.ssh_private_key
end
if ssh_auth.ssh_known_hosts.present?
vars['GITLAB_SHELL_KNOWN_HOSTS'] = ssh_auth.ssh_known_hosts
end end
end end
gitlab_shell_fast_execute_raise_error(args, vars)
end end
# Move repository # Move repository
...@@ -302,6 +293,26 @@ module Gitlab ...@@ -302,6 +293,26 @@ module Gitlab
private private
def local_fetch_remote(storage, name, remote, ssh_auth: nil, forced: false, no_tags: false)
args = [gitlab_shell_projects_path, 'fetch-remote', storage, name, remote, "#{Gitlab.config.gitlab_shell.git_timeout}"]
args << '--force' if forced
args << '--no-tags' if no_tags
vars = {}
if ssh_auth&.ssh_import?
if ssh_auth.ssh_key_auth? && ssh_auth.ssh_private_key.present?
vars['GITLAB_SHELL_SSH_KEY'] = ssh_auth.ssh_private_key
end
if ssh_auth.ssh_known_hosts.present?
vars['GITLAB_SHELL_KNOWN_HOSTS'] = ssh_auth.ssh_known_hosts
end
end
gitlab_shell_fast_execute_raise_error(args, vars)
end
def gitlab_shell_fast_execute(cmd) def gitlab_shell_fast_execute(cmd)
output, status = gitlab_shell_fast_execute_helper(cmd) output, status = gitlab_shell_fast_execute_helper(cmd)
...@@ -325,5 +336,13 @@ module Gitlab ...@@ -325,5 +336,13 @@ module Gitlab
# from wasting I/O by searching through GEM_PATH # from wasting I/O by searching through GEM_PATH
Bundler.with_original_env { Popen.popen(cmd, nil, vars) } Bundler.with_original_env { Popen.popen(cmd, nil, vars) }
end end
def gitaly_migrate(method, &block)
Gitlab::GitalyClient.migrate(method, &block)
rescue GRPC::NotFound, GRPC::BadStatus => e
# Old Popen code returns [Error, output] to the caller, so we
# need to do the same here...
raise Error, e
end
end end
end end
module Gitlab module Gitlab
class UsageData class UsageData
include Gitlab::CurrentSettings
class << self class << self
include Gitlab::CurrentSettings
def data(force_refresh: false) def data(force_refresh: false)
Rails.cache.fetch('usage_data', force: force_refresh, expires_in: 2.weeks) { uncached_data } Rails.cache.fetch('usage_data', force: force_refresh, expires_in: 2.weeks) { uncached_data }
end end
......
...@@ -80,7 +80,7 @@ class GithubImport ...@@ -80,7 +80,7 @@ class GithubImport
end end
def visibility_level def visibility_level
@repo['private'] ? Gitlab::VisibilityLevel::PRIVATE : current_application_settings.default_project_visibility @repo['private'] ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::CurrentSettings.current_application_settings.default_project_visibility
end end
end end
......
...@@ -150,6 +150,18 @@ describe Admin::UsersController do ...@@ -150,6 +150,18 @@ describe Admin::UsersController do
post :update, params post :update, params
end end
context 'when the admin changes his own password' do
it 'updates the password' do
expect { update_password(admin, 'AValidPassword1') }
.to change { admin.reload.encrypted_password }
end
it 'does not set the new password to expire immediately' do
expect { update_password(admin, 'AValidPassword1') }
.not_to change { admin.reload.password_expires_at }
end
end
context 'when the new password is valid' do context 'when the new password is valid' do
it 'redirects to the user' do it 'redirects to the user' do
update_password(user, 'AValidPassword1') update_password(user, 'AValidPassword1')
...@@ -158,15 +170,13 @@ describe Admin::UsersController do ...@@ -158,15 +170,13 @@ describe Admin::UsersController do
end end
it 'updates the password' do it 'updates the password' do
update_password(user, 'AValidPassword1') expect { update_password(user, 'AValidPassword1') }
.to change { user.reload.encrypted_password }
expect { user.reload }.to change { user.encrypted_password }
end end
it 'sets the new password to expire immediately' do it 'sets the new password to expire immediately' do
update_password(user, 'AValidPassword1') expect { update_password(user, 'AValidPassword1') }
.to change { user.reload.password_expires_at }.to be_within(2.seconds).of(Time.now)
expect { user.reload }.to change { user.password_expires_at }.to(a_value <= Time.now)
end end
end end
...@@ -184,9 +194,8 @@ describe Admin::UsersController do ...@@ -184,9 +194,8 @@ describe Admin::UsersController do
end end
it 'does not update the password' do it 'does not update the password' do
update_password(user, 'invalid') expect { update_password(user, 'invalid') }
.not_to change { user.reload.encrypted_password }
expect { user.reload }.not_to change { user.encrypted_password }
end end
end end
...@@ -204,9 +213,8 @@ describe Admin::UsersController do ...@@ -204,9 +213,8 @@ describe Admin::UsersController do
end end
it 'does not update the password' do it 'does not update the password' do
update_password(user, 'AValidPassword1', 'AValidPassword2') expect { update_password(user, 'AValidPassword1', 'AValidPassword2') }
.not_to change { user.reload.encrypted_password }
expect { user.reload }.not_to change { user.encrypted_password }
end end
end end
end end
......
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe Projects::PagesController do describe Projects::PagesController do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :public, :access_requestable) } let(:project) { create(:project, :public) }
let(:request_params) do let(:request_params) do
{ {
...@@ -23,6 +23,17 @@ describe Projects::PagesController do ...@@ -23,6 +23,17 @@ describe Projects::PagesController do
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
context 'when the project is in a subgroup' do
let(:group) { create(:group, :nested) }
let(:project) { create(:project, namespace: group) }
it 'returns a 404 status code' do
get :show, request_params
expect(response).to have_http_status(404)
end
end
end end
describe 'DELETE destroy' do describe 'DELETE destroy' do
......
...@@ -7,6 +7,38 @@ describe ProjectsController do ...@@ -7,6 +7,38 @@ describe ProjectsController do
let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
describe 'GET new' do
context 'with an authenticated user' do
let(:group) { create(:group) }
before do
sign_in(user)
end
context 'when namespace_id param is present' do
context 'when user has access to the namespace' do
it 'renders the template' do
group.add_owner(user)
get :new, namespace_id: group.id
expect(response).to have_http_status(200)
expect(response).to render_template('new')
end
end
context 'when user does not have access to the namespace' do
it 'responds with status 404' do
get :new, namespace_id: group.id
expect(response).to have_http_status(404)
expect(response).not_to render_template('new')
end
end
end
end
end
describe 'GET index' do describe 'GET index' do
context 'as a user' do context 'as a user' do
it 'redirects to root page' do it 'redirects to root page' do
......
require 'spec_helper' require 'spec_helper'
feature 'New project' do feature 'New project' do
include Select2Helper
let(:user) { create(:admin) } let(:user) { create(:admin) }
before do before do
...@@ -68,26 +70,10 @@ feature 'New project' do ...@@ -68,26 +70,10 @@ feature 'New project' do
expect(namespace.text).to eq group.name expect(namespace.text).to eq group.name
end end
context 'on validation error' do
before do
fill_in('project_path', with: 'private-group-project')
choose('Internal')
click_button('Create project')
expect(page).to have_css '.project-edit-errors .alert.alert-danger'
end
it 'selects the group namespace' do
namespace = find('#project_namespace_id option[selected]')
expect(namespace.text).to eq group.name
end
end
end end
context 'with subgroup namespace' do context 'with subgroup namespace' do
let(:group) { create(:group, :private, owner: user) } let(:group) { create(:group, owner: user) }
let(:subgroup) { create(:group, parent: group) } let(:subgroup) { create(:group, parent: group) }
before do before do
...@@ -101,6 +87,41 @@ feature 'New project' do ...@@ -101,6 +87,41 @@ feature 'New project' do
expect(namespace.text).to eq subgroup.full_path expect(namespace.text).to eq subgroup.full_path
end end
end end
context 'when changing namespaces dynamically', :js do
let(:public_group) { create(:group, :public) }
let(:internal_group) { create(:group, :internal) }
let(:private_group) { create(:group, :private) }
before do
public_group.add_owner(user)
internal_group.add_owner(user)
private_group.add_owner(user)
visit new_project_path(namespace_id: public_group.id)
end
it 'enables the correct visibility options' do
select2(user.namespace_id, from: '#project_namespace_id')
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled
select2(public_group.id, from: '#project_namespace_id')
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled
select2(internal_group.id, from: '#project_namespace_id')
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled
select2(private_group.id, from: '#project_namespace_id')
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).to be_disabled
expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled
end
end
end end
context 'Import project options' do context 'Import project options' do
......
...@@ -4,7 +4,7 @@ describe VersionCheckHelper do ...@@ -4,7 +4,7 @@ describe VersionCheckHelper do
describe '#version_status_badge' do describe '#version_status_badge' do
it 'should return nil if not dev environment and not enabled' do it 'should return nil if not dev environment and not enabled' do
allow(Rails.env).to receive(:production?) { false } allow(Rails.env).to receive(:production?) { false }
allow(current_application_settings).to receive(:version_check_enabled) { false } allow(helper.current_application_settings).to receive(:version_check_enabled) { false }
expect(helper.version_status_badge).to be(nil) expect(helper.version_status_badge).to be(nil)
end end
...@@ -12,7 +12,7 @@ describe VersionCheckHelper do ...@@ -12,7 +12,7 @@ describe VersionCheckHelper do
context 'when production and enabled' do context 'when production and enabled' do
before do before do
allow(Rails.env).to receive(:production?) { true } allow(Rails.env).to receive(:production?) { true }
allow(current_application_settings).to receive(:version_check_enabled) { true } allow(helper.current_application_settings).to receive(:version_check_enabled) { true }
allow_any_instance_of(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' } allow_any_instance_of(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' }
@image_tag = helper.version_status_badge @image_tag = helper.version_status_badge
......
...@@ -58,35 +58,82 @@ describe VisibilityLevelHelper do ...@@ -58,35 +58,82 @@ describe VisibilityLevelHelper do
end end
end end
describe "skip_level?" do describe "disallowed_visibility_level?" do
describe "forks" do describe "forks" do
let(:project) { create(:project, :internal) } let(:project) { create(:project, :internal) }
let(:fork_project) { create(:project, forked_from_project: project) } let(:fork_project) { create(:project, forked_from_project: project) }
it "skips levels" do it "disallows levels" do
expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy expect(disallowed_visibility_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
expect(skip_level?(fork_project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey expect(disallowed_visibility_level?(fork_project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey expect(disallowed_visibility_level?(fork_project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
end end
end end
describe "non-forked project" do describe "non-forked project" do
let(:project) { create(:project, :internal) } let(:project) { create(:project, :internal) }
it "skips levels" do it "disallows levels" do
expect(skip_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey expect(disallowed_visibility_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
expect(skip_level?(project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey expect(disallowed_visibility_level?(project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
expect(skip_level?(project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey expect(disallowed_visibility_level?(project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
end end
end end
describe "Snippet" do describe "group" do
let(:group) { create(:group, :internal) }
it "disallows levels" do
expect(disallowed_visibility_level?(group, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
expect(disallowed_visibility_level?(group, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
expect(disallowed_visibility_level?(group, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
end
end
describe "sub-group" do
let(:group) { create(:group, :private) }
let(:subgroup) { create(:group, :private, parent: group) }
it "disallows levels" do
expect(disallowed_visibility_level?(subgroup, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
expect(disallowed_visibility_level?(subgroup, Gitlab::VisibilityLevel::INTERNAL)).to be_truthy
expect(disallowed_visibility_level?(subgroup, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
end
end
describe "snippet" do
let(:snippet) { create(:snippet, :internal) } let(:snippet) { create(:snippet, :internal) }
it "skips levels" do it "disallows levels" do
expect(skip_level?(snippet, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey expect(disallowed_visibility_level?(snippet, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
expect(skip_level?(snippet, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey expect(disallowed_visibility_level?(snippet, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
expect(skip_level?(snippet, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey expect(disallowed_visibility_level?(snippet, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
end
end
end
describe "disallowed_visibility_level_description" do
let(:group) { create(:group, :internal) }
let!(:subgroup) { create(:group, :internal, parent: group) }
let!(:project) { create(:project, :internal, group: group) }
describe "project" do
it "provides correct description for disabled levels" do
expect(disallowed_visibility_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
expect(strip_tags disallowed_visibility_level_description(Gitlab::VisibilityLevel::PUBLIC, project))
.to include "the visibility of #{project.group.name} is internal"
end
end
describe "group" do
it "provides correct description for disabled levels" do
expect(disallowed_visibility_level?(group, Gitlab::VisibilityLevel::PRIVATE)).to be_truthy
expect(disallowed_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, group))
.to include "it contains projects with higher visibility", "it contains sub-groups with higher visibility"
expect(disallowed_visibility_level?(subgroup, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
expect(strip_tags disallowed_visibility_level_description(Gitlab::VisibilityLevel::PUBLIC, subgroup))
.to include "the visibility of #{group.name} is internal"
end end
end end
end end
......
...@@ -76,6 +76,87 @@ import '~/lib/utils/pretty_time'; ...@@ -76,6 +76,87 @@ import '~/lib/utils/pretty_time';
expect(aboveOneWeek.days).toBe(3); expect(aboveOneWeek.days).toBe(3);
expect(aboveOneWeek.weeks).toBe(173); expect(aboveOneWeek.weeks).toBe(173);
}); });
it('should correctly accept a custom param for hoursPerDay', function () {
const parser = prettyTime.parseSeconds;
const config = { hoursPerDay: 24 };
const aboveOneHour = parser(4800, config);
expect(aboveOneHour.minutes).toBe(20);
expect(aboveOneHour.hours).toBe(1);
expect(aboveOneHour.days).toBe(0);
expect(aboveOneHour.weeks).toBe(0);
const aboveOneDay = parser(110000, config);
expect(aboveOneDay.minutes).toBe(33);
expect(aboveOneDay.hours).toBe(6);
expect(aboveOneDay.days).toBe(1);
expect(aboveOneDay.weeks).toBe(0);
const aboveOneWeek = parser(25000000, config);
expect(aboveOneWeek.minutes).toBe(26);
expect(aboveOneWeek.hours).toBe(8);
expect(aboveOneWeek.days).toBe(4);
expect(aboveOneWeek.weeks).toBe(57);
});
it('should correctly accept a custom param for daysPerWeek', function () {
const parser = prettyTime.parseSeconds;
const config = { daysPerWeek: 7 };
const aboveOneHour = parser(4800, config);
expect(aboveOneHour.minutes).toBe(20);
expect(aboveOneHour.hours).toBe(1);
expect(aboveOneHour.days).toBe(0);
expect(aboveOneHour.weeks).toBe(0);
const aboveOneDay = parser(110000, config);
expect(aboveOneDay.minutes).toBe(33);
expect(aboveOneDay.hours).toBe(6);
expect(aboveOneDay.days).toBe(3);
expect(aboveOneDay.weeks).toBe(0);
const aboveOneWeek = parser(25000000, config);
expect(aboveOneWeek.minutes).toBe(26);
expect(aboveOneWeek.hours).toBe(0);
expect(aboveOneWeek.days).toBe(0);
expect(aboveOneWeek.weeks).toBe(124);
});
it('should correctly accept custom params for daysPerWeek and hoursPerDay', function () {
const parser = prettyTime.parseSeconds;
const config = { daysPerWeek: 55, hoursPerDay: 14 };
const aboveOneHour = parser(4800, config);
expect(aboveOneHour.minutes).toBe(20);
expect(aboveOneHour.hours).toBe(1);
expect(aboveOneHour.days).toBe(0);
expect(aboveOneHour.weeks).toBe(0);
const aboveOneDay = parser(110000, config);
expect(aboveOneDay.minutes).toBe(33);
expect(aboveOneDay.hours).toBe(2);
expect(aboveOneDay.days).toBe(2);
expect(aboveOneDay.weeks).toBe(0);
const aboveOneWeek = parser(25000000, config);
expect(aboveOneWeek.minutes).toBe(26);
expect(aboveOneWeek.hours).toBe(0);
expect(aboveOneWeek.days).toBe(1);
expect(aboveOneWeek.weeks).toBe(9);
});
}); });
describe('stringifyTime', function () { describe('stringifyTime', function () {
......
...@@ -32,11 +32,6 @@ describe('Project Select Combo Button', function () { ...@@ -32,11 +32,6 @@ describe('Project Select Combo Button', function () {
this.comboButton = new ProjectSelectComboButton(this.projectSelectInput); this.comboButton = new ProjectSelectComboButton(this.projectSelectInput);
}); });
it('newItemBtn is disabled', function () {
expect(this.newItemBtn.hasAttribute('disabled')).toBe(true);
expect(this.newItemBtn.classList.contains('disabled')).toBe(true);
});
it('newItemBtn href is null', function () { it('newItemBtn href is null', function () {
expect(this.newItemBtn.getAttribute('href')).toBe(''); expect(this.newItemBtn.getAttribute('href')).toBe('');
}); });
...@@ -53,11 +48,6 @@ describe('Project Select Combo Button', function () { ...@@ -53,11 +48,6 @@ describe('Project Select Combo Button', function () {
this.comboButton = new ProjectSelectComboButton(this.projectSelectInput); this.comboButton = new ProjectSelectComboButton(this.projectSelectInput);
}); });
it('newItemBtn is not disabled', function () {
expect(this.newItemBtn.hasAttribute('disabled')).toBe(false);
expect(this.newItemBtn.classList.contains('disabled')).toBe(false);
});
it('newItemBtn href is correctly set', function () { it('newItemBtn href is correctly set', function () {
expect(this.newItemBtn.getAttribute('href')).toBe(this.defaults.projectMeta.url); expect(this.newItemBtn.getAttribute('href')).toBe(this.defaults.projectMeta.url);
}); });
...@@ -82,11 +72,6 @@ describe('Project Select Combo Button', function () { ...@@ -82,11 +72,6 @@ describe('Project Select Combo Button', function () {
.trigger('change'); .trigger('change');
}); });
it('newItemBtn is not disabled', function () {
expect(this.newItemBtn.hasAttribute('disabled')).toBe(false);
expect(this.newItemBtn.classList.contains('disabled')).toBe(false);
});
it('newItemBtn href is correctly set', function () { it('newItemBtn href is correctly set', function () {
expect(this.newItemBtn.getAttribute('href')) expect(this.newItemBtn.getAttribute('href'))
.toBe('http://myothercoolproject.com/issues/new'); .toBe('http://myothercoolproject.com/issues/new');
......
...@@ -41,7 +41,7 @@ describe Gitlab::Auth::UniqueIpsLimiter, :clean_gitlab_redis_shared_state do ...@@ -41,7 +41,7 @@ describe Gitlab::Auth::UniqueIpsLimiter, :clean_gitlab_redis_shared_state do
context 'allow 2 unique ips' do context 'allow 2 unique ips' do
before do before do
current_application_settings.update!(unique_ips_limit_per_user: 2) Gitlab::CurrentSettings.current_application_settings.update!(unique_ips_limit_per_user: 2)
end end
it 'blocks user trying to login from third ip' do it 'blocks user trying to login from third ip' do
......
require 'spec_helper'
describe Gitlab::ReferenceCounter do
let(:redis) { double('redis') }
let(:reference_counter_key) { "git-receive-pack-reference-counter:project-1" }
let(:reference_counter) { described_class.new('project-1') }
before do
allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis)
end
it 'increases and set the expire time of a reference count for a path' do
expect(redis).to receive(:incr).with(reference_counter_key)
expect(redis).to receive(:expire).with(reference_counter_key,
described_class::REFERENCE_EXPIRE_TIME)
expect(reference_counter.increase).to be(true)
end
it 'decreases the reference count for a path' do
allow(redis).to receive(:decr).and_return(0)
expect(redis).to receive(:decr).with(reference_counter_key)
expect(reference_counter.decrease).to be(true)
end
it 'warns if attempting to decrease a counter with a value of one or less, and resets the counter' do
expect(redis).to receive(:decr).and_return(-1)
expect(redis).to receive(:del)
expect(Rails.logger).to receive(:warn).with("Reference counter for project-1" \
" decreased when its value was less than 1. Reseting the counter.")
expect(reference_counter.decrease).to be(true)
end
it 'get the reference count for a path' do
allow(redis).to receive(:get).and_return(1)
expect(reference_counter.value).to be(1)
end
end
...@@ -186,22 +186,48 @@ describe Gitlab::Shell do ...@@ -186,22 +186,48 @@ describe Gitlab::Shell do
end end
end end
describe '#fetch_remote' do shared_examples 'fetch_remote' do |gitaly_on|
let(:project2) { create(:project, :repository) }
let(:repository) { project2.repository }
def fetch_remote(ssh_auth = nil) def fetch_remote(ssh_auth = nil)
gitlab_shell.fetch_remote('current/storage', 'project/path', 'new/storage', ssh_auth: ssh_auth) gitlab_shell.fetch_remote(repository.raw_repository, 'new/storage', ssh_auth: ssh_auth)
end end
def expect_popen(vars = {}) def expect_popen(fail = false, vars = {})
popen_args = [ popen_args = [
projects_path, projects_path,
'fetch-remote', 'fetch-remote',
'current/storage', TestEnv.repos_path,
'project/path.git', repository.relative_path,
'new/storage', 'new/storage',
Gitlab.config.gitlab_shell.git_timeout.to_s Gitlab.config.gitlab_shell.git_timeout.to_s
] ]
expect(Gitlab::Popen).to receive(:popen).with(popen_args, nil, popen_vars.merge(vars)) return_value = fail ? ["error", 1] : [nil, 0]
expect(Gitlab::Popen).to receive(:popen).with(popen_args, nil, popen_vars.merge(vars)).and_return(return_value)
end
def expect_gitaly_call(fail, vars = {})
receive_fetch_remote =
if fail
receive(:fetch_remote).and_raise(GRPC::NotFound)
else
receive(:fetch_remote).and_return(true)
end
expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive_fetch_remote
end
if gitaly_on
def expect_call(fail, vars = {})
expect_gitaly_call(fail, vars)
end
else
def expect_call(fail, vars = {})
expect_popen(fail, vars)
end
end end
def build_ssh_auth(opts = {}) def build_ssh_auth(opts = {})
...@@ -216,20 +242,20 @@ describe Gitlab::Shell do ...@@ -216,20 +242,20 @@ describe Gitlab::Shell do
end end
it 'returns true when the command succeeds' do it 'returns true when the command succeeds' do
expect_popen.and_return([nil, 0]) expect_call(false)
expect(fetch_remote).to be_truthy expect(fetch_remote).to be_truthy
end end
it 'raises an exception when the command fails' do it 'raises an exception when the command fails' do
expect_popen.and_return(["error", 1]) expect_call(true)
expect { fetch_remote }.to raise_error(Gitlab::Shell::Error, "error") expect { fetch_remote }.to raise_error(Gitlab::Shell::Error)
end end
context 'SSH auth' do context 'SSH auth' do
it 'passes the SSH key if specified' do it 'passes the SSH key if specified' do
expect_popen('GITLAB_SHELL_SSH_KEY' => 'foo').and_return([nil, 0]) expect_call(false, 'GITLAB_SHELL_SSH_KEY' => 'foo')
ssh_auth = build_ssh_auth(ssh_key_auth?: true, ssh_private_key: 'foo') ssh_auth = build_ssh_auth(ssh_key_auth?: true, ssh_private_key: 'foo')
...@@ -237,7 +263,7 @@ describe Gitlab::Shell do ...@@ -237,7 +263,7 @@ describe Gitlab::Shell do
end end
it 'does not pass an empty SSH key' do it 'does not pass an empty SSH key' do
expect_popen.and_return([nil, 0]) expect_call(false)
ssh_auth = build_ssh_auth(ssh_key_auth: true, ssh_private_key: '') ssh_auth = build_ssh_auth(ssh_key_auth: true, ssh_private_key: '')
...@@ -245,7 +271,7 @@ describe Gitlab::Shell do ...@@ -245,7 +271,7 @@ describe Gitlab::Shell do
end end
it 'does not pass the key unless SSH key auth is to be used' do it 'does not pass the key unless SSH key auth is to be used' do
expect_popen.and_return([nil, 0]) expect_call(false)
ssh_auth = build_ssh_auth(ssh_key_auth: false, ssh_private_key: 'foo') ssh_auth = build_ssh_auth(ssh_key_auth: false, ssh_private_key: 'foo')
...@@ -253,7 +279,7 @@ describe Gitlab::Shell do ...@@ -253,7 +279,7 @@ describe Gitlab::Shell do
end end
it 'passes the known_hosts data if specified' do it 'passes the known_hosts data if specified' do
expect_popen('GITLAB_SHELL_KNOWN_HOSTS' => 'foo').and_return([nil, 0]) expect_call(false, 'GITLAB_SHELL_KNOWN_HOSTS' => 'foo')
ssh_auth = build_ssh_auth(ssh_known_hosts: 'foo') ssh_auth = build_ssh_auth(ssh_known_hosts: 'foo')
...@@ -261,7 +287,7 @@ describe Gitlab::Shell do ...@@ -261,7 +287,7 @@ describe Gitlab::Shell do
end end
it 'does not pass empty known_hosts data' do it 'does not pass empty known_hosts data' do
expect_popen.and_return([nil, 0]) expect_call(false)
ssh_auth = build_ssh_auth(ssh_known_hosts: '') ssh_auth = build_ssh_auth(ssh_known_hosts: '')
...@@ -269,7 +295,7 @@ describe Gitlab::Shell do ...@@ -269,7 +295,7 @@ describe Gitlab::Shell do
end end
it 'does not pass known_hosts data unless SSH is to be used' do it 'does not pass known_hosts data unless SSH is to be used' do
expect_popen(popen_vars).and_return([nil, 0]) expect_call(false, popen_vars)
ssh_auth = build_ssh_auth(ssh_import?: false, ssh_known_hosts: 'foo') ssh_auth = build_ssh_auth(ssh_import?: false, ssh_known_hosts: 'foo')
...@@ -278,6 +304,14 @@ describe Gitlab::Shell do ...@@ -278,6 +304,14 @@ describe Gitlab::Shell do
end end
end end
describe '#fetch_remote local', skip_gitaly_mock: true do
it_should_behave_like 'fetch_remote', false
end
describe '#fetch_remote gitaly' do
it_should_behave_like 'fetch_remote', true
end
describe '#import_repository' do describe '#import_repository' do
it 'returns true when the command succeeds' do it 'returns true when the command succeeds' do
expect(Gitlab::Popen).to receive(:popen) expect(Gitlab::Popen).to receive(:popen)
......
...@@ -1257,8 +1257,12 @@ describe Ci::Build do ...@@ -1257,8 +1257,12 @@ describe Ci::Build do
context 'when build has user' do context 'when build has user' do
let(:user_variables) do let(:user_variables) do
[{ key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, [
{ key: 'GITLAB_USER_EMAIL', value: user.email, public: true }] { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
{ key: 'GITLAB_USER_EMAIL', value: user.email, public: true },
{ key: 'GITLAB_USER_LOGIN', value: user.username, public: true },
{ key: 'GITLAB_USER_NAME', value: user.name, public: true }
]
end end
before do before do
......
...@@ -84,6 +84,83 @@ describe Group do ...@@ -84,6 +84,83 @@ describe Group do
expect(group).not_to be_valid expect(group).not_to be_valid
end end
end end
describe '#visibility_level_allowed_by_parent' do
let(:parent) { create(:group, :internal) }
let(:sub_group) { build(:group, parent_id: parent.id) }
context 'without a parent' do
it 'is valid' do
sub_group.parent_id = nil
expect(sub_group).to be_valid
end
end
context 'with a parent' do
context 'when visibility of sub group is greater than the parent' do
it 'is invalid' do
sub_group.visibility_level = Gitlab::VisibilityLevel::PUBLIC
expect(sub_group).to be_invalid
end
end
context 'when visibility of sub group is lower or equal to the parent' do
[Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PRIVATE].each do |level|
it 'is valid' do
sub_group.visibility_level = level
expect(sub_group).to be_valid
end
end
end
end
end
describe '#visibility_level_allowed_by_projects' do
let!(:internal_group) { create(:group, :internal) }
let!(:internal_project) { create(:project, :internal, group: internal_group) }
context 'when group has a lower visibility' do
it 'is invalid' do
internal_group.visibility_level = Gitlab::VisibilityLevel::PRIVATE
expect(internal_group).to be_invalid
expect(internal_group.errors[:visibility_level]).to include('private is not allowed since this group contains projects with higher visibility.')
end
end
context 'when group has a higher visibility' do
it 'is valid' do
internal_group.visibility_level = Gitlab::VisibilityLevel::PUBLIC
expect(internal_group).to be_valid
end
end
end
describe '#visibility_level_allowed_by_sub_groups' do
let!(:internal_group) { create(:group, :internal) }
let!(:internal_sub_group) { create(:group, :internal, parent: internal_group) }
context 'when parent group has a lower visibility' do
it 'is invalid' do
internal_group.visibility_level = Gitlab::VisibilityLevel::PRIVATE
expect(internal_group).to be_invalid
expect(internal_group.errors[:visibility_level]).to include('private is not allowed since there are sub-groups with higher visibility.')
end
end
context 'when parent group has a higher visibility' do
it 'is valid' do
internal_group.visibility_level = Gitlab::VisibilityLevel::PUBLIC
expect(internal_group).to be_valid
end
end
end
end end
describe '.visible_to_user' do describe '.visible_to_user' do
......
...@@ -769,4 +769,22 @@ describe Issue do ...@@ -769,4 +769,22 @@ describe Issue do
expect(described_class.public_only).to eq([public_issue]) expect(described_class.public_only).to eq([public_issue])
end end
end end
describe '#update_project_counter_caches?' do
it 'returns true when the state changes' do
subject.state = 'closed'
expect(subject.update_project_counter_caches?).to eq(true)
end
it 'returns true when the confidential flag changes' do
subject.confidential = true
expect(subject.update_project_counter_caches?).to eq(true)
end
it 'returns false when the state or confidential flag did not change' do
expect(subject.update_project_counter_caches?).to eq(false)
end
end
end end
...@@ -1700,4 +1700,16 @@ describe MergeRequest do ...@@ -1700,4 +1700,16 @@ describe MergeRequest do
.to change { project.open_merge_requests_count }.from(1).to(0) .to change { project.open_merge_requests_count }.from(1).to(0)
end end
end end
describe '#update_project_counter_caches?' do
it 'returns true when the state changes' do
subject.state = 'closed'
expect(subject.update_project_counter_caches?).to eq(true)
end
it 'returns false when the state did not change' do
expect(subject.update_project_counter_caches?).to eq(false)
end
end
end end
...@@ -2234,6 +2234,28 @@ describe Project do ...@@ -2234,6 +2234,28 @@ describe Project do
end end
end end
describe '#pages_available?' do
let(:project) { create(:project, group: group) }
subject { project.pages_available? }
before do
allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
end
context 'when the project is in a top level namespace' do
let(:group) { create(:group) }
it { is_expected.to be(true) }
end
context 'when the project is in a subgroup' do
let(:group) { create(:group, :nested) }
it { is_expected.to be(false) }
end
end
describe '#remove_private_deploy_keys' do describe '#remove_private_deploy_keys' do
let!(:project) { create(:project) } let!(:project) { create(:project) }
......
...@@ -125,6 +125,15 @@ describe API::Files do ...@@ -125,6 +125,15 @@ describe API::Files do
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
it 'returns raw file info for files with dots' do
url = route('.gitignore') + "/raw"
expect(Gitlab::Workhorse).to receive(:send_git_blob)
get api(url, current_user), params
expect(response).to have_http_status(200)
end
it 'returns file by commit sha' do it 'returns file by commit sha' do
# This file is deleted on HEAD # This file is deleted on HEAD
file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee" file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
......
...@@ -660,6 +660,95 @@ describe API::Internal do ...@@ -660,6 +660,95 @@ describe API::Internal do
# end # end
# end # end
describe 'POST /internal/post_receive' do
let(:gl_repository) { "project-#{project.id}" }
let(:identifier) { 'key-123' }
let(:reference_counter) { double('ReferenceCounter') }
let(:valid_params) do
{
gl_repository: gl_repository,
secret_token: secret_token,
identifier: identifier,
changes: changes
}
end
let(:changes) do
"#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch"
end
before do
project.team << [user, :developer]
end
it 'enqueues a PostReceive worker job' do
expect(PostReceive).to receive(:perform_async)
.with(gl_repository, identifier, changes)
post api("/internal/post_receive"), valid_params
end
it 'decreases the reference counter and returns the result' do
expect(Gitlab::ReferenceCounter).to receive(:new).with(gl_repository)
.and_return(reference_counter)
expect(reference_counter).to receive(:decrease).and_return(true)
post api("/internal/post_receive"), valid_params
expect(json_response['reference_counter_decreased']).to be(true)
end
it 'returns link to create new merge request' do
post api("/internal/post_receive"), valid_params
expect(json_response['merge_request_urls']).to match [{
"branch_name" => "new_branch",
"url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
"new_merge_request" => true
}]
end
it 'returns empty array if printing_merge_request_link_enabled is false' do
project.update!(printing_merge_request_link_enabled: false)
post api("/internal/post_receive"), valid_params
expect(json_response['merge_request_urls']).to eq([])
end
context 'broadcast message exists' do
let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) }
it 'returns one broadcast message' do
post api("/internal/post_receive"), valid_params
expect(response).to have_http_status(200)
expect(json_response['broadcast_message']).to eq(broadcast_message.message)
end
end
context 'broadcast message does not exist' do
it 'returns empty string' do
post api("/internal/post_receive"), valid_params
expect(response).to have_http_status(200)
expect(json_response['broadcast_message']).to eq(nil)
end
end
context 'nil broadcast message' do
it 'returns empty string' do
allow(BroadcastMessage).to receive(:current).and_return(nil)
post api("/internal/post_receive"), valid_params
expect(response).to have_http_status(200)
expect(json_response['broadcast_message']).to eq(nil)
end
end
end
def project_with_repo_path(path) def project_with_repo_path(path)
double().tap do |fake_project| double().tap do |fake_project|
allow(fake_project).to receive_message_chain('repository.path_to_repo' => path) allow(fake_project).to receive_message_chain('repository.path_to_repo' => path)
......
...@@ -3,9 +3,9 @@ require 'spec_helper' ...@@ -3,9 +3,9 @@ require 'spec_helper'
describe SystemNoteService do describe SystemNoteService do
include Gitlab::Routing include Gitlab::Routing
let(:group) { create(:group) } set(:group) { create(:group) }
let(:project) { create(:project, group: group) } set(:project) { create(:project, :repository, group: group) }
let(:author) { create(:user) } set(:author) { create(:user) }
let(:noteable) { create(:issue, project: project) } let(:noteable) { create(:issue, project: project) }
let(:issue) { noteable } let(:issue) { noteable }
...@@ -29,8 +29,7 @@ describe SystemNoteService do ...@@ -29,8 +29,7 @@ describe SystemNoteService do
describe '.add_commits' do describe '.add_commits' do
subject { described_class.add_commits(noteable, project, author, new_commits, old_commits, oldrev) } subject { described_class.add_commits(noteable, project, author, new_commits, old_commits, oldrev) }
let(:project) { create(:project, :repository) } let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
let(:noteable) { create(:merge_request, source_project: project) }
let(:new_commits) { noteable.commits } let(:new_commits) { noteable.commits }
let(:old_commits) { [] } let(:old_commits) { [] }
let(:oldrev) { nil } let(:oldrev) { nil }
...@@ -185,7 +184,7 @@ describe SystemNoteService do ...@@ -185,7 +184,7 @@ describe SystemNoteService do
describe '.change_label' do describe '.change_label' do
subject { described_class.change_label(noteable, project, author, added, removed) } subject { described_class.change_label(noteable, project, author, added, removed) }
let(:labels) { create_list(:label, 2) } let(:labels) { create_list(:label, 2, project: project) }
let(:added) { [] } let(:added) { [] }
let(:removed) { [] } let(:removed) { [] }
...@@ -294,7 +293,6 @@ describe SystemNoteService do ...@@ -294,7 +293,6 @@ describe SystemNoteService do
end end
describe '.merge_when_pipeline_succeeds' do describe '.merge_when_pipeline_succeeds' do
let(:project) { create(:project, :repository) }
let(:pipeline) { build(:ci_pipeline_without_jobs )} let(:pipeline) { build(:ci_pipeline_without_jobs )}
let(:noteable) do let(:noteable) do
create(:merge_request, source_project: project, target_project: project) create(:merge_request, source_project: project, target_project: project)
...@@ -312,7 +310,6 @@ describe SystemNoteService do ...@@ -312,7 +310,6 @@ describe SystemNoteService do
end end
describe '.cancel_merge_when_pipeline_succeeds' do describe '.cancel_merge_when_pipeline_succeeds' do
let(:project) { create(:project, :repository) }
let(:noteable) do let(:noteable) do
create(:merge_request, source_project: project, target_project: project) create(:merge_request, source_project: project, target_project: project)
end end
...@@ -390,7 +387,6 @@ describe SystemNoteService do ...@@ -390,7 +387,6 @@ describe SystemNoteService do
describe '.change_branch' do describe '.change_branch' do
subject { described_class.change_branch(noteable, project, author, 'target', old_branch, new_branch) } subject { described_class.change_branch(noteable, project, author, 'target', old_branch, new_branch) }
let(:project) { create(:project, :repository) }
let(:old_branch) { 'old_branch'} let(:old_branch) { 'old_branch'}
let(:new_branch) { 'new_branch'} let(:new_branch) { 'new_branch'}
...@@ -408,8 +404,6 @@ describe SystemNoteService do ...@@ -408,8 +404,6 @@ describe SystemNoteService do
describe '.change_branch_presence' do describe '.change_branch_presence' do
subject { described_class.change_branch_presence(noteable, project, author, :source, 'feature', :delete) } subject { described_class.change_branch_presence(noteable, project, author, :source, 'feature', :delete) }
let(:project) { create(:project, :repository) }
it_behaves_like 'a system note' do it_behaves_like 'a system note' do
let(:action) { 'branch' } let(:action) { 'branch' }
end end
...@@ -424,8 +418,6 @@ describe SystemNoteService do ...@@ -424,8 +418,6 @@ describe SystemNoteService do
describe '.new_issue_branch' do describe '.new_issue_branch' do
subject { described_class.new_issue_branch(noteable, project, author, "1-mepmep") } subject { described_class.new_issue_branch(noteable, project, author, "1-mepmep") }
let(:project) { create(:project, :repository) }
it_behaves_like 'a system note' do it_behaves_like 'a system note' do
let(:action) { 'branch' } let(:action) { 'branch' }
end end
...@@ -471,7 +463,7 @@ describe SystemNoteService do ...@@ -471,7 +463,7 @@ describe SystemNoteService do
describe 'note_body' do describe 'note_body' do
context 'cross-project' do context 'cross-project' do
let(:project2) { create(:project, :repository) } let(:project2) { create(:project, :repository) }
let(:mentioner) { create(:issue, project: project2) } let(:mentioner) { create(:issue, project: project2) }
context 'from Commit' do context 'from Commit' do
...@@ -491,7 +483,6 @@ describe SystemNoteService do ...@@ -491,7 +483,6 @@ describe SystemNoteService do
context 'within the same project' do context 'within the same project' do
context 'from Commit' do context 'from Commit' do
let(:project) { create(:project, :repository) }
let(:mentioner) { project.repository.commit } let(:mentioner) { project.repository.commit }
it 'references the mentioning commit' do it 'references the mentioning commit' do
...@@ -533,7 +524,6 @@ describe SystemNoteService do ...@@ -533,7 +524,6 @@ describe SystemNoteService do
end end
context 'when mentioner is a MergeRequest' do context 'when mentioner is a MergeRequest' do
let(:project) { create(:project, :repository) }
let(:mentioner) { create(:merge_request, :simple, source_project: project) } let(:mentioner) { create(:merge_request, :simple, source_project: project) }
let(:noteable) { project.commit } let(:noteable) { project.commit }
...@@ -561,7 +551,6 @@ describe SystemNoteService do ...@@ -561,7 +551,6 @@ describe SystemNoteService do
end end
describe '.cross_reference_exists?' do describe '.cross_reference_exists?' do
let(:project) { create(:project, :repository) }
let(:commit0) { project.commit } let(:commit0) { project.commit }
let(:commit1) { project.commit('HEAD~2') } let(:commit1) { project.commit('HEAD~2') }
...@@ -899,9 +888,8 @@ describe SystemNoteService do ...@@ -899,9 +888,8 @@ describe SystemNoteService do
end end
describe '.discussion_continued_in_issue' do describe '.discussion_continued_in_issue' do
let(:discussion) { create(:diff_note_on_merge_request).to_discussion } let(:discussion) { create(:diff_note_on_merge_request, project: project).to_discussion }
let(:merge_request) { discussion.noteable } let(:merge_request) { discussion.noteable }
let(:project) { merge_request.source_project }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
def reloaded_merge_request def reloaded_merge_request
...@@ -1023,7 +1011,6 @@ describe SystemNoteService do ...@@ -1023,7 +1011,6 @@ describe SystemNoteService do
end end
describe '.add_merge_request_wip_from_commit' do describe '.add_merge_request_wip_from_commit' do
let(:project) { create(:project, :repository) }
let(:noteable) do let(:noteable) do
create(:merge_request, source_project: project, target_project: project) create(:merge_request, source_project: project, target_project: project)
end end
...@@ -1078,9 +1065,8 @@ describe SystemNoteService do ...@@ -1078,9 +1065,8 @@ describe SystemNoteService do
end end
describe '.diff_discussion_outdated' do describe '.diff_discussion_outdated' do
let(:discussion) { create(:diff_note_on_merge_request).to_discussion } let(:discussion) { create(:diff_note_on_merge_request, project: project).to_discussion }
let(:merge_request) { discussion.noteable } let(:merge_request) { discussion.noteable }
let(:project) { merge_request.source_project }
let(:change_position) { discussion.position } let(:change_position) { discussion.position }
def reloaded_merge_request def reloaded_merge_request
......
# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb # Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb
module StubENV module StubENV
include Gitlab::CurrentSettings
def stub_env(key_or_hash, value = nil) def stub_env(key_or_hash, value = nil)
init_stub unless env_stubbed? init_stub unless env_stubbed?
if key_or_hash.is_a? Hash if key_or_hash.is_a? Hash
......
...@@ -9,6 +9,7 @@ describe 'admin/dashboard/index.html.haml' do ...@@ -9,6 +9,7 @@ describe 'admin/dashboard/index.html.haml' do
assign(:groups, create_list(:group, 1)) assign(:groups, create_list(:group, 1))
allow(view).to receive(:admin?).and_return(true) allow(view).to receive(:admin?).and_return(true)
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
end end
it "shows version of GitLab Workhorse" do it "shows version of GitLab Workhorse" do
......
...@@ -5,6 +5,7 @@ describe 'devise/shared/_signin_box' do ...@@ -5,6 +5,7 @@ describe 'devise/shared/_signin_box' do
before do before do
stub_devise stub_devise
assign(:ldap_servers, []) assign(:ldap_servers, [])
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
end end
it 'is shown when Crowd is enabled' do it 'is shown when Crowd is enabled' do
......
...@@ -37,5 +37,6 @@ describe 'help/index' do ...@@ -37,5 +37,6 @@ describe 'help/index' do
def stub_helpers def stub_helpers
allow(view).to receive(:markdown).and_return('') allow(view).to receive(:markdown).and_return('')
allow(view).to receive(:version_status_badge).and_return('') allow(view).to receive(:version_status_badge).and_return('')
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
end end
end end
require 'spec_helper' require 'spec_helper'
describe 'layouts/_head' do describe 'layouts/_head' do
before do
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
end
it 'escapes HTML-safe strings in page_title' do it 'escapes HTML-safe strings in page_title' do
stub_helper_with_safe_string(:page_title) stub_helper_with_safe_string(:page_title)
......
require 'spec_helper' require 'spec_helper'
describe 'projects/commits/_commit.html.haml' do describe 'projects/commits/_commit.html.haml' do
before do
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
end
context 'with a singed commit' do context 'with a singed commit' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:repository) { project.repository } let(:repository) { project.repository }
......
...@@ -10,7 +10,9 @@ describe 'projects/edit' do ...@@ -10,7 +10,9 @@ describe 'projects/edit' do
assign(:project, project) assign(:project, project)
allow(controller).to receive(:current_user).and_return(user) allow(controller).to receive(:current_user).and_return(user)
allow(view).to receive_messages(current_user: user, can?: true) allow(view).to receive_messages(current_user: user,
can?: true,
current_application_settings: Gitlab::CurrentSettings.current_application_settings)
end end
context 'LFS enabled setting' do context 'LFS enabled setting' do
......
...@@ -14,6 +14,7 @@ describe 'projects/merge_requests/creations/_new_submit.html.haml' do ...@@ -14,6 +14,7 @@ describe 'projects/merge_requests/creations/_new_submit.html.haml' do
allow(view).to receive(:can?).and_return(true) allow(view).to receive(:can?).and_return(true)
allow(view).to receive(:url_for).and_return('#') allow(view).to receive(:url_for).and_return('#')
allow(view).to receive(:current_user).and_return(merge_request.author) allow(view).to receive(:current_user).and_return(merge_request.author)
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
end end
context 'when there are pipelines for merge request but no pipeline for last commit' do context 'when there are pipelines for merge request but no pipeline for last commit' do
......
...@@ -25,7 +25,9 @@ describe 'projects/merge_requests/show.html.haml' do ...@@ -25,7 +25,9 @@ describe 'projects/merge_requests/show.html.haml' do
assign(:notes, []) assign(:notes, [])
assign(:pipelines, Ci::Pipeline.none) assign(:pipelines, Ci::Pipeline.none)
allow(view).to receive_messages(current_user: user, can?: true) allow(view).to receive_messages(current_user: user,
can?: true,
current_application_settings: Gitlab::CurrentSettings.current_application_settings)
end end
context 'when the merge request is closed' do context 'when the merge request is closed' do
......
...@@ -12,6 +12,7 @@ describe 'projects/tree/show' do ...@@ -12,6 +12,7 @@ describe 'projects/tree/show' do
allow(view).to receive(:can?).and_return(true) allow(view).to receive(:can?).and_return(true)
allow(view).to receive(:can_collaborate_with_project?).and_return(true) allow(view).to receive(:can_collaborate_with_project?).and_return(true)
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
end end
context 'for branch names ending on .json' do context 'for branch names ending on .json' do
......
...@@ -3,6 +3,10 @@ require 'spec_helper' ...@@ -3,6 +3,10 @@ require 'spec_helper'
describe 'shared/projects/_project.html.haml' do describe 'shared/projects/_project.html.haml' do
let(:project) { create(:project) } let(:project) { create(:project) }
before do
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
end
it 'should render creator avatar if project has a creator' do it 'should render creator avatar if project has a creator' do
render 'shared/projects/project', use_creator_avatar: true, project: project render 'shared/projects/project', use_creator_avatar: true, project: project
......
...@@ -990,7 +990,7 @@ brace-expansion@^1.0.0: ...@@ -990,7 +990,7 @@ brace-expansion@^1.0.0:
balanced-match "^0.4.1" balanced-match "^0.4.1"
concat-map "0.0.1" concat-map "0.0.1"
brace-expansion@^1.1.7: brace-expansion@^1.1.8:
version "1.1.8" version "1.1.8"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
dependencies: dependencies:
......
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