Commit 1fd10d4f authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'live-trace-v2' into live-trace-v2-efficient-destroy-all

parents 5627f28c 1f39fcd1
......@@ -68,6 +68,8 @@ eslint-report.html
/shared/*
/.gitlab_workhorse_secret
/webpack-report/
/knapsack/
/rspec_flaky/
/locale/**/LC_MESSAGES
/locale/**/*.time_stamp
/.rspec
......
......@@ -30,7 +30,7 @@ export default class IssuableForm {
}
this.initAutosave();
this.form.on('submit', this.handleSubmit);
this.form.on('submit:success', this.handleSubmit);
this.form.on('click', '.btn-cancel', this.resetAutosave);
this.initWip();
......
import $ from 'jquery';
import NewBranchForm from '~/new_branch_form';
import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list';
document.addEventListener('DOMContentLoaded', () => {
new NewBranchForm($('.js-new-pipeline-form')); // eslint-disable-line no-new
setupNativeFormVariableList({
container: $('.js-ci-variable-list-section'),
formField: 'variables_attributes',
});
});
......@@ -61,3 +61,4 @@
@import 'framework/stacked_progress_bar';
@import 'framework/ci_variable_list';
@import 'framework/feature_highlight';
@import 'framework/terms';
.terms {
.alert-wrapper {
min-height: $header-height + $gl-padding;
}
.content {
padding-top: $gl-padding;
}
.panel {
.panel-heading {
display: -webkit-flex;
display: flex;
align-items: center;
justify-content: space-between;
.title {
display: flex;
align-items: center;
.logo-text {
width: 55px;
height: 24px;
display: flex;
flex-direction: column;
justify-content: center;
}
}
.navbar-collapse {
padding-right: 0;
}
.nav li a {
color: $theme-gray-700;
}
}
.panel-content {
padding: $gl-padding;
*:first-child {
margin-top: 0;
}
*:last-child {
margin-bottom: 0;
}
}
.footer-block {
margin: 0;
}
}
}
......@@ -440,6 +440,7 @@
padding-right: 3px;
.projects-sidebar {
min-height: 0;
display: flex;
flex-direction: column;
flex: 1;
......
......@@ -13,12 +13,14 @@ class ApplicationController < ActionController::Base
before_action :authenticate_sessionless_user!
before_action :authenticate_user!
before_action :enforce_terms!, if: -> { Gitlab::CurrentSettings.current_application_settings.enforce_terms },
unless: :peek_request?
before_action :validate_user_service_ticket!
before_action :check_password_expiration
before_action :ldap_security_check
before_action :sentry_context
before_action :default_headers
before_action :add_gon_variables, unless: -> { request.path.start_with?('/-/peek') }
before_action :add_gon_variables, unless: :peek_request?
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller?
......@@ -269,6 +271,27 @@ class ApplicationController < ActionController::Base
end
end
def enforce_terms!
return unless current_user
return if current_user.terms_accepted?
if sessionless_user?
render_403
else
# Redirect to the destination if the request is a get.
# Redirect to the source if it was a post, so the user can re-submit after
# accepting the terms.
redirect_path = if request.get?
request.fullpath
else
URI(request.referer).path if request.referer
end
flash[:notice] = _("Please accept the Terms of Service before continuing.")
redirect_to terms_path(redirect: redirect_path), status: :found
end
end
def import_sources_enabled?
!Gitlab::CurrentSettings.import_sources.empty?
end
......@@ -342,4 +365,12 @@ class ApplicationController < ActionController::Base
# Per https://tools.ietf.org/html/rfc5987, headers need to be ISO-8859-1, not UTF-8
response.headers['Page-Title'] = URI.escape(page_title('GitLab'))
end
def sessionless_user?
current_user && !session.keys.include?('warden.user.user.key')
end
def peek_request?
request.path.start_with?('/-/peek')
end
end
module ContinueParams
include InternalRedirect
extend ActiveSupport::Concern
def continue_params
......@@ -6,8 +7,7 @@ module ContinueParams
return nil unless continue_params
continue_params = continue_params.permit(:to, :notice, :notice_now)
return unless continue_params[:to] && continue_params[:to].start_with?('/')
return if continue_params[:to].start_with?('//')
continue_params[:to] = safe_redirect_path(continue_params[:to])
continue_params
end
......
module InternalRedirect
extend ActiveSupport::Concern
def safe_redirect_path(path)
return unless path
# Verify that the string starts with a `/` but not a double `/`.
return unless path =~ %r{^/\w.*$}
uri = URI(path)
# Ignore anything path of the redirect except for the path, querystring and,
# fragment, forcing the redirect within the same host.
full_path_for_uri(uri)
rescue URI::InvalidURIError
nil
end
def safe_redirect_path_for_url(url)
return unless url
uri = URI(url)
safe_redirect_path(full_path_for_uri(uri)) if host_allowed?(uri)
rescue URI::InvalidURIError
nil
end
def host_allowed?(uri)
uri.host == request.host &&
uri.port == request.port
end
def full_path_for_uri(uri)
path_with_query = [uri.path, uri.query].compact.join('?')
[path_with_query, uri.fragment].compact.join("#")
end
end
class Import::BaseController < ApplicationController
private
def find_already_added_projects(import_type)
current_user.created_projects.where(import_type: import_type).includes(:import_state)
end
def find_jobs(import_type)
current_user.created_projects
.includes(:import_state)
.where(import_type: import_type)
.to_json(only: [:id], methods: [:import_status])
end
def find_or_create_namespace(names, owner)
names = params[:target_namespace].presence || names
......
......@@ -22,16 +22,14 @@ class Import::BitbucketController < Import::BaseController
@repos, @incompatible_repos = repos.partition { |repo| repo.valid? }
@already_added_projects = current_user.created_projects.where(import_type: 'bitbucket')
@already_added_projects = find_already_added_projects('bitbucket')
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.to_a.reject! { |repo| already_added_projects_names.include?(repo.full_name) }
end
def jobs
render json: current_user.created_projects
.where(import_type: 'bitbucket')
.to_json(only: [:id, :import_status])
render json: find_jobs('bitbucket')
end
def create
......
......@@ -46,15 +46,14 @@ class Import::FogbugzController < Import::BaseController
@repos = client.repos
@already_added_projects = current_user.created_projects.where(import_type: 'fogbugz')
@already_added_projects = find_already_added_projects('fogbugz')
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.reject! { |repo| already_added_projects_names.include? repo.name }
end
def jobs
jobs = current_user.created_projects.where(import_type: 'fogbugz').to_json(only: [:id, :import_status])
render json: jobs
render json: find_jobs('fogbugz')
end
def create
......
......@@ -24,15 +24,14 @@ class Import::GithubController < Import::BaseController
def status
@repos = client.repos
@already_added_projects = current_user.created_projects.where(import_type: provider)
@already_added_projects = find_already_added_projects(provider)
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.reject! { |repo| already_added_projects_names.include? repo.full_name }
end
def jobs
jobs = current_user.created_projects.where(import_type: provider).to_json(only: [:id, :import_status])
render json: jobs
render json: find_jobs(provider)
end
def create
......
......@@ -12,15 +12,14 @@ class Import::GitlabController < Import::BaseController
def status
@repos = client.projects
@already_added_projects = current_user.created_projects.where(import_type: "gitlab")
@already_added_projects = find_already_added_projects('gitlab')
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos = @repos.to_a.reject { |repo| already_added_projects_names.include? repo["path_with_namespace"] }
end
def jobs
jobs = current_user.created_projects.where(import_type: "gitlab").to_json(only: [:id, :import_status])
render json: jobs
render json: find_jobs('gitlab')
end
def create
......
......@@ -73,15 +73,14 @@ class Import::GoogleCodeController < Import::BaseController
@repos = client.repos
@incompatible_repos = client.incompatible_repos
@already_added_projects = current_user.created_projects.where(import_type: "google_code")
@already_added_projects = find_already_added_projects('google_code')
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.reject! { |repo| already_added_projects_names.include? repo.name }
end
def jobs
jobs = current_user.created_projects.where(import_type: "google_code").to_json(only: [:id, :import_status])
render json: jobs
render json: find_jobs('google_code')
end
def create
......
......@@ -82,7 +82,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if identity_linker.changed?
redirect_identity_linked
elsif identity_linker.error_message.present?
elsif identity_linker.failed?
redirect_identity_link_failed(identity_linker.error_message)
else
redirect_identity_exists
......
......@@ -157,7 +157,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def create_params
params.require(:pipeline).permit(:ref)
params.require(:pipeline).permit(:ref, variables_attributes: %i[key secret_value])
end
def pipeline
......
......@@ -52,6 +52,12 @@ class Projects::RunnersController < Projects::ApplicationController
redirect_to project_settings_ci_cd_path(@project)
end
def toggle_group_runners
project.toggle_ci_cd_settings!(:group_runners_enabled)
redirect_to project_settings_ci_cd_path(@project)
end
protected
def set_runner
......
......@@ -67,10 +67,18 @@ module Projects
def define_runners_variables
@project_runners = @project.runners.ordered
@assignable_runners = current_user.ci_authorized_runners
.assignable_for(project).ordered.page(params[:page]).per(20)
@assignable_runners = current_user
.ci_authorized_runners
.assignable_for(project)
.ordered
.page(params[:page]).per(20)
@shared_runners = ::Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all)
@group_runners = ::Ci::Runner.belonging_to_parent_group_of_project(@project.id)
end
def define_secret_variables
......
class SessionsController < Devise::SessionsController
include InternalRedirect
include AuthenticatesWithTwoFactor
include Devise::Controllers::Rememberable
include Recaptcha::ClientHelper
......@@ -102,18 +103,12 @@ class SessionsController < Devise::SessionsController
# we should never redirect to '/users/sign_in' after signing in successfully.
return true if redirect_uri.path == new_user_session_path
redirect_to = redirect_uri.to_s if redirect_allowed_to?(redirect_uri)
redirect_to = redirect_uri.to_s if host_allowed?(redirect_uri)
@redirect_to = redirect_to
store_location_for(:redirect, redirect_to)
end
# Overridden in EE
def redirect_allowed_to?(uri)
uri.host == Gitlab.config.gitlab.host &&
uri.port == Gitlab.config.gitlab.port
end
def two_factor_enabled?
find_user&.two_factor_enabled?
end
......
module Users
class TermsController < ApplicationController
include InternalRedirect
skip_before_action :enforce_terms!
before_action :terms
layout 'terms'
def index
@redirect = redirect_path
end
def accept
agreement = Users::RespondToTermsService.new(current_user, viewed_term)
.execute(accepted: true)
if agreement.persisted?
redirect_to redirect_path
else
flash[:alert] = agreement.errors.full_messages.join(', ')
redirect_to terms_path, redirect: redirect_path
end
end
def decline
agreement = Users::RespondToTermsService.new(current_user, viewed_term)
.execute(accepted: false)
if agreement.persisted?
sign_out(current_user)
redirect_to root_path
else
flash[:alert] = agreement.errors.full_messages.join(', ')
redirect_to terms_path, redirect: redirect_path
end
end
private
def viewed_term
@viewed_term ||= ApplicationSetting::Term.find(params[:id])
end
def terms
unless @term = Gitlab::CurrentSettings.current_application_settings.latest_terms
redirect_to redirect_path
end
end
def redirect_path
redirect_to_path = safe_redirect_path(params[:redirect]) || safe_redirect_path_for_url(request.referer)
if redirect_to_path &&
excluded_redirect_paths.none? { |excluded| redirect_to_path.include?(excluded) }
redirect_to_path
else
root_path
end
end
def excluded_redirect_paths
[terms_path, new_user_session_path]
end
end
end
......@@ -248,7 +248,9 @@ module ApplicationSettingsHelper
:user_default_external,
:user_oauth_applications,
:version_check_enabled,
:allow_local_requests_from_hooks_and_services
:allow_local_requests_from_hooks_and_services,
:enforce_terms,
:terms
]
end
end
......@@ -23,9 +23,42 @@ module UsersHelper
profile_tabs.include?(tab)
end
def current_user_menu_items
@current_user_menu_items ||= get_current_user_menu_items
end
def current_user_menu?(item)
current_user_menu_items.include?(item)
end
private
def get_profile_tabs
[:activity, :groups, :contributed, :projects, :snippets]
end
def get_current_user_menu_items
items = []
items << :sign_out if current_user
# TODO: Remove these conditions when the permissions are prevented in
# https://gitlab.com/gitlab-org/gitlab-ce/issues/45849
terms_not_enforced = !Gitlab::CurrentSettings
.current_application_settings
.enforce_terms?
required_terms_accepted = terms_not_enforced || current_user.terms_accepted?
items << :help if required_terms_accepted
if can?(current_user, :read_user, current_user) && required_terms_accepted
items << :profile
end
if can?(current_user, :update_user, current_user) && required_terms_accepted
items << :settings
end
items
end
end
......@@ -220,12 +220,15 @@ class ApplicationSetting < ActiveRecord::Base
end
end
validate :terms_exist, if: :enforce_terms?
before_validation :ensure_uuid!
before_save :ensure_runners_registration_token
before_save :ensure_health_check_access_token
after_commit do
reset_memoized_terms
Rails.cache.write(CACHE_KEY, self)
end
......@@ -507,6 +510,16 @@ class ApplicationSetting < ActiveRecord::Base
password_authentication_enabled_for_web? || password_authentication_enabled_for_git?
end
delegate :terms, to: :latest_terms, allow_nil: true
def latest_terms
@latest_terms ||= Term.latest
end
def reset_memoized_terms
@latest_terms = nil
latest_terms
end
private
def ensure_uuid!
......@@ -520,4 +533,10 @@ class ApplicationSetting < ActiveRecord::Base
errors.add(:repository_storages, "can't include: #{invalid.join(", ")}") unless
invalid.empty?
end
def terms_exist
return unless enforce_terms?
errors.add(:terms, "You need to set terms to be enforced") unless terms.present?
end
end
class ApplicationSetting
class Term < ActiveRecord::Base
include CacheMarkdownField
validates :terms, presence: true
cache_markdown_field :terms
def self.latest
order(:id).last
end
end
end
......@@ -32,6 +32,8 @@ module Ci
has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id'
has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id'
accepts_nested_attributes_for :variables, reject_if: :persisted?
delegate :id, to: :project, prefix: true
delegate :full_path, to: :project, prefix: true
......
......@@ -5,6 +5,8 @@ module Ci
belongs_to :pipeline
alias_attribute :secret_value, :value
validates :key, uniqueness: { scope: :pipeline_id }
end
end
......@@ -14,31 +14,49 @@ module Ci
has_many :builds
has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :runner_projects
has_many :runner_namespaces
has_many :groups, through: :runner_namespaces
has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build'
before_validation :set_default_values
scope :specific, ->() { where(is_shared: false) }
scope :shared, ->() { where(is_shared: true) }
scope :active, ->() { where(active: true) }
scope :paused, ->() { where(active: false) }
scope :online, ->() { where('contacted_at > ?', contact_time_deadline) }
scope :ordered, ->() { order(id: :desc) }
scope :specific, -> { where(is_shared: false) }
scope :shared, -> { where(is_shared: true) }
scope :active, -> { where(active: true) }
scope :paused, -> { where(active: false) }
scope :online, -> { where('contacted_at > ?', contact_time_deadline) }
scope :ordered, -> { order(id: :desc) }
scope :owned_or_shared, ->(project_id) do
joins('LEFT JOIN ci_runner_projects ON ci_runner_projects.runner_id = ci_runners.id')
.where("ci_runner_projects.project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
scope :belonging_to_project, -> (project_id) {
joins(:runner_projects).where(ci_runner_projects: { project_id: project_id })
}
scope :belonging_to_parent_group_of_project, -> (project_id) {
project_groups = ::Group.joins(:projects).where(projects: { id: project_id })
hierarchy_groups = Gitlab::GroupHierarchy.new(project_groups).base_and_ancestors
joins(:groups).where(namespaces: { id: hierarchy_groups })
}
scope :owned_or_shared, -> (project_id) do
union = Gitlab::SQL::Union.new(
[belonging_to_project(project_id), belonging_to_parent_group_of_project(project_id), shared],
remove_duplicates: false
)
from("(#{union.to_sql}) ci_runners")
end
scope :assignable_for, ->(project) do
# FIXME: That `to_sql` is needed to workaround a weird Rails bug.
# Without that, placeholders would miss one and couldn't match.
where(locked: false)
.where.not("id IN (#{project.runners.select(:id).to_sql})").specific
.where.not("ci_runners.id IN (#{project.runners.select(:id).to_sql})")
.specific
end
validate :tag_constraints
validate :either_projects_or_group
validates :access_level, presence: true
acts_as_taggable
......@@ -50,6 +68,12 @@ module Ci
ref_protected: 1
}
enum runner_type: {
instance_type: 1,
group_type: 2,
project_type: 3
}
cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address
chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout
......@@ -120,6 +144,14 @@ module Ci
!shared?
end
def assigned_to_group?
runner_namespaces.any?
end
def assigned_to_project?
runner_projects.any?
end
def can_pick?(build)
return false if self.ref_protected? && !build.protected?
......@@ -174,6 +206,12 @@ module Ci
end
end
def pick_build!(build)
if can_pick?(build)
tick_runner_queue
end
end
private
def cleanup_runner_queue
......@@ -205,7 +243,17 @@ module Ci
end
def assignable_for?(project_id)
is_shared? || projects.exists?(id: project_id)
self.class.owned_or_shared(project_id).where(id: self.id).any?
end
def either_projects_or_group
if groups.many?
errors.add(:runner, 'can only be assigned to one group')
end
if assigned_to_group? && assigned_to_project?
errors.add(:runner, 'can only be assigned either to projects or to a group')
end
end
def accepting_tags?(build)
......
module Ci
class RunnerNamespace < ActiveRecord::Base
extend Gitlab::Ci::Model
belongs_to :runner
belongs_to :namespace, class_name: '::Namespace'
belongs_to :group, class_name: '::Group', foreign_key: :namespace_id
end
end
......@@ -9,6 +9,7 @@ class Group < Namespace
include SelectForProjectAuthorization
include LoadedInGroupList
include GroupDescendant
include TokenAuthenticatable
has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
alias_method :members, :group_members
......@@ -43,6 +44,8 @@ class Group < Namespace
validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 }
add_authentication_token_field :runners_token
after_create :post_create_hook
after_destroy :post_destroy_hook
after_save :update_two_factor_requirement
......@@ -294,6 +297,13 @@ class Group < Namespace
refresh_members_authorized_projects(blocking: false)
end
# each existing group needs to have a `runners_token`.
# we do this on read since migrating all existing groups is not a feasible
# solution.
def runners_token
ensure_runners_token!
end
private
def update_two_factor_requirement
......
......@@ -21,6 +21,9 @@ class Namespace < ActiveRecord::Base
has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :project_statistics
has_many :runner_namespaces, class_name: 'Ci::RunnerNamespace'
has_many :runners, through: :runner_namespaces, source: :runner, class_name: 'Ci::Runner'
# This should _not_ be `inverse_of: :namespace`, because that would also set
# `user.namespace` when this user creates a group with themselves as `owner`.
belongs_to :owner, class_name: "User"
......
......@@ -68,6 +68,9 @@ class Project < ActiveRecord::Base
before_save :ensure_runners_token
after_save :update_project_statistics, if: :namespace_id_changed?
after_save :create_import_state, if: ->(project) { project.import? && project.import_state.nil? }
after_create :create_project_feature, unless: :project_feature
after_create :create_ci_cd_settings,
......@@ -161,6 +164,8 @@ class Project < ActiveRecord::Base
has_one :fork_network_member
has_one :fork_network, through: :fork_network_member
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
# Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id'
has_many :source_of_merge_requests, foreign_key: 'source_project_id', class_name: 'MergeRequest'
......@@ -235,13 +240,11 @@ class Project < ActiveRecord::Base
has_many :project_deploy_tokens
has_many :deploy_tokens, through: :project_deploy_tokens
has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
has_one :auto_devops, class_name: 'ProjectAutoDevops'
has_many :custom_attributes, class_name: 'ProjectCustomAttribute'
has_many :project_badges, class_name: 'ProjectBadge'
has_one :ci_cd_settings, class_name: 'ProjectCiCdSetting'
has_one :ci_cd_settings, class_name: 'ProjectCiCdSetting', inverse_of: :project, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature, update_only: true
......@@ -252,6 +255,7 @@ class Project < ActiveRecord::Base
delegate :members, to: :team, prefix: true
delegate :add_user, :add_users, to: :team
delegate :add_guest, :add_reporter, :add_developer, :add_master, :add_role, to: :team
delegate :group_runners_enabled, :group_runners_enabled=, :group_runners_enabled?, to: :ci_cd_settings
# Validations
validates :creator, presence: true, on: :create
......@@ -337,6 +341,11 @@ class Project < ActiveRecord::Base
scope :with_issues_available_for_user, ->(current_user) { with_feature_available_for_user(:issues, current_user) }
scope :with_merge_requests_enabled, -> { with_feature_enabled(:merge_requests) }
scope :with_group_runners_enabled, -> do
joins(:ci_cd_settings)
.where(project_ci_cd_settings: { group_runners_enabled: true })
end
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600
......@@ -386,55 +395,9 @@ class Project < ActiveRecord::Base
scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) }
scope :excluding_project, ->(project) { where.not(id: project) }
scope :import_started, -> { where(import_status: 'started') }
state_machine :import_status, initial: :none do
event :import_schedule do
transition [:none, :finished, :failed] => :scheduled
end
event :force_import_start do
transition [:none, :finished, :failed] => :started
end
event :import_start do
transition scheduled: :started
end
event :import_finish do
transition started: :finished
end
event :import_fail do
transition [:scheduled, :started] => :failed
end
event :import_retry do
transition failed: :started
end
state :scheduled
state :started
state :finished
state :failed
after_transition [:none, :finished, :failed] => :scheduled do |project, _|
project.run_after_commit do
job_id = add_import_job
update(import_jid: job_id) if job_id
end
end
after_transition started: :finished do |project, _|
project.reset_cache_and_import_attrs
if Gitlab::ImportSources.importer_names.include?(project.import_type) && project.repo_exists?
project.run_after_commit do
Projects::AfterImportService.new(project).execute
end
end
end
end
scope :joins_import_state, -> { joins("LEFT JOIN project_mirror_data import_state ON import_state.project_id = projects.id") }
scope :import_started, -> { joins_import_state.where("import_state.status = 'started' OR projects.import_status = 'started'") }
class << self
# Searches for a list of projects based on the query given in `query`.
......@@ -664,10 +627,6 @@ class Project < ActiveRecord::Base
external_import? || forked? || gitlab_project_import? || bare_repository_import?
end
def no_import?
import_status == 'none'
end
def external_import?
import_url.present?
end
......@@ -680,6 +639,93 @@ class Project < ActiveRecord::Base
import_started? || import_scheduled?
end
def import_state_args
{
status: self[:import_status],
jid: self[:import_jid],
last_error: self[:import_error]
}
end
def ensure_import_state
return if self[:import_status] == 'none' || self[:import_status].nil?
return unless import_state.nil?
create_import_state(import_state_args)
update_column(:import_status, 'none')
end
def import_schedule
ensure_import_state
import_state&.schedule
end
def force_import_start
ensure_import_state
import_state&.force_start
end
def import_start
ensure_import_state
import_state&.start
end
def import_fail
ensure_import_state
import_state&.fail_op
end
def import_finish
ensure_import_state
import_state&.finish
end
def import_jid=(new_jid)
ensure_import_state
import_state&.jid = new_jid
end
def import_jid
ensure_import_state
import_state&.jid
end
def import_error=(new_error)
ensure_import_state
import_state&.last_error = new_error
end
def import_error
ensure_import_state
import_state&.last_error
end
def import_status=(new_status)
ensure_import_state
import_state&.status = new_status
end
def import_status
ensure_import_state
import_state&.status || 'none'
end
def no_import?
import_status == 'none'
end
def import_started?
# import? does SQL work so only run it if it looks like there's an import running
import_status == 'started' && import?
......@@ -1306,12 +1352,17 @@ class Project < ActiveRecord::Base
@shared_runners ||= shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none
end
def active_shared_runners
@active_shared_runners ||= shared_runners.active
def group_runners
@group_runners ||= group_runners_enabled? ? Ci::Runner.belonging_to_parent_group_of_project(self.id) : Ci::Runner.none
end
def all_runners
union = Gitlab::SQL::Union.new([runners, group_runners, shared_runners])
Ci::Runner.from("(#{union.to_sql}) ci_runners")
end
def any_runners?(&block)
active_runners.any?(&block) || active_shared_runners.any?(&block)
all_runners.active.any?(&block)
end
def valid_runners_token?(token)
......@@ -1476,7 +1527,7 @@ class Project < ActiveRecord::Base
def rename_repo_notify!
# When we import a project overwriting the original project, there
# is a move operation. In that case we don't want to send the instructions.
send_move_instructions(full_path_was) unless started?
send_move_instructions(full_path_was) unless import_started?
expires_full_path_cache
self.old_path_with_namespace = full_path_was
......@@ -1530,7 +1581,8 @@ class Project < ActiveRecord::Base
return unless import_jid
Gitlab::SidekiqStatus.unset(import_jid)
update_column(:import_jid, nil)
import_state.update_column(:jid, nil)
end
def running_or_pending_build_count(force: false)
......@@ -1549,7 +1601,8 @@ class Project < ActiveRecord::Base
sanitized_message = Gitlab::UrlSanitizer.sanitize(error_message)
import_fail
update_column(:import_error, sanitized_message)
import_state.update_column(:last_error, sanitized_message)
rescue ActiveRecord::ActiveRecordError => e
Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}")
ensure
......@@ -1879,6 +1932,10 @@ class Project < ActiveRecord::Base
[]
end
def toggle_ci_cd_settings!(settings_attribute)
ci_cd_settings.toggle!(settings_attribute)
end
def gitlab_deploy_token
@gitlab_deploy_token ||= deploy_tokens.gitlab_deploy_token
end
......
class ProjectCiCdSetting < ActiveRecord::Base
belongs_to :project
belongs_to :project, inverse_of: :ci_cd_settings
# The version of the schema that first introduced this model/table.
MINIMUM_SCHEMA_VERSION = 20180403035759
......
class ProjectImportState < ActiveRecord::Base
include AfterCommitQueue
self.table_name = "project_mirror_data"
belongs_to :project, inverse_of: :import_state
validates :project, presence: true
state_machine :status, initial: :none do
event :schedule do
transition [:none, :finished, :failed] => :scheduled
end
event :force_start do
transition [:none, :finished, :failed] => :started
end
event :start do
transition scheduled: :started
end
event :finish do
transition started: :finished
end
event :fail_op do
transition [:scheduled, :started] => :failed
end
state :scheduled
state :started
state :finished
state :failed
after_transition [:none, :finished, :failed] => :scheduled do |state, _|
state.run_after_commit do
job_id = project.add_import_job
update(jid: job_id) if job_id
end
end
after_transition started: :finished do |state, _|
project = state.project
project.reset_cache_and_import_attrs
if Gitlab::ImportSources.importer_names.include?(project.import_type) && project.repo_exists?
state.run_after_commit do
Projects::AfterImportService.new(project).execute
end
end
end
end
end
class TermAgreement < ActiveRecord::Base
belongs_to :term, class_name: 'ApplicationSetting::Term'
belongs_to :user
validates :user, :term, presence: true
end
......@@ -138,6 +138,8 @@ class User < ActiveRecord::Base
has_many :custom_attributes, class_name: 'UserCustomAttribute'
has_many :callouts, class_name: 'UserCallout'
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :term_agreements
belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
#
# Validations
......@@ -1187,6 +1189,10 @@ class User < ActiveRecord::Base
max_member_access_for_group_ids([group_id])[group_id]
end
def terms_accepted?
accepted_term_id.present?
end
protected
# override, from Devise::Validatable
......
class ApplicationSetting
class TermPolicy < BasePolicy
include Gitlab::Utils::StrongMemoize
condition(:current_terms, scope: :subject) do
Gitlab::CurrentSettings.current_application_settings.latest_terms == @subject
end
condition(:terms_accepted, score: 1) do
agreement&.accepted
end
rule { ~anonymous & current_terms }.policy do
enable :accept_terms
enable :decline_terms
end
rule { terms_accepted }.prevent :accept_terms
def agreement
strong_memoize(:agreement) do
next nil if @user.nil? || @subject.nil?
@user.term_agreements.find_by(term: @subject)
end
end
end
end
......@@ -8,6 +8,8 @@ class UserPolicy < BasePolicy
rule { ~restricted_public_level }.enable :read_user
rule { ~anonymous }.enable :read_user
rule { user_is_self | admin }.enable :destroy_user
rule { subject_ghost }.prevent :destroy_user
rule { ~subject_ghost & (user_is_self | admin) }.policy do
enable :destroy_user
enable :update_user
end
end
module ApplicationSettings
class UpdateService < ApplicationSettings::BaseService
def execute
update_terms(@params.delete(:terms))
@application_setting.update(@params)
end
private
def update_terms(terms)
return unless terms.present?
# Avoid creating a new terms record if the text is exactly the same.
terms = terms.strip
return if terms == @application_setting.terms
ApplicationSetting::Term.create(terms: terms)
@application_setting.reset_memoized_terms
end
end
end
......@@ -24,6 +24,7 @@ module Ci
ignore_skip_ci: ignore_skip_ci,
save_incompleted: save_on_errors,
seeds_block: block,
variables_attributes: params[:variables_attributes],
project: project,
current_user: current_user)
......
......@@ -17,8 +17,10 @@ module Ci
builds =
if runner.shared?
builds_for_shared_runner
elsif runner.group_type?
builds_for_group_runner
else
builds_for_specific_runner
builds_for_project_runner
end
valid = true
......@@ -75,15 +77,24 @@ module Ci
.joins('LEFT JOIN project_features ON ci_builds.project_id = project_features.project_id')
.where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
# Implement fair scheduling
# this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all
joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.project_id=project_builds.project_id")
# Implement fair scheduling
# this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all
joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.project_id=project_builds.project_id")
.order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
end
def builds_for_specific_runner
new_builds.where(project: runner.projects.without_deleted.with_builds_enabled).order('created_at ASC')
def builds_for_project_runner
new_builds.where(project: runner.projects.without_deleted.with_builds_enabled).order('id ASC')
end
def builds_for_group_runner
hierarchy_groups = Gitlab::GroupHierarchy.new(runner.groups).base_and_descendants
projects = Project.where(namespace_id: hierarchy_groups)
.with_group_runners_enabled
.with_builds_enabled
.without_deleted
new_builds.where(project: projects).order('id ASC')
end
def running_builds_for_shared_runners
......@@ -97,10 +108,6 @@ module Ci
builds
end
def shared_runner_build_limits_feature_enabled?
ENV['DISABLE_SHARED_RUNNER_BUILD_MINUTES_LIMIT'].to_s != 'true'
end
def register_failure
failed_attempt_counter.increment
attempt_counter.increment
......
module Ci
class UpdateBuildQueueService
def execute(build)
build.project.runners.each do |runner|
if runner.can_pick?(build)
runner.tick_runner_queue
end
end
tick_for(build, build.project.all_runners)
end
return unless build.project.shared_runners_enabled?
private
Ci::Runner.shared.each do |runner|
if runner.can_pick?(build)
runner.tick_runner_queue
end
def tick_for(build, runners)
runners.each do |runner|
runner.pick_build!(build)
end
end
end
......
......@@ -142,7 +142,7 @@ module Projects
if @project
@project.errors.add(:base, message)
@project.mark_import_as_failed(message) if @project.import?
@project.mark_import_as_failed(message) if @project.persisted? && @project.import?
end
@project
......
module Users
class RespondToTermsService
def initialize(user, term)
@user, @term = user, term
end
def execute(accepted:)
agreement = @user.term_agreements.find_or_initialize_by(term: @term)
agreement.accepted = accepted
if agreement.save
store_accepted_term(accepted)
end
agreement
end
private
def store_accepted_term(accepted)
@user.update_column(:accepted_term_id, accepted ? @term.id : nil)
end
end
end
......@@ -41,7 +41,7 @@ class WebHookService
http_status: response.code,
message: response.to_s
}
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout => e
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError => e
log_execution(
trigger: hook_name,
url: hook.url,
......
= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
.form-group
.col-sm-12
.checkbox
= f.label :enforce_terms do
= f.check_box :enforce_terms
= _("Require all users to accept Terms of Service when they access GitLab.")
.help-block
= _("When enabled, users cannot use GitLab until the terms have been accepted.")
.form-group
.col-sm-12
= f.label :terms do
= _("Terms of Service Agreement")
.col-sm-12
= f.text_area :terms, class: 'form-control', rows: 8
.help-block
= _("Markdown enabled")
= f.submit _("Save changes"), class: "btn btn-success"
......@@ -8,7 +8,7 @@
%h4
= _('Visibility and access controls')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
= expanded ? _('Collapse') : _('Expand')
%p
= _('Set default and restrict visibility levels. Configure import sources and git access protocol.')
.settings-content
......@@ -19,7 +19,7 @@
%h4
= _('Account and limit settings')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
= expanded ? _('Collapse') : _('Expand')
%p
= _('Session expiration, projects limit and attachment size.')
.settings-content
......@@ -30,7 +30,7 @@
%h4
= _('Sign-up restrictions')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
= expanded ? _('Collapse') : _('Expand')
%p
= _('Configure the way a user creates a new account.')
.settings-content
......@@ -41,18 +41,29 @@
%h4
= _('Sign-in restrictions')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
= expanded ? _('Collapse') : _('Expand')
%p
= _('Set requirements for a user to sign-in. Enable mandatory two-factor authentication.')
.settings-content
= render 'signin'
%section.settings.as-terms.no-animate#js-terms-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
= _('Terms of Service')
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Include a Terms of Service agreement that all users must accept.')
.settings-content
= render 'terms'
%section.settings.as-help-page.no-animate#js-help-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
= _('Help page')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Help page text and support page url.')
.settings-content
......@@ -62,8 +73,8 @@
.settings-header
%h4
= _('Pages')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Size and domain settings for static websites')
.settings-content
......@@ -73,8 +84,8 @@
.settings-header
%h4
= _('Continuous Integration and Deployment')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Auto DevOps, runners and job artifacts')
.settings-content
......@@ -84,8 +95,8 @@
.settings-header
%h4
= _('Metrics - Influx')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Enable and configure InfluxDB metrics.')
.settings-content
......@@ -95,8 +106,8 @@
.settings-header
%h4
= _('Metrics - Prometheus')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Enable and configure Prometheus metrics.')
.settings-content
......@@ -106,8 +117,8 @@
.settings-header
%h4
= _('Profiling - Performance bar')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Enable the Performance Bar for a given group.')
= link_to icon('question-circle'), help_page_path('administration/monitoring/performance/performance_bar')
......@@ -118,8 +129,8 @@
.settings-header
%h4
= _('Background jobs')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Configure Sidekiq job throttling.')
.settings-content
......@@ -129,8 +140,8 @@
.settings-header
%h4
= _('Spam and Anti-bot Protection')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Enable reCAPTCHA or Akismet and set IP limits.')
.settings-content
......@@ -140,8 +151,8 @@
.settings-header
%h4
= _('Abuse reports')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Set notification email for abuse reports.')
.settings-content
......@@ -151,8 +162,8 @@
.settings-header
%h4
= _('Error Reporting and Logging')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Enable Sentry for error reporting and logging.')
.settings-content
......@@ -162,8 +173,8 @@
.settings-header
%h4
= _('Repository storage')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Configure storage path and circuit breaker settings.')
.settings-content
......@@ -173,8 +184,8 @@
.settings-header
%h4
= _('Repository maintenance')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Configure automatic git checks and housekeeping on repositories.')
.settings-content
......@@ -185,8 +196,8 @@
.settings-header
%h4
= _('Container Registry')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Various container registry settings.')
.settings-content
......@@ -197,8 +208,8 @@
.settings-header
%h4
= _('Koding')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Online IDE integration settings.')
.settings-content
......@@ -208,8 +219,8 @@
.settings-header
%h4
= _('PlantUML')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Allow rendering of PlantUML diagrams in Asciidoc documents.')
.settings-content
......@@ -219,8 +230,8 @@
.settings-header#usage-statistics
%h4
= _('Usage statistics')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Enable or disable version check and usage ping.')
.settings-content
......@@ -230,8 +241,8 @@
.settings-header
%h4
= _('Email')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Various email settings.')
.settings-content
......@@ -241,8 +252,8 @@
.settings-header
%h4
= _('Gitaly')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Configure Gitaly timeouts.')
.settings-content
......@@ -252,8 +263,8 @@
.settings-header
%h4
= _('Web terminal')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Set max session time for web terminal.')
.settings-content
......@@ -263,8 +274,8 @@
.settings-header
%h4
= _('Real-time features')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Change this value to influence how frequently the GitLab UI polls for updates.')
.settings-content
......@@ -274,8 +285,8 @@
.settings-header
%h4
= _('Performance optimization')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Various settings that affect GitLab performance.')
.settings-content
......@@ -285,8 +296,8 @@
.settings-header
%h4
= _('User and IP Rate Limits')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Configure limits for web and API requests.')
.settings-content
......@@ -296,8 +307,8 @@
.settings-header
%h4
= _('Outbound requests')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Allow requests to the local network from hooks and services.')
.settings-content
......
......@@ -2,6 +2,8 @@
%td
- if runner.shared?
%span.label.label-success shared
- elsif runner.group_type?
%span.label.label-success group
- else
%span.label.label-info specific
- if runner.locked?
......@@ -19,7 +21,7 @@
%td
= runner.ip_address
%td
- if runner.shared?
- if runner.shared? || runner.group_type?
n/a
- else
= runner.projects.count(:all)
......@@ -31,7 +33,7 @@
= tag
%td
- if runner.contacted_at
= time_ago_with_tooltip runner.contacted_at
#{time_ago_in_words(runner.contacted_at)} ago
- else
Never
%td.admin-runner-btn-group-cell
......
......@@ -16,6 +16,9 @@
%li
%span.label.label-success shared
\- Runner runs jobs from all unassigned projects
%li
%span.label.label-success group
\- Runner runs jobs from all unassigned projects in its group
%li
%span.label.label-info specific
\- Runner runs jobs from assigned projects
......
......@@ -19,6 +19,9 @@
%p
If you want Runners to build only specific projects, enable them in the table below.
Keep in mind that this is a one way transition.
- elsif @runner.group_type?
.bs-callout.bs-callout-success
%h4 This runner will process jobs from all projects in its group and subgroups
- else
.bs-callout.bs-callout-info
%h4 This Runner will process jobs only from ASSIGNED projects
......
......@@ -42,31 +42,31 @@
= nav_link(html_options: { class: active_when(params[:filter].nil?) }) do
= link_to admin_users_path do
Active
%small.badge= number_with_delimiter(User.active.count)
%small.badge= limited_counter_with_delimiter(User.active)
= nav_link(html_options: { class: active_when(params[:filter] == 'admins') }) do
= link_to admin_users_path(filter: "admins") do
Admins
%small.badge= number_with_delimiter(User.admins.count)
%small.badge= limited_counter_with_delimiter(User.admins)
= nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_enabled')} filter-two-factor-enabled" }) do
= link_to admin_users_path(filter: 'two_factor_enabled') do
2FA Enabled
%small.badge= number_with_delimiter(User.with_two_factor.count)
%small.badge= limited_counter_with_delimiter(User.with_two_factor)
= nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_disabled')} filter-two-factor-disabled" }) do
= link_to admin_users_path(filter: 'two_factor_disabled') do
2FA Disabled
%small.badge= number_with_delimiter(User.without_two_factor.count)
%small.badge= limited_counter_with_delimiter(User.without_two_factor)
= nav_link(html_options: { class: active_when(params[:filter] == 'external') }) do
= link_to admin_users_path(filter: 'external') do
External
%small.badge= number_with_delimiter(User.external.count)
%small.badge= limited_counter_with_delimiter(User.external)
= nav_link(html_options: { class: active_when(params[:filter] == 'blocked') }) do
= link_to admin_users_path(filter: "blocked") do
Blocked
%small.badge= number_with_delimiter(User.blocked.count)
%small.badge= limited_counter_with_delimiter(User.blocked)
= nav_link(html_options: { class: active_when(params[:filter] == 'wop') }) do
= link_to admin_users_path(filter: "wop") do
Without projects
%small.badge= number_with_delimiter(User.without_projects.count)
%small.badge= limited_counter_with_delimiter(User.without_projects)
%ul.flex-list.content-list
- if @users.empty?
......
- extra_flash_class = local_assigns.fetch(:extra_flash_class, nil)
.flash-container.flash-container-page
-# We currently only support `alert`, `notice`, `success`
- flash.each do |key, value|
-# Don't show a flash message if the message is nil
- if value
%div{ class: "flash-#{key}" }
%div{ class: (container_class) }
%div{ class: "#{container_class} #{extra_flash_class}" }
%span= value
- return unless current_user
%ul
%li.current-user
.user-name.bold
= current_user.name
= current_user.to_reference
%li.divider
- if current_user_menu?(:profile)
%li
= link_to s_("CurrentUser|Profile"), current_user, class: 'profile-link', data: { user: current_user.username }
- if current_user_menu?(:settings)
%li
= link_to s_("CurrentUser|Settings"), profile_path
- if current_user_menu?(:help)
%li
= link_to _("Help"), help_path
- if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
%li.divider
- if current_user_menu?(:sign_out)
%li
= link_to _("Sign out"), destroy_user_session_path, class: "sign-out-link"
......@@ -53,22 +53,7 @@
= image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar"
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
%li.current-user
.user-name.bold
= current_user.name
@#{current_user.username}
%li.divider
%li
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li
= link_to "Settings", profile_path
- if current_user
%li
= link_to "Help", help_path
%li.divider
%li
= link_to "Sign out", destroy_user_session_path, class: "sign-out-link"
= render 'layouts/header/current_user_dropdown'
- if header_link?(:admin_impersonation)
%li.impersonation
= link_to admin_impersonation_path, class: 'impersonation-btn', method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
......
!!! 5
- @hide_breadcrumbs = true
%html{ lang: I18n.locale, class: page_class }
= render "layouts/head"
%body{ data: { page: body_data_page } }
.layout-page.terms{ class: page_class }
.content-wrapper.prepend-top-0
.mobile-overlay
.alert-wrapper
= render "layouts/broadcast"
= render 'layouts/header/read_only_banner'
= render "layouts/flash", extra_flash_class: 'limit-container-width'
%div{ class: "#{container_class} limit-container-width" }
.content{ id: "content-body" }
.panel.panel-default
.panel-heading
.title
= brand_header_logo
- logo_text = brand_header_logo_type
- if logo_text.present?
%span.logo-text.hidden-xs.prepend-left-8
= logo_text
- if header_link?(:user_dropdown)
.navbar-collapse.collapse
%ul.nav.navbar-nav
%li.header-user.dropdown
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
= image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar"
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu-nav.dropdown-menu-align-right
= render 'layouts/header/current_user_dropdown'
= yield
- active_tab = local_assigns.fetch(:active_tab, 'blank')
- f = local_assigns.fetch(:f)
.project-import.row
.col-lg-12
.form-group.import-btn-container.clearfix
= f.label :visibility_level, class: 'label-light' do #the label here seems wrong
Import project from
.import-buttons
- if gitlab_project_import_enabled?
.import_gitlab_project.has-tooltip{ data: { container: 'body' } }
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export')
%div
- if github_import_enabled?
= link_to new_import_github_path, class: 'btn js-import-github' do
= icon('github', text: 'GitHub')
%div
- if bitbucket_import_enabled?
= link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do
= icon('bitbucket', text: 'Bitbucket')
- unless bitbucket_import_configured?
= render 'bitbucket_import_modal'
%div
- if gitlab_import_enabled?
= link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do
= icon('gitlab', text: 'GitLab.com')
- unless gitlab_import_configured?
= render 'gitlab_import_modal'
%div
- if google_code_import_enabled?
= link_to new_import_google_code_path, class: 'btn import_google_code' do
= icon('google', text: 'Google Code')
%div
- if fogbugz_import_enabled?
= link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
= icon('bug', text: 'Fogbugz')
%div
- if gitea_import_enabled?
= link_to new_import_gitea_path, class: 'btn import_gitea' do
= custom_icon('go_logo')
Gitea
%div
- if git_import_enabled?
%button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' } }
= icon('git', text: 'Repo by URL')
.col-lg-12
.js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') }
%hr
= render "shared/import_form", f: f
= render 'new_project_fields', f: f, project_name_id: "import-url-name"
......@@ -57,54 +57,11 @@
.tab-pane.import-project-pane.js-toggle-container{ id: 'import-project-pane', class: active_when(active_tab == 'import'), role: 'tabpanel' }
= form_for @project, html: { class: 'new_project' } do |f|
- if import_sources_enabled?
.project-import.row
.col-lg-12
.form-group.import-btn-container.clearfix
= f.label :visibility_level, class: 'label-light' do #the label here seems wrong
Import project from
.import-buttons
- if gitlab_project_import_enabled?
.import_gitlab_project.has-tooltip{ data: { container: 'body' } }
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
= icon('gitlab', text: 'GitLab export')
%div
- if github_import_enabled?
= link_to new_import_github_path, class: 'btn js-import-github' do
= icon('github', text: 'GitHub')
%div
- if bitbucket_import_enabled?
= link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}" do
= icon('bitbucket', text: 'Bitbucket')
- unless bitbucket_import_configured?
= render 'bitbucket_import_modal'
%div
- if gitlab_import_enabled?
= link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}" do
= icon('gitlab', text: 'GitLab.com')
- unless gitlab_import_configured?
= render 'gitlab_import_modal'
%div
- if google_code_import_enabled?
= link_to new_import_google_code_path, class: 'btn import_google_code' do
= icon('google', text: 'Google Code')
%div
- if fogbugz_import_enabled?
= link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
= icon('bug', text: 'Fogbugz')
%div
- if gitea_import_enabled?
= link_to new_import_gitea_path, class: 'btn import_gitea' do
= custom_icon('go_logo')
Gitea
%div
- if git_import_enabled?
%button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' } }
= icon('git', text: 'Repo by URL')
.col-lg-12
.js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') }
%hr
= render "shared/import_form", f: f
= render 'new_project_fields', f: f, project_name_id: "import-url-name"
= render 'import_project_pane', f: f, active_tab: active_tab
- else
.nothing-here-block
%h4 No import options available
%p Contact an administrator to enable options for importing your project.
.save-project-loader.hide
.center
......
- breadcrumb_title "Pipelines"
- page_title = s_("Pipeline|Run Pipeline")
- settings_link = link_to _('CI/CD settings'), project_settings_ci_cd_path(@project)
%h3.page-title
= s_("Pipeline|Run Pipeline")
......@@ -8,17 +9,26 @@
= form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do |f|
= form_errors(@pipeline)
.form-group
= f.label :ref, s_('Pipeline|Run on'), class: 'control-label'
.col-sm-10
.col-sm-12
= f.label :ref, s_('Pipeline|Create for')
= hidden_field_tag 'pipeline[ref]', params[:ref] || @project.default_branch
= dropdown_tag(params[:ref] || @project.default_branch,
options: { toggle_class: 'js-branch-select wide git-revision-dropdown-toggle',
filter: true, dropdown_class: "dropdown-menu-selectable git-revision-dropdown", placeholder: s_("Pipeline|Search branches"),
data: { selected: params[:ref] || @project.default_branch, field_name: 'pipeline[ref]' } })
.help-block
= s_("Pipeline|Existing branch name, tag")
= s_("Pipeline|Existing branch name or tag")
.col-sm-12.prepend-top-10.js-ci-variable-list-section
%label
= s_('Pipeline|Variables')
%ul.ci-variable-list
= render 'ci/variables/variable_row', form_field: 'pipeline', only_key_value: true
.help-block
= (s_("Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default.") % {settings_link: settings_link}).html_safe
.form-actions
= f.submit s_('Pipeline|Run pipeline'), class: 'btn btn-success', tabindex: 3
= f.submit s_('Pipeline|Create pipeline'), class: 'btn btn-success js-variables-save-button', tabindex: 3
= link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-default pull-right'
-# haml-lint:disable InlineJavaScript
......
%h3 Group Runners
.bs-callout.bs-callout-warning
GitLab Group Runners can execute code for all the projects in this group.
They can be managed using the #{link_to 'Runners API', help_page_path('api/runners.md')}.
- if @project.group
%hr
- if @project.group_runners_enabled?
= link_to toggle_group_runners_project_runners_path(@project), class: 'btn btn-warning', method: :post do
Disable group Runners
- else
= link_to toggle_group_runners_project_runners_path(@project), class: 'btn btn-success', method: :post do
Enable group Runners
&nbsp; for this project
- if !@project.group
This project does not belong to a group and can therefore not make use of group Runners.
- elsif @group_runners.empty?
This group does not provide any group Runners yet.
- if can?(current_user, :admin_pipeline, @project.group)
= render partial: 'ci/runner/how_to_setup_runner',
locals: { registration_token: @project.group.runners_token, type: 'group' }
- else
Ask your group master to setup a group Runner.
- else
%h4.underlined-title Available group Runners : #{@group_runners.count}
%ul.bordered-list
= render partial: 'projects/runners/runner', collection: @group_runners, as: :runner
......@@ -23,3 +23,7 @@
= render 'projects/runners/specific_runners'
.col-sm-6
= render 'projects/runners/shared_runners'
.row
.col-sm-6
.col-sm-6
= render 'projects/runners/group_runners'
......@@ -26,7 +26,7 @@
- else
- runner_project = @project.runner_projects.find_by(runner_id: runner)
= link_to 'Disable for this project', project_runner_project_path(@project, runner_project), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
- elsif runner.specific?
- elsif !(runner.is_shared? || runner.group_type?) # We can simplify this to `runner.project_type?` when migrating #runner_type is complete
= form_for [@project.namespace.becomes(Namespace), @project, @project.runner_projects.new] do |f|
= f.hidden_field :runner_id, value: runner.id
= f.submit 'Enable for this project', class: 'btn btn-sm'
......
......@@ -62,6 +62,6 @@
%td Last contact
%td
- if @runner.contacted_at
= time_ago_with_tooltip @runner.contacted_at
#{time_ago_in_words(@runner.contacted_at)} ago
- else
Never
- redirect_params = { redirect: @redirect } if @redirect
.panel-content.rendered-terms
= markdown_field(@term, :terms)
.row-content-block.footer-block.clearfix
- if can?(current_user, :accept_terms, @term)
.pull-right
= button_to accept_term_path(@term, redirect_params), class: 'btn btn-success prepend-left-8' do
= _('Accept terms')
- if can?(current_user, :decline_terms, @term)
.pull-right
= button_to decline_term_path(@term, redirect_params), class: 'btn btn-default prepend-left-8' do
= _('Decline and sign out')
......@@ -52,6 +52,7 @@
- pipeline_creation:create_pipeline
- pipeline_creation:run_pipeline_schedule
- pipeline_background:archive_trace
- pipeline_background:ci_build_trace_chunk_flush
- pipeline_default:build_coverage
- pipeline_default:build_trace_sections
- pipeline_default:pipeline_metrics
......@@ -66,7 +67,6 @@
- pipeline_processing:pipeline_update
- pipeline_processing:stage_update
- pipeline_processing:update_head_pipeline_for_merge_request
- pipeline_processing:ci_build_trace_chunk_flush
- repository_check:repository_check_clear
- repository_check:repository_check_single_repository
......
module Ci
class BuildTraceChunkFlushWorker
include ApplicationWorker
queue_namespace :pipeline_processing
include PipelineBackgroundQueue
def perform(build_trace_chunk_id)
::Ci::BuildTraceChunk.find_by(id: build_trace_chunk_id).try do |build_trace_chunk|
......
......@@ -63,11 +63,10 @@ module Gitlab
end
def find_project(id)
# We only care about the import JID so we can refresh it. We also only
# want the project if it hasn't been marked as failed yet. It's possible
# the import gets marked as stuck when jobs of the current stage failed
# somehow.
Project.select(:import_jid).import_started.find_by(id: id)
# TODO: Only select the JID
# This is due to the fact that the JID could be present in either the project record or
# its associated import_state record
Project.import_started.find_by(id: id)
end
end
end
......
......@@ -31,7 +31,10 @@ module Gitlab
end
def find_project(id)
Project.select(:import_jid).import_started.find_by(id: id)
# TODO: Only select the JID
# This is due to the fact that the JID could be present in either the project record or
# its associated import_state record
Project.import_started.find_by(id: id)
end
end
end
......
......@@ -22,7 +22,8 @@ class StuckImportJobsWorker
end
def mark_projects_with_jid_as_failed!
jids_and_ids = enqueued_projects_with_jid.pluck(:import_jid, :id).to_h
# TODO: Rollback this change to use SQL through #pluck
jids_and_ids = enqueued_projects_with_jid.map { |project| [project.import_jid, project.id] }.to_h
# Find the jobs that aren't currently running or that exceeded the threshold.
completed_jids = Gitlab::SidekiqStatus.completed_jids(jids_and_ids.keys)
......@@ -42,15 +43,15 @@ class StuckImportJobsWorker
end
def enqueued_projects
Project.with_import_status(:scheduled, :started)
Project.joins_import_state.where("(import_state.status = 'scheduled' OR import_state.status = 'started') OR (projects.import_status = 'scheduled' OR projects.import_status = 'started')")
end
def enqueued_projects_with_jid
enqueued_projects.where.not(import_jid: nil)
enqueued_projects.where.not("import_state.jid IS NULL AND projects.import_jid IS NULL")
end
def enqueued_projects_without_jid
enqueued_projects.where(import_jid: nil)
enqueued_projects.where("import_state.jid IS NULL AND projects.import_jid IS NULL")
end
def error_message
......
---
title: Reconcile project templates with Auto DevOps
merge_request: 18737
author:
type: changed
---
title: Enable specifying variables when executing a manual pipeline
merge_request: 18440
author:
type: changed
---
title: Resolve Import/Export ci_cd_settings error updating the project
merge_request: 46049
author:
type: fixed
---
title: Allow admins to enforce accepting Terms of Service on an instance
merge_request: 18570
author:
type: added
---
title: Ensure web hook 'blocked URL' errors are stored in web hook logs and properly
surfaced to the user
merge_request:
author:
type: fixed
---
title: Allow group masters to configure runners for groups
merge_request: 9646
author: Alexis Reigel
type: added
---
title: Inform the user when there are no project import options available
merge_request: 18716
author: George Tsiolis
type: changed
......@@ -27,7 +27,7 @@ module Sidekiq
Use an `after_commit` hook, or include `AfterCommitQueue` and use a `run_after_commit` block instead.
MSG
rescue Sidekiq::Worker::EnqueueFromTransactionError => e
Rails.logger.error(e.message) if Rails.env.production?
::Rails.logger.error(e.message) if ::Rails.env.production?
Gitlab::Sentry.track_exception(e)
end
end
......
......@@ -409,6 +409,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
collection do
post :toggle_shared_runners
post :toggle_group_runners
end
end
......
......@@ -27,6 +27,13 @@ devise_scope :user do
get '/users/almost_there' => 'confirmations#almost_there'
end
scope '-/users', module: :users do
resources :terms, only: [:index] do
post :accept, on: :member
post :decline, on: :member
end
end
scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) do
scope(path: 'users/:username',
as: :user,
......
class AddCiRunnerNamespaces < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :ci_runner_namespaces do |t|
t.integer :runner_id
t.integer :namespace_id
t.index [:runner_id, :namespace_id], unique: true
t.index :namespace_id
t.foreign_key :ci_runners, column: :runner_id, on_delete: :cascade
t.foreign_key :namespaces, column: :namespace_id, on_delete: :cascade
end
end
end
class AddRunnersTokenToGroups < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :namespaces, :runners_token, :string
end
end
class AddEnforceTermsToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :application_settings, :enforce_terms, :boolean, default: false
end
end
class CreateApplicationSettingTerms < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :application_setting_terms do |t|
t.integer :cached_markdown_version
t.text :terms, null: false
t.text :terms_html
end
end
end
class CreateTermAgreements < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
create_table :term_agreements do |t|
t.references :term, index: true, null: false
t.foreign_key :application_setting_terms, column: :term_id
t.references :user, index: true, null: false, foreign_key: { on_delete: :cascade }
t.boolean :accepted, default: false, null: false
t.timestamps_with_timezone null: false
end
add_index :term_agreements, [:user_id, :term_id],
unique: true,
name: 'term_agreements_unique_index'
end
def down
remove_index :term_agreements, name: 'term_agreements_unique_index'
drop_table :term_agreements
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddAcceptedTermToUsers < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
change_table :users do |t|
t.references :accepted_term,
null: true
end
add_concurrent_foreign_key :users, :application_setting_terms, column: :accepted_term_id
end
def down
remove_foreign_key :users, column: :accepted_term_id
remove_column :users, :accepted_term_id
end
end
class AddRunnerTypeToCiRunners < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :ci_runners, :runner_type, :smallint
end
end
class CreateProjectMirrorData < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
return if table_exists?(:project_mirror_data)
create_table :project_mirror_data do |t|
t.references :project, index: true, foreign_key: { on_delete: :cascade }
t.string :status
t.string :jid
t.text :last_error
end
end
def down
drop_table(:project_mirror_data) if table_exists?(:project_mirror_data)
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddIndexToNamespacesRunnersToken < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :namespaces, :runners_token, unique: true
end
def down
if index_exists?(:namespaces, :runners_token, unique: true)
remove_index :namespaces, :runners_token
end
end
end
class AddIndexesToProjectMirrorData < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :project_mirror_data, :jid
add_concurrent_index :project_mirror_data, :status
end
def down
remove_index :project_mirror_data, :jid if index_exists? :project_mirror_data, :jid
remove_index :project_mirror_data, :status if index_exists? :project_mirror_data, :status
end
end
class BackfillRunnerTypeForCiRunnersPostMigrate < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INSTANCE_RUNNER_TYPE = 1
PROJECT_RUNNER_TYPE = 3
disable_ddl_transaction!
def up
update_column_in_batches(:ci_runners, :runner_type, INSTANCE_RUNNER_TYPE) do |table, query|
query.where(table[:is_shared].eq(true)).where(table[:runner_type].eq(nil))
end
update_column_in_batches(:ci_runners, :runner_type, PROJECT_RUNNER_TYPE) do |table, query|
query.where(table[:is_shared].eq(false)).where(table[:runner_type].eq(nil))
end
end
def down
end
end
class MigrateImportAttributesDataFromProjectsToProjectMirrorData < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
UP_MIGRATION = 'PopulateImportState'.freeze
DOWN_MIGRATION = 'RollbackImportStateData'.freeze
BATCH_SIZE = 1000
DELAY_INTERVAL = 5.minutes
disable_ddl_transaction!
class Project < ActiveRecord::Base
include EachBatch
self.table_name = 'projects'
end
class ProjectImportState < ActiveRecord::Base
include EachBatch
self.table_name = 'project_mirror_data'
end
def up
projects = Project.where.not(import_status: :none)
queue_background_migration_jobs_by_range_at_intervals(projects, UP_MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
end
def down
import_state = ProjectImportState.where.not(status: :none)
queue_background_migration_jobs_by_range_at_intervals(import_state, DOWN_MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180425131009) do
ActiveRecord::Schema.define(version: 20180503175054) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -40,6 +40,12 @@ ActiveRecord::Schema.define(version: 20180425131009) do
t.text "new_project_guidelines_html"
end
create_table "application_setting_terms", force: :cascade do |t|
t.integer "cached_markdown_version"
t.text "terms", null: false
t.text "terms_html"
end
create_table "application_settings", force: :cascade do |t|
t.integer "default_projects_limit"
t.boolean "signup_enabled"
......@@ -158,6 +164,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do
t.string "auto_devops_domain"
t.boolean "pages_domain_verification_enabled", default: true, null: false
t.boolean "allow_local_requests_from_hooks_and_services", default: false, null: false
t.boolean "enforce_terms", default: false
end
create_table "audit_events", force: :cascade do |t|
......@@ -453,6 +460,14 @@ ActiveRecord::Schema.define(version: 20180425131009) do
add_index "ci_pipelines", ["status"], name: "index_ci_pipelines_on_status", using: :btree
add_index "ci_pipelines", ["user_id"], name: "index_ci_pipelines_on_user_id", using: :btree
create_table "ci_runner_namespaces", force: :cascade do |t|
t.integer "runner_id"
t.integer "namespace_id"
end
add_index "ci_runner_namespaces", ["namespace_id"], name: "index_ci_runner_namespaces_on_namespace_id", using: :btree
add_index "ci_runner_namespaces", ["runner_id", "namespace_id"], name: "index_ci_runner_namespaces_on_runner_id_and_namespace_id", unique: true, using: :btree
create_table "ci_runner_projects", force: :cascade do |t|
t.integer "runner_id", null: false
t.datetime "created_at"
......@@ -481,6 +496,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do
t.integer "access_level", default: 0, null: false
t.string "ip_address"
t.integer "maximum_timeout"
t.integer "runner_type", limit: 2
end
add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree
......@@ -1270,6 +1286,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do
t.boolean "require_two_factor_authentication", default: false, null: false
t.integer "two_factor_grace_period", default: 48, null: false
t.integer "cached_markdown_version"
t.string "runners_token"
end
add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree
......@@ -1280,6 +1297,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do
add_index "namespaces", ["path"], name: "index_namespaces_on_path", using: :btree
add_index "namespaces", ["path"], name: "index_namespaces_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
add_index "namespaces", ["require_two_factor_authentication"], name: "index_namespaces_on_require_two_factor_authentication", using: :btree
add_index "namespaces", ["runners_token"], name: "index_namespaces_on_runners_token", unique: true, using: :btree
add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree
create_table "notes", force: :cascade do |t|
......@@ -1509,6 +1527,17 @@ ActiveRecord::Schema.define(version: 20180425131009) do
add_index "project_import_data", ["project_id"], name: "index_project_import_data_on_project_id", using: :btree
create_table "project_mirror_data", force: :cascade do |t|
t.integer "project_id"
t.string "status"
t.string "jid"
t.text "last_error"
end
add_index "project_mirror_data", ["jid"], name: "index_project_mirror_data_on_jid", using: :btree
add_index "project_mirror_data", ["project_id"], name: "index_project_mirror_data_on_project_id", using: :btree
add_index "project_mirror_data", ["status"], name: "index_project_mirror_data_on_status", using: :btree
create_table "project_statistics", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "namespace_id", null: false
......@@ -1815,6 +1844,18 @@ ActiveRecord::Schema.define(version: 20180425131009) do
add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree
create_table "term_agreements", force: :cascade do |t|
t.integer "term_id", null: false
t.integer "user_id", null: false
t.boolean "accepted", default: false, null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
end
add_index "term_agreements", ["term_id"], name: "index_term_agreements_on_term_id", using: :btree
add_index "term_agreements", ["user_id", "term_id"], name: "term_agreements_unique_index", unique: true, using: :btree
add_index "term_agreements", ["user_id"], name: "index_term_agreements_on_user_id", using: :btree
create_table "timelogs", force: :cascade do |t|
t.integer "time_spent", null: false
t.integer "user_id"
......@@ -2003,6 +2044,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do
t.string "preferred_language"
t.string "rss_token"
t.integer "theme_id", limit: 2
t.integer "accepted_term_id"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......@@ -2097,6 +2139,8 @@ ActiveRecord::Schema.define(version: 20180425131009) do
add_foreign_key "ci_pipelines", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_3d34ab2e06", on_delete: :nullify
add_foreign_key "ci_pipelines", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_262d4c2d19", on_delete: :nullify
add_foreign_key "ci_pipelines", "projects", name: "fk_86635dbd80", on_delete: :cascade
add_foreign_key "ci_runner_namespaces", "ci_runners", column: "runner_id", on_delete: :cascade
add_foreign_key "ci_runner_namespaces", "namespaces", on_delete: :cascade
add_foreign_key "ci_runner_projects", "projects", name: "fk_4478a6f1e4", on_delete: :cascade
add_foreign_key "ci_stages", "ci_pipelines", column: "pipeline_id", name: "fk_fb57e6cc56", on_delete: :cascade
add_foreign_key "ci_stages", "projects", name: "fk_2360681d1d", on_delete: :cascade
......@@ -2188,6 +2232,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do
add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade
add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade
add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade
add_foreign_key "project_mirror_data", "projects", on_delete: :cascade
add_foreign_key "project_statistics", "projects", on_delete: :cascade
add_foreign_key "protected_branch_merge_access_levels", "protected_branches", name: "fk_8a3072ccb3", on_delete: :cascade
add_foreign_key "protected_branch_push_access_levels", "protected_branches", name: "fk_9ffc86a3d9", on_delete: :cascade
......@@ -2202,6 +2247,8 @@ ActiveRecord::Schema.define(version: 20180425131009) do
add_foreign_key "snippets", "projects", name: "fk_be41fd4bb7", on_delete: :cascade
add_foreign_key "subscriptions", "projects", on_delete: :cascade
add_foreign_key "system_note_metadata", "notes", name: "fk_d83a918cb1", on_delete: :cascade
add_foreign_key "term_agreements", "application_setting_terms", column: "term_id"
add_foreign_key "term_agreements", "users", on_delete: :cascade
add_foreign_key "timelogs", "issues", name: "fk_timelogs_issues_issue_id", on_delete: :cascade
add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade
add_foreign_key "todos", "notes", name: "fk_91d1f47b13", on_delete: :cascade
......@@ -2215,6 +2262,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do
add_foreign_key "user_interacted_projects", "projects", name: "fk_722ceba4f7", on_delete: :cascade
add_foreign_key "user_interacted_projects", "users", name: "fk_0894651f08", on_delete: :cascade
add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade
add_foreign_key "users", "application_setting_terms", column: "accepted_term_id", name: "fk_789cd90b35", on_delete: :cascade
add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
add_foreign_key "web_hook_logs", "web_hooks", on_delete: :cascade
add_foreign_key "web_hooks", "projects", name: "fk_0c8ca6d9d1", on_delete: :cascade
......
......@@ -40,6 +40,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
[source installations](../install/installation.md#installation-from-source).
- [Environment variables](environment_variables.md): Supported environment variables that can be used to override their defaults values in order to configure GitLab.
- [Plugins](plugins.md): With custom plugins, GitLab administrators can introduce custom integrations without modifying GitLab's source code.
- [Enforcing Terms of Service](../user/admin_area/settings/terms.md)
#### Customizing GitLab's appearance
......
......@@ -53,6 +53,8 @@ Example response:
"dsa_key_restriction": 0,
"ecdsa_key_restriction": 0,
"ed25519_key_restriction": 0,
"enforce_terms": true,
"terms": "Hello world!",
}
```
......@@ -153,6 +155,8 @@ PUT /application/settings
| `user_default_external` | boolean | no | Newly registered users will by default be external |
| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider |
| `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. |
| `enforce_terms` | boolean | no | Enforce application ToS to all users |
| `terms` | text | yes (if `enforce_terms` is true) | Markdown content for the ToS |
```bash
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/application/settings?signup_enabled=false&default_project_visibility=internal
......@@ -195,5 +199,7 @@ Example response:
"dsa_key_restriction": 0,
"ecdsa_key_restriction": 0,
"ed25519_key_restriction": 0,
"enforce_terms": true,
"terms": "Hello world!",
}
```
......@@ -310,7 +310,7 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
}));
```
1. Don not use a singleton for the service or the store
1. Do not use a singleton for the service or the store
```javascript
// bad
class Store {
......@@ -328,9 +328,11 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
}
}
```
1. Use `.vue` for Vue templates. Do not use `%template` in HAML.
#### Naming
1. **Extensions**: Use `.vue` extension for Vue components.
1. **Extensions**: Use `.vue` extension for Vue components. Do not use `.js` as file extension ([#34371]).
1. **Reference Naming**: Use PascalCase for their instances:
```javascript
// bad
......@@ -364,6 +366,8 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
<component my-prop="prop" />
```
[#34371]: https://gitlab.com/gitlab-org/gitlab-ce/issues/34371
#### Alignment
1. Follow these alignment styles for the template method:
1. With more than one attribute, all attributes should be on a new line:
......
......@@ -389,7 +389,7 @@ If you have installed GitLab using a different method, you need to:
1. [Deploy Prometheus](../../user/project/integrations/prometheus.md#configuring-your-own-prometheus-server-within-kubernetes) into your Kubernetes cluster
1. If you would like response metrics, ensure you are running at least version
0.9.0 of NGINX Ingress and
[enable Prometheus metrics](https://github.com/kubernetes/ingress/blob/master/examples/customization/custom-vts-metrics/nginx/nginx-vts-metrics-conf.yaml).
[enable Prometheus metrics](https://github.com/kubernetes/ingress-nginx/blob/master/docs/examples/customization/custom-vts-metrics-prometheus/nginx-vts-metrics-conf.yaml).
1. Finally, [annotate](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/)
the NGINX Ingress deployment to be scraped by Prometheus using
`prometheus.io/scrape: "true"` and `prometheus.io/port: "10254"`.
......
# Enforce accepting Terms of Service
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18570)
> in [GitLab Core](https://about.gitlab.com/pricing/) 10.8
## Configuration
When it is required for all users of the GitLab instance to accept the
Terms of Service, this can be configured by an admin on the settings
page:
![Enable enforcing Terms of Service](img/enforce_terms.png).
The terms itself can be entered using Markdown. For each update to the
terms, a new version is stored. When a user accepts or declines the
terms, GitLab will keep track of which version they accepted or
declined.
When an admin enables this feature, they will automattically be
directed to the page to accept the terms themselves. After they
accept, they will be directed back to the settings page.
## Accepting terms
When this feature was enabled, the users that have not accepted the
terms of service will be presented with a screen where they can either
accept or decline the terms.
![Respond to terms](img/respond_to_terms.png)
When the user accepts the terms, they will be directed to where they
were going. After a sign-in or sign-up this will most likely be the
dashboard.
When the user was already logged in when the feature was turned on,
they will be asked to accept the terms on their next interaction.
When a user declines the terms, they will be signed out.
@project_commits
Feature: Project Commits
Background:
Given I sign in as a user
And I own a project
And I visit my project's commits page
Scenario: I browse commits list for master branch
Then I see project commits
And I should not see button to create a new merge request
Then I click the "Compare" tab
And I should not see button to create a new merge request
Scenario: I browse commits list for feature branch without a merge request
Given I visit commits list page for feature branch
Then I see feature branch commits
And I see button to create a new merge request
Then I click the "Compare" tab
And I see button to create a new merge request
Scenario: I browse commits list for feature branch with an open merge request
Given project have an open merge request
And I visit commits list page for feature branch
Then I see feature branch commits
And I should not see button to create a new merge request
And I should see button to the merge request
Then I click the "Compare" tab
And I should not see button to create a new merge request
And I should see button to the merge request
Scenario: I browse atom feed of commits list for master branch
Given I click atom feed link
Then I see commits atom feed
Scenario: I browse commit from list
Given I click on commit link
Then I see commit info
And I see side-by-side diff button
Scenario: I browse commit from list and create a new tag
Given I click on commit link
And I click on tag link
Then I see commit SHA pre-filled
Scenario: I browse commit with ci from list
Given commit has ci status
And repository contains ".gitlab-ci.yml" file
When I click on commit link
Then I see commit ci info
Scenario: I browse commit with side-by-side diff view
Given I click on commit link
And I click side-by-side diff button
Then I see inline diff button
@javascript
Scenario: I compare branches without a merge request
Given I visit compare refs page
And I fill compare fields with branches
Then I see compared branches
And I see button to create a new merge request
@javascript
Scenario: I compare branches with an open merge request
Given project have an open merge request
And I visit compare refs page
And I fill compare fields with branches
Then I see compared branches
And I should not see button to create a new merge request
And I should see button to the merge request
@javascript
Scenario: I compare refs
Given I visit compare refs page
And I fill compare fields with refs
Then I see compared refs
And I unfold diff
Then I should see additional file lines
Scenario: I browse commits for a specific path
Given I visit my project's commits page for a specific path
Then I see breadcrumb links
# TODO: Implement feature in graphs
#Scenario: I browse commits stats
#Given I visit my project's commits stats page
#Then I see commits stats
Scenario: I browse a commit with an image
Given I visit a commit with an image that changed
Then The diff links to both the previous and current image
@javascript
Scenario: I filter commits by message
When I search "submodules" commits
Then I should see only "submodules" commits
class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
include SharedDiffNote
include RepoHelpers
step 'I see project commits' do
commit = @project.repository.commit
expect(page).to have_content(@project.name)
expect(page).to have_content(commit.message[0..20])
expect(page).to have_content(commit.short_id)
end
step 'I click atom feed link' do
click_link "Commits feed"
end
step 'I see commits atom feed' do
commit = @project.repository.commit
expect(response_headers['Content-Type']).to have_content("application/atom+xml")
expect(body).to have_selector("title", text: "#{@project.name}:master commits")
expect(body).to have_selector("author email", text: commit.author_email)
expect(body).to have_selector("entry summary", text: commit.description[0..10].delete("\r\n"))
end
step 'I click on tag link' do
click_link "Tag"
end
step 'I see commit SHA pre-filled' do
expect(page).to have_selector("input[value='#{sample_commit.id}']")
end
step 'I click on commit link' do
visit project_commit_path(@project, sample_commit.id)
end
step 'I see commit info' do
expect(page).to have_content sample_commit.message
expect(page).to have_content "Showing #{sample_commit.files_changed_count} changed files"
end
step 'I fill compare fields with branches' do
select_using_dropdown('from', 'feature')
select_using_dropdown('to', 'master')
click_button 'Compare'
end
step 'I fill compare fields with refs' do
select_using_dropdown('from', sample_commit.parent_id, true)
select_using_dropdown('to', sample_commit.id, true)
click_button "Compare"
end
step 'I unfold diff' do
@diff = first('.js-unfold')
@diff.click
sleep 2
end
step 'I should see additional file lines' do
page.within @diff.query_scope do
expect(first('.new_line').text).not_to have_content "..."
end
end
step 'I see compared refs' do
expect(page).to have_content "Commits (1)"
expect(page).to have_content "Showing 2 changed files"
end
step 'I visit commits list page for feature branch' do
visit project_commits_path(@project, 'feature', { limit: 5 })
end
step 'I see feature branch commits' do
commit = @project.repository.commit('0b4bc9a')
expect(page).to have_content(@project.name)
expect(page).to have_content(commit.message[0..12])
expect(page).to have_content(commit.short_id)
end
step 'project have an open merge request' do
create(:merge_request,
title: 'Feature',
source_project: @project,
source_branch: 'feature',
target_branch: 'master',
author: @project.users.first
)
end
step 'I click the "Compare" tab' do
click_link('Compare')
end
step 'I fill compare fields with branches' do
select_using_dropdown('from', 'master')
select_using_dropdown('to', 'feature')
click_button 'Compare'
end
step 'I see compared branches' do
expect(page).to have_content 'Commits (1)'
expect(page).to have_content 'Showing 1 changed file with 5 additions and 0 deletions'
end
step 'I see button to create a new merge request' do
expect(page).to have_link 'Create merge request'
end
step 'I should not see button to create a new merge request' do
expect(page).not_to have_link 'Create merge request'
end
step 'I should see button to the merge request' do
merge_request = MergeRequest.find_by(title: 'Feature')
expect(page).to have_link "View open merge request", href: project_merge_request_path(@project, merge_request)
end
step 'I see breadcrumb links' do
expect(page).to have_selector('ul.breadcrumb')
expect(page).to have_selector('ul.breadcrumb a', count: 4)
end
step 'I see commits stats' do
expect(page).to have_content 'Top 50 Committers'
expect(page).to have_content 'Committers'
expect(page).to have_content 'Total commits'
expect(page).to have_content 'Authors'
end
step 'I visit a commit with an image that changed' do
visit project_commit_path(@project, sample_image_commit.id)
end
step 'The diff links to both the previous and current image' do
links = page.all('.file-actions a')
expect(links[0]['href']).to match %r{blob/#{sample_image_commit.old_blob_id}}
expect(links[1]['href']).to match %r{blob/#{sample_image_commit.new_blob_id}}
end
step 'I see inline diff button' do
expect(page).to have_content "Inline"
end
step 'I click side-by-side diff button' do
find('#parallel-diff-btn').click
end
step 'commit has ci status' do
@project.enable_ci
@pipeline = create(:ci_pipeline, project: @project, sha: sample_commit.id)
create(:ci_build, pipeline: @pipeline)
end
step 'repository contains ".gitlab-ci.yml" file' do
allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file).and_return(String.new)
end
step 'I see commit ci info' do
expect(page).to have_content "Pipeline ##{@pipeline.id} pending"
end
step 'I search "submodules" commits' do
fill_in 'commits-search', with: 'submodules'
end
step 'I should see only "submodules" commits' do
expect(page).to have_content "More submodules"
expect(page).not_to have_content "Change some files"
end
def select_using_dropdown(dropdown_type, selection, is_commit = false)
dropdown = find(".js-compare-#{dropdown_type}-dropdown")
dropdown.find(".compare-dropdown-toggle").click
dropdown.find('.dropdown-menu', visible: true)
dropdown.fill_in("Filter by Git revision", with: selection)
if is_commit
dropdown.find('input[type="search"]').send_keys(:return)
else
find_link(selection, visible: true).click
end
dropdown.find('.dropdown-menu', visible: false)
end
end
......@@ -136,6 +136,7 @@ module API
def self.preload_relation(projects_relation, options = {})
projects_relation.preload(:project_feature, :route)
.preload(:import_state)
.preload(namespace: [:route, :owner],
tags: :taggings)
end
......@@ -242,13 +243,18 @@ module API
expose :requested_at
end
class Group < Grape::Entity
expose :id, :name, :path, :description, :visibility
class BasicGroupDetails < Grape::Entity
expose :id
expose :web_url
expose :name
end
class Group < BasicGroupDetails
expose :path, :description, :visibility
expose :lfs_enabled?, as: :lfs_enabled
expose :avatar_url do |group, options|
group.avatar_url(only_path: false)
end
expose :web_url
expose :request_access_enabled
expose :full_name, :full_path
......@@ -984,6 +990,13 @@ module API
options[:current_user].authorized_projects.where(id: runner.projects)
end
end
expose :groups, with: Entities::BasicGroupDetails do |runner, options|
if options[:current_user].admin?
runner.groups
else
options[:current_user].authorized_groups.where(id: runner.groups)
end
end
end
class RunnerRegistrationDetails < Grape::Entity
......
......@@ -23,10 +23,13 @@ module API
runner =
if runner_registration_token_valid?
# Create shared runner. Requires admin access
Ci::Runner.create(attributes.merge(is_shared: true))
Ci::Runner.create(attributes.merge(is_shared: true, runner_type: :instance_type))
elsif project = Project.find_by(runners_token: params[:token])
# Create a specific runner for project.
project.runners.create(attributes)
# Create a specific runner for the project
project.runners.create(attributes.merge(runner_type: :project_type))
elsif group = Group.find_by(runners_token: params[:token])
# Create a specific runner for the group
group.runners.create(attributes.merge(runner_type: :group_type))
end
break forbidden! unless runner
......
......@@ -17,6 +17,10 @@ module Gitlab
@changed
end
def failed?
error_message.present?
end
def error_message
identity.validate
......
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# This background migration creates all the records on the
# import state table for projects that are considered imports or forks
class PopulateImportState
def perform(start_id, end_id)
move_attributes_data_to_import_state(start_id, end_id)
rescue ActiveRecord::RecordNotUnique
retry
end
def move_attributes_data_to_import_state(start_id, end_id)
Rails.logger.info("#{self.class.name} - Moving import attributes data to project mirror data table: #{start_id} - #{end_id}")
ActiveRecord::Base.connection.execute <<~SQL
INSERT INTO project_mirror_data (project_id, status, jid, last_error)
SELECT id, import_status, import_jid, import_error
FROM projects
WHERE projects.import_status != 'none'
AND projects.id BETWEEN #{start_id} AND #{end_id}
AND NOT EXISTS (
SELECT id
FROM project_mirror_data
WHERE project_id = projects.id
)
SQL
ActiveRecord::Base.connection.execute <<~SQL
UPDATE projects
SET import_status = 'none'
WHERE import_status != 'none'
AND id BETWEEN #{start_id} AND #{end_id}
SQL
end
end
end
end
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# This background migration migrates all the data of import_state
# back to the projects table for projects that are considered imports or forks
class RollbackImportStateData
def perform(start_id, end_id)
move_attributes_data_to_project(start_id, end_id)
end
def move_attributes_data_to_project(start_id, end_id)
Rails.logger.info("#{self.class.name} - Moving import attributes data to projects table: #{start_id} - #{end_id}")
if Gitlab::Database.mysql?
ActiveRecord::Base.connection.execute <<~SQL
UPDATE projects, project_mirror_data
SET
projects.import_status = project_mirror_data.status,
projects.import_jid = project_mirror_data.jid,
projects.import_error = project_mirror_data.last_error
WHERE project_mirror_data.project_id = projects.id
AND project_mirror_data.id BETWEEN #{start_id} AND #{end_id}
SQL
else
ActiveRecord::Base.connection.execute <<~SQL
UPDATE projects
SET
import_status = project_mirror_data.status,
import_jid = project_mirror_data.jid,
import_error = project_mirror_data.last_error
FROM project_mirror_data
WHERE project_mirror_data.project_id = projects.id
AND project_mirror_data.id BETWEEN #{start_id} AND #{end_id}
SQL
end
end
end
end
end
......@@ -14,7 +14,8 @@ module Gitlab
trigger_requests: Array(@command.trigger_request),
user: @command.current_user,
pipeline_schedule: @command.schedule,
protected: @command.protected_ref?
protected: @command.protected_ref?,
variables_attributes: Array(@command.variables_attributes)
)
@pipeline.set_config_source
......
......@@ -7,7 +7,7 @@ module Gitlab # rubocop:disable Naming/FileName
:origin_ref, :checkout_sha, :after_sha, :before_sha,
:trigger_request, :schedule,
:ignore_skip_ci, :save_incompleted,
:seeds_block
:seeds_block, :variables_attributes
) do
include Gitlab::Utils::StrongMemoize
......
......@@ -20,6 +20,9 @@ module Gitlab
GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE
].freeze
SEARCH_CONTEXT_LINES = 3
# In https://gitlab.com/gitlab-org/gitaly/merge_requests/698
# We copied these two prefixes into gitaly-go, so don't change these
# or things will break! (REBASE_WORKTREE_PREFIX and SQUASH_WORKTREE_PREFIX)
REBASE_WORKTREE_PREFIX = 'rebase'.freeze
SQUASH_WORKTREE_PREFIX = 'squash'.freeze
GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git'.freeze
......@@ -1671,10 +1674,14 @@ module Gitlab
end
end
# This function is duplicated in Gitaly-Go, don't change it!
# https://gitlab.com/gitlab-org/gitaly/merge_requests/698
def fresh_worktree?(path)
File.exist?(path) && !clean_stuck_worktree(path)
end
# This function is duplicated in Gitaly-Go, don't change it!
# https://gitlab.com/gitlab-org/gitaly/merge_requests/698
def clean_stuck_worktree(path)
return false unless File.mtime(path) < 15.minutes.ago
......
......@@ -32,7 +32,8 @@ module Gitlab
Gitlab::SidekiqStatus
.set(jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION)
project.update_column(:import_jid, jid)
project.ensure_import_state
project.import_state&.update_column(:jid, jid)
Stage::ImportRepositoryWorker
.perform_async(project.id)
......
......@@ -78,7 +78,8 @@ module Gitlab
def handle_errors
return unless errors.any?
project.update_column(:import_error, {
project.ensure_import_state
project.import_state&.update_column(:last_error, {
message: 'The remote data could not be fully imported.',
errors: errors
}.to_json)
......
......@@ -25,9 +25,9 @@ module Gitlab
end
TEMPLATES_TABLE = [
ProjectTemplate.new('rails', 'Ruby on Rails', 'Includes an MVC structure, gemfile, rakefile, and .gitlab-ci.yml file, along with many others, to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/rails'),
ProjectTemplate.new('spring', 'Spring', 'Includes an MVC structure, mvnw, pom.xml, and .gitlab-ci.yml file to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/spring'),
ProjectTemplate.new('express', 'NodeJS Express', 'Includes an MVC structure and .gitlab-ci.yml file to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/express')
ProjectTemplate.new('rails', 'Ruby on Rails', 'Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/rails'),
ProjectTemplate.new('spring', 'Spring', 'Includes an MVC structure, mvnw and pom.xml to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/spring'),
ProjectTemplate.new('express', 'NodeJS Express', 'Includes an MVC structure to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/express')
].freeze
class << self
......
......@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-04-24 13:19+0000\n"
"PO-Revision-Date: 2018-04-24 13:19+0000\n"
"POT-Creation-Date: 2018-05-02 22:28+0200\n"
"PO-Revision-Date: 2018-05-02 22:28+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
......@@ -84,6 +84,9 @@ msgstr ""
msgid "%{openOrClose} %{noteable}"
msgstr ""
msgid "%{percent}%% complete"
msgstr ""
msgid "%{storage_name}: failed storage access attempt on host:"
msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
msgstr[0] ""
......@@ -92,6 +95,9 @@ msgstr[1] ""
msgid "%{text} is available"
msgstr ""
msgid "%{title} changes"
msgstr ""
msgid "(checkout the %{link} for information on how to install it)."
msgstr ""
......@@ -101,6 +107,41 @@ msgstr ""
msgid "- show less"
msgstr ""
msgid "1 %{type} addition"
msgid_plural "%d %{type} additions"
msgstr[0] ""
msgstr[1] ""
msgid "1 %{type} modification"
msgid_plural "%d %{type} modifications"
msgstr[0] ""
msgstr[1] ""
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] ""
msgstr[1] ""
msgid "1 closed merge request"
msgid_plural "%d closed merge requests"
msgstr[0] ""
msgstr[1] ""
msgid "1 merged merge request"
msgid_plural "%d merged merge requests"
msgstr[0] ""
msgstr[1] ""
msgid "1 open issue"
msgid_plural "%d open issues"
msgstr[0] ""
msgstr[1] ""
msgid "1 open merge request"
msgid_plural "%d open merge requests"
msgstr[0] ""
msgstr[1] ""
msgid "1 pipeline"
msgid_plural "%d pipelines"
msgstr[0] ""
......@@ -136,6 +177,9 @@ msgstr ""
msgid "Abuse reports"
msgstr ""
msgid "Accept terms"
msgstr ""
msgid "Access Tokens"
msgstr ""
......@@ -367,6 +411,9 @@ msgstr ""
msgid "Assignee"
msgstr ""
msgid "Assignee(s)"
msgstr ""
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr ""
......@@ -669,9 +716,39 @@ msgstr ""
msgid "CI/CD configuration"
msgstr ""
msgid "CICD|An explicit %{ci_file} needs to be specified before you can begin using Continuous Integration and Delivery."
msgstr ""
msgid "CICD|Auto DevOps (Beta)"
msgstr ""
msgid "CICD|Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration."
msgstr ""
msgid "CICD|Disable Auto DevOps"
msgstr ""
msgid "CICD|Enable Auto DevOps"
msgstr ""
msgid "CICD|Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific %{ci_file}."
msgstr ""
msgid "CICD|Instance default (%{state})"
msgstr ""
msgid "CICD|Jobs"
msgstr ""
msgid "CICD|Learn more about Auto DevOps"
msgstr ""
msgid "CICD|The Auto DevOps pipeline configuration will be used when there is no %{ci_file} in the project."
msgstr ""
msgid "CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages."
msgstr ""
msgid "Cancel"
msgstr ""
......@@ -825,6 +902,9 @@ msgstr ""
msgid "CircuitBreakerApiLink|circuitbreaker api"
msgstr ""
msgid "Clear search input"
msgstr ""
msgid "Click any <strong>project name</strong> in the project list below to navigate to the project milestone."
msgstr ""
......@@ -1128,6 +1208,12 @@ msgstr ""
msgid "ClusterIntegration|properly configured"
msgstr ""
msgid "Collapse"
msgstr ""
msgid "Collapse sidebar"
msgstr ""
msgid "Comment and resolve discussion"
msgstr ""
......@@ -1411,16 +1497,16 @@ msgstr ""
msgid "CreateTokenToCloneLink|create a personal access token"
msgstr ""
msgid "Creates a new branch from %{branchName}"
msgid "Cron Timezone"
msgstr ""
msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request"
msgid "Cron syntax"
msgstr ""
msgid "Cron Timezone"
msgid "CurrentUser|Profile"
msgstr ""
msgid "Cron syntax"
msgid "CurrentUser|Settings"
msgstr ""
msgid "Custom notification events"
......@@ -1465,6 +1551,9 @@ msgstr ""
msgid "December"
msgstr ""
msgid "Decline and sign out"
msgstr ""
msgid "Define a custom pattern with cron syntax"
msgstr ""
......@@ -1563,12 +1652,18 @@ msgstr ""
msgid "Directory name"
msgstr ""
msgid "Discard changes"
msgstr ""
msgid "Discard draft"
msgstr ""
msgid "Dismiss Cycle Analytics introduction box"
msgstr ""
msgid "Domain"
msgstr ""
msgid "Don't show again"
msgstr ""
......@@ -1734,6 +1829,9 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
msgid "Estimated"
msgstr ""
msgid "EventFilterBy|Filter by all"
msgstr ""
......@@ -1761,6 +1859,12 @@ msgstr ""
msgid "Every week (Sundays at 4:00am)"
msgstr ""
msgid "Expand"
msgstr ""
msgid "Expand sidebar"
msgstr ""
msgid "Explore projects"
msgstr ""
......@@ -1797,9 +1901,6 @@ msgstr ""
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
msgid "File name"
msgstr ""
msgid "Files"
msgstr ""
......@@ -1901,6 +2002,9 @@ msgstr ""
msgid "Got it!"
msgstr ""
msgid "Group ID"
msgstr ""
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
......@@ -2023,6 +2127,9 @@ msgstr ""
msgid "Import repository"
msgstr ""
msgid "Include a Terms of Service agreement that all users must accept."
msgstr ""
msgid "Install Runner on Kubernetes"
msgstr ""
......@@ -2199,6 +2306,9 @@ msgstr ""
msgid "Loading the GitLab IDE..."
msgstr ""
msgid "Loading..."
msgstr ""
msgid "Lock"
msgstr ""
......@@ -2229,7 +2339,10 @@ msgstr ""
msgid "March"
msgstr ""
msgid "Mark done"
msgid "Mark todo as done"
msgstr ""
msgid "Markdown enabled"
msgstr ""
msgid "Maximum git storage failures"
......@@ -2244,6 +2357,9 @@ msgstr ""
msgid "Members"
msgstr ""
msgid "Merge Request:"
msgstr ""
msgid "Merge Requests"
msgstr ""
......@@ -2253,6 +2369,9 @@ msgstr ""
msgid "Merge request"
msgstr ""
msgid "Merge requests"
msgstr ""
msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
msgstr ""
......@@ -2313,6 +2432,9 @@ msgstr ""
msgid "Move issue"
msgstr ""
msgid "Name"
msgstr ""
msgid "Name new label"
msgstr ""
......@@ -2387,6 +2509,9 @@ msgstr ""
msgid "No file chosen"
msgstr ""
msgid "No files found."
msgstr ""
msgid "No labels created yet."
msgstr ""
......@@ -2675,12 +2800,27 @@ msgstr ""
msgid "Pipelines|This project is not currently set up to run pipelines."
msgstr ""
msgid "Pipeline|Existing branch name, tag"
msgstr ""
msgid "Pipeline|Retry pipeline"
msgstr ""
msgid "Pipeline|Retry pipeline #%{pipelineId}?"
msgstr ""
msgid "Pipeline|Run Pipeline"
msgstr ""
msgid "Pipeline|Run on"
msgstr ""
msgid "Pipeline|Run pipeline"
msgstr ""
msgid "Pipeline|Search branches"
msgstr ""
msgid "Pipeline|Stop pipeline"
msgstr ""
......@@ -2714,6 +2854,9 @@ msgstr ""
msgid "Please <a href=%{link_to_billing} target=\"_blank\" rel=\"noopener noreferrer\">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again."
msgstr ""
msgid "Please accept the Terms of Service before continuing."
msgstr ""
msgid "Please select at least one filter to see results"
msgstr ""
......@@ -2798,6 +2941,9 @@ msgstr ""
msgid "Programming languages used in this repository"
msgstr ""
msgid "Progress"
msgstr ""
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
......@@ -3041,6 +3187,9 @@ msgstr ""
msgid "Request Access"
msgstr ""
msgid "Require all users to accept Terms of Service when they access GitLab."
msgstr ""
msgid "Reset git storage health information"
msgstr ""
......@@ -3053,6 +3202,9 @@ msgstr ""
msgid "Resolve discussion"
msgstr ""
msgid "Retry"
msgstr ""
msgid "Retry this job"
msgstr ""
......@@ -3115,6 +3267,9 @@ msgstr ""
msgid "Search branches and tags"
msgstr ""
msgid "Search files"
msgstr ""
msgid "Search milestones"
msgstr ""
......@@ -3219,6 +3374,9 @@ msgid_plural "Showing %d events"
msgstr[0] ""
msgstr[1] ""
msgid "Sign out"
msgstr ""
msgid "Sign-in restrictions"
msgstr ""
......@@ -3360,6 +3518,18 @@ msgstr ""
msgid "Specify the following URL during the Runner setup:"
msgstr ""
msgid "Stage all"
msgstr ""
msgid "Stage changes"
msgstr ""
msgid "Staged"
msgstr ""
msgid "Staged %{type}"
msgstr ""
msgid "StarProject|Star"
msgstr ""
......@@ -3410,6 +3580,9 @@ msgstr[1] ""
msgid "Tags"
msgstr ""
msgid "Tags:"
msgstr ""
msgid "TagsPage|Browse commits"
msgstr ""
......@@ -3488,6 +3661,12 @@ msgstr ""
msgid "Team"
msgstr ""
msgid "Terms of Service"
msgstr ""
msgid "Terms of Service Agreement"
msgstr ""
msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
msgstr ""
......@@ -3665,6 +3844,12 @@ msgstr ""
msgid "Time between merge request creation and merge/close"
msgstr ""
msgid "Time remaining"
msgstr ""
msgid "Time spent"
msgstr ""
msgid "Time tracking"
msgstr ""
......@@ -3837,6 +4022,9 @@ msgstr ""
msgid "Todo"
msgstr ""
msgid "Toggle Sidebar"
msgstr ""
msgid "Toggle sidebar"
msgstr ""
......@@ -3861,6 +4049,12 @@ msgstr ""
msgid "Trigger this manual action"
msgstr ""
msgid "Try again"
msgstr ""
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
msgid "Unlock"
msgstr ""
......@@ -3870,6 +4064,21 @@ msgstr ""
msgid "Unresolve discussion"
msgstr ""
msgid "Unstage all"
msgstr ""
msgid "Unstage changes"
msgstr ""
msgid "Unstaged"
msgstr ""
msgid "Unstaged %{type}"
msgstr ""
msgid "Unstaged and staged %{type}"
msgstr ""
msgid "Unstar"
msgstr ""
......@@ -3978,6 +4187,9 @@ msgstr ""
msgid "Web terminal"
msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
msgid "Wiki"
msgstr ""
......@@ -4200,6 +4412,9 @@ msgstr ""
msgid "Your projects"
msgstr ""
msgid "ago"
msgstr ""
msgid "among other things"
msgstr ""
......@@ -4223,6 +4438,12 @@ msgstr[1] ""
msgid "deploy token"
msgstr ""
msgid "disabled"
msgstr ""
msgid "enabled"
msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
......@@ -4422,6 +4643,9 @@ msgstr ""
msgid "personal access token"
msgstr ""
msgid "remaining"
msgstr ""
msgid "remove due date"
msgstr ""
......
......@@ -2,12 +2,15 @@ module QA
module Page
module Menu
class Main < Page::Base
view 'app/views/layouts/header/_current_user_dropdown.html.haml' do
element :user_sign_out_link, 'link_to _("Sign out")'
element :settings_link, 'link_to s_("CurrentUser|Settings")'
end
view 'app/views/layouts/header/_default.html.haml' do
element :navbar
element :user_avatar
element :user_menu, '.dropdown-menu-nav'
element :user_sign_out_link, 'link_to "Sign out"'
element :settings_link, 'link_to "Settings"'
end
view 'app/views/layouts/nav/_dashboard.html.haml' do
......
require 'spec_helper'
describe ApplicationController do
include TermsHelper
let(:user) { create(:user) }
describe '#check_password_expiration' do
......@@ -406,4 +408,65 @@ describe ApplicationController do
end
end
end
context 'terms' do
controller(described_class) do
def index
render text: 'authenticated'
end
end
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in user
end
it 'does not query more when terms are enforced' do
control = ActiveRecord::QueryRecorder.new { get :index }
enforce_terms
expect { get :index }.not_to exceed_query_limit(control)
end
context 'when terms are enforced' do
before do
enforce_terms
end
it 'redirects if the user did not accept the terms' do
get :index
expect(response).to have_gitlab_http_status(302)
end
it 'does not redirect when the user accepted terms' do
accept_terms(user)
get :index
expect(response).to have_gitlab_http_status(200)
end
context 'for sessionless users' do
before do
sign_out user
end
it 'renders a 403 when the sessionless user did not accept the terms' do
get :index, rss_token: user.rss_token, format: :atom
expect(response).to have_gitlab_http_status(403)
end
it 'renders a 200 when the sessionless user accepted the terms' do
accept_terms(user)
get :index, rss_token: user.rss_token, format: :atom
expect(response).to have_gitlab_http_status(200)
end
end
end
end
end
require 'spec_helper'
describe ContinueParams do
let(:controller_class) do
Class.new(ActionController::Base) do
include ContinueParams
def request
@request ||= Struct.new(:host, :port).new('test.host', 80)
end
end
end
subject(:controller) { controller_class.new }
def strong_continue_params(params)
ActionController::Parameters.new(continue: params)
end
it 'cleans up any params that are not allowed' do
allow(controller).to receive(:params) do
strong_continue_params(to: '/hello',
notice: 'world',
notice_now: '!',
something: 'else')
end
expect(controller.continue_params.keys).to contain_exactly(*%w(to notice notice_now))
end
it 'does not allow cross host redirection' do
allow(controller).to receive(:params) do
strong_continue_params(to: '//example.com')
end
expect(controller.continue_params[:to]).to be_nil
end
it 'allows redirecting to a path with querystring' do
allow(controller).to receive(:params) do
strong_continue_params(to: '/hello/world?query=string')
end
expect(controller.continue_params[:to]).to eq('/hello/world?query=string')
end
end
require 'spec_helper'
describe InternalRedirect do
let(:controller_class) do
Class.new do
include InternalRedirect
def request
@request ||= Struct.new(:host, :port).new('test.host', 80)
end
end
end
subject(:controller) { controller_class.new }
describe '#safe_redirect_path' do
it 'is `nil` for invalid uris' do
expect(controller.safe_redirect_path('Hello world')).to be_nil
end
it 'is `nil` for paths trying to include a host' do
expect(controller.safe_redirect_path('//example.com/hello/world')).to be_nil
end
it 'returns the path if it is valid' do
expect(controller.safe_redirect_path('/hello/world')).to eq('/hello/world')
end
it 'returns the path with querystring if it is valid' do
expect(controller.safe_redirect_path('/hello/world?hello=world#L123'))
.to eq('/hello/world?hello=world#L123')
end
end
describe '#safe_redirect_path_for_url' do
it 'is `nil` for invalid urls' do
expect(controller.safe_redirect_path_for_url('Hello world')).to be_nil
end
it 'is `nil` for urls from a with a different host' do
expect(controller.safe_redirect_path_for_url('http://example.com/hello/world')).to be_nil
end
it 'is `nil` for urls from a with a different port' do
expect(controller.safe_redirect_path_for_url('http://test.host:3000/hello/world')).to be_nil
end
it 'returns the path if the url is on the same host' do
expect(controller.safe_redirect_path_for_url('http://test.host/hello/world')).to eq('/hello/world')
end
it 'returns the path including querystring if the url is on the same host' do
expect(controller.safe_redirect_path_for_url('http://test.host/hello/world?hello=world#L123'))
.to eq('/hello/world?hello=world#L123')
end
end
describe '#host_allowed?' do
it 'allows uris with the same host and port' do
expect(controller.host_allowed?(URI('http://test.host/test'))).to be(true)
end
it 'rejects uris with other host and port' do
expect(controller.host_allowed?(URI('http://example.com/test'))).to be(false)
end
end
end
......@@ -17,6 +17,23 @@ describe Projects::Settings::CiCdController do
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template(:show)
end
context 'with group runners' do
let(:group_runner) { create(:ci_runner) }
let(:parent_group) { create(:group) }
let(:group) { create(:group, runners: [group_runner], parent: parent_group) }
let(:other_project) { create(:project, group: group) }
let!(:project_runner) { create(:ci_runner, projects: [other_project]) }
let!(:shared_runner) { create(:ci_runner, :shared) }
it 'sets assignable project runners only' do
group.add_master(user)
get :show, namespace_id: project.namespace, project_id: project
expect(assigns(:assignable_runners)).to eq [project_runner]
end
end
end
describe '#reset_cache' do
......
......@@ -265,7 +265,7 @@ describe SessionsController do
it 'redirects correctly for referer on same host with params' do
search_path = '/search?search=seed_project'
allow(controller.request).to receive(:referer)
.and_return('http://%{host}%{path}' % { host: Gitlab.config.gitlab.host, path: search_path })
.and_return('http://%{host}%{path}' % { host: 'test.host', path: search_path })
get(:new, redirect_to_referer: :yes)
......
require 'spec_helper'
describe Users::TermsController do
let(:user) { create(:user) }
let(:term) { create(:term) }
before do
sign_in user
end
describe 'GET #index' do
it 'redirects when no terms exist' do
get :index
expect(response).to have_gitlab_http_status(:redirect)
end
it 'shows terms when they exist' do
term
expect(response).to have_gitlab_http_status(:success)
end
end
describe 'POST #accept' do
it 'saves that the user accepted the terms' do
post :accept, id: term.id
agreement = user.term_agreements.find_by(term: term)
expect(agreement.accepted).to eq(true)
end
it 'redirects to a path when specified' do
post :accept, id: term.id, redirect: groups_path
expect(response).to redirect_to(groups_path)
end
it 'redirects to the referer when no redirect specified' do
request.env["HTTP_REFERER"] = groups_url
post :accept, id: term.id
expect(response).to redirect_to(groups_path)
end
context 'redirecting to another domain' do
it 'is prevented when passing a redirect param' do
post :accept, id: term.id, redirect: '//example.com/random/path'
expect(response).to redirect_to(root_path)
end
it 'is prevented when redirecting to the referer' do
request.env["HTTP_REFERER"] = 'http://example.com/and/a/path'
post :accept, id: term.id
expect(response).to redirect_to(root_path)
end
end
end
describe 'POST #decline' do
it 'stores that the user declined the terms' do
post :decline, id: term.id
agreement = user.term_agreements.find_by(term: term)
expect(agreement.accepted).to eq(false)
end
it 'signs out the user' do
post :decline, id: term.id
expect(response).to redirect_to(root_path)
expect(assigns(:current_user)).to be_nil
end
end
end
FactoryBot.define do
factory :import_state, class: ProjectImportState do
status :none
association :project, factory: :project
transient do
import_url { generate(:url) }
end
trait :repository do
association :project, factory: [:project, :repository]
end
trait :none do
status :none
end
trait :scheduled do
status :scheduled
end
trait :started do
status :started
end
trait :finished do
status :finished
end
trait :failed do
status :failed
end
after(:create) do |import_state, evaluator|
import_state.project.update_columns(import_url: evaluator.import_url)
end
end
end
......@@ -15,14 +15,18 @@ FactoryBot.define do
namespace
creator { group ? create(:user) : namespace&.owner }
# Nest Project Feature attributes
transient do
# Nest Project Feature attributes
wiki_access_level ProjectFeature::ENABLED
builds_access_level ProjectFeature::ENABLED
snippets_access_level ProjectFeature::ENABLED
issues_access_level ProjectFeature::ENABLED
merge_requests_access_level ProjectFeature::ENABLED
repository_access_level ProjectFeature::ENABLED
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
group_runners_enabled nil
end
after(:create) do |project, evaluator|
......@@ -47,6 +51,9 @@ FactoryBot.define do
end
project.group&.refresh_members_authorized_projects
# assign the delegated `#ci_cd_settings` attributes after create
project.reload.group_runners_enabled = evaluator.group_runners_enabled unless evaluator.group_runners_enabled.nil?
end
trait :public do
......@@ -62,19 +69,43 @@ FactoryBot.define do
end
trait :import_scheduled do
import_status :scheduled
transient do
status :scheduled
end
before(:create) do |project, evaluator|
project.create_import_state(status: evaluator.status)
end
end
trait :import_started do
import_status :started
transient do
status :started
end
before(:create) do |project, evaluator|
project.create_import_state(status: evaluator.status)
end
end
trait :import_finished do
import_status :finished
transient do
status :finished
end
before(:create) do |project, evaluator|
project.create_import_state(status: evaluator.status)
end
end
trait :import_failed do
import_status :failed
transient do
status :failed
end
before(:create) do |project, evaluator|
project.create_import_state(status: evaluator.status)
end
end
trait :archived do
......
FactoryBot.define do
factory :term_agreement do
term
user
end
end
FactoryBot.define do
factory :term, class: ApplicationSetting::Term do
terms "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
end
end
......@@ -59,6 +59,47 @@ describe "Admin Runners" do
expect(page).to have_text 'No runners found'
end
end
context 'group runner' do
let(:group) { create(:group) }
let!(:runner) { create(:ci_runner, groups: [group], runner_type: :group_type) }
it 'shows the label and does not show the project count' do
visit admin_runners_path
within "#runner_#{runner.id}" do
expect(page).to have_selector '.label', text: 'group'
expect(page).to have_text 'n/a'
end
end
end
context 'shared runner' do
it 'shows the label and does not show the project count' do
runner = create :ci_runner, :shared
visit admin_runners_path
within "#runner_#{runner.id}" do
expect(page).to have_selector '.label', text: 'shared'
expect(page).to have_text 'n/a'
end
end
end
context 'specific runner' do
it 'shows the label and the project count' do
project = create :project
runner = create :ci_runner, projects: [project]
visit admin_runners_path
within "#runner_#{runner.id}" do
expect(page).to have_selector '.label', text: 'specific'
expect(page).to have_text '1'
end
end
end
end
describe "Runner show page" do
......
......@@ -2,10 +2,13 @@ require 'spec_helper'
feature 'Admin updates settings' do
include StubENV
include TermsHelper
let(:admin) { create(:admin) }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(create(:admin))
sign_in(admin)
visit admin_application_settings_path
end
......@@ -85,6 +88,22 @@ feature 'Admin updates settings' do
expect(page).to have_content "Application settings saved successfully"
end
scenario 'Terms of Service' do
# Already have the admin accept terms, so they don't need to accept in this spec.
_existing_terms = create(:term)
accept_terms(admin)
page.within('.as-terms') do
check 'Require all users to accept Terms of Service when they access GitLab.'
fill_in 'Terms of Service Agreement', with: 'Be nice!'
click_button 'Save changes'
end
expect(Gitlab::CurrentSettings.enforce_terms).to be(true)
expect(Gitlab::CurrentSettings.terms).to eq 'Be nice!'
expect(page).to have_content 'Application settings saved successfully'
end
scenario 'Modify oauth providers' do
expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).to be_empty
......
......@@ -285,7 +285,7 @@ describe "Admin::Users" do
it "lists group projects" do
within(:css, '.append-bottom-default + .panel') do
expect(page).to have_content 'Group projects'
expect(page).to have_link group.name, admin_group_path(group)
expect(page).to have_link group.name, href: admin_group_path(group)
end
end
......
require 'spec_helper'
describe 'User browses commits' do
include RepoHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) }
......@@ -9,13 +11,68 @@ describe 'User browses commits' do
sign_in(user)
end
it 'renders commit' do
visit project_commit_path(project, sample_commit.id)
expect(page).to have_content(sample_commit.message)
.and have_content("Showing #{sample_commit.files_changed_count} changed files")
.and have_content('Side-by-side')
end
it 'fill commit sha when click new tag from commit page' do
visit project_commit_path(project, sample_commit.id)
click_link 'Tag'
expect(page).to have_selector("input[value='#{sample_commit.id}']", visible: false)
end
it 'renders inline diff button when click side-by-side diff button' do
visit project_commit_path(project, sample_commit.id)
find('#parallel-diff-btn').click
expect(page).to have_content 'Inline'
end
it 'renders breadcrumbs on specific commit path' do
visit project_commits_path(project, project.repository.root_ref + '/files/ruby/regex.rb', limit: 5)
expect(page).to have_selector('ul.breadcrumb')
.and have_selector('ul.breadcrumb a', count: 4)
end
it 'renders diff links to both the previous and current image' do
visit project_commit_path(project, sample_image_commit.id)
links = page.all('.file-actions a')
expect(links[0]['href']).to match %r{blob/#{sample_image_commit.old_blob_id}}
expect(links[1]['href']).to match %r{blob/#{sample_image_commit.new_blob_id}}
end
context 'when commit has ci status' do
let(:pipeline) { create(:ci_pipeline, project: project, sha: sample_commit.id) }
before do
project.enable_ci
create(:ci_build, pipeline: pipeline)
allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file).and_return('')
end
it 'renders commit ci info' do
visit project_commit_path(project, sample_commit.id)
expect(page).to have_content "Pipeline ##{pipeline.id} pending"
end
end
context 'primary email' do
it 'finds a commit by a primary email' do
user = create(:user, email: 'dmitriy.zaporozhets@gmail.com')
visit(project_commit_path(project, RepoHelpers.sample_commit.id))
visit(project_commit_path(project, sample_commit.id))
check_author_link(RepoHelpers.sample_commit.author_email, user)
check_author_link(sample_commit.author_email, user)
end
end
......@@ -26,9 +83,9 @@ describe 'User browses commits' do
create(:email, { user: user, email: 'dmitriy.zaporozhets@gmail.com' })
end
visit(project_commit_path(project, RepoHelpers.sample_commit.parent_id))
visit(project_commit_path(project, sample_commit.parent_id))
check_author_link(RepoHelpers.sample_commit.author_email, user)
check_author_link(sample_commit.author_email, user)
end
end
......@@ -44,6 +101,135 @@ describe 'User browses commits' do
expect(find('.diff-file-changes', visible: false)).to have_content('No file name available')
end
end
describe 'commits list' do
let(:visit_commits_page) do
visit project_commits_path(project, project.repository.root_ref, limit: 5)
end
it 'searches commit', :js do
visit_commits_page
fill_in 'commits-search', with: 'submodules'
expect(page).to have_content 'More submodules'
expect(page).not_to have_content 'Change some files'
end
it 'renders commits atom feed' do
visit_commits_page
click_link('Commits feed')
commit = project.repository.commit
expect(response_headers['Content-Type']).to have_content("application/atom+xml")
expect(body).to have_selector('title', text: "#{project.name}:master commits")
.and have_selector('author email', text: commit.author_email)
.and have_selector('entry summary', text: commit.description[0..10].delete("\r\n"))
end
context 'master branch' do
before do
visit_commits_page
end
it 'renders project commits' do
commit = project.repository.commit
expect(page).to have_content(project.name)
.and have_content(commit.message[0..20])
.and have_content(commit.short_id)
end
it 'does not render create merge request button' do
expect(page).not_to have_link 'Create merge request'
end
context 'when click the compare tab' do
before do
click_link('Compare')
end
it 'does not render create merge request button' do
expect(page).not_to have_link 'Create merge request'
end
end
end
context 'feature branch' do
let(:visit_commits_page) do
visit project_commits_path(project, 'feature')
end
context 'when project does not have open merge requests' do
before do
visit_commits_page
end
it 'renders project commits' do
commit = project.repository.commit('0b4bc9a')
expect(page).to have_content(project.name)
.and have_content(commit.message[0..12])
.and have_content(commit.short_id)
end
it 'renders create merge request button' do
expect(page).to have_link 'Create merge request'
end
context 'when click the compare tab' do
before do
click_link('Compare')
end
it 'renders create merge request button' do
expect(page).to have_link 'Create merge request'
end
end
end
context 'when project have open merge request' do
let!(:merge_request) do
create(
:merge_request,
title: 'Feature',
source_project: project,
source_branch: 'feature',
target_branch: 'master',
author: project.users.first
)
end
before do
visit_commits_page
end
it 'renders project commits' do
commit = project.repository.commit('0b4bc9a')
expect(page).to have_content(project.name)
.and have_content(commit.message[0..12])
.and have_content(commit.short_id)
end
it 'renders button to the merge request' do
expect(page).not_to have_link 'Create merge request'
expect(page).to have_link 'View open merge request', href: project_merge_request_path(project, merge_request)
end
context 'when click the compare tab' do
before do
click_link('Compare')
end
it 'renders button to the merge request' do
expect(page).not_to have_link 'Create merge request'
expect(page).to have_link 'View open merge request', href: project_merge_request_path(project, merge_request)
end
end
end
end
end
end
private
......
......@@ -7,16 +7,19 @@ describe "Compare", :js do
before do
project.add_master(user)
sign_in user
visit project_compare_index_path(project, from: "master", to: "master")
end
describe "branches" do
it "pre-populates fields" do
visit project_compare_index_path(project, from: "master", to: "master")
expect(find(".js-compare-from-dropdown .dropdown-toggle-text")).to have_content("master")
expect(find(".js-compare-to-dropdown .dropdown-toggle-text")).to have_content("master")
end
it "compares branches" do
visit project_compare_index_path(project, from: "master", to: "master")
select_using_dropdown "from", "feature"
expect(find(".js-compare-from-dropdown .dropdown-toggle-text")).to have_content("feature")
......@@ -26,9 +29,58 @@ describe "Compare", :js do
click_button "Compare"
expect(page).to have_content "Commits"
expect(page).to have_link 'Create merge request'
end
it 'renders additions info when click unfold diff' do
visit project_compare_index_path(project)
select_using_dropdown('from', RepoHelpers.sample_commit.parent_id, commit: true)
select_using_dropdown('to', RepoHelpers.sample_commit.id, commit: true)
click_button 'Compare'
expect(page).to have_content 'Commits (1)'
expect(page).to have_content "Showing 2 changed files"
diff = first('.js-unfold')
diff.click
wait_for_requests
page.within diff.query_scope do
expect(first('.new_line').text).not_to have_content "..."
end
end
context 'when project have an open merge request' do
let!(:merge_request) do
create(
:merge_request,
title: 'Feature',
source_project: project,
source_branch: 'feature',
target_branch: 'master',
author: project.users.first
)
end
it 'compares branches' do
visit project_compare_index_path(project)
select_using_dropdown('from', 'master')
select_using_dropdown('to', 'feature')
click_button 'Compare'
expect(page).to have_content 'Commits (1)'
expect(page).to have_content 'Showing 1 changed file with 5 additions and 0 deletions'
expect(page).to have_link 'View open merge request', href: project_merge_request_path(project, merge_request)
expect(page).not_to have_link 'Create merge request'
end
end
it "filters branches" do
visit project_compare_index_path(project, from: "master", to: "master")
select_using_dropdown("from", "wip")
find(".js-compare-from-dropdown .compare-dropdown-toggle").click
......@@ -39,6 +91,8 @@ describe "Compare", :js do
describe "tags" do
it "compares tags" do
visit project_compare_index_path(project, from: "master", to: "master")
select_using_dropdown "from", "v1.0.0"
expect(find(".js-compare-from-dropdown .dropdown-toggle-text")).to have_content("v1.0.0")
......@@ -50,15 +104,20 @@ describe "Compare", :js do
end
end
def select_using_dropdown(dropdown_type, selection)
def select_using_dropdown(dropdown_type, selection, commit: false)
dropdown = find(".js-compare-#{dropdown_type}-dropdown")
dropdown.find(".compare-dropdown-toggle").click
# find input before using to wait for the inputs visiblity
dropdown.find('.dropdown-menu')
dropdown.fill_in("Filter by Git revision", with: selection)
wait_for_requests
# find before all to wait for the items visiblity
dropdown.find("a[data-ref=\"#{selection}\"]", match: :first)
dropdown.all("a[data-ref=\"#{selection}\"]").last.click
if commit
dropdown.find('input[type="search"]').send_keys(:return)
else
# find before all to wait for the items visiblity
dropdown.find("a[data-ref=\"#{selection}\"]", match: :first)
dropdown.all("a[data-ref=\"#{selection}\"]").last.click
end
end
end
......@@ -46,7 +46,7 @@ feature 'Import/Export - project import integration test', :js do
expect(project.merge_requests).not_to be_empty
expect(project_hook_exists?(project)).to be true
expect(wiki_exists?(project)).to be true
expect(project.import_status).to eq('finished')
expect(project.import_state.status).to eq('finished')
end
end
......
......@@ -517,16 +517,31 @@ describe 'Pipelines', :js do
end
it 'creates a new pipeline' do
expect { click_on 'Run pipeline' }
expect { click_on 'Create pipeline' }
.to change { Ci::Pipeline.count }.by(1)
expect(Ci::Pipeline.last).to be_web
end
context 'when variables are specified' do
it 'creates a new pipeline with variables' do
page.within '.ci-variable-row-body' do
fill_in "Input variable key", with: "key_name"
fill_in "Input variable value", with: "value"
end
expect { click_on 'Create pipeline' }
.to change { Ci::Pipeline.count }.by(1)
expect(Ci::Pipeline.last.variables.map { |var| var.slice(:key, :secret_value) })
.to eq [{ key: "key_name", secret_value: "value" }.with_indifferent_access]
end
end
end
context 'without gitlab-ci.yml' do
before do
click_on 'Run pipeline'
click_on 'Create pipeline'
end
it { expect(page).to have_content('Missing .gitlab-ci.yml file') }
......@@ -539,7 +554,7 @@ describe 'Pipelines', :js do
click_link 'master'
end
expect { click_on 'Run pipeline' }
expect { click_on 'Create pipeline' }
.to change { Ci::Pipeline.count }.by(1)
end
end
......@@ -557,7 +572,7 @@ describe 'Pipelines', :js do
it 'has field to add a new pipeline' do
expect(page).to have_selector('.js-branch-select')
expect(find('.js-branch-select')).to have_content project.default_branch
expect(page).to have_content('Run on')
expect(page).to have_content('Create for')
end
end
......
......@@ -181,4 +181,84 @@ feature 'Runners' do
expect(page.find('.shared-runners-description')).to have_content('Disable shared Runners')
end
end
context 'group runners' do
background do
project.add_master(user)
end
given(:group) { create :group }
context 'as project and group master' do
background do
group.add_master(user)
end
context 'project with a group but no group runner' do
given(:project) { create :project, group: group }
scenario 'group runners are not available' do
visit runners_path(project)
expect(page).to have_content 'This group does not provide any group Runners yet.'
expect(page).to have_content 'Setup a group Runner manually'
expect(page).not_to have_content 'Ask your group master to setup a group Runner.'
end
end
end
context 'as project master' do
context 'project without a group' do
given(:project) { create :project }
scenario 'group runners are not available' do
visit runners_path(project)
expect(page).to have_content 'This project does not belong to a group and can therefore not make use of group Runners.'
end
end
context 'project with a group but no group runner' do
given(:group) { create :group }
given(:project) { create :project, group: group }
scenario 'group runners are not available' do
visit runners_path(project)
expect(page).to have_content 'This group does not provide any group Runners yet.'
expect(page).not_to have_content 'Setup a group Runner manually'
expect(page).to have_content 'Ask your group master to setup a group Runner.'
end
end
context 'project with a group and a group runner' do
given(:group) { create :group }
given(:project) { create :project, group: group }
given!(:ci_runner) { create :ci_runner, groups: [group], description: 'group-runner' }
scenario 'group runners are available' do
visit runners_path(project)
expect(page).to have_content 'Available group Runners : 1'
expect(page).to have_content 'group-runner'
end
scenario 'group runners may be disabled for a project' do
visit runners_path(project)
click_on 'Disable group Runners'
expect(page).to have_content 'Enable group Runners'
expect(project.reload.group_runners_enabled).to be false
click_on 'Enable group Runners'
expect(page).to have_content 'Disable group Runners'
expect(project.reload.group_runners_enabled).to be true
end
end
end
end
end
require 'spec_helper'
feature 'Login' do
include TermsHelper
scenario 'Successful user signin invalidates password reset token' do
user = create(:user)
......@@ -399,4 +401,41 @@ feature 'Login' do
expect(page).to have_selector('.tab-pane.active', count: 1)
end
end
context 'when terms are enforced' do
let(:user) { create(:user) }
before do
enforce_terms
end
it 'asks to accept the terms on first login' do
visit new_user_session_path
fill_in 'user_login', with: user.email
fill_in 'user_password', with: '12345678'
click_button 'Sign in'
expect_to_be_on_terms_page
click_button 'Accept terms'
expect(current_path).to eq(root_path)
expect(page).not_to have_content('You are already signed in.')
end
it 'does not ask for terms when the user already accepted them' do
accept_terms(user)
visit new_user_session_path
fill_in 'user_login', with: user.email
fill_in 'user_password', with: '12345678'
click_button 'Sign in'
expect(current_path).to eq(root_path)
end
end
end
require 'spec_helper'
describe 'Signup' do
include TermsHelper
let(:new_user) { build_stubbed(:user) }
describe 'username validation', :js do
......@@ -132,4 +134,27 @@ describe 'Signup' do
expect(page.body).not_to match(/#{new_user.password}/)
end
end
context 'when terms are enforced' do
before do
enforce_terms
end
it 'asks the user to accept terms before going to the dashboard' do
visit root_path
fill_in 'new_user_name', with: new_user.name
fill_in 'new_user_username', with: new_user.username
fill_in 'new_user_email', with: new_user.email
fill_in 'new_user_email_confirmation', with: new_user.email
fill_in 'new_user_password', with: new_user.password
click_button "Register"
expect_to_be_on_terms_page
click_button 'Accept terms'
expect(current_path).to eq dashboard_projects_path
end
end
end
require 'spec_helper'
describe 'Users > Terms' do
include TermsHelper
let(:user) { create(:user) }
let!(:term) { create(:term, terms: 'By accepting, you promise to be nice!') }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(user)
end
it 'shows the terms' do
visit terms_path
expect(page).to have_content('By accepting, you promise to be nice!')
end
context 'declining the terms' do
it 'returns the user to the app' do
visit terms_path
click_button 'Decline and sign out'
expect(page).not_to have_content(term.terms)
expect(user.reload.terms_accepted?).to be(false)
end
end
context 'accepting the terms' do
it 'returns the user to the app' do
visit terms_path
click_button 'Accept terms'
expect(page).not_to have_content(term.terms)
expect(user.reload.terms_accepted?).to be(true)
end
end
context 'terms were enforced while session is active', :js do
let(:project) { create(:project) }
before do
project.add_developer(user)
end
it 'redirects to terms and back to where the user was going' do
visit project_path(project)
enforce_terms
within('.nav-sidebar') do
click_link 'Issues'
end
expect_to_be_on_terms_page
click_button('Accept terms')
expect(current_path).to eq(project_issues_path(project))
end
it 'redirects back to the page the user was trying to save' do
visit new_project_issue_path(project)
fill_in :issue_title, with: 'Hello world, a new issue'
fill_in :issue_description, with: "We don't want to lose what the user typed"
enforce_terms
click_button 'Submit issue'
expect(current_path).to eq(terms_path)
click_button('Accept terms')
expect(current_path).to eq(new_project_issue_path(project))
expect(find_field('issue_title').value).to eq('Hello world, a new issue')
expect(find_field('issue_description').value).to eq("We don't want to lose what the user typed")
end
end
end
require 'rails_helper'
describe UsersHelper do
include TermsHelper
let(:user) { create(:user) }
describe '#user_link' do
......@@ -27,4 +29,39 @@ describe UsersHelper do
expect(tabs).to include(:activity, :groups, :contributed, :projects, :snippets)
end
end
describe '#current_user_menu_items' do
subject(:items) { helper.current_user_menu_items }
before do
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?).and_return(false)
end
it 'includes all default items' do
expect(items).to include(:help, :sign_out)
end
it 'includes the profile tab if the user can read themself' do
expect(helper).to receive(:can?).with(user, :read_user, user) { true }
expect(items).to include(:profile)
end
it 'includes the settings tab if the user can update themself' do
expect(helper).to receive(:can?).with(user, :read_user, user) { true }
expect(items).to include(:profile)
end
context 'when terms are enforced' do
before do
enforce_terms
end
it 'hides the profile and the settings tab' do
expect(items).not_to include(:settings, :profile, :help)
end
end
end
end
require 'spec_helper'
describe Gitlab::BackgroundMigration::PopulateImportState, :migration, schema: 20180502134117 do
let(:migration) { described_class.new }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:import_state) { table(:project_mirror_data) }
before do
namespaces.create(id: 1, name: 'gitlab-org', path: 'gitlab-org')
projects.create!(id: 1, namespace_id: 1, name: 'gitlab1',
path: 'gitlab1', import_error: "foo", import_status: :started,
import_url: generate(:url))
projects.create!(id: 2, namespace_id: 1, name: 'gitlab2', path: 'gitlab2',
import_status: :none, import_url: generate(:url))
projects.create!(id: 3, namespace_id: 1, name: 'gitlab3',
path: 'gitlab3', import_error: "bar", import_status: :failed,
import_url: generate(:url))
allow(BackgroundMigrationWorker).to receive(:perform_in)
end
it "creates new import_state records with project's import data" do
expect(projects.where.not(import_status: :none).count).to eq(2)
expect do
migration.perform(1, 3)
end.to change { import_state.all.count }.from(0).to(2)
expect(import_state.first.last_error).to eq("foo")
expect(import_state.last.last_error).to eq("bar")
expect(import_state.first.status).to eq("started")
expect(import_state.last.status).to eq("failed")
expect(projects.first.import_status).to eq("none")
expect(projects.last.import_status).to eq("none")
end
end
require 'spec_helper'
describe Gitlab::BackgroundMigration::RollbackImportStateData, :migration, schema: 20180502134117 do
let(:migration) { described_class.new }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:import_state) { table(:project_mirror_data) }
before do
namespaces.create(id: 1, name: 'gitlab-org', path: 'gitlab-org')
projects.create!(id: 1, namespace_id: 1, name: 'gitlab1', import_url: generate(:url))
projects.create!(id: 2, namespace_id: 1, name: 'gitlab2', path: 'gitlab2', import_url: generate(:url))
import_state.create!(id: 1, project_id: 1, status: :started, last_error: "foo")
import_state.create!(id: 2, project_id: 2, status: :failed)
allow(BackgroundMigrationWorker).to receive(:perform_in)
end
it "creates new import_state records with project's import data" do
migration.perform(1, 2)
expect(projects.first.import_status).to eq("started")
expect(projects.second.import_status).to eq("failed")
expect(projects.first.import_error).to eq("foo")
end
end
......@@ -5,6 +5,10 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
set(:user) { create(:user) }
let(:pipeline) { Ci::Pipeline.new }
let(:variables_attributes) do
[{ key: 'first', secret_value: 'world' },
{ key: 'second', secret_value: 'second_world' }]
end
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
source: :push,
......@@ -15,7 +19,8 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
trigger_request: nil,
schedule: nil,
project: project,
current_user: user)
current_user: user,
variables_attributes: variables_attributes)
end
let(:step) { described_class.new(pipeline, command) }
......@@ -39,6 +44,8 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
expect(pipeline.tag).to be false
expect(pipeline.user).to eq user
expect(pipeline.project).to eq project
expect(pipeline.variables.map { |var| var.slice(:key, :secret_value) })
.to eq variables_attributes.map(&:with_indifferent_access)
end
it 'sets a valid config source' do
......
......@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::GithubImport::Importer::RepositoryImporter do
let(:repository) { double(:repository) }
let(:import_state) { double(:import_state) }
let(:client) { double(:client) }
let(:project) do
......@@ -12,7 +13,8 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
repository_storage: 'foo',
disk_path: 'foo',
repository: repository,
create_wiki: true
create_wiki: true,
import_state: import_state
)
end
......
......@@ -12,6 +12,8 @@ describe Gitlab::GithubImport::ParallelImporter do
let(:importer) { described_class.new(project) }
before do
create(:import_state, :started, project: project)
expect(Gitlab::GithubImport::Stage::ImportRepositoryWorker)
.to receive(:perform_async)
.with(project.id)
......@@ -34,7 +36,7 @@ describe Gitlab::GithubImport::ParallelImporter do
it 'updates the import JID of the project' do
importer.execute
expect(project.import_jid).to eq("github-importer/#{project.id}")
expect(project.reload.import_jid).to eq("github-importer/#{project.id}")
end
end
end
......@@ -258,7 +258,6 @@ project:
- builds
- runner_projects
- runners
- active_runners
- variables
- triggers
- pipeline_schedules
......@@ -274,6 +273,7 @@ project:
- statistics
- container_repositories
- uploads
- import_state
- members_and_requesters
- build_trace_section_names
- build_trace_chunks
......@@ -287,6 +287,7 @@ project:
- internal_ids
- project_deploy_tokens
- deploy_tokens
- settings
- ci_cd_settings
award_emoji:
- awardable
......
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20180502134117_migrate_import_attributes_data_from_projects_to_project_mirror_data.rb')
describe MigrateImportAttributesDataFromProjectsToProjectMirrorData, :sidekiq, :migration do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:import_state) { table(:project_mirror_data) }
before do
stub_const("#{described_class}::BATCH_SIZE", 1)
namespaces.create(id: 1, name: 'gitlab-org', path: 'gitlab-org')
projects.create!(id: 1, namespace_id: 1, name: 'gitlab1',
path: 'gitlab1', import_error: "foo", import_status: :started,
import_url: generate(:url))
projects.create!(id: 2, namespace_id: 1, name: 'gitlab2',
path: 'gitlab2', import_error: "bar", import_status: :failed,
import_url: generate(:url))
projects.create!(id: 3, namespace_id: 1, name: 'gitlab3', path: 'gitlab3', import_status: :none, import_url: generate(:url))
end
it 'schedules delayed background migrations in batches in bulk' do
Sidekiq::Testing.fake! do
Timecop.freeze do
expect(projects.where.not(import_status: :none).count).to eq(2)
subject.up
expect(BackgroundMigrationWorker.jobs.size).to eq 2
expect(described_class::UP_MIGRATION).to be_scheduled_delayed_migration(5.minutes, 1, 1)
expect(described_class::UP_MIGRATION).to be_scheduled_delayed_migration(10.minutes, 2, 2)
end
end
end
describe '#down' do
before do
import_state.create!(id: 1, project_id: 1, status: :started)
import_state.create!(id: 2, project_id: 2, status: :started)
end
it 'schedules delayed background migrations in batches in bulk for rollback' do
Sidekiq::Testing.fake! do
Timecop.freeze do
expect(import_state.where.not(status: :none).count).to eq(2)
subject.down
expect(BackgroundMigrationWorker.jobs.size).to eq 2
expect(described_class::DOWN_MIGRATION).to be_scheduled_delayed_migration(5.minutes, 1, 1)
expect(described_class::DOWN_MIGRATION).to be_scheduled_delayed_migration(10.minutes, 2, 2)
end
end
end
end
end
require 'spec_helper'
describe ApplicationSetting::Term do
describe 'validations' do
it { is_expected.to validate_presence_of(:terms) }
end
describe '.latest' do
it 'finds the latest terms' do
terms = create(:term)
expect(described_class.latest).to eq(terms)
end
end
end
......@@ -301,6 +301,21 @@ describe ApplicationSetting do
expect(subject).to be_invalid
end
end
describe 'enforcing terms' do
it 'requires the terms to present when enforcing users to accept' do
subject.enforce_terms = true
expect(subject).to be_invalid
end
it 'is valid when terms are created' do
create(:term)
subject.enforce_terms = true
expect(subject).to be_valid
end
end
end
describe '.current' do
......
......@@ -19,6 +19,63 @@ describe Ci::Runner do
end
end
end
context 'either_projects_or_group' do
let(:group) { create(:group) }
it 'disallows assigning to a group if already assigned to a group' do
runner = create(:ci_runner, groups: [group])
runner.groups << build(:group)
expect(runner).not_to be_valid
expect(runner.errors.full_messages).to eq ['Runner can only be assigned to one group']
end
it 'disallows assigning to a group if already assigned to a project' do
project = create(:project)
runner = create(:ci_runner, projects: [project])
runner.groups << build(:group)
expect(runner).not_to be_valid
expect(runner.errors.full_messages).to eq ['Runner can only be assigned either to projects or to a group']
end
it 'disallows assigning to a project if already assigned to a group' do
runner = create(:ci_runner, groups: [group])
runner.projects << build(:project)
expect(runner).not_to be_valid
expect(runner.errors.full_messages).to eq ['Runner can only be assigned either to projects or to a group']
end
it 'allows assigning to a group if not assigned to a group nor a project' do
runner = create(:ci_runner)
runner.groups << build(:group)
expect(runner).to be_valid
end
it 'allows assigning to a project if not assigned to a group nor a project' do
runner = create(:ci_runner)
runner.projects << build(:project)
expect(runner).to be_valid
end
it 'allows assigning to a project if already assigned to a project' do
project = create(:project)
runner = create(:ci_runner, projects: [project])
runner.projects << build(:project)
expect(runner).to be_valid
end
end
end
describe '#access_level' do
......@@ -49,6 +106,80 @@ describe Ci::Runner do
end
end
describe '.shared' do
let(:group) { create(:group) }
let(:project) { create(:project) }
it 'returns the shared group runner' do
runner = create(:ci_runner, :shared, groups: [group])
expect(described_class.shared).to eq [runner]
end
it 'returns the shared project runner' do
runner = create(:ci_runner, :shared, projects: [project])
expect(described_class.shared).to eq [runner]
end
end
describe '.belonging_to_project' do
it 'returns the specific project runner' do
# own
specific_project = create(:project)
specific_runner = create(:ci_runner, :specific, projects: [specific_project])
# other
other_project = create(:project)
create(:ci_runner, :specific, projects: [other_project])
expect(described_class.belonging_to_project(specific_project.id)).to eq [specific_runner]
end
end
describe '.belonging_to_parent_group_of_project' do
let(:project) { create(:project, group: group) }
let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :specific, groups: [group]) }
let!(:unrelated_group) { create(:group) }
let!(:unrelated_project) { create(:project, group: unrelated_group) }
let!(:unrelated_runner) { create(:ci_runner, :specific, groups: [unrelated_group]) }
it 'returns the specific group runner' do
expect(described_class.belonging_to_parent_group_of_project(project.id)).to contain_exactly(runner)
end
context 'with a parent group with a runner', :nested_groups do
let(:runner) { create(:ci_runner, :specific, groups: [parent_group]) }
let(:project) { create(:project, group: group) }
let(:group) { create(:group, parent: parent_group) }
let(:parent_group) { create(:group) }
it 'returns the group runner from the parent group' do
expect(described_class.belonging_to_parent_group_of_project(project.id)).to contain_exactly(runner)
end
end
end
describe '.owned_or_shared' do
it 'returns a globally shared, a project specific and a group specific runner' do
# group specific
group = create(:group)
project = create(:project, group: group)
group_runner = create(:ci_runner, :specific, groups: [group])
# project specific
project_runner = create(:ci_runner, :specific, projects: [project])
# globally shared
shared_runner = create(:ci_runner, :shared)
expect(described_class.owned_or_shared(project.id)).to contain_exactly(
group_runner, project_runner, shared_runner
)
end
end
describe '#display_name' do
it 'returns the description if it has a value' do
runner = FactoryBot.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448')
......@@ -163,7 +294,9 @@ describe Ci::Runner do
describe '#can_pick?' do
let(:pipeline) { create(:ci_pipeline) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:runner) { create(:ci_runner) }
let(:runner) { create(:ci_runner, tag_list: tag_list, run_untagged: run_untagged) }
let(:tag_list) { [] }
let(:run_untagged) { true }
subject { runner.can_pick?(build) }
......@@ -171,6 +304,13 @@ describe Ci::Runner do
build.project.runners << runner
end
context 'a different runner' do
it 'cannot handle builds' do
other_runner = create(:ci_runner)
expect(other_runner.can_pick?(build)).to be_falsey
end
end
context 'when runner does not have tags' do
it 'can handle builds without tags' do
expect(runner.can_pick?(build)).to be_truthy
......@@ -184,9 +324,7 @@ describe Ci::Runner do
end
context 'when runner has tags' do
before do
runner.tag_list = %w(bb cc)
end
let(:tag_list) { %w(bb cc) }
shared_examples 'tagged build picker' do
it 'can handle build with matching tags' do
......@@ -211,9 +349,7 @@ describe Ci::Runner do
end
context 'when runner cannot pick untagged jobs' do
before do
runner.run_untagged = false
end
let(:run_untagged) { false }
it 'cannot handle builds without tags' do
expect(runner.can_pick?(build)).to be_falsey
......@@ -224,8 +360,9 @@ describe Ci::Runner do
end
context 'when runner is shared' do
let(:runner) { create(:ci_runner, :shared) }
before do
runner.is_shared = true
build.project.runners = []
end
......@@ -234,9 +371,7 @@ describe Ci::Runner do
end
context 'when runner is locked' do
before do
runner.locked = true
end
let(:runner) { create(:ci_runner, :shared, locked: true) }
it 'can handle builds' do
expect(runner.can_pick?(build)).to be_truthy
......@@ -260,6 +395,17 @@ describe Ci::Runner do
expect(runner.can_pick?(build)).to be_falsey
end
end
context 'when runner is assigned to a group' do
before do
build.project.runners = []
runner.groups << create(:group, projects: [build.project])
end
it 'can handle builds' do
expect(runner.can_pick?(build)).to be_truthy
end
end
end
context 'when access_level of runner is not_protected' do
......@@ -583,4 +729,76 @@ describe Ci::Runner do
expect(described_class.search(runner.description.upcase)).to eq([runner])
end
end
describe '#assigned_to_group?' do
subject { runner.assigned_to_group? }
context 'when project runner' do
let(:runner) { create(:ci_runner, description: 'Project runner', projects: [project]) }
let(:project) { create(:project) }
it { is_expected.to be_falsey }
end
context 'when shared runner' do
let(:runner) { create(:ci_runner, :shared, description: 'Shared runner') }
it { is_expected.to be_falsey }
end
context 'when group runner' do
let(:group) { create(:group) }
let(:runner) { create(:ci_runner, description: 'Group runner', groups: [group]) }
it { is_expected.to be_truthy }
end
end
describe '#assigned_to_project?' do
subject { runner.assigned_to_project? }
context 'when group runner' do
let(:runner) { create(:ci_runner, description: 'Group runner', groups: [group]) }
let(:group) { create(:group) }
it { is_expected.to be_falsey }
end
context 'when shared runner' do
let(:runner) { create(:ci_runner, :shared, description: 'Shared runner') }
it { is_expected.to be_falsey }
end
context 'when project runner' do
let(:runner) { create(:ci_runner, description: 'Group runner', projects: [project]) }
let(:project) { create(:project) }
it { is_expected.to be_truthy }
end
end
describe '#pick_build!' do
context 'runner can pick the build' do
it 'calls #tick_runner_queue' do
ci_build = build(:ci_build)
runner = build(:ci_runner)
allow(runner).to receive(:can_pick?).with(ci_build).and_return(true)
expect(runner).to receive(:tick_runner_queue)
runner.pick_build!(ci_build)
end
end
context 'runner cannot pick the build' do
it 'does not call #tick_runner_queue' do
ci_build = build(:ci_build)
runner = build(:ci_runner)
allow(runner).to receive(:can_pick?).with(ci_build).and_return(false)
expect(runner).not_to receive(:tick_runner_queue)
runner.pick_build!(ci_build)
end
end
end
end
require 'rails_helper'
describe ProjectImportState, type: :model do
subject { create(:import_state) }
describe 'associations' do
it { is_expected.to belong_to(:project) }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:project) }
end
end
......@@ -63,7 +63,6 @@ describe Project do
it { is_expected.to have_many(:build_trace_section_names)}
it { is_expected.to have_many(:runner_projects) }
it { is_expected.to have_many(:runners) }
it { is_expected.to have_many(:active_runners) }
it { is_expected.to have_many(:variables) }
it { is_expected.to have_many(:triggers) }
it { is_expected.to have_many(:pages_domains) }
......@@ -102,6 +101,14 @@ describe Project do
end
end
context 'updating cd_cd_settings' do
it 'does not raise an error' do
project = create(:project)
expect { project.update(ci_cd_settings: nil) }.not_to raise_exception
end
end
describe '#members & #requesters' do
let(:project) { create(:project, :public, :access_requestable) }
let(:requester) { create(:user) }
......@@ -1139,45 +1146,106 @@ describe Project do
end
end
describe '#any_runners' do
let(:project) { create(:project, shared_runners_enabled: shared_runners_enabled) }
let(:specific_runner) { create(:ci_runner) }
let(:shared_runner) { create(:ci_runner, :shared) }
describe '#any_runners?' do
context 'shared runners' do
let(:project) { create :project, shared_runners_enabled: shared_runners_enabled }
let(:specific_runner) { create :ci_runner }
let(:shared_runner) { create :ci_runner, :shared }
context 'for shared runners disabled' do
let(:shared_runners_enabled) { false }
context 'for shared runners disabled' do
let(:shared_runners_enabled) { false }
it 'has no runners available' do
expect(project.any_runners?).to be_falsey
end
it 'has no runners available' do
expect(project.any_runners?).to be_falsey
end
it 'has a specific runner' do
project.runners << specific_runner
expect(project.any_runners?).to be_truthy
end
it 'has a specific runner' do
project.runners << specific_runner
expect(project.any_runners?).to be_truthy
end
it 'has a shared runner, but they are prohibited to use' do
shared_runner
expect(project.any_runners?).to be_falsey
end
it 'checks the presence of specific runner' do
project.runners << specific_runner
expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
end
it 'returns false if match cannot be found' do
project.runners << specific_runner
it 'has a shared runner, but they are prohibited to use' do
shared_runner
expect(project.any_runners?).to be_falsey
expect(project.any_runners? { false }).to be_falsey
end
end
it 'checks the presence of specific runner' do
project.runners << specific_runner
expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
context 'for shared runners enabled' do
let(:shared_runners_enabled) { true }
it 'has a shared runner' do
shared_runner
expect(project.any_runners?).to be_truthy
end
it 'checks the presence of shared runner' do
shared_runner
expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
end
it 'returns false if match cannot be found' do
shared_runner
expect(project.any_runners? { false }).to be_falsey
end
end
end
context 'for shared runners enabled' do
let(:shared_runners_enabled) { true }
context 'group runners' do
let(:project) { create :project, group_runners_enabled: group_runners_enabled }
let(:group) { create :group, projects: [project] }
let(:group_runner) { create :ci_runner, groups: [group] }
context 'for group runners disabled' do
let(:group_runners_enabled) { false }
it 'has no runners available' do
expect(project.any_runners?).to be_falsey
end
it 'has a group runner, but they are prohibited to use' do
group_runner
it 'has a shared runner' do
shared_runner
expect(project.any_runners?).to be_truthy
expect(project.any_runners?).to be_falsey
end
end
it 'checks the presence of shared runner' do
shared_runner
expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
context 'for group runners enabled' do
let(:group_runners_enabled) { true }
it 'has a group runner' do
group_runner
expect(project.any_runners?).to be_truthy
end
it 'checks the presence of group runner' do
group_runner
expect(project.any_runners? { |runner| runner == group_runner }).to be_truthy
end
it 'returns false if match cannot be found' do
group_runner
expect(project.any_runners? { false }).to be_falsey
end
end
end
end
......@@ -1635,7 +1703,8 @@ describe Project do
it 'resets project import_error' do
error_message = 'Some error'
mirror = create(:project_empty_repo, :import_started, import_error: error_message)
mirror = create(:project_empty_repo, :import_started)
mirror.import_state.update_attributes(last_error: error_message)
expect { mirror.import_finish }.to change { mirror.import_error }.from(error_message).to(nil)
end
......@@ -3279,7 +3348,8 @@ describe Project do
context 'with an import JID' do
it 'unsets the import JID' do
project = create(:project, import_jid: '123')
project = create(:project)
create(:import_state, project: project, jid: '123')
expect(Gitlab::SidekiqStatus)
.to receive(:unset)
......@@ -3541,6 +3611,18 @@ describe Project do
end
end
describe '#toggle_ci_cd_settings!' do
it 'toggles the value on #settings' do
project = create(:project, group_runners_enabled: false)
expect(project.group_runners_enabled).to be false
project.toggle_ci_cd_settings!(:group_runners_enabled)
expect(project.group_runners_enabled).to be true
end
end
describe '#gitlab_deploy_token' do
let(:project) { create(:project) }
......
require 'spec_helper'
describe TermAgreement do
describe 'validations' do
it { is_expected.to validate_presence_of(:term) }
it { is_expected.to validate_presence_of(:user) }
end
end
require 'spec_helper'
describe ApplicationSetting::TermPolicy do
include TermsHelper
set(:term) { create(:term) }
let(:user) { create(:user) }
subject(:policy) { described_class.new(user, term) }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
it 'has the correct permissions', :aggregate_failures do
is_expected.to be_allowed(:accept_terms)
is_expected.to be_allowed(:decline_terms)
end
context 'for anonymous users' do
let(:user) { nil }
it 'has the correct permissions', :aggregate_failures do
is_expected.to be_disallowed(:accept_terms)
is_expected.to be_disallowed(:decline_terms)
end
end
context 'when the terms are not current' do
before do
create(:term)
end
it 'has the correct permissions', :aggregate_failures do
is_expected.to be_disallowed(:accept_terms)
is_expected.to be_disallowed(:decline_terms)
end
end
context 'when the user already accepted the terms' do
before do
accept_terms(user)
end
it 'has the correct permissions', :aggregate_failures do
is_expected.to be_disallowed(:accept_terms)
is_expected.to be_allowed(:decline_terms)
end
end
end
require 'spec_helper'
describe GlobalPolicy do
include TermsHelper
let(:current_user) { create(:user) }
let(:user) { create(:user) }
......
......@@ -10,28 +10,36 @@ describe UserPolicy do
it { is_expected.to be_allowed(:read_user) }
end
describe "destroying a user" do
shared_examples 'changing a user' do |ability|
context "when a regular user tries to destroy another regular user" do
it { is_expected.not_to be_allowed(:destroy_user) }
it { is_expected.not_to be_allowed(ability) }
end
context "when a regular user tries to destroy themselves" do
let(:current_user) { user }
it { is_expected.to be_allowed(:destroy_user) }
it { is_expected.to be_allowed(ability) }
end
context "when an admin user tries to destroy a regular user" do
let(:current_user) { create(:user, :admin) }
it { is_expected.to be_allowed(:destroy_user) }
it { is_expected.to be_allowed(ability) }
end
context "when an admin user tries to destroy a ghost user" do
let(:current_user) { create(:user, :admin) }
let(:user) { create(:user, :ghost) }
it { is_expected.not_to be_allowed(:destroy_user) }
it { is_expected.not_to be_allowed(ability) }
end
end
describe "destroying a user" do
it_behaves_like 'changing a user', :destroy_user
end
describe "updating a user" do
it_behaves_like 'changing a user', :update_user
end
end
......@@ -145,7 +145,7 @@ describe API::ProjectImport do
describe 'GET /projects/:id/import' do
it 'returns the import status' do
project = create(:project, import_status: 'started')
project = create(:project, :import_started)
project.add_master(user)
get api("/projects/#{project.id}/import", user)
......@@ -155,8 +155,9 @@ describe API::ProjectImport do
end
it 'returns the import status and the error if failed' do
project = create(:project, import_status: 'failed', import_error: 'error')
project = create(:project, :import_failed)
project.add_master(user)
project.import_state.update_attributes(last_error: 'error')
get api("/projects/#{project.id}/import", user)
......
......@@ -42,18 +42,36 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(json_response['token']).to eq(runner.token)
expect(runner.run_untagged).to be true
expect(runner.token).not_to eq(registration_token)
expect(runner).to be_instance_type
end
context 'when project token is used' do
let(:project) { create(:project) }
it 'creates runner' do
it 'creates project runner' do
post api('/runners'), token: project.runners_token
expect(response).to have_gitlab_http_status 201
expect(project.runners.size).to eq(1)
expect(Ci::Runner.first.token).not_to eq(registration_token)
expect(Ci::Runner.first.token).not_to eq(project.runners_token)
runner = Ci::Runner.first
expect(runner.token).not_to eq(registration_token)
expect(runner.token).not_to eq(project.runners_token)
expect(runner).to be_project_type
end
end
context 'when group token is used' do
let(:group) { create(:group) }
it 'creates a group runner' do
post api('/runners'), token: group.runners_token
expect(response).to have_http_status 201
expect(group.runners.size).to eq(1)
runner = Ci::Runner.first
expect(runner.token).not_to eq(registration_token)
expect(runner.token).not_to eq(group.runners_token)
expect(runner).to be_group_type
end
end
end
......@@ -901,7 +919,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when we resend full trace' do
before do
patch_the_trace('BUILD TRACE appended appended hello')
patch_the_trace('BUILD TRACE appended appended hello', headers.merge({ 'Content-Range' => "0-32" }))
end
it 'succeeds with updating trace' do
......
......@@ -8,22 +8,27 @@ describe API::Runners do
let(:project) { create(:project, creator_id: user.id) }
let(:project2) { create(:project, creator_id: user.id) }
let!(:shared_runner) { create(:ci_runner, :shared) }
let!(:unused_specific_runner) { create(:ci_runner) }
let(:group) { create(:group).tap { |group| group.add_owner(user) } }
let(:group2) { create(:group).tap { |group| group.add_owner(user) } }
let!(:specific_runner) do
create(:ci_runner).tap do |runner|
let!(:shared_runner) { create(:ci_runner, :shared, description: 'Shared runner') }
let!(:unused_project_runner) { create(:ci_runner) }
let!(:project_runner) do
create(:ci_runner, description: 'Project runner').tap do |runner|
create(:ci_runner_project, runner: runner, project: project)
end
end
let!(:two_projects_runner) do
create(:ci_runner).tap do |runner|
create(:ci_runner, description: 'Two projects runner').tap do |runner|
create(:ci_runner_project, runner: runner, project: project)
create(:ci_runner_project, runner: runner, project: project2)
end
end
let!(:group_runner) { create(:ci_runner, description: 'Group runner', groups: [group]) }
before do
# Set project access for users
create(:project_member, :master, user: user, project: project)
......@@ -37,9 +42,13 @@ describe API::Runners do
get api('/runners', user)
shared = json_response.any? { |r| r['is_shared'] }
descriptions = json_response.map { |runner| runner['description'] }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(descriptions).to contain_exactly(
'Project runner', 'Two projects runner'
)
expect(shared).to be_falsey
end
......@@ -129,10 +138,16 @@ describe API::Runners do
context 'when runner is not shared' do
it "returns runner's details" do
get api("/runners/#{specific_runner.id}", admin)
get api("/runners/#{project_runner.id}", admin)
expect(response).to have_gitlab_http_status(200)
expect(json_response['description']).to eq(specific_runner.description)
expect(json_response['description']).to eq(project_runner.description)
end
it "returns the project's details for a project runner" do
get api("/runners/#{project_runner.id}", admin)
expect(json_response['projects'].first['id']).to eq(project.id)
end
end
......@@ -146,10 +161,10 @@ describe API::Runners do
context "runner project's administrative user" do
context 'when runner is not shared' do
it "returns runner's details" do
get api("/runners/#{specific_runner.id}", user)
get api("/runners/#{project_runner.id}", user)
expect(response).to have_gitlab_http_status(200)
expect(json_response['description']).to eq(specific_runner.description)
expect(json_response['description']).to eq(project_runner.description)
end
end
......@@ -164,18 +179,18 @@ describe API::Runners do
end
context 'other authorized user' do
it "does not return runner's details" do
get api("/runners/#{specific_runner.id}", user2)
it "does not return project runner's details" do
get api("/runners/#{project_runner.id}", user2)
expect(response).to have_gitlab_http_status(403)
expect(response).to have_http_status(403)
end
end
context 'unauthorized user' do
it "does not return runner's details" do
get api("/runners/#{specific_runner.id}")
it "does not return project runner's details" do
get api("/runners/#{project_runner.id}")
expect(response).to have_gitlab_http_status(401)
expect(response).to have_http_status(401)
end
end
end
......@@ -212,16 +227,16 @@ describe API::Runners do
context 'when runner is not shared' do
it 'updates runner' do
description = specific_runner.description
runner_queue_value = specific_runner.ensure_runner_queue_value
description = project_runner.description
runner_queue_value = project_runner.ensure_runner_queue_value
update_runner(specific_runner.id, admin, description: 'test')
specific_runner.reload
update_runner(project_runner.id, admin, description: 'test')
project_runner.reload
expect(response).to have_gitlab_http_status(200)
expect(specific_runner.description).to eq('test')
expect(specific_runner.description).not_to eq(description)
expect(specific_runner.ensure_runner_queue_value)
expect(project_runner.description).to eq('test')
expect(project_runner.description).not_to eq(description)
expect(project_runner.ensure_runner_queue_value)
.not_to eq(runner_queue_value)
end
end
......@@ -247,29 +262,29 @@ describe API::Runners do
end
context 'when runner is not shared' do
it 'does not update runner without access to it' do
put api("/runners/#{specific_runner.id}", user2), description: 'test'
it 'does not update project runner without access to it' do
put api("/runners/#{project_runner.id}", user2), description: 'test'
expect(response).to have_gitlab_http_status(403)
expect(response).to have_http_status(403)
end
it 'updates runner with access to it' do
description = specific_runner.description
put api("/runners/#{specific_runner.id}", admin), description: 'test'
specific_runner.reload
it 'updates project runner with access to it' do
description = project_runner.description
put api("/runners/#{project_runner.id}", admin), description: 'test'
project_runner.reload
expect(response).to have_gitlab_http_status(200)
expect(specific_runner.description).to eq('test')
expect(specific_runner.description).not_to eq(description)
expect(project_runner.description).to eq('test')
expect(project_runner.description).not_to eq(description)
end
end
end
context 'unauthorized user' do
it 'does not delete runner' do
put api("/runners/#{specific_runner.id}")
it 'does not delete project runner' do
put api("/runners/#{project_runner.id}")
expect(response).to have_gitlab_http_status(401)
expect(response).to have_http_status(401)
end
end
end
......@@ -293,17 +308,17 @@ describe API::Runners do
context 'when runner is not shared' do
it 'deletes unused runner' do
expect do
delete api("/runners/#{unused_specific_runner.id}", admin)
delete api("/runners/#{unused_project_runner.id}", admin)
expect(response).to have_gitlab_http_status(204)
end.to change { Ci::Runner.specific.count }.by(-1)
end
it 'deletes used runner' do
it 'deletes used project runner' do
expect do
delete api("/runners/#{specific_runner.id}", admin)
delete api("/runners/#{project_runner.id}", admin)
expect(response).to have_gitlab_http_status(204)
expect(response).to have_http_status(204)
end.to change { Ci::Runner.specific.count }.by(-1)
end
end
......@@ -325,34 +340,34 @@ describe API::Runners do
context 'when runner is not shared' do
it 'does not delete runner without access to it' do
delete api("/runners/#{specific_runner.id}", user2)
delete api("/runners/#{project_runner.id}", user2)
expect(response).to have_gitlab_http_status(403)
end
it 'does not delete runner with more than one associated project' do
it 'does not delete project runner with more than one associated project' do
delete api("/runners/#{two_projects_runner.id}", user)
expect(response).to have_gitlab_http_status(403)
end
it 'deletes runner for one owned project' do
it 'deletes project runner for one owned project' do
expect do
delete api("/runners/#{specific_runner.id}", user)
delete api("/runners/#{project_runner.id}", user)
expect(response).to have_gitlab_http_status(204)
expect(response).to have_http_status(204)
end.to change { Ci::Runner.specific.count }.by(-1)
end
it_behaves_like '412 response' do
let(:request) { api("/runners/#{specific_runner.id}", user) }
let(:request) { api("/runners/#{project_runner.id}", user) }
end
end
end
context 'unauthorized user' do
it 'does not delete runner' do
delete api("/runners/#{specific_runner.id}")
it 'does not delete project runner' do
delete api("/runners/#{project_runner.id}")
expect(response).to have_gitlab_http_status(401)
expect(response).to have_http_status(401)
end
end
end
......@@ -361,8 +376,8 @@ describe API::Runners do
set(:job_1) { create(:ci_build) }
let!(:job_2) { create(:ci_build, :running, runner: shared_runner, project: project) }
let!(:job_3) { create(:ci_build, :failed, runner: shared_runner, project: project) }
let!(:job_4) { create(:ci_build, :running, runner: specific_runner, project: project) }
let!(:job_5) { create(:ci_build, :failed, runner: specific_runner, project: project) }
let!(:job_4) { create(:ci_build, :running, runner: project_runner, project: project) }
let!(:job_5) { create(:ci_build, :failed, runner: project_runner, project: project) }
context 'admin user' do
context 'when runner exists' do
......@@ -380,7 +395,7 @@ describe API::Runners do
context 'when runner is specific' do
it 'return jobs' do
get api("/runners/#{specific_runner.id}/jobs", admin)
get api("/runners/#{project_runner.id}/jobs", admin)
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
......@@ -392,7 +407,7 @@ describe API::Runners do
context 'when valid status is provided' do
it 'return filtered jobs' do
get api("/runners/#{specific_runner.id}/jobs?status=failed", admin)
get api("/runners/#{project_runner.id}/jobs?status=failed", admin)
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
......@@ -405,7 +420,7 @@ describe API::Runners do
context 'when invalid status is provided' do
it 'return 400' do
get api("/runners/#{specific_runner.id}/jobs?status=non-existing", admin)
get api("/runners/#{project_runner.id}/jobs?status=non-existing", admin)
expect(response).to have_gitlab_http_status(400)
end
......@@ -433,7 +448,7 @@ describe API::Runners do
context 'when runner is specific' do
it 'return jobs' do
get api("/runners/#{specific_runner.id}/jobs", user)
get api("/runners/#{project_runner.id}/jobs", user)
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
......@@ -445,7 +460,7 @@ describe API::Runners do
context 'when valid status is provided' do
it 'return filtered jobs' do
get api("/runners/#{specific_runner.id}/jobs?status=failed", user)
get api("/runners/#{project_runner.id}/jobs?status=failed", user)
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
......@@ -458,7 +473,7 @@ describe API::Runners do
context 'when invalid status is provided' do
it 'return 400' do
get api("/runners/#{specific_runner.id}/jobs?status=non-existing", user)
get api("/runners/#{project_runner.id}/jobs?status=non-existing", user)
expect(response).to have_gitlab_http_status(400)
end
......@@ -476,7 +491,7 @@ describe API::Runners do
context 'other authorized user' do
it 'does not return jobs' do
get api("/runners/#{specific_runner.id}/jobs", user2)
get api("/runners/#{project_runner.id}/jobs", user2)
expect(response).to have_gitlab_http_status(403)
end
......@@ -484,7 +499,7 @@ describe API::Runners do
context 'unauthorized user' do
it 'does not return jobs' do
get api("/runners/#{specific_runner.id}/jobs")
get api("/runners/#{project_runner.id}/jobs")
expect(response).to have_gitlab_http_status(401)
end
......@@ -523,7 +538,7 @@ describe API::Runners do
describe 'POST /projects/:id/runners' do
context 'authorized user' do
let(:specific_runner2) do
let(:project_runner2) do
create(:ci_runner).tap do |runner|
create(:ci_runner_project, runner: runner, project: project2)
end
......@@ -531,23 +546,23 @@ describe API::Runners do
it 'enables specific runner' do
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
post api("/projects/#{project.id}/runners", user), runner_id: project_runner2.id
end.to change { project.runners.count }.by(+1)
expect(response).to have_gitlab_http_status(201)
end
it 'avoids changes when enabling already enabled runner' do
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id
post api("/projects/#{project.id}/runners", user), runner_id: project_runner.id
end.to change { project.runners.count }.by(0)
expect(response).to have_gitlab_http_status(409)
end
it 'does not enable locked runner' do
specific_runner2.update(locked: true)
project_runner2.update(locked: true)
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
post api("/projects/#{project.id}/runners", user), runner_id: project_runner2.id
end.to change { project.runners.count }.by(0)
expect(response).to have_gitlab_http_status(403)
......@@ -559,10 +574,16 @@ describe API::Runners do
expect(response).to have_gitlab_http_status(403)
end
it 'does not enable group runner' do
post api("/projects/#{project.id}/runners", user), runner_id: group_runner.id
expect(response).to have_http_status(403)
end
context 'user is admin' do
it 'enables any specific runner' do
expect do
post api("/projects/#{project.id}/runners", admin), runner_id: unused_specific_runner.id
post api("/projects/#{project.id}/runners", admin), runner_id: unused_project_runner.id
end.to change { project.runners.count }.by(+1)
expect(response).to have_gitlab_http_status(201)
end
......@@ -570,7 +591,7 @@ describe API::Runners do
context 'user is not admin' do
it 'does not enable runner without access to' do
post api("/projects/#{project.id}/runners", user), runner_id: unused_specific_runner.id
post api("/projects/#{project.id}/runners", user), runner_id: unused_project_runner.id
expect(response).to have_gitlab_http_status(403)
end
......@@ -619,7 +640,7 @@ describe API::Runners do
context 'when runner have one associated projects' do
it "does not disable project's runner" do
expect do
delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user)
delete api("/projects/#{project.id}/runners/#{project_runner.id}", user)
end.to change { project.runners.count }.by(0)
expect(response).to have_gitlab_http_status(403)
end
......@@ -634,7 +655,7 @@ describe API::Runners do
context 'authorized user without permissions' do
it "does not disable project's runner" do
delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user2)
delete api("/projects/#{project.id}/runners/#{project_runner.id}", user2)
expect(response).to have_gitlab_http_status(403)
end
......@@ -642,7 +663,7 @@ describe API::Runners do
context 'unauthorized user' do
it "does not disable project's runner" do
delete api("/projects/#{project.id}/runners/#{specific_runner.id}")
delete api("/projects/#{project.id}/runners/#{project_runner.id}")
expect(response).to have_gitlab_http_status(401)
end
......
......@@ -54,7 +54,9 @@ describe API::Settings, 'Settings' do
dsa_key_restriction: 2048,
ecdsa_key_restriction: 384,
ed25519_key_restriction: 256,
circuitbreaker_check_interval: 2
circuitbreaker_check_interval: 2,
enforce_terms: true,
terms: 'Hello world!'
expect(response).to have_gitlab_http_status(200)
expect(json_response['default_projects_limit']).to eq(3)
......@@ -76,6 +78,8 @@ describe API::Settings, 'Settings' do
expect(json_response['ecdsa_key_restriction']).to eq(384)
expect(json_response['ed25519_key_restriction']).to eq(256)
expect(json_response['circuitbreaker_check_interval']).to eq(2)
expect(json_response['enforce_terms']).to be(true)
expect(json_response['terms']).to eq('Hello world!')
end
end
......
......@@ -118,7 +118,7 @@ describe PipelineSerializer do
it 'verifies number of queries', :request_store do
recorded = ActiveRecord::QueryRecorder.new { subject }
expect(recorded.count).to be_within(1).of(36)
expect(recorded.count).to be_within(1).of(44)
expect(recorded.cached_count).to eq(0)
end
end
......
require 'spec_helper'
describe ApplicationSettings::UpdateService do
let(:application_settings) { Gitlab::CurrentSettings.current_application_settings }
let(:admin) { create(:user, :admin) }
let(:params) { {} }
subject { described_class.new(application_settings, admin, params) }
before do
# So the caching behaves like it would in production
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
describe 'updating terms' do
context 'when the passed terms are blank' do
let(:params) { { terms: '' } }
it 'does not create terms' do
expect { subject.execute }.not_to change { ApplicationSetting::Term.count }
end
end
context 'when passing terms' do
let(:params) { { terms: 'Be nice! ' } }
it 'creates the terms' do
expect { subject.execute }.to change { ApplicationSetting::Term.count }.by(1)
end
it 'does not create terms if they are the same as the existing ones' do
create(:term, terms: 'Be nice!')
expect { subject.execute }.not_to change { ApplicationSetting::Term.count }
end
it 'updates terms if they already existed' do
create(:term, terms: 'Other terms')
subject.execute
expect(application_settings.terms).to eq('Be nice!')
end
it 'Only queries once when the terms are changed' do
create(:term, terms: 'Other terms')
expect(application_settings.terms).to eq('Other terms')
subject.execute
expect(application_settings.terms).to eq('Be nice!')
expect { 2.times { application_settings.terms } }
.not_to exceed_query_limit(0)
end
end
end
end
......@@ -17,11 +17,13 @@ describe Ci::CreatePipelineService do
after: project.commit.id,
message: 'Message',
ref: ref_name,
trigger_request: nil)
trigger_request: nil,
variables_attributes: nil)
params = { ref: ref,
before: '00000000',
after: after,
commits: [{ message: message }] }
commits: [{ message: message }],
variables_attributes: variables_attributes }
described_class.new(project, user, params).execute(
source, trigger_request: trigger_request)
......@@ -545,5 +547,19 @@ describe Ci::CreatePipelineService do
expect(pipeline.tag?).to be true
end
end
context 'when pipeline variables are specified' do
let(:variables_attributes) do
[{ key: 'first', secret_value: 'world' },
{ key: 'second', secret_value: 'second_world' }]
end
subject { execute_service(variables_attributes: variables_attributes) }
it 'creates a pipeline with specified variables' do
expect(subject.variables.map { |var| var.slice(:key, :secret_value) })
.to eq variables_attributes.map(&:with_indifferent_access)
end
end
end
end
......@@ -2,11 +2,13 @@ require 'spec_helper'
module Ci
describe RegisterJobService do
let!(:project) { FactoryBot.create :project, shared_runners_enabled: false }
let!(:pipeline) { FactoryBot.create :ci_pipeline, project: project }
let!(:pending_job) { FactoryBot.create :ci_build, pipeline: pipeline }
let!(:shared_runner) { FactoryBot.create(:ci_runner, is_shared: true) }
let!(:specific_runner) { FactoryBot.create(:ci_runner, is_shared: false) }
set(:group) { create(:group) }
set(:project) { create(:project, group: group, shared_runners_enabled: false, group_runners_enabled: false) }
set(:pipeline) { create(:ci_pipeline, project: project) }
let!(:shared_runner) { create(:ci_runner, is_shared: true) }
let!(:specific_runner) { create(:ci_runner, is_shared: false) }
let!(:group_runner) { create(:ci_runner, groups: [group], runner_type: :group_type) }
let!(:pending_job) { create(:ci_build, pipeline: pipeline) }
before do
specific_runner.assign_to(project)
......@@ -150,7 +152,7 @@ module Ci
context 'disallow when builds are disabled' do
before do
project.update(shared_runners_enabled: true)
project.update(shared_runners_enabled: true, group_runners_enabled: true)
project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
end
......@@ -160,13 +162,90 @@ module Ci
it { expect(build).to be_nil }
end
context 'and uses specific runner' do
context 'and uses group runner' do
let(:build) { execute(group_runner) }
it { expect(build).to be_nil }
end
context 'and uses project runner' do
let(:build) { execute(specific_runner) }
it { expect(build).to be_nil }
end
end
context 'allow group runners' do
before do
project.update!(group_runners_enabled: true)
end
context 'for multiple builds' do
let!(:project2) { create :project, group_runners_enabled: true, group: group }
let!(:pipeline2) { create :ci_pipeline, project: project2 }
let!(:project3) { create :project, group_runners_enabled: true, group: group }
let!(:pipeline3) { create :ci_pipeline, project: project3 }
let!(:build1_project1) { pending_job }
let!(:build2_project1) { create :ci_build, pipeline: pipeline }
let!(:build3_project1) { create :ci_build, pipeline: pipeline }
let!(:build1_project2) { create :ci_build, pipeline: pipeline2 }
let!(:build2_project2) { create :ci_build, pipeline: pipeline2 }
let!(:build1_project3) { create :ci_build, pipeline: pipeline3 }
# these shouldn't influence the scheduling
let!(:unrelated_group) { create :group }
let!(:unrelated_project) { create :project, group_runners_enabled: true, group: unrelated_group }
let!(:unrelated_pipeline) { create :ci_pipeline, project: unrelated_project }
let!(:build1_unrelated_project) { create :ci_build, pipeline: unrelated_pipeline }
let!(:unrelated_group_runner) { create :ci_runner, groups: [unrelated_group] }
it 'does not consider builds from other group runners' do
expect(described_class.new(group_runner).send(:builds_for_group_runner).count).to eq 6
execute(group_runner)
expect(described_class.new(group_runner).send(:builds_for_group_runner).count).to eq 5
execute(group_runner)
expect(described_class.new(group_runner).send(:builds_for_group_runner).count).to eq 4
execute(group_runner)
expect(described_class.new(group_runner).send(:builds_for_group_runner).count).to eq 3
execute(group_runner)
expect(described_class.new(group_runner).send(:builds_for_group_runner).count).to eq 2
execute(group_runner)
expect(described_class.new(group_runner).send(:builds_for_group_runner).count).to eq 1
execute(group_runner)
expect(described_class.new(group_runner).send(:builds_for_group_runner).count).to eq 0
expect(execute(group_runner)).to be_nil
end
end
context 'group runner' do
let(:build) { execute(group_runner) }
it { expect(build).to be_kind_of(Build) }
it { expect(build).to be_valid }
it { expect(build).to be_running }
it { expect(build.runner).to eq(group_runner) }
end
end
context 'disallow group runners' do
before do
project.update!(group_runners_enabled: false)
end
context 'group runner' do
let(:build) { execute(group_runner) }
it { expect(build).to be_nil }
end
end
context 'when first build is stalled' do
before do
pending_job.update(lock_version: 0)
......@@ -178,7 +257,7 @@ module Ci
let!(:other_build) { create :ci_build, pipeline: pipeline }
before do
allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner)
allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_project_runner)
.and_return(Ci::Build.where(id: [pending_job, other_build]))
end
......@@ -190,7 +269,7 @@ module Ci
context 'when single build is in queue' do
before do
allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner)
allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_project_runner)
.and_return(Ci::Build.where(id: pending_job))
end
......@@ -201,7 +280,7 @@ module Ci
context 'when there is no build in queue' do
before do
allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_specific_runner)
allow_any_instance_of(Ci::RegisterJobService).to receive(:builds_for_project_runner)
.and_return(Ci::Build.none)
end
......
......@@ -8,21 +8,19 @@ describe Ci::UpdateBuildQueueService do
context 'when updating specific runners' do
let(:runner) { create(:ci_runner) }
context 'when there are runner that can pick build' do
context 'when there is a runner that can pick build' do
before do
build.project.runners << runner
end
it 'ticks runner queue value' do
expect { subject.execute(build) }
.to change { runner.ensure_runner_queue_value }
expect { subject.execute(build) }.to change { runner.ensure_runner_queue_value }
end
end
context 'when there are no runners that can pick build' do
context 'when there is no runner that can pick build' do
it 'does not tick runner queue value' do
expect { subject.execute(build) }
.not_to change { runner.ensure_runner_queue_value }
expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
end
end
end
......@@ -30,21 +28,61 @@ describe Ci::UpdateBuildQueueService do
context 'when updating shared runners' do
let(:runner) { create(:ci_runner, :shared) }
context 'when there are runner that can pick build' do
context 'when there is no runner that can pick build' do
it 'ticks runner queue value' do
expect { subject.execute(build) }
.to change { runner.ensure_runner_queue_value }
expect { subject.execute(build) }.to change { runner.ensure_runner_queue_value }
end
end
context 'when there are no runners that can pick build' do
context 'when there is no runner that can pick build due to tag mismatch' do
before do
build.tag_list = [:docker]
end
it 'does not tick runner queue value' do
expect { subject.execute(build) }
.not_to change { runner.ensure_runner_queue_value }
expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
end
end
context 'when there is no runner that can pick build due to being disabled on project' do
before do
build.project.shared_runners_enabled = false
end
it 'does not tick runner queue value' do
expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
end
end
end
context 'when updating group runners' do
let(:group) { create :group }
let(:project) { create :project, group: group }
let(:runner) { create :ci_runner, groups: [group] }
context 'when there is a runner that can pick build' do
it 'ticks runner queue value' do
expect { subject.execute(build) }.to change { runner.ensure_runner_queue_value }
end
end
context 'when there is no runner that can pick build due to tag mismatch' do
before do
build.tag_list = [:docker]
end
it 'does not tick runner queue value' do
expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
end
end
context 'when there is no runner that can pick build due to being disabled on project' do
before do
build.project.group_runners_enabled = false
end
it 'does not tick runner queue value' do
expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
end
end
end
......
......@@ -23,7 +23,7 @@ describe Projects::CreateFromTemplateService do
project = subject.execute
expect(project).to be_saved
expect(project.scheduled?).to be(true)
expect(project.import_scheduled?).to be(true)
end
context 'the result project' do
......
require 'spec_helper'
describe Users::RespondToTermsService do
let(:user) { create(:user) }
let(:term) { create(:term) }
subject(:service) { described_class.new(user, term) }
describe '#execute' do
it 'creates a new agreement if it did not exist' do
expect { service.execute(accepted: true) }
.to change { user.term_agreements.size }.by(1)
end
it 'updates an agreement if it existed' do
agreement = create(:term_agreement, user: user, term: term, accepted: true)
service.execute(accepted: true)
expect(agreement.reload.accepted).to be_truthy
end
it 'adds the accepted terms to the user' do
service.execute(accepted: true)
expect(user.reload.accepted_term).to eq(term)
end
it 'removes accepted terms when declining' do
user.update!(accepted_term: term)
service.execute(accepted: false)
expect(user.reload.accepted_term).to be_nil
end
end
end
......@@ -67,7 +67,7 @@ describe WebHookService do
end
it 'handles exceptions' do
exceptions = [SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout]
exceptions = [SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError]
exceptions.each do |exception_class|
exception = exception_class.new('Exception message')
......
module TermsHelper
def enforce_terms
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
settings = Gitlab::CurrentSettings.current_application_settings
ApplicationSettings::UpdateService.new(
settings, nil, terms: 'These are the terms', enforce_terms: true
).execute
end
def accept_terms(user)
terms = Gitlab::CurrentSettings.current_application_settings.latest_terms
Users::RespondToTermsService.new(user, terms).execute(accepted: true)
end
def expect_to_be_on_terms_page
expect(current_path).to eq terms_path
expect(page).to have_content('Please accept the Terms of Service before continuing.')
end
end
......@@ -4,9 +4,10 @@ describe "projects/imports/new.html.haml" do
let(:user) { create(:user) }
context 'when import fails' do
let(:project) { create(:project_empty_repo, import_status: :failed, import_error: '<a href="http://googl.com">Foo</a>', import_type: :gitlab_project, import_source: '/var/opt/gitlab/gitlab-rails/shared/tmp/project_exports/uploads/t.tar.gz', import_url: nil) }
let(:project) { create(:project_empty_repo, :import_failed, import_type: :gitlab_project, import_source: '/var/opt/gitlab/gitlab-rails/shared/tmp/project_exports/uploads/t.tar.gz', import_url: nil) }
before do
project.import_state.update_attributes(last_error: '<a href="http://googl.com">Foo</a>')
sign_in(user)
project.add_master(user)
end
......
require 'spec_helper'
describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_state do
let(:project) { create(:project, import_jid: '123') }
let(:project) { create(:project) }
let(:import_state) { create(:import_state, project: project, jid: '123') }
let(:worker) { described_class.new }
describe '#perform' do
......@@ -105,7 +106,8 @@ describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_st
# This test is there to make sure we only select the columns we care
# about.
expect(found.attributes).to eq({ 'id' => nil, 'import_jid' => '123' })
# TODO: enable this assertion back again
# expect(found.attributes).to include({ 'id' => nil, 'import_jid' => '123' })
end
it 'returns nil if the project import is not running' do
......
......@@ -14,7 +14,8 @@ describe Gitlab::GithubImport::RefreshImportJidWorker do
end
describe '#perform' do
let(:project) { create(:project, import_jid: '123abc') }
let(:project) { create(:project) }
let(:import_state) { create(:import_state, project: project, jid: '123abc') }
context 'when the project does not exist' do
it 'does nothing' do
......@@ -70,20 +71,21 @@ describe Gitlab::GithubImport::RefreshImportJidWorker do
describe '#find_project' do
it 'returns a Project' do
project = create(:project, import_status: 'started')
project = create(:project, :import_started)
expect(worker.find_project(project.id)).to be_an_instance_of(Project)
end
it 'only selects the import JID field' do
project = create(:project, import_status: 'started', import_jid: '123abc')
expect(worker.find_project(project.id).attributes)
.to eq({ 'id' => nil, 'import_jid' => '123abc' })
end
# it 'only selects the import JID field' do
# project = create(:project, :import_started)
# project.import_state.update_attributes(jid: '123abc')
#
# expect(worker.find_project(project.id).attributes)
# .to eq({ 'id' => nil, 'import_jid' => '123abc' })
# end
it 'returns nil for a project for which the import process failed' do
project = create(:project, import_status: 'failed')
project = create(:project, :import_failed)
expect(worker.find_project(project.id)).to be_nil
end
......
......@@ -11,10 +11,12 @@ describe RepositoryImportWorker do
let(:project) { create(:project, :import_scheduled) }
context 'when worker was reset without cleanup' do
let(:jid) { '12345678' }
let(:started_project) { create(:project, :import_started, import_jid: jid) }
it 'imports the project successfully' do
jid = '12345678'
started_project = create(:project)
create(:import_state, :started, project: started_project, jid: jid)
allow(subject).to receive(:jid).and_return(jid)
expect_any_instance_of(Projects::ImportService).to receive(:execute)
......
......@@ -48,13 +48,21 @@ describe StuckImportJobsWorker do
describe 'with scheduled import_status' do
it_behaves_like 'project import job detection' do
let(:project) { create(:project, :import_scheduled, import_jid: '123') }
let(:project) { create(:project, :import_scheduled) }
before do
project.import_state.update_attributes(jid: '123')
end
end
end
describe 'with started import_status' do
it_behaves_like 'project import job detection' do
let(:project) { create(:project, :import_started, import_jid: '123') }
let(:project) { create(:project, :import_started) }
before do
project.import_state.update_attributes(jid: '123')
end
end
end
end
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