Commit 7ac065d0 authored by Paweł Chojnacki's avatar Paweł Chojnacki

Merge branch 'master' into 'bump-prometheus-version-to-fix-text-representation-problem'

# Conflicts:
#   Gemfile.lock
parents b5f3903b d453bb86
......@@ -285,6 +285,7 @@ group :metrics do
# Prometheus
gem 'prometheus-client-mmap', '~>0.7.0.beta5'
gem 'raindrops', '~> 0.18'
end
group :development do
......
......@@ -658,7 +658,7 @@ GEM
thor (>= 0.18.1, < 2.0)
rainbow (2.2.2)
rake
raindrops (0.17.0)
raindrops (0.18.0)
rake (10.5.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
......@@ -1062,6 +1062,7 @@ DEPENDENCIES
rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 4.0.9)
rainbow (~> 2.2)
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rdoc (~> 4.2)
recaptcha (~> 3.0)
......
......@@ -206,8 +206,6 @@ function UsersSelect(currentUser, els) {
return $dropdown.glDropdown({
showMenuAbove: showMenuAbove,
data: function(term, callback) {
var isAuthorFilter;
isAuthorFilter = $('.js-author-search');
return _this.users(term, options, function(users) {
// GitLabDropdownFilter returns this.instance
// GitLabDropdownRemote returns this.options.instance
......
......@@ -60,13 +60,13 @@ class UsersFinder
end
def by_external_identity(users)
return users unless current_user.admin? && params[:extern_uid] && params[:provider]
return users unless current_user&.admin? && params[:extern_uid] && params[:provider]
users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid]))
end
def by_external(users)
return users = users.where.not(external: true) unless current_user.admin?
return users = users.where.not(external: true) unless current_user&.admin?
return users unless params[:external]
users.external
......
......@@ -16,8 +16,8 @@ module FormHelper
end
end
def issue_dropdown_options(issuable, has_multiple_assignees = true)
options = {
def issue_assignees_dropdown_options
{
toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data',
title: 'Select assignee',
filter: true,
......@@ -27,8 +27,8 @@ module FormHelper
first_user: current_user&.username,
null_user: true,
current_user: true,
project_id: issuable.project.try(:id),
field_name: "#{issuable.class.model_name.param_key}[assignee_ids][]",
project_id: @project.id,
field_name: 'issue[assignee_ids][]',
default_label: 'Unassigned',
'max-select': 1,
'dropdown-header': 'Assignee',
......@@ -38,13 +38,5 @@ module FormHelper
current_user_info: current_user.to_json(only: [:id, :name])
}
}
if has_multiple_assignees
options[:title] = 'Select assignee(s)'
options[:data][:'dropdown-header'] = 'Assignee(s)'
options[:data].delete(:'max-select')
end
options
end
end
......@@ -58,6 +58,11 @@ module GroupsHelper
IssuesFinder.new(current_user, group_id: group.id).execute
end
def remove_group_message(group)
_("You are going to remove %{group_name}.\nRemoved groups CANNOT be restored!\nAre you ABSOLUTELY sure?") %
{ group_name: group.name }
end
private
def group_title_link(group, hidable: false)
......
......@@ -126,6 +126,18 @@ module SearchHelper
search_path(options)
end
def search_filter_input_options(type)
{
id: "filtered-search-#{type}",
placeholder: 'Search or filter results...',
data: {
'project-id' => @project.id,
'username-params' => @users.to_json(only: [:id, :username]),
'base-endpoint' => namespace_project_path(@project.namespace, @project)
}
}
end
# Sanitize a HTML field for search display. Most tags are stripped out and the
# maximum length is set to 200 characters.
def search_md_sanitize(object, field)
......
......@@ -102,6 +102,14 @@ module Issuable
def locking_enabled?
title_changed? || description_changed?
end
def allows_multiple_assignees?
false
end
def has_multiple_assignees?
assignees.count > 1
end
end
module ClassMethods
......
......@@ -197,11 +197,19 @@ class MergeRequest < ActiveRecord::Base
}
end
# This method is needed for compatibility with issues to not mess view and other code
# These method are needed for compatibility with issues to not mess view and other code
def assignees
Array(assignee)
end
def assignee_ids
Array(assignee_id)
end
def assignee_ids=(ids)
write_attribute(:assignee_id, ids.last)
end
def assignee_or_author?(user)
author_id == user.id || assignee_id == user.id
end
......
require_dependency 'declarative_policy'
class BasePolicy < DeclarativePolicy::Base
include Gitlab::CurrentSettings
desc "User is an instance admin"
with_options scope: :user, score: 0
condition(:admin) { @user&.admin? }
......@@ -10,4 +12,9 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0
condition(:can_create_group) { @user&.can_create_group }
desc "The application is restricted from public visibility"
condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
end
......@@ -11,10 +11,16 @@ class GlobalPolicy < BasePolicy
with_options scope: :user, score: 0
condition(:access_locked) { @user.access_locked? }
rule { anonymous }.prevent_all
rule { anonymous }.policy do
prevent :log_in
prevent :access_api
prevent :access_git
prevent :receive_notifications
prevent :use_quick_actions
prevent :create_group
end
rule { default }.policy do
enable :read_users_list
enable :log_in
enable :access_api
enable :access_git
......@@ -37,4 +43,8 @@ class GlobalPolicy < BasePolicy
rule { access_locked }.policy do
prevent :log_in
end
rule { ~restricted_public_level }.policy do
enable :read_users_list
end
end
class UserPolicy < BasePolicy
include Gitlab::CurrentSettings
desc "The application is restricted from public visibility"
condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
desc "The current user is the user in question"
condition(:user_is_self, score: 0) { @subject == @user }
......
......@@ -92,9 +92,12 @@ module QuickActions
desc 'Assign'
explanation do |users|
"Assigns #{users.first.to_reference}." if users.any?
users = issuable.allows_multiple_assignees? ? users : users.take(1)
"Assigns #{users.map(&:to_reference).to_sentence}."
end
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
end
params '@user'
condition do
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
......@@ -104,28 +107,69 @@ module QuickActions
command :assign do |users|
next if users.empty?
if issuable.is_a?(Issue)
@updates[:assignee_ids] = [users.last.id]
@updates[:assignee_ids] =
if issuable.allows_multiple_assignees?
issuable.assignees.pluck(:id) + users.map(&:id)
else
[users.last.id]
end
end
desc do
if issuable.allows_multiple_assignees?
'Remove all or specific assignee(s)'
else
@updates[:assignee_id] = users.last.id
'Remove assignee'
end
end
desc 'Remove assignee'
explanation do
"Removes assignee #{issuable.assignees.first.to_reference}."
"Removes #{'assignee'.pluralize(issuable.assignees.size)} #{issuable.assignees.map(&:to_reference).to_sentence}."
end
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
end
condition do
issuable.persisted? &&
issuable.assignees.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
command :unassign do
if issuable.is_a?(Issue)
@updates[:assignee_ids] = []
else
@updates[:assignee_id] = nil
end
parse_params do |unassign_param|
# When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
extract_users(unassign_param) if issuable.allows_multiple_assignees?
end
command :unassign do |users = nil|
@updates[:assignee_ids] =
if users&.any?
issuable.assignees.pluck(:id) - users.map(&:id)
else
[]
end
end
desc do
"Change assignee#{'(s)' if issuable.allows_multiple_assignees?}"
end
explanation do |users|
users = issuable.allows_multiple_assignees? ? users : users.take(1)
"Change #{'assignee'.pluralize(users.size)} to #{users.map(&:to_reference).to_sentence}."
end
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
end
condition do
issuable.persisted? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
parse_params do |assignee_param|
extract_users(assignee_param)
end
command :reassign do |users|
@updates[:assignee_ids] =
if issuable.allows_multiple_assignees?
users.map(&:id)
else
[users.last.id]
end
end
desc 'Set milestone'
......
......@@ -45,10 +45,13 @@
.panel.panel-danger
.panel-heading Remove group
.panel-body
%p
Removing group will cause all child projects and resources to be removed.
%br
%strong Removed group can not be restored!
= form_tag(@group, method: :delete) do
%p
Removing group will cause all child projects and resources to be removed.
%br
%strong Removed group can not be restored!
.form-actions
= link_to 'Remove group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove"
.form-actions
= button_to 'Remove group', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_group_message(@group) }
= render 'shared/confirm_modal', phrase: @group.path
......@@ -19,10 +19,11 @@
":data-name" => "assignee.name",
":data-username" => "assignee.username" }
.dropdown
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: "button", ref: "assigneeDropdown", data: { toggle: "dropdown", field_name: "issue[assignee_ids][]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true", multi_select: "true", 'max-select' => 1, dropdown: { header: 'Assignee' } },
- dropdown_options = issue_assignees_dropdown_options
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: { toggle: 'dropdown', field_name: 'issue[assignee_ids][]', first_user: current_user&.username, current_user: 'true', project_id: @project.id, null_user: 'true', multi_select: 'true', 'dropdown-header': dropdown_options[:data][:'dropdown-header'], 'max-select': dropdown_options[:data][:'max-select'] },
":data-issuable-id" => "issue.id",
":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
Select assignee
= dropdown_options[:title]
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
= dropdown_title("Assign to")
......
- @no_container = true
- page_title "Charts", "Pipelines"
- page_title _("Charts"), _("Pipelines")
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
......@@ -8,7 +8,7 @@
%div{ class: container_class }
.sub-header-block
.oneline
A collection of graphs for Continuous Integration
= _("A collection of graphs regarding Continuous Integration")
#charts.ci-charts
.row
......
%h4 Overall stats
%h4= s_("PipelineCharts|Overall statistics")
%ul
%li
Total:
%strong= pluralize @counts[:total], 'pipeline'
= s_("PipelineCharts|Total:")
%strong= n_("1 pipeline", "%d pipelines", @counts[:total]) % @counts[:total]
%li
Successful:
%strong= pluralize @counts[:success], 'pipeline'
= s_("PipelineCharts|Successful:")
%strong= n_("1 pipeline", "%d pipelines", @counts[:success]) % @counts[:success]
%li
Failed:
%strong= pluralize @counts[:failed], 'pipeline'
= s_("PipelineCharts|Failed:")
%strong= n_("1 pipeline", "%d pipelines", @counts[:failed]) % @counts[:failed]
%li
Success ratio:
= s_("PipelineCharts|Success ratio:")
%strong
#{success_ratio(@counts)}%
%div
%p.light
Commit duration in minutes for last 30 commits
= _("Commit duration in minutes for last 30 commits")
%canvas#build_timesChart{ height: 200 }
......
%h4 Pipelines charts
%h4= _("Pipelines charts")
%p
&nbsp;
%span.cgreen
= icon("circle")
success
= s_("Pipeline|success")
&nbsp;
%span.cgray
= icon("circle")
all
= s_("Pipeline|all")
.prepend-top-default
%p.light
Jobs for last week
= _("Jobs for last week")
(#{date_from_to(Date.today - 7.days, Date.today)})
%canvas#weekChart{ height: 200 }
.prepend-top-default
%p.light
Jobs for last month
= _("Jobs for last month")
(#{date_from_to(Date.today - 30.days, Date.today)})
%canvas#monthChart{ height: 200 }
.prepend-top-default
%p.light
Jobs for last year
= _("Jobs for last year")
%canvas#yearChart.padded{ height: 250 }
- [:week, :month, :year].each do |scope|
......
......@@ -23,7 +23,7 @@
.scroll-container
%ul.tokens-container.list-unstyled
%li.input-token
%input.form-control.filtered-search{ id: "filtered-search-#{type.to_s}", placeholder: 'Search or filter results...', data: { 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } }
%input.form-control.filtered-search{ search_filter_input_options(type) }
= icon('filter')
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
%ul{ data: { dropdown: true } }
......
......@@ -37,19 +37,20 @@
- issuable.assignees.each do |assignee|
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
- options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
- options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: current_user&.username, current_user: true, project_id: @project&.id, author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
- title = 'Select assignee'
- if issuable.is_a?(Issue)
- unless issuable.assignees.any?
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil
- dropdown_options = issue_assignees_dropdown_options
- title = dropdown_options[:title]
- options[:toggle_class] += ' js-multiselect js-save-user-data'
- data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" }
- data[:multi_select] = true
- data['dropdown-title'] = title
- data['dropdown-header'] = 'Assignee'
- data['max-select'] = 1
- data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
- data['max-select'] = dropdown_options[:data][:'max-select']
- options[:data].merge!(data)
= dropdown_tag(title, options: options)
......@@ -7,5 +7,5 @@
- if issuable.assignees.length === 0
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' }
= dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_dropdown_options(issuable,false))
= dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_assignees_dropdown_options)
= link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
---
title: "Remove group modal like remove project modal (requires typing + confirmation)"
merge_request: 12569
author: Diego Souza
---
title: Allow unauthenticated access to the /api/v4/users API
merge_request: 12445
author:
......@@ -543,6 +543,10 @@ production: &base
# enabled: true
# host: localhost
# port: 3808
prometheus:
# Time between sampling of unicorn socket metrics, in seconds
# unicorn_sampler_interval: 10
#
# 5. Extra customization
......
......@@ -494,6 +494,12 @@ Settings.webpack.dev_server['enabled'] ||= false
Settings.webpack.dev_server['host'] ||= 'localhost'
Settings.webpack.dev_server['port'] ||= 3808
#
# Prometheus metrics settings
#
Settings['prometheus'] ||= Settingslogic.new({})
Settings.prometheus['unicorn_sampler_interval'] ||= 10
#
# Testing settings
#
......
......@@ -119,6 +119,13 @@ def instrument_classes(instrumentation)
end
# rubocop:enable Metrics/AbcSize
Gitlab::Metrics::UnicornSampler.initialize_instance(Settings.prometheus.unicorn_sampler_interval).start
Gitlab::Application.configure do |config|
# 0 should be Sentry to catch errors in this middleware
config.middleware.insert(1, Gitlab::Metrics::ConnectionRackMiddleware)
end
if Gitlab::Metrics.enabled?
require 'pathname'
require 'influxdb'
......@@ -175,7 +182,7 @@ if Gitlab::Metrics.enabled?
GC::Profiler.enable
Gitlab::Metrics::Sampler.new.start
Gitlab::Metrics::InfluxSampler.initialize_instance.start
module TrackNewRedisConnections
def connect(*args)
......
# Using Docker Images
# Using Docker images
GitLab CI in conjunction with [GitLab Runner](../runners/README.md) can use
[Docker Engine](https://www.docker.com/) to test and build any application.
......@@ -17,14 +17,16 @@ can also run on your workstation. The added benefit is that you can test all
the commands that we will explore later from your shell, rather than having to
test them on a dedicated CI server.
## Register docker runner
## Register Docker Runner
To use GitLab Runner with docker you need to register a new runner to use the
`docker` executor:
To use GitLab Runner with Docker you need to [register a new Runner][register]
to use the `docker` executor.
A one-line example can be seen below:
```bash
gitlab-ci-multi-runner register \
--url "https://gitlab.com/" \
sudo gitlab-runner register \
--url "https://gitlab.example.com/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \
--description "docker-ruby-2.1" \
--executor "docker" \
......@@ -33,26 +35,26 @@ gitlab-ci-multi-runner register \
--docker-mysql latest
```
The registered runner will use the `ruby:2.1` docker image and will run two
The registered runner will use the `ruby:2.1` Docker image and will run two
services, `postgres:latest` and `mysql:latest`, both of which will be
accessible during the build process.
## What is an image
The `image` keyword is the name of the docker image the docker executor
will run to perform the CI tasks.
The `image` keyword is the name of the Docker image the Docker executor
will run to perform the CI tasks.
By default the executor will only pull images from [Docker Hub][hub],
By default, the executor will only pull images from [Docker Hub][hub],
but this can be configured in the `gitlab-runner/config.toml` by setting
the [docker pull policy][] to allow using local images.
the [Docker pull policy][] to allow using local images.
For more information about images and Docker Hub please read
the [Docker Fundamentals][] documentation.
## What is a service
The `services` keyword defines just another docker image that is run during
your job and is linked to the docker image that the `image` keyword defines.
The `services` keyword defines just another Docker image that is run during
your job and is linked to the Docker image that the `image` keyword defines.
This allows you to access the service image during build time.
The service image can run any application, but the most common use case is to
......@@ -60,6 +62,11 @@ run a database container, eg. `mysql`. It's easier and faster to use an
existing image and run it as an additional container than install `mysql` every
time the project is built.
You are not limited to have only database services. You can add as many
services you need to `.gitlab-ci.yml` or manually modify `config.toml`.
Any image found at [Docker Hub][hub] or your private Container Registry can be
used as a service.
You can see some widely used services examples in the relevant documentation of
[CI services examples](../services/README.md).
......@@ -73,22 +80,49 @@ then be used to create a container that is linked to the job container.
The service container for MySQL will be accessible under the hostname `mysql`.
So, in order to access your database service you have to connect to the host
named `mysql` instead of a socket or `localhost`.
named `mysql` instead of a socket or `localhost`. Read more in [accessing the
services](#accessing-the-services).
## Overwrite image and services
### Accessing the services
See [How to use other images as services](#how-to-use-other-images-as-services).
Let's say that you need a Wordpress instance to test some API integration with
your application.
## How to use other images as services
You can then use for example the [tutum/wordpress][] image in your
`.gitlab-ci.yml`:
You are not limited to have only database services. You can add as many
services you need to `.gitlab-ci.yml` or manually modify `config.toml`.
Any image found at [Docker Hub][hub] can be used as a service.
```yaml
services:
- tutum/wordpress:latest
```
If you don't [specify a service alias](#available-settings-for-services-entry),
when the job is run, `tutum/wordpress` will be started and you will have
access to it from your build container under two hostnames to choose from:
## Define image and services from `.gitlab-ci.yml`
- `tutum-wordpress`
- `tutum__wordpress`
>**Note:**
Hostnames with underscores are not RFC valid and may cause problems in 3rd party
applications.
The default aliases for the service's hostname are created from its image name
following these rules:
- Everything after the colon (`:`) is stripped
- Slash (`/`) is replaced with double underscores (`__`) and the primary alias
is created
- Slash (`/`) is replaced with a single dash (`-`) and the secondary alias is
created (requires GitLab Runner v1.1.0 or higher)
To override the default behavior, you can
[specify a service alias](#available-settings-for-services-entry).
## Define `image` and `services` from `.gitlab-ci.yml`
You can simply define an image that will be used for all jobs and a list of
services that you want to use during build time.
services that you want to use during build time:
```yaml
image: ruby:2.2
......@@ -125,6 +159,203 @@ test:2.2:
- bundle exec rake spec
```
Or you can pass some [extended configuration options](#extended-docker-configuration-options)
for `image` and `services`:
```yaml
image:
name: ruby:2.2
entrypoint: ["/bin/bash"]
services:
- name: my-postgres:9.4
alias: db-postgres
entrypoint: ["/usr/local/bin/db-postgres"]
command: ["start"]
before_script:
- bundle install
test:
script:
- bundle exec rake spec
```
## Extended Docker configuration options
> **Note:**
This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
When configuring the `image` or `services` entries, you can use a string or a map as
options:
- when using a string as an option, it must be the full name of the image to use
(including the Registry part if you want to download the image from a Registry
other than Docker Hub)
- when using a map as an option, then it must contain at least the `name`
option, which is the same name of the image as used for the string setting
For example, the following two definitions are equal:
1. Using a string as an option to `image` and `services`:
```yaml
image: "registry.example.com/my/image:latest"
services:
- postgresql:9.4
- redis:latest
```
1. Using a map as an option to `image` and `services`. The use of `image:name` is
required:
```yaml
image:
name: "registry.example.com/my/image:latest"
services:
- name: postgresql:9.4
- name: redis:latest
```
### Available settings for `image`
> **Note:**
This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
| Setting | Required | Description |
|------------|----------|-------------|
| `name` | yes, when used with any other option | Full name of the image that should be used. It should contain the Registry part if needed. |
| `entrypoint` | no | Command or script that should be executed as the container's entrypoint. It will be translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`][entrypoint] directive, where each shell token is a separate string in the array. |
### Available settings for `services`
> **Note:**
This feature requires GitLab 9.4 and GitLab Runner 9.4 or higher.
| Setting | Required | Description |
|------------|----------|-------------|
| `name` | yes, when used with any other option | Full name of the image that should be used. It should contain the Registry part if needed. |
| `entrypoint` | no | Command or script that should be executed as the container's entrypoint. It will be translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`][entrypoint] directive, where each shell token is a separate string in the array. |
| `command` | no | Command or script that should be used as the container's command. It will be translated to arguments passed to Docker after the image's name. The syntax is similar to [`Dockerfile`'s `CMD`][cmd] directive, where each shell token is a separate string in the array. |
| `alias` | no | Additional alias that can be used to access the service from the job's container. Read [Accessing the services](#accessing-the-services) for more information. |
### Starting multiple services from the same image
Before the new extended Docker configuration options, the following configuration
would not work properly:
```yaml
services:
- mysql:latest
- mysql:latest
```
The Runner would start two containers using the `mysql:latest` image, but both
of them would be added to the job's container with the `mysql` alias based on
the [default hostname naming](#accessing-the-services). This would end with one
of the services not being accessible.
After the new extended Docker configuration options, the above example would
look like:
```yaml
services:
- name: mysql:latest
alias: mysql-1
- name: mysql:latest
alias: mysql-2
```
The Runner will still start two containers using the `mysql:latest` image,
but now each of them will also be accessible with the alias configured
in `.gitlab-ci.yml` file.
### Setting a command for the service
Let's assume you have a `super/sql:latest` image with some SQL database
inside it and you would like to use it as a service for your job. Let's also
assume that this image doesn't start the database process while starting
the container and the user needs to manually use `/usr/bin/super-sql run` as
a command to start the database.
Before the new extended Docker configuration options, you would need to create
your own image based on the `super/sql:latest` image, add the default command,
and then use it in job's configuration, like:
```Dockerfile
# my-super-sql:latest image's Dockerfile
FROM super/sql:latest
CMD ["/usr/bin/super-sql", "run"]
```
```yaml
# .gitlab-ci.yml
services:
- my-super-sql:latest
```
After the new extended Docker configuration options, you can now simply
set a `command` in `.gitlab-ci.yml`, like:
```yaml
# .gitlab-ci.yml
services:
- name: super/sql:latest
command: ["/usr/bin/super-sql", "run"]
```
As you can see, the syntax of `command` is similar to [Dockerfile's `CMD`][cmd].
### Overriding the entrypoint of an image
Let's assume you have a `super/sql:experimental` image with some SQL database
inside it and you would like to use it as a base image for your job because you
want to execute some tests with this database binary. Let's also assume that
this image is configured with `/usr/bin/super-sql run` as an entrypoint. That
means, that when starting the container without additional options, it will run
the database's process, while Runner expects that the image will have no
entrypoint or at least will start with a shell as its entrypoint.
Previously we would need to create our own image based on the
`super/sql:experimental` image, set the entrypoint to a shell, and then use
it in job's configuration, e.g.:
Before the new extended Docker configuration options, you would need to create
your own image based on the `super/sql:experimental` image, set the entrypoint
to a shell and then use it in job's configuration, like:
```Dockerfile
# my-super-sql:experimental image's Dockerfile
FROM super/sql:experimental
ENTRYPOINT ["/bin/sh"]
```
```yaml
# .gitlab-ci.yml
image: my-super-sql:experimental
```
After the new extended Docker configuration options, you can now simply
set an `entrypoint` in `.gitlab-ci.yml`, like:
```yaml
# .gitlab-ci.yml
image:
name: super/sql:experimental
entrypoint: ["/bin/sh"]
```
As you can see the syntax of `entrypoint` is similar to
[Dockerfile's `ENTRYPOINT`][entrypoint].
## Define image and services in `config.toml`
Look for the `[runners.docker]` section:
......@@ -138,7 +369,7 @@ Look for the `[runners.docker]` section:
The image and services defined this way will be added to all job run by
that runner.
## Define an image from a private Docker registry
## Define an image from a private Container Registry
> **Notes:**
- This feature requires GitLab Runner **1.8** or higher
......@@ -193,44 +424,6 @@ To configure access for `registry.example.com`, follow these steps:
You can add configuration for as many registries as you want, adding more
registries to the `"auths"` hash as described above.
## Accessing the services
Let's say that you need a Wordpress instance to test some API integration with
your application.
You can then use for example the [tutum/wordpress][] image in your
`.gitlab-ci.yml`:
```yaml
services:
- tutum/wordpress:latest
```
When the job is run, `tutum/wordpress` will be started and you will have
access to it from your build container under the hostnames `tutum-wordpress`
(requires GitLab Runner v1.1.0 or newer) and `tutum__wordpress`.
When using a private registry, the image name also includes a hostname and port
of the registry.
```yaml
services:
- docker.example.com:5000/wordpress:latest
```
The service hostname will also include the registry hostname. Service will be
available under hostnames `docker.example.com-wordpress` (requires GitLab Runner v1.1.0 or newer)
and `docker.example.com__wordpress`.
*Note: hostname with underscores is not RFC valid and may cause problems in 3rd party applications.*
The alias hostnames for the service are made from the image name following these
rules:
1. Everything after `:` is stripped
2. Slash (`/`) is replaced with double underscores (`__`) - primary alias
3. Slash (`/`) is replaced with dash (`-`) - secondary alias, requires GitLab Runner v1.1.0 or newer
## Configuring services
Many services accept environment variables which allow you to easily change
......@@ -257,7 +450,7 @@ See the specific documentation for
## How Docker integration works
Below is a high level overview of the steps performed by docker during job
Below is a high level overview of the steps performed by Docker during job
time.
1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`.
......@@ -274,7 +467,7 @@ time.
## How to debug a job locally
*Note: The following commands are run without root privileges. You should be
able to run docker with your regular user account.*
able to run Docker with your regular user account.*
First start with creating a file named `build_script`:
......@@ -334,3 +527,6 @@ creation.
[mysql-hub]: https://hub.docker.com/r/_/mysql/
[runner-priv-reg]: http://docs.gitlab.com/runner/configuration/advanced-configuration.html#using-a-private-container-registry
[secret variable]: ../variables/README.md#secret-variables
[entrypoint]: https://docs.docker.com/engine/reference/builder/#entrypoint
[cmd]: https://docs.docker.com/engine/reference/builder/#cmd
[register]: https://docs.gitlab.com/runner/register/
......@@ -4,10 +4,13 @@ module API
before do
allow_access_with_scope :read_user if request.get?
authenticate!
end
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
before do
authenticate_non_get!
end
helpers do
def find_user(params)
id = params[:user_id] || params[:id]
......@@ -51,15 +54,22 @@ module API
use :pagination
end
get do
unless can?(current_user, :read_users_list)
render_api_error!("Not authorized.", 403)
end
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
users = UsersFinder.new(current_user, params).execute
entity = current_user.admin? ? Entities::UserWithAdmin : Entities::UserBasic
authorized = can?(current_user, :read_users_list)
# When `current_user` is not present, require that the `username`
# parameter is passed, to prevent an unauthenticated user from accessing
# a list of all the users on the GitLab instance. `UsersFinder` performs
# an exact match on the `username` parameter, so we are guaranteed to
# get either 0 or 1 `users` here.
authorized &&= params[:username].present? if current_user.blank?
forbidden!("Not authorized to access /api/v4/users") unless authorized
entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
present paginate(users), with: entity
end
......@@ -398,6 +408,10 @@ module API
end
resource :user do
before do
authenticate!
end
desc 'Get the currently authenticated user' do
success Entities::UserPublic
end
......
require 'logger'
module Gitlab
module Metrics
class BaseSampler
def self.initialize_instance(*args)
raise "#{name} singleton instance already initialized" if @instance
@instance = new(*args)
at_exit(&@instance.method(:stop))
@instance
end
def self.instance
@instance
end
attr_reader :running
# interval - The sampling interval in seconds.
def initialize(interval)
interval_half = interval.to_f / 2
@interval = interval
@interval_steps = (-interval_half..interval_half).step(0.1).to_a
@mutex = Mutex.new
end
def enabled?
true
end
def start
return unless enabled?
@mutex.synchronize do
return if running
@running = true
@thread = Thread.new do
sleep(sleep_interval)
while running
safe_sample
sleep(sleep_interval)
end
end
end
end
def stop
@mutex.synchronize do
return unless running
@running = false
if @thread
@thread.wakeup if @thread.alive?
@thread.join
@thread = nil
end
end
end
def safe_sample
sample
rescue => e
Rails.logger.warn("#{self.class}: #{e}, stopping")
stop
end
def sample
raise NotImplementedError
end
# Returns the sleep interval with a random adjustment.
#
# The random adjustment is put in place to ensure we:
#
# 1. Don't generate samples at the exact same interval every time (thus
# potentially missing anything that happens in between samples).
# 2. Don't sample data at the same interval two times in a row.
def sleep_interval
while step = @interval_steps.sample
if step != @last_step
@last_step = step
return @interval + @last_step
end
end
end
end
end
end
module Gitlab
module Metrics
class ConnectionRackMiddleware
def initialize(app)
@app = app
end
def self.rack_request_count
@rack_request_count ||= Gitlab::Metrics.counter(:rack_request, 'Rack request count')
end
def self.rack_response_count
@rack_response_count ||= Gitlab::Metrics.counter(:rack_response, 'Rack response count')
end
def self.rack_uncaught_errors_count
@rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors, 'Rack connections handling uncaught errors count')
end
def self.rack_execution_time
@rack_execution_time ||= Gitlab::Metrics.histogram(:rack_execution_time, 'Rack connection handling execution time',
{}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 1.5, 2, 2.5, 3, 5, 7, 10])
end
def call(env)
method = env['REQUEST_METHOD'].downcase
started = Time.now.to_f
begin
ConnectionRackMiddleware.rack_request_count.increment(method: method)
status, headers, body = @app.call(env)
ConnectionRackMiddleware.rack_response_count.increment(method: method, status: status)
[status, headers, body]
rescue
ConnectionRackMiddleware.rack_uncaught_errors_count.increment
raise
ensure
elapsed = Time.now.to_f - started
ConnectionRackMiddleware.rack_execution_time.observe({}, elapsed)
end
end
end
end
end
......@@ -5,14 +5,11 @@ module Gitlab
# This class is used to gather statistics that can't be directly associated
# with a transaction such as system memory usage, garbage collection
# statistics, etc.
class Sampler
class InfluxSampler < BaseSampler
# interval - The sampling interval in seconds.
def initialize(interval = Metrics.settings[:sample_interval])
interval_half = interval.to_f / 2
@interval = interval
@interval_steps = (-interval_half..interval_half).step(0.1).to_a
@last_step = nil
super(interval)
@last_step = nil
@metrics = []
......@@ -26,18 +23,6 @@ module Gitlab
end
end
def start
Thread.new do
Thread.current.abort_on_exception = true
loop do
sleep(sleep_interval)
sample
end
end
end
def sample
sample_memory_usage
sample_file_descriptors
......@@ -86,7 +71,7 @@ module Gitlab
end
def sample_gc
time = GC::Profiler.total_time * 1000.0
time = GC::Profiler.total_time * 1000.0
stats = GC.stat.merge(total_time: time)
# We want the difference of GC runs compared to the last sample, not the
......@@ -111,23 +96,6 @@ module Gitlab
def sidekiq?
Sidekiq.server?
end
# Returns the sleep interval with a random adjustment.
#
# The random adjustment is put in place to ensure we:
#
# 1. Don't generate samples at the exact same interval every time (thus
# potentially missing anything that happens in between samples).
# 2. Don't sample data at the same interval two times in a row.
def sleep_interval
while step = @interval_steps.sample
if step != @last_step
@last_step = step
return @interval + @last_step
end
end
end
end
end
end
......@@ -29,8 +29,8 @@ module Gitlab
provide_metric(name) || registry.summary(name, docstring, base_labels)
end
def gauge(name, docstring, base_labels = {})
provide_metric(name) || registry.gauge(name, docstring, base_labels)
def gauge(name, docstring, base_labels = {}, multiprocess_mode = :all)
provide_metric(name) || registry.gauge(name, docstring, base_labels, multiprocess_mode)
end
def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS)
......
module Gitlab
module Metrics
class UnicornSampler < BaseSampler
def initialize(interval)
super(interval)
end
def unicorn_active_connections
@unicorn_active_connections ||= Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max)
end
def unicorn_queued_connections
@unicorn_queued_connections ||= Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max)
end
def enabled?
# Raindrops::Linux.tcp_listener_stats is only present on Linux
unicorn_with_listeners? && Raindrops::Linux.respond_to?(:tcp_listener_stats)
end
def sample
Raindrops::Linux.tcp_listener_stats(tcp_listeners).each do |addr, stats|
unicorn_active_connections.set({ type: 'tcp', address: addr }, stats.active)
unicorn_queued_connections.set({ type: 'tcp', address: addr }, stats.queued)
end
Raindrops::Linux.unix_listener_stats(unix_listeners).each do |addr, stats|
unicorn_active_connections.set({ type: 'unix', address: addr }, stats.active)
unicorn_queued_connections.set({ type: 'unix', address: addr }, stats.queued)
end
end
private
def tcp_listeners
@tcp_listeners ||= Unicorn.listener_names.grep(%r{\A[^/]+:\d+\z})
end
def unix_listeners
@unix_listeners ||= Unicorn.listener_names - tcp_listeners
end
def unicorn_with_listeners?
defined?(Unicorn) && Unicorn.listener_names.any?
end
end
end
end
......@@ -17,9 +17,27 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"\n"
msgid "%d additional commit has been omitted to prevent performance issues."
msgid_plural "%d additional commits have been omitted to prevent performance issues."
msgstr[0] ""
msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
msgstr[0] ""
msgstr[1] ""
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr ""
msgid "1 pipeline"
msgid_plural "%d pipelines"
msgstr[0] ""
msgstr[1] ""
msgid "A collection of graphs regarding Continuous Integration"
msgstr ""
msgid "About auto deploy"
msgstr ""
......@@ -61,9 +79,24 @@ msgstr[1] ""
msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
msgstr ""
msgid "BranchSwitcherPlaceholder|Search branches"
msgstr ""
msgid "BranchSwitcherTitle|Switch branch"
msgstr ""
msgid "Branches"
msgstr ""
msgid "Browse Directory"
msgstr ""
msgid "Browse File"
msgstr ""
msgid "Browse Files"
msgstr ""
msgid "Browse files"
msgstr ""
......@@ -159,6 +192,9 @@ msgid_plural "Commits"
msgstr[0] ""
msgstr[1] ""
msgid "Commit duration in minutes for last 30 commits"
msgstr ""
msgid "Commit message"
msgstr ""
......@@ -171,6 +207,9 @@ msgstr ""
msgid "Commits"
msgstr ""
msgid "Commits feed"
msgstr ""
msgid "Commits|History"
msgstr ""
......@@ -195,6 +234,9 @@ msgstr ""
msgid "Create New Directory"
msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
msgid "Create directory"
msgstr ""
......@@ -213,6 +255,9 @@ msgstr ""
msgid "CreateTag|Tag"
msgstr ""
msgid "CreateTokenToCloneLink|create a personal access token"
msgstr ""
msgid "Cron Timezone"
msgstr ""
......@@ -323,6 +368,9 @@ msgstr ""
msgid "Files"
msgstr ""
msgid "Filter by commit message"
msgstr ""
msgid "Find by path"
msgstr ""
......@@ -370,6 +418,15 @@ msgstr ""
msgid "Introducing Cycle Analytics"
msgstr ""
msgid "Jobs for last month"
msgstr ""
msgid "Jobs for last week"
msgstr ""
msgid "Jobs for last year"
msgstr ""
msgid "LFSStatus|Disabled"
msgstr ""
......@@ -535,6 +592,21 @@ msgstr ""
msgid "Pipeline Schedules"
msgstr ""
msgid "PipelineCharts|Failed:"
msgstr ""
msgid "PipelineCharts|Overall statistics"
msgstr ""
msgid "PipelineCharts|Success ratio:"
msgstr ""
msgid "PipelineCharts|Successful:"
msgstr ""
msgid "PipelineCharts|Total:"
msgstr ""
msgid "PipelineSchedules|Activated"
msgstr ""
......@@ -565,6 +637,18 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
msgid "Pipelines"
msgstr ""
msgid "Pipelines charts"
msgstr ""
msgid "Pipeline|all"
msgstr ""
msgid "Pipeline|success"
msgstr ""
msgid "Pipeline|with stage"
msgstr ""
......@@ -688,7 +772,7 @@ msgstr ""
msgid "Select target branch"
msgstr ""
msgid "Set a password on your account to pull or push via %{protocol}"
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
msgid "Set up CI"
......@@ -714,10 +798,7 @@ msgstr ""
msgid "StarProject|Star"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
msgid "Start a <strong>new merge request</strong> with these changes"
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
msgid "Switch branch/tag"
......@@ -948,9 +1029,15 @@ msgstr ""
msgid "Upload file"
msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
msgid "Use your global notification setting"
msgstr ""
msgid "View open merge request"
msgstr ""
msgid "VisibilityLevel|Internal"
msgstr ""
......
......@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-19 15:50-0500\n"
"PO-Revision-Date: 2017-06-19 15:50-0500\n"
"POT-Creation-Date: 2017-06-28 13:32+0200\n"
"PO-Revision-Date: 2017-06-28 13:32+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
......@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
msgid "%d additional commit have been omitted to prevent performance issues."
msgid "%d additional commit has been omitted to prevent performance issues."
msgid_plural "%d additional commits have been omitted to prevent performance issues."
msgstr[0] ""
msgstr[1] ""
......@@ -31,6 +31,14 @@ msgstr[1] ""
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr ""
msgid "1 pipeline"
msgid_plural "%d pipelines"
msgstr[0] ""
msgstr[1] ""
msgid "A collection of graphs regarding Continuous Integration"
msgstr ""
msgid "About auto deploy"
msgstr ""
......@@ -185,6 +193,9 @@ msgid_plural "Commits"
msgstr[0] ""
msgstr[1] ""
msgid "Commit duration in minutes for last 30 commits"
msgstr ""
msgid "Commit message"
msgstr ""
......@@ -224,6 +235,9 @@ msgstr ""
msgid "Create New Directory"
msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
msgid "Create directory"
msgstr ""
......@@ -242,6 +256,9 @@ msgstr ""
msgid "CreateTag|Tag"
msgstr ""
msgid "CreateTokenToCloneLink|create a personal access token"
msgstr ""
msgid "Cron Timezone"
msgstr ""
......@@ -402,6 +419,15 @@ msgstr ""
msgid "Introducing Cycle Analytics"
msgstr ""
msgid "Jobs for last month"
msgstr ""
msgid "Jobs for last week"
msgstr ""
msgid "Jobs for last year"
msgstr ""
msgid "LFSStatus|Disabled"
msgstr ""
......@@ -567,6 +593,21 @@ msgstr ""
msgid "Pipeline Schedules"
msgstr ""
msgid "PipelineCharts|Failed:"
msgstr ""
msgid "PipelineCharts|Overall statistics"
msgstr ""
msgid "PipelineCharts|Success ratio:"
msgstr ""
msgid "PipelineCharts|Successful:"
msgstr ""
msgid "PipelineCharts|Total:"
msgstr ""
msgid "PipelineSchedules|Activated"
msgstr ""
......@@ -597,6 +638,18 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
msgid "Pipelines"
msgstr ""
msgid "Pipelines charts"
msgstr ""
msgid "Pipeline|all"
msgstr ""
msgid "Pipeline|success"
msgstr ""
msgid "Pipeline|with stage"
msgstr ""
......@@ -720,7 +773,7 @@ msgstr ""
msgid "Select target branch"
msgstr ""
msgid "Set a password on your account to pull or push via %{protocol}"
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
msgid "Set up CI"
......
......@@ -135,7 +135,7 @@ feature 'Group', feature: true do
expect(page).not_to have_content('secret-group')
end
describe 'group edit' do
describe 'group edit', js: true do
let(:group) { create(:group) }
let(:path) { edit_group_path(group) }
let(:new_name) { 'new-name' }
......@@ -157,8 +157,8 @@ feature 'Group', feature: true do
end
it 'removes group' do
click_link 'Remove group'
expect { remove_with_confirm('Remove group', group.path) }.to change {Group.count}.by(-1)
expect(group.members.all.count).to be_zero
expect(page).to have_content "scheduled for deletion"
end
end
......@@ -212,4 +212,10 @@ feature 'Group', feature: true do
expect(page).to have_content(nested_group.name)
end
end
def remove_with_confirm(button_text, confirm_with)
click_button button_text
fill_in 'confirm_name_input', with: confirm_with
click_button 'Confirm'
end
end
......@@ -31,8 +31,8 @@ describe 'New/edit issue', :feature, :js do
# the original method, resulting in infinite recurison when called.
# This is likely a bug with helper modules included into dynamically generated view classes.
# To work around this, we have to hold on to and call to the original implementation manually.
original_issue_dropdown_options = FormHelper.instance_method(:issue_dropdown_options)
allow_any_instance_of(FormHelper).to receive(:issue_dropdown_options).and_wrap_original do |original, *args|
original_issue_dropdown_options = FormHelper.instance_method(:issue_assignees_dropdown_options)
allow_any_instance_of(FormHelper).to receive(:issue_assignees_dropdown_options).and_wrap_original do |original, *args|
options = original_issue_dropdown_options.bind(original.receiver).call(*args)
options[:data][:per_page] = 2
......
require 'spec_helper'
require_relative '../../config/initializers/8_metrics'
describe 'instrument_classes', lib: true do
let(:config) { double(:config) }
let(:unicorn_sampler) { double(:unicorn_sampler) }
let(:influx_sampler) { double(:influx_sampler) }
before do
allow(config).to receive(:instrument_method)
allow(config).to receive(:instrument_methods)
allow(config).to receive(:instrument_instance_method)
allow(config).to receive(:instrument_instance_methods)
allow(Gitlab::Metrics::UnicornSampler).to receive(:initialize_instance).and_return(unicorn_sampler)
allow(Gitlab::Metrics::InfluxSampler).to receive(:initialize_instance).and_return(influx_sampler)
allow(unicorn_sampler).to receive(:start)
allow(influx_sampler).to receive(:start)
allow(Gitlab::Application).to receive(:configure)
end
it 'can autoload and instrument all files' do
require_relative '../../config/initializers/8_metrics'
expect { instrument_classes(config) }.not_to raise_error
end
end
......@@ -97,30 +97,40 @@ describe Gitlab::HealthChecks::FsShardsCheck do
}.with_indifferent_access
end
it { is_expected.to all(have_attributes(labels: { shard: :default })) }
# Unsolved intermittent failure in CI https://gitlab.com/gitlab-org/gitlab-ce/issues/31128
around(:each) do |example| # rubocop:disable RSpec/AroundBlock
times_to_try = ENV['CI'] ? 4 : 1
example.run_with_retry retry: times_to_try
end
it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 0)) }
it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) }
it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) }
it 'provides metrics' do
expect(subject).to all(have_attributes(labels: { shard: :default }))
expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 0))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) }
it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) }
it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) }
expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
end
end
context 'storage points to directory that has both read and write rights' do
before do
FileUtils.chmod_R(0755, tmp_dir)
end
it { is_expected.to all(have_attributes(labels: { shard: :default })) }
it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 1)) }
it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 1)) }
it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 1)) }
it 'provides metrics' do
expect(subject).to all(have_attributes(labels: { shard: :default }))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) }
it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) }
it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) }
expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 1))
expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 1))
expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 1))
expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
end
end
end
end
......
require 'spec_helper'
describe Gitlab::Metrics::ConnectionRackMiddleware do
let(:app) { double('app') }
subject { described_class.new(app) }
around do |example|
Timecop.freeze { example.run }
end
describe '#call' do
let(:status) { 100 }
let(:env) { { 'REQUEST_METHOD' => 'GET' } }
let(:stack_result) { [status, {}, 'body'] }
before do
allow(app).to receive(:call).and_return(stack_result)
end
context '@app.call succeeds with 200' do
before do
allow(app).to receive(:call).and_return([200, nil, nil])
end
it 'increments response count with status label' do
expect(described_class).to receive_message_chain(:rack_response_count, :increment).with(include(status: 200, method: 'get'))
subject.call(env)
end
it 'increments requests count' do
expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get')
subject.call(env)
end
it 'measures execution time' do
execution_time = 10
allow(app).to receive(:call) do |*args|
Timecop.freeze(execution_time.seconds)
end
expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time)
subject.call(env)
end
end
context '@app.call throws exception' do
let(:rack_response_count) { double('rack_response_count') }
before do
allow(app).to receive(:call).and_raise(StandardError)
allow(described_class).to receive(:rack_response_count).and_return(rack_response_count)
end
it 'increments exceptions count' do
expect(described_class).to receive_message_chain(:rack_uncaught_errors_count, :increment)
expect { subject.call(env) }.to raise_error(StandardError)
end
it 'increments requests count' do
expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get')
expect { subject.call(env) }.to raise_error(StandardError)
end
it "does't increment response count" do
expect(described_class.rack_response_count).not_to receive(:increment)
expect { subject.call(env) }.to raise_error(StandardError)
end
it 'measures execution time' do
execution_time = 10
allow(app).to receive(:call) do |*args|
Timecop.freeze(execution_time.seconds)
raise StandardError
end
expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time)
expect { subject.call(env) }.to raise_error(StandardError)
end
end
end
end
require 'spec_helper'
describe Gitlab::Metrics::Sampler do
describe Gitlab::Metrics::InfluxSampler do
let(:sampler) { described_class.new(5) }
after do
......@@ -8,10 +8,10 @@ describe Gitlab::Metrics::Sampler do
end
describe '#start' do
it 'gathers a sample at a given interval' do
expect(sampler).to receive(:sleep).with(a_kind_of(Numeric))
expect(sampler).to receive(:sample)
expect(sampler).to receive(:loop).and_yield
it 'runs once and gathers a sample at a given interval' do
expect(sampler).to receive(:sleep).with(a_kind_of(Numeric)).twice
expect(sampler).to receive(:sample).once
expect(sampler).to receive(:running).and_return(false, true, false)
sampler.start.join
end
......
require 'spec_helper'
describe Gitlab::Metrics::UnicornSampler do
subject { described_class.new(1.second) }
describe '#sample' do
let(:unicorn) { double('unicorn') }
let(:raindrops) { double('raindrops') }
let(:stats) { double('stats') }
before do
stub_const('Unicorn', unicorn)
stub_const('Raindrops::Linux', raindrops)
allow(raindrops).to receive(:unix_listener_stats).and_return({})
allow(raindrops).to receive(:tcp_listener_stats).and_return({})
end
context 'unicorn listens on unix sockets' do
let(:socket_address) { '/some/sock' }
let(:sockets) { [socket_address] }
before do
allow(unicorn).to receive(:listener_names).and_return(sockets)
end
it 'samples socket data' do
expect(raindrops).to receive(:unix_listener_stats).with(sockets)
subject.sample
end
context 'stats collected' do
before do
allow(stats).to receive(:active).and_return('active')
allow(stats).to receive(:queued).and_return('queued')
allow(raindrops).to receive(:unix_listener_stats).and_return({ socket_address => stats })
end
it 'updates metrics type unix and with addr' do
labels = { type: 'unix', address: socket_address }
expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
subject.sample
end
end
end
context 'unicorn listens on tcp sockets' do
let(:tcp_socket_address) { '0.0.0.0:8080' }
let(:tcp_sockets) { [tcp_socket_address] }
before do
allow(unicorn).to receive(:listener_names).and_return(tcp_sockets)
end
it 'samples socket data' do
expect(raindrops).to receive(:tcp_listener_stats).with(tcp_sockets)
subject.sample
end
context 'stats collected' do
before do
allow(stats).to receive(:active).and_return('active')
allow(stats).to receive(:queued).and_return('queued')
allow(raindrops).to receive(:tcp_listener_stats).and_return({ tcp_socket_address => stats })
end
it 'updates metrics type unix and with addr' do
labels = { type: 'tcp', address: tcp_socket_address }
expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
subject.sample
end
end
end
end
describe '#start' do
context 'when enabled' do
before do
allow(subject).to receive(:enabled?).and_return(true)
end
it 'creates new thread' do
expect(Thread).to receive(:new)
subject.start
end
end
context 'when disabled' do
before do
allow(subject).to receive(:enabled?).and_return(false)
end
it "doesn't create new thread" do
expect(Thread).not_to receive(:new)
subject.start
end
end
end
end
......@@ -105,6 +105,22 @@ describe MergeRequest, models: true do
end
end
describe '#assignee_ids' do
it 'returns an array of the assigned user id' do
subject.assignee_id = 123
expect(subject.assignee_ids).to eq([123])
end
end
describe '#assignee_ids=' do
it 'sets assignee_id to the last id in the array' do
subject.assignee_ids = [123, 456]
expect(subject.assignee_id).to eq(456)
end
end
describe '#assignee_or_author?' do
let(:user) { create(:user) }
......
require 'spec_helper'
describe GlobalPolicy, models: true do
let(:current_user) { create(:user) }
let(:user) { create(:user) }
subject { GlobalPolicy.new(current_user, [user]) }
describe "reading the list of users" do
context "for a logged in user" do
it { is_expected.to be_allowed(:read_users_list) }
end
context "for an anonymous user" do
let(:current_user) { nil }
context "when the public level is restricted" do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
it { is_expected.not_to be_allowed(:read_users_list) }
end
context "when the public level is not restricted" do
before do
stub_application_setting(restricted_visibility_levels: [])
end
it { is_expected.to be_allowed(:read_users_list) }
end
end
end
end
......@@ -13,9 +13,40 @@ describe API::Users do
describe 'GET /users' do
context "when unauthenticated" do
it "returns authentication error" do
it "returns authorization error when the `username` parameter is not passed" do
get api("/users")
expect(response).to have_http_status(401)
expect(response).to have_http_status(403)
end
it "returns the user when a valid `username` parameter is passed" do
user = create(:user)
get api("/users"), username: user.username
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response[0]['id']).to eq(user.id)
expect(json_response[0]['username']).to eq(user.username)
end
it "returns authorization error when the `username` parameter refers to an inaccessible user" do
user = create(:user)
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
get api("/users"), username: user.username
expect(response).to have_http_status(403)
end
it "returns an empty response when an invalid `username` parameter is passed" do
get api("/users"), username: 'invalid'
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(0)
end
end
......@@ -138,6 +169,7 @@ describe API::Users do
describe "GET /users/:id" do
it "returns a user by id" do
get api("/users/#{user.id}", user)
expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username)
end
......@@ -148,9 +180,22 @@ describe API::Users do
expect(json_response['is_admin']).to be_nil
end
it "returns a 401 if unauthenticated" do
get api("/users/9998")
expect(response).to have_http_status(401)
context 'for an anonymous user' do
it "returns a user by id" do
get api("/users/#{user.id}")
expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username)
end
it "returns a 404 if the target user is present but inaccessible" do
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?).with(nil, :read_user, user).and_return(false)
get api("/users/#{user.id}")
expect(response).to have_http_status(404)
end
end
it "returns a 404 error if user id not found" do
......
......@@ -359,18 +359,18 @@ describe QuickActions::InterpretService, services: true do
let(:content) { "/assign @#{developer.username}" }
context 'Issue' do
it 'fetches assignee and populates assignee_id if content contains /assign' do
it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, issue)
expect(updates).to eq(assignee_ids: [developer.id])
expect(updates[:assignee_ids]).to match_array([developer.id])
end
end
context 'Merge Request' do
it 'fetches assignee and populates assignee_id if content contains /assign' do
it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, merge_request)
expect(updates).to eq(assignee_id: developer.id)
expect(updates).to eq(assignee_ids: [developer.id])
end
end
end
......@@ -383,7 +383,7 @@ describe QuickActions::InterpretService, services: true do
end
context 'Issue' do
it 'fetches assignee and populates assignee_id if content contains /assign' do
it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, issue)
expect(updates[:assignee_ids]).to match_array([developer.id])
......@@ -391,10 +391,10 @@ describe QuickActions::InterpretService, services: true do
end
context 'Merge Request' do
it 'fetches assignee and populates assignee_id if content contains /assign' do
it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, merge_request)
expect(updates).to eq(assignee_id: developer.id)
expect(updates).to eq(assignee_ids: [developer.id])
end
end
end
......@@ -422,11 +422,27 @@ describe QuickActions::InterpretService, services: true do
end
context 'Merge Request' do
it 'populates assignee_id: nil if content contains /unassign' do
merge_request.update(assignee_id: developer.id)
it 'populates assignee_ids: [] if content contains /unassign' do
merge_request.update(assignee_ids: [developer.id])
_, updates = service.execute(content, merge_request)
expect(updates).to eq(assignee_id: nil)
expect(updates).to eq(assignee_ids: [])
end
end
end
context 'reassign command' do
let(:content) { '/reassign' }
context 'Issue' do
it 'reassigns user if content contains /reassign @user' do
user = create(:user)
issue.update(assignee_ids: [developer.id])
_, updates = service.execute("/reassign @#{user.username}", issue)
expect(updates).to eq(assignee_ids: [user.id])
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