summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile103
-rw-r--r--Gemfile.lock13
-rw-r--r--app/assets/javascripts/dispatcher.js5
-rw-r--r--app/assets/javascripts/monitoring/prometheus_graph.js333
-rw-r--r--app/assets/stylesheets/pages/environments.scss68
-rw-r--r--app/assets/stylesheets/pages/projects.scss2
-rw-r--r--app/controllers/admin/applications_controller.rb2
-rw-r--r--app/controllers/admin/impersonation_tokens_controller.rb2
-rw-r--r--app/controllers/concerns/repository_settings_redirect.rb7
-rw-r--r--app/controllers/oauth/authorizations_controller.rb44
-rw-r--r--app/controllers/profiles/personal_access_tokens_controller.rb2
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb30
-rw-r--r--app/controllers/projects/environments_controller.rb15
-rw-r--r--app/controllers/projects/protected_branches_controller.rb36
-rw-r--r--app/controllers/projects/settings/repository_controller.rb50
-rw-r--r--app/controllers/uploads_controller.rb2
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/gitlab_routing_helper.rb4
-rw-r--r--app/helpers/sorting_helper.rb2
-rw-r--r--app/models/chat_team.rb1
-rw-r--r--app/models/ci/build.rb37
-rw-r--r--app/models/environment.rb8
-rw-r--r--app/models/oauth_access_grant.rb4
-rw-r--r--app/models/oauth_access_token.rb2
-rw-r--r--app/models/personal_access_token.rb11
-rw-r--r--app/models/project.rb11
-rw-r--r--app/models/project_services/monitoring_service.rb16
-rw-r--r--app/models/project_services/prometheus_service.rb93
-rw-r--r--app/models/repository.rb4
-rw-r--r--app/models/service.rb1
-rw-r--r--app/presenters/projects/settings/deploy_keys_presenter.rb60
-rw-r--r--app/views/doorkeeper/authorizations/new.html.haml2
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml10
-rw-r--r--app/views/projects/deploy_keys/_deploy_key.html.haml2
-rw-r--r--app/views/projects/deploy_keys/_form.html.haml4
-rw-r--r--app/views/projects/deploy_keys/_index.html.haml34
-rw-r--r--app/views/projects/deploy_keys/index.html.haml36
-rw-r--r--app/views/projects/environments/_metrics_button.html.haml6
-rw-r--r--app/views/projects/environments/metrics.html.haml21
-rw-r--r--app/views/projects/environments/show.html.haml1
-rw-r--r--app/views/projects/protected_branches/_branches_list.html.haml2
-rw-r--r--app/views/projects/protected_branches/_create_protected_branch.html.haml2
-rw-r--r--app/views/projects/protected_branches/_index.html.haml (renamed from app/views/projects/protected_branches/index.html.haml)7
-rw-r--r--app/views/projects/protected_branches/_protected_branch.html.haml2
-rw-r--r--app/views/projects/settings/repository/show.html.haml4
-rw-r--r--app/workers/post_receive.rb4
-rw-r--r--changelogs/unreleased/26732-combine-deploy-keys-and-push-rules-and-mirror-repository-and-protect-branches-settings-pages.yml5
-rw-r--r--changelogs/unreleased/27568-refactor-very-slow-dropdown-asignee-spec.yml4
-rw-r--r--changelogs/unreleased/27936-make-all-uploads-require-revalidation-on-each-browser-fetch.yml4
-rw-r--r--changelogs/unreleased/28447-hybrid-repository-storages.yml4
-rw-r--r--changelogs/unreleased/feature-openid-connect.yml4
-rw-r--r--changelogs/unreleased/fix-gb-deprecate-ci-config-types.yml4
-rw-r--r--changelogs/unreleased/priority-to-label-priority.yml4
-rw-r--r--changelogs/unreleased/rfr-20170307-change-default-project-number-limit.yml4
-rw-r--r--changelogs/unreleased/zj-variables-build-job.yml4
-rw-r--r--config/gitlab.yml.example6
-rw-r--r--config/initializers/1_settings.rb17
-rw-r--r--config/initializers/6_validations.rb20
-rw-r--r--config/initializers/doorkeeper.rb11
-rw-r--r--config/initializers/doorkeeper_openid_connect.rb36
-rw-r--r--config/initializers/rspec_profiling.rb37
-rw-r--r--config/initializers/secret_token.rb7
-rw-r--r--config/locales/doorkeeper.en.yml1
-rw-r--r--config/routes.rb2
-rw-r--r--config/routes/project.rb2
-rw-r--r--db/migrate/20140502125220_migrate_repo_size.rb2
-rw-r--r--db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb2
-rw-r--r--db/migrate/20161209165216_create_doorkeeper_openid_connect_tables.rb37
-rw-r--r--db/migrate/20161220141214_remove_dot_git_from_group_names.rb2
-rw-r--r--db/migrate/20161226122833_remove_dot_git_from_usernames.rb4
-rw-r--r--db/post_migrate/20170209140523_validate_foreign_keys_on_oauth_openid_requests.rb20
-rw-r--r--db/schema.rb10
-rw-r--r--doc/administration/repository_storage_paths.md15
-rw-r--r--doc/api/settings.md6
-rw-r--r--doc/ci/variables/README.md78
-rw-r--r--doc/ci/yaml/README.md4
-rw-r--r--doc/integration/README.md1
-rw-r--r--doc/integration/openid_connect_provider.md47
-rw-r--r--doc/update/8.17-to-9.0.md65
-rw-r--r--features/project/active_tab.feature6
-rw-r--r--features/steps/project/active_tab.rb10
-rw-r--r--features/steps/project/deploy_keys.rb2
-rw-r--r--lib/api/helpers/internal_helpers.rb6
-rw-r--r--lib/api/services.rb9
-rw-r--r--lib/backup/repository.rb5
-rw-r--r--lib/gitlab/auth.rb14
-rw-r--r--lib/gitlab/prometheus.rb70
-rw-r--r--lib/tasks/gitlab/check.rake16
-rw-r--r--lib/tasks/gitlab/cleanup.rake6
-rw-r--r--lib/tasks/gitlab/import.rake3
-rw-r--r--lib/tasks/gitlab/info.rake4
-rw-r--r--lib/tasks/gitlab/task_helpers.rb6
-rw-r--r--spec/controllers/admin/applications_controller_spec.rb65
-rw-r--r--spec/controllers/profiles/personal_access_tokens_spec.rb26
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb46
-rw-r--r--spec/controllers/projects/settings/repository_controller_spec.rb20
-rw-r--r--spec/controllers/uploads_controller_spec.rb82
-rw-r--r--spec/factories/chat_teams.rb9
-rw-r--r--spec/factories/oauth_access_grants.rb11
-rw-r--r--spec/factories/oauth_access_tokens.rb3
-rw-r--r--spec/factories/oauth_applications.rb2
-rw-r--r--spec/factories/projects.rb11
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb74
-rw-r--r--spec/features/projects/environments/environment_metrics_spec.rb39
-rw-r--r--spec/features/projects/environments/environment_spec.rb (renamed from spec/features/environment_spec.rb)19
-rw-r--r--spec/features/projects/environments/environments_spec.rb (renamed from spec/features/environments_spec.rb)0
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb4
-rw-r--r--spec/features/security/project/internal_access_spec.rb14
-rw-r--r--spec/features/security/project/private_access_spec.rb14
-rw-r--r--spec/features/security/project/public_access_spec.rb14
-rw-r--r--spec/features/todos/todos_sorting_spec.rb8
-rw-r--r--spec/initializers/6_validations_spec.rb28
-rw-r--r--spec/initializers/doorkeeper_spec.rb71
-rw-r--r--spec/initializers/secret_token_spec.rb25
-rw-r--r--spec/javascripts/fixtures/environments/metrics.html.haml12
-rw-r--r--spec/javascripts/monitoring/prometheus_graph_spec.js78
-rw-r--r--spec/javascripts/monitoring/prometheus_mock_data.js1014
-rw-r--r--spec/lib/gitlab/auth_spec.rb30
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/prometheus_spec.rb143
-rw-r--r--spec/models/chat_team_spec.rb5
-rw-r--r--spec/models/ci/build_spec.rb41
-rw-r--r--spec/models/environment_spec.rb83
-rw-r--r--spec/models/namespace_spec.rb2
-rw-r--r--spec/models/personal_access_token_spec.rb24
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb104
-rw-r--r--spec/models/project_spec.rb8
-rw-r--r--spec/presenters/projects/settings/deploy_keys_presenter_spec.rb66
-rw-r--r--spec/requests/api/api_internal_helpers_spec.rb2
-rw-r--r--spec/requests/api/doorkeeper_access_spec.rb18
-rw-r--r--spec/requests/api/oauth_tokens_spec.rb22
-rw-r--r--spec/requests/api/session_spec.rb18
-rw-r--r--spec/requests/api/v3/services_spec.rb4
-rw-r--r--spec/requests/git_http_spec.rb12
-rw-r--r--spec/requests/openid_connect_spec.rb134
-rw-r--r--spec/routing/openid_connect_spec.rb30
-rw-r--r--spec/support/prometheus_helpers.rb117
-rw-r--r--spec/support/test_env.rb2
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb4
-rw-r--r--spec/workers/post_receive_spec.rb2
140 files changed, 3854 insertions, 442 deletions
diff --git a/Gemfile b/Gemfile
index 4ac5a0ccfc1..2f813324a35 100644
--- a/Gemfile
+++ b/Gemfile
@@ -18,25 +18,26 @@ gem 'pg', '~> 0.18.2', group: :postgres
gem 'rugged', '~> 0.24.0'
# Authentication libraries
-gem 'devise', '~> 4.2'
-gem 'doorkeeper', '~> 4.2.0'
-gem 'omniauth', '~> 1.4.2'
-gem 'omniauth-auth0', '~> 1.4.1'
-gem 'omniauth-azure-oauth2', '~> 0.0.6'
-gem 'omniauth-cas3', '~> 1.1.2'
-gem 'omniauth-facebook', '~> 4.0.0'
-gem 'omniauth-github', '~> 1.1.1'
-gem 'omniauth-gitlab', '~> 1.0.2'
+gem 'devise', '~> 4.2'
+gem 'doorkeeper', '~> 4.2.0'
+gem 'doorkeeper-openid_connect', '~> 1.1.0'
+gem 'omniauth', '~> 1.4.2'
+gem 'omniauth-auth0', '~> 1.4.1'
+gem 'omniauth-azure-oauth2', '~> 0.0.6'
+gem 'omniauth-cas3', '~> 1.1.2'
+gem 'omniauth-facebook', '~> 4.0.0'
+gem 'omniauth-github', '~> 1.1.1'
+gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.4.1'
-gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
+gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2'
-gem 'omniauth-saml', '~> 1.7.0'
-gem 'omniauth-shibboleth', '~> 1.2.0'
-gem 'omniauth-twitter', '~> 1.2.0'
-gem 'omniauth_crowd', '~> 2.2.0'
-gem 'omniauth-authentiq', '~> 0.3.0'
-gem 'rack-oauth2', '~> 1.2.1'
-gem 'jwt', '~> 1.5.6'
+gem 'omniauth-saml', '~> 1.7.0'
+gem 'omniauth-shibboleth', '~> 1.2.0'
+gem 'omniauth-twitter', '~> 1.2.0'
+gem 'omniauth_crowd', '~> 2.2.0'
+gem 'omniauth-authentiq', '~> 0.3.0'
+gem 'rack-oauth2', '~> 1.2.1'
+gem 'jwt', '~> 1.5.6'
# Spam and anti-bot protection
gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'
@@ -68,9 +69,9 @@ gem 'gollum-rugged_adapter', '~> 0.4.2', require: false
gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API
-gem 'grape', '~> 0.19.0'
+gem 'grape', '~> 0.19.0'
gem 'grape-entity', '~> 0.6.0'
-gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
+gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
# Pagination
gem 'kaminari', '~> 0.17.0'
@@ -102,19 +103,19 @@ gem 'unf', '~> 0.1.4'
gem 'seed-fu', '~> 2.3.5'
# Markdown and HTML processing
-gem 'html-pipeline', '~> 1.11.0'
-gem 'deckar01-task_list', '1.0.6', require: 'task_list/railtie'
-gem 'gitlab-markup', '~> 1.5.1'
-gem 'redcarpet', '~> 3.4'
-gem 'RedCloth', '~> 4.3.2'
-gem 'rdoc', '~> 4.2'
-gem 'org-ruby', '~> 0.9.12'
-gem 'creole', '~> 0.5.0'
-gem 'wikicloth', '0.8.1'
-gem 'asciidoctor', '~> 1.5.2'
+gem 'html-pipeline', '~> 1.11.0'
+gem 'deckar01-task_list', '1.0.6', require: 'task_list/railtie'
+gem 'gitlab-markup', '~> 1.5.1'
+gem 'redcarpet', '~> 3.4'
+gem 'RedCloth', '~> 4.3.2'
+gem 'rdoc', '~> 4.2'
+gem 'org-ruby', '~> 0.9.12'
+gem 'creole', '~> 0.5.0'
+gem 'wikicloth', '0.8.1'
+gem 'asciidoctor', '~> 1.5.2'
gem 'asciidoctor-plantuml', '0.0.7'
-gem 'rouge', '~> 2.0'
-gem 'truncato', '~> 0.7.8'
+gem 'rouge', '~> 2.0'
+gem 'truncato', '~> 0.7.8'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
@@ -229,18 +230,18 @@ gem 'sass-rails', '~> 5.0.6'
gem 'coffee-rails', '~> 4.1.0'
gem 'uglifier', '~> 2.7.2'
-gem 'addressable', '~> 2.3.8'
-gem 'bootstrap-sass', '~> 3.3.0'
+gem 'addressable', '~> 2.3.8'
+gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.7'
-gem 'gemojione', '~> 3.0'
-gem 'gon', '~> 6.1.0'
+gem 'gemojione', '~> 3.0'
+gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2'
-gem 'jquery-rails', '~> 4.1.0'
-gem 'request_store', '~> 1.3'
-gem 'select2-rails', '~> 3.5.9'
-gem 'virtus', '~> 1.0.1'
-gem 'net-ssh', '~> 3.0.1'
-gem 'base32', '~> 0.3.0'
+gem 'jquery-rails', '~> 4.1.0'
+gem 'request_store', '~> 1.3'
+gem 'select2-rails', '~> 3.5.9'
+gem 'virtus', '~> 1.0.1'
+gem 'net-ssh', '~> 3.0.1'
+gem 'base32', '~> 0.3.0'
# Sentry integration
gem 'sentry-raven', '~> 2.0.0'
@@ -278,13 +279,13 @@ group :development, :test do
gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0'
- gem 'database_cleaner', '~> 1.5.0'
+ gem 'database_cleaner', '~> 1.5.0'
gem 'factory_girl_rails', '~> 4.7.0'
- gem 'rspec-rails', '~> 3.5.0'
- gem 'rspec-retry', '~> 0.4.5'
- gem 'spinach-rails', '~> 0.2.1'
+ gem 'rspec-rails', '~> 3.5.0'
+ gem 'rspec-retry', '~> 0.4.5'
+ gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
- gem 'rspec_profiling', '~> 0.0.5'
+ gem 'rspec_profiling', '~> 0.0.5'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0'
@@ -292,13 +293,13 @@ group :development, :test do
# Generate Fake data
gem 'ffaker', '~> 2.4'
- gem 'capybara', '~> 2.6.2'
+ gem 'capybara', '~> 2.6.2'
gem 'capybara-screenshot', '~> 1.0.0'
- gem 'poltergeist', '~> 1.9.0'
+ gem 'poltergeist', '~> 1.9.0'
- gem 'spring', '~> 1.7.0'
- gem 'spring-commands-rspec', '~> 1.0.4'
- gem 'spring-commands-spinach', '~> 1.1.0'
+ gem 'spring', '~> 1.7.0'
+ gem 'spring-commands-rspec', '~> 1.0.4'
+ gem 'spring-commands-spinach', '~> 1.1.0'
gem 'rubocop', '~> 0.47.1', require: false
gem 'rubocop-rspec', '~> 1.12.0', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index d4131a3dede..62388628eaa 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -78,6 +78,7 @@ GEM
better_errors (1.0.1)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
+ bindata (2.3.5)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
bootstrap-sass (3.3.6)
@@ -167,6 +168,9 @@ GEM
unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.2.0)
railties (>= 4.2)
+ doorkeeper-openid_connect (1.1.2)
+ doorkeeper (~> 4.0)
+ json-jwt (~> 1.6)
dropzonejs-rails (0.7.2)
rails (> 3.1)
email_reply_trimmer (0.1.6)
@@ -376,6 +380,12 @@ GEM
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (1.8.6)
+ json-jwt (1.7.1)
+ activesupport
+ bindata
+ multi_json (>= 1.3)
+ securecompare
+ url_safe_base64
json-schema (2.6.2)
addressable (~> 2.3.8)
jwt (1.5.6)
@@ -684,6 +694,7 @@ GEM
scss_lint (0.47.1)
rake (>= 0.9, < 11)
sass (~> 3.4.15)
+ securecompare (1.0.0)
seed-fu (2.3.6)
activerecord (>= 3.1)
activesupport (>= 3.1)
@@ -789,6 +800,7 @@ GEM
get_process_mem (~> 0)
unicorn (>= 4, < 6)
uniform_notifier (1.10.0)
+ url_safe_base64 (0.2.2)
validates_hostname (1.0.6)
activerecord (>= 3.0)
activesupport (>= 3.0)
@@ -866,6 +878,7 @@ DEPENDENCIES
devise-two-factor (~> 3.0.0)
diffy (~> 3.1.0)
doorkeeper (~> 4.2.0)
+ doorkeeper-openid_connect (~> 1.1.0)
dropzonejs-rails (~> 0.7.1)
email_reply_trimmer (~> 0.1)
email_spec (~> 1.6.0)
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 31f10f89245..546bdc9c8d7 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -1,3 +1,4 @@
+import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make this a bundle
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
/* global UsernameValidator */
/* global ActiveTabMemoizer */
@@ -286,7 +287,7 @@ const UserCallout = require('./user_callout');
case 'search:show':
new Search();
break;
- case 'projects:protected_branches:index':
+ case 'projects:repository:show':
new gl.ProtectedBranchCreate();
new gl.ProtectedBranchEditList();
break;
@@ -297,6 +298,8 @@ const UserCallout = require('./user_callout');
case 'ci:lints:show':
new gl.CILintEditor();
break;
+ case 'projects:environments:metrics':
+ new PrometheusGraph();
case 'users:show':
new UserCallout();
break;
diff --git a/app/assets/javascripts/monitoring/prometheus_graph.js b/app/assets/javascripts/monitoring/prometheus_graph.js
new file mode 100644
index 00000000000..9384fe3f276
--- /dev/null
+++ b/app/assets/javascripts/monitoring/prometheus_graph.js
@@ -0,0 +1,333 @@
+/* eslint-disable no-new*/
+import d3 from 'd3';
+import _ from 'underscore';
+import statusCodes from '~/lib/utils/http_status';
+import '~/lib/utils/common_utils';
+import Flash from '~/flash';
+
+const prometheusGraphsContainer = '.prometheus-graph';
+const metricsEndpoint = 'metrics.json';
+const timeFormat = d3.time.format('%H:%M');
+const dayFormat = d3.time.format('%b %e, %a');
+const bisectDate = d3.bisector(d => d.time).left;
+const extraAddedWidthParent = 100;
+
+class PrometheusGraph {
+
+ constructor() {
+ this.margin = { top: 80, right: 180, bottom: 80, left: 100 };
+ this.marginLabelContainer = { top: 40, right: 0, bottom: 40, left: 0 };
+ const parentContainerWidth = $(prometheusGraphsContainer).parent().width() +
+ extraAddedWidthParent;
+ this.originalWidth = parentContainerWidth;
+ this.originalHeight = 400;
+ this.width = parentContainerWidth - this.margin.left - this.margin.right;
+ this.height = 400 - this.margin.top - this.margin.bottom;
+ this.backOffRequestCounter = 0;
+ this.configureGraph();
+ this.init();
+ }
+
+ createGraph() {
+ const self = this;
+ _.each(this.data, (value, key) => {
+ if (value.length > 0 && (key === 'cpu_values' || key === 'memory_values')) {
+ self.plotValues(value, key);
+ }
+ });
+ }
+
+ init() {
+ const self = this;
+ this.getData().then((metricsResponse) => {
+ if (metricsResponse === {}) {
+ new Flash('Empty metrics', 'alert');
+ } else {
+ self.transformData(metricsResponse);
+ self.createGraph();
+ }
+ });
+ }
+
+ plotValues(valuesToPlot, key) {
+ const x = d3.time.scale()
+ .range([0, this.width]);
+
+ const y = d3.scale.linear()
+ .range([this.height, 0]);
+
+ const prometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`;
+
+ const graphSpecifics = this.graphSpecificProperties[key];
+
+ const chart = d3.select(prometheusGraphContainer)
+ .attr('width', this.width + this.margin.left + this.margin.right)
+ .attr('height', this.height + this.margin.bottom + this.margin.top)
+ .append('g')
+ .attr('transform', `translate(${this.margin.left},${this.margin.top})`);
+
+ const axisLabelContainer = d3.select(prometheusGraphContainer)
+ .attr('width', this.originalWidth + this.marginLabelContainer.left + this.marginLabelContainer.right)
+ .attr('height', this.originalHeight + this.marginLabelContainer.bottom + this.marginLabelContainer.top)
+ .append('g')
+ .attr('transform', `translate(${this.marginLabelContainer.left},${this.marginLabelContainer.top})`);
+
+ x.domain(d3.extent(valuesToPlot, d => d.time));
+ y.domain([0, d3.max(valuesToPlot.map(metricValue => metricValue.value))]);
+
+ const xAxis = d3.svg.axis()
+ .scale(x)
+ .ticks(this.commonGraphProperties.axis_no_ticks)
+ .orient('bottom');
+
+ const yAxis = d3.svg.axis()
+ .scale(y)
+ .ticks(this.commonGraphProperties.axis_no_ticks)
+ .tickSize(-this.width)
+ .orient('left');
+
+ this.createAxisLabelContainers(axisLabelContainer, key);
+
+ chart.append('g')
+ .attr('class', 'x-axis')
+ .attr('transform', `translate(0,${this.height})`)
+ .call(xAxis);
+
+ chart.append('g')
+ .attr('class', 'y-axis')
+ .call(yAxis);
+
+ const area = d3.svg.area()
+ .x(d => x(d.time))
+ .y0(this.height)
+ .y1(d => y(d.value))
+ .interpolate('linear');
+
+ const line = d3.svg.line()
+ .x(d => x(d.time))
+ .y(d => y(d.value));
+
+ chart.append('path')
+ .datum(valuesToPlot)
+ .attr('d', area)
+ .attr('class', 'metric-area')
+ .attr('fill', graphSpecifics.area_fill_color);
+
+ chart.append('path')
+ .datum(valuesToPlot)
+ .attr('class', 'metric-line')
+ .attr('stroke', graphSpecifics.line_color)
+ .attr('fill', 'none')
+ .attr('stroke-width', this.commonGraphProperties.area_stroke_width)
+ .attr('d', line);
+
+ // Overlay area for the mouseover events
+ chart.append('rect')
+ .attr('class', 'prometheus-graph-overlay')
+ .attr('width', this.width)
+ .attr('height', this.height)
+ .on('mousemove', this.handleMouseOverGraph.bind(this, x, y, valuesToPlot, chart, prometheusGraphContainer, key));
+ }
+
+ // The legends from the metric
+ createAxisLabelContainers(axisLabelContainer, key) {
+ const graphSpecifics = this.graphSpecificProperties[key];
+
+ axisLabelContainer.append('line')
+ .attr('class', 'label-x-axis-line')
+ .attr('stroke', '#000000')
+ .attr('stroke-width', '1')
+ .attr({
+ x1: 0,
+ y1: this.originalHeight - this.marginLabelContainer.top,
+ x2: this.originalWidth - this.margin.right,
+ y2: this.originalHeight - this.marginLabelContainer.top,
+ });
+
+ axisLabelContainer.append('line')
+ .attr('class', 'label-y-axis-line')
+ .attr('stroke', '#000000')
+ .attr('stroke-width', '1')
+ .attr({
+ x1: 0,
+ y1: 0,
+ x2: 0,
+ y2: this.originalHeight - this.marginLabelContainer.top,
+ });
+
+ axisLabelContainer.append('text')
+ .attr('class', 'label-axis-text')
+ .attr('text-anchor', 'middle')
+ .attr('transform', `translate(15, ${(this.originalHeight - this.marginLabelContainer.top) / 2}) rotate(-90)`)
+ .text(graphSpecifics.graph_legend_title);
+
+ axisLabelContainer.append('rect')
+ .attr('class', 'rect-axis-text')
+ .attr('x', (this.originalWidth / 2) - this.margin.right)
+ .attr('y', this.originalHeight - this.marginLabelContainer.top - 20)
+ .attr('width', 30)
+ .attr('height', 80);
+
+ axisLabelContainer.append('text')
+ .attr('class', 'label-axis-text')
+ .attr('x', (this.originalWidth / 2) - this.margin.right)
+ .attr('y', this.originalHeight - this.marginLabelContainer.top)
+ .attr('dy', '.35em')
+ .text('Time');
+
+ // Legends
+
+ // Metric Usage
+ axisLabelContainer.append('rect')
+ .attr('x', this.originalWidth - 170)
+ .attr('y', (this.originalHeight / 2) - 80)
+ .style('fill', graphSpecifics.area_fill_color)
+ .attr('width', 20)
+ .attr('height', 35);
+
+ axisLabelContainer.append('text')
+ .attr('class', 'label-axis-text')
+ .attr('x', this.originalWidth - 140)
+ .attr('y', (this.originalHeight / 2) - 65)
+ .text(graphSpecifics.graph_legend_title);
+
+ axisLabelContainer.append('text')
+ .attr('class', 'text-metric-usage')
+ .attr('x', this.originalWidth - 140)
+ .attr('y', (this.originalHeight / 2) - 50);
+ }
+
+ handleMouseOverGraph(x, y, valuesToPlot, chart, prometheusGraphContainer, key) {
+ const rectOverlay = document.querySelector(`${prometheusGraphContainer} .prometheus-graph-overlay`);
+ const timeValueFromOverlay = x.invert(d3.mouse(rectOverlay)[0]);
+ const timeValueIndex = bisectDate(valuesToPlot, timeValueFromOverlay, 1);
+ const d0 = valuesToPlot[timeValueIndex - 1];
+ const d1 = valuesToPlot[timeValueIndex];
+ const currentData = timeValueFromOverlay - d0.time > d1.time - timeValueFromOverlay ? d1 : d0;
+ const maxValueMetric = y(d3.max(valuesToPlot.map(metricValue => metricValue.value)));
+ const currentTimeCoordinate = x(currentData.time);
+ const graphSpecifics = this.graphSpecificProperties[key];
+ // Remove the current selectors
+ d3.selectAll(`${prometheusGraphContainer} .selected-metric-line`).remove();
+ d3.selectAll(`${prometheusGraphContainer} .circle-metric`).remove();
+ d3.selectAll(`${prometheusGraphContainer} .rect-text-metric`).remove();
+ d3.selectAll(`${prometheusGraphContainer} .text-metric`).remove();
+
+ chart.append('line')
+ .attr('class', 'selected-metric-line')
+ .attr({
+ x1: currentTimeCoordinate,
+ y1: y(0),
+ x2: currentTimeCoordinate,
+ y2: maxValueMetric,
+ });
+
+ chart.append('circle')
+ .attr('class', 'circle-metric')
+ .attr('fill', graphSpecifics.line_color)
+ .attr('cx', currentTimeCoordinate)
+ .attr('cy', y(currentData.value))
+ .attr('r', this.commonGraphProperties.circle_radius_metric);
+
+ // The little box with text
+ const rectTextMetric = chart.append('g')
+ .attr('class', 'rect-text-metric')
+ .attr('translate', `(${currentTimeCoordinate}, ${y(currentData.value)})`);
+
+ rectTextMetric.append('rect')
+ .attr('class', 'rect-metric')
+ .attr('x', currentTimeCoordinate + 10)
+ .attr('y', maxValueMetric)
+ .attr('width', this.commonGraphProperties.rect_text_width)
+ .attr('height', this.commonGraphProperties.rect_text_height);
+
+ rectTextMetric.append('text')
+ .attr('class', 'text-metric')
+ .attr('x', currentTimeCoordinate + 35)
+ .attr('y', maxValueMetric + 35)
+ .text(timeFormat(currentData.time));
+
+ rectTextMetric.append('text')
+ .attr('class', 'text-metric-date')
+ .attr('x', currentTimeCoordinate + 15)
+ .attr('y', maxValueMetric + 15)
+ .text(dayFormat(currentData.time));
+
+ // Update the text
+ d3.select(`${prometheusGraphContainer} .text-metric-usage`)
+ .text(currentData.value.substring(0, 8));
+ }
+
+ configureGraph() {
+ this.graphSpecificProperties = {
+ cpu_values: {
+ area_fill_color: '#edf3fc',
+ line_color: '#5b99f7',
+ graph_legend_title: 'CPU Usage (Cores)',
+ },
+ memory_values: {
+ area_fill_color: '#fca326',
+ line_color: '#fc6d26',
+ graph_legend_title: 'Memory Usage (MB)',
+ },
+ };
+
+ this.commonGraphProperties = {
+ area_stroke_width: 2,
+ median_total_characters: 8,
+ circle_radius_metric: 5,
+ rect_text_width: 90,
+ rect_text_height: 40,
+ axis_no_ticks: 3,
+ };
+ }
+
+ getData() {
+ const maxNumberOfRequests = 3;
+ return gl.utils.backOff((next, stop) => {
+ $.ajax({
+ url: metricsEndpoint,
+ dataType: 'json',
+ })
+ .done((data, statusText, resp) => {
+ if (resp.status === statusCodes.NO_CONTENT) {
+ this.backOffRequestCounter = this.backOffRequestCounter += 1;
+ if (this.backOffRequestCounter < maxNumberOfRequests) {
+ next();
+ } else {
+ stop({
+ status: resp.status,
+ metrics: data,
+ });
+ }
+ } else {
+ stop({
+ status: resp.status,
+ metrics: data,
+ });
+ }
+ }).fail(stop);
+ })
+ .then((resp) => {
+ if (resp.status === statusCodes.NO_CONTENT) {
+ return {};
+ }
+ return resp.metrics;
+ })
+ .catch(() => new Flash('An error occurred while fetching metrics.', 'alert'));
+ }
+
+ transformData(metricsResponse) {
+ const metricTypes = {};
+ _.each(metricsResponse.metrics, (value, key) => {
+ const metricValues = value[0].values;
+ metricTypes[key] = _.map(metricValues, metric => ({
+ time: new Date(metric[0] * 1000),
+ value: metric[1],
+ }));
+ });
+ this.data = metricTypes;
+ }
+}
+
+export default PrometheusGraph;
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 77e09e66340..0e2b8dba780 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -143,3 +143,71 @@
}
}
}
+
+.prometheus-graph {
+ text {
+ fill: $stat-graph-axis-fill;
+ }
+}
+
+.x-axis path,
+.y-axis path,
+.label-x-axis-line,
+.label-y-axis-line {
+ fill: none;
+ stroke-width: 1;
+ shape-rendering: crispEdges;
+}
+
+.x-axis path,
+.y-axis path {
+ stroke: $stat-graph-axis-fill;
+}
+
+.label-x-axis-line,
+.label-y-axis-line {
+ stroke: $border-color;
+}
+
+.y-axis {
+ line {
+ stroke: $stat-graph-axis-fill;
+ stroke-width: 1;
+ }
+}
+
+.metric-area {
+ opacity: 0.8;
+}
+
+.prometheus-graph-overlay {
+ fill: none;
+ opacity: 0.0;
+ pointer-events: all;
+}
+
+.rect-text-metric {
+ fill: $white-light;
+ stroke-width: 1;
+ stroke: $black;
+}
+
+.rect-axis-text {
+ fill: $white-light;
+}
+
+.text-metric,
+.text-median-metric,
+.text-metric-usage,
+.text-metric-date {
+ fill: $black;
+}
+
+.text-metric-date {
+ font-weight: 200;
+}
+
+.selected-metric-line {
+ stroke: $black;
+ stroke-width: 1;
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 09b85db7d45..4914933430f 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -746,6 +746,8 @@ pre.light-well {
}
.protected-branches-list {
+ margin-bottom: 30px;
+
a {
color: $gl-text-color;
diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb
index 62f62e99a97..9c9f420c1e0 100644
--- a/app/controllers/admin/applications_controller.rb
+++ b/app/controllers/admin/applications_controller.rb
@@ -2,7 +2,7 @@ class Admin::ApplicationsController < Admin::ApplicationController
include OauthApplications
before_action :set_application, only: [:show, :edit, :update, :destroy]
- before_action :load_scopes, only: [:new, :edit]
+ before_action :load_scopes, only: [:new, :create, :edit, :update]
def index
@applications = Doorkeeper::Application.where("owner_id IS NULL")
diff --git a/app/controllers/admin/impersonation_tokens_controller.rb b/app/controllers/admin/impersonation_tokens_controller.rb
index d26004539b5..07c8bf714fc 100644
--- a/app/controllers/admin/impersonation_tokens_controller.rb
+++ b/app/controllers/admin/impersonation_tokens_controller.rb
@@ -44,7 +44,7 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
end
def set_index_vars
- @scopes = Gitlab::Auth::SCOPES
+ @scopes = Gitlab::Auth::API_SCOPES
@impersonation_token ||= finder.build
@inactive_impersonation_tokens = finder(state: 'inactive').execute
diff --git a/app/controllers/concerns/repository_settings_redirect.rb b/app/controllers/concerns/repository_settings_redirect.rb
new file mode 100644
index 00000000000..0854c73a02f
--- /dev/null
+++ b/app/controllers/concerns/repository_settings_redirect.rb
@@ -0,0 +1,7 @@
+module RepositorySettingsRedirect
+ extend ActiveSupport::Concern
+
+ def redirect_to_repository_settings(project)
+ redirect_to namespace_project_settings_repository_path(project.namespace, project)
+ end
+end
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
index c721dca58d9..05190103767 100644
--- a/app/controllers/oauth/authorizations_controller.rb
+++ b/app/controllers/oauth/authorizations_controller.rb
@@ -1,8 +1,8 @@
class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
- before_action :authenticate_resource_owner!
-
layout 'profile'
+ # Overriden from Doorkeeper::AuthorizationsController to
+ # include the call to session.delete
def new
if pre_auth.authorizable?
if skip_authorization? || matching_token?
@@ -16,44 +16,4 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
render "doorkeeper/authorizations/error"
end
end
-
- # TODO: Handle raise invalid authorization
- def create
- redirect_or_render authorization.authorize
- end
-
- def destroy
- redirect_or_render authorization.deny
- end
-
- private
-
- def matching_token?
- Doorkeeper::AccessToken.matching_token_for(pre_auth.client,
- current_resource_owner.id,
- pre_auth.scopes)
- end
-
- def redirect_or_render(auth)
- if auth.redirectable?
- redirect_to auth.redirect_uri
- else
- render json: auth.body, status: auth.status
- end
- end
-
- def pre_auth
- @pre_auth ||=
- Doorkeeper::OAuth::PreAuthorization.new(Doorkeeper.configuration,
- server.client_via_uid,
- params)
- end
-
- def authorization
- @authorization ||= strategy.request
- end
-
- def strategy
- @strategy ||= server.authorization_request(pre_auth.response_type)
- end
end
diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb
index d1f2374e9eb..0abe7ea3c9b 100644
--- a/app/controllers/profiles/personal_access_tokens_controller.rb
+++ b/app/controllers/profiles/personal_access_tokens_controller.rb
@@ -38,7 +38,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
end
def set_index_vars
- @scopes = Gitlab::Auth::SCOPES
+ @scopes = Gitlab::Auth::API_SCOPES
@personal_access_token = finder.build
@inactive_personal_access_tokens = finder(state: 'inactive').execute
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index b094491e006..1502b734f37 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -1,4 +1,5 @@
class Projects::DeployKeysController < Projects::ApplicationController
+ include RepositorySettingsRedirect
respond_to :html
# Authorize
@@ -7,51 +8,36 @@ class Projects::DeployKeysController < Projects::ApplicationController
layout "project_settings"
def index
- @key = DeployKey.new
- set_index_vars
+ redirect_to_repository_settings(@project)
end
def new
- redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
+ redirect_to_repository_settings(@project)
end
def create
@key = DeployKey.new(deploy_key_params.merge(user: current_user))
- set_index_vars
- if @key.valid? && @project.deploy_keys << @key
- redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
- else
- render "index"
+ unless @key.valid? && @project.deploy_keys << @key
+ flash[:alert] = @key.errors.full_messages.join(', ').html_safe
end
+ redirect_to_repository_settings(@project)
end
def enable
Projects::EnableDeployKeyService.new(@project, current_user, params).execute
- redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
+ redirect_to_repository_settings(@project)
end
def disable
@project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy
- redirect_back_or_default(default: { action: 'index' })
+ redirect_to_repository_settings(@project)
end
protected
- def set_index_vars
- @enabled_keys ||= @project.deploy_keys
-
- @available_keys ||= current_user.accessible_deploy_keys - @enabled_keys
- @available_project_keys ||= current_user.project_deploy_keys - @enabled_keys
- @available_public_keys ||= DeployKey.are_public - @enabled_keys
-
- # Public keys that are already used by another accessible project are already
- # in @available_project_keys.
- @available_public_keys -= @available_project_keys
- end
-
def deploy_key_params
params.require(:deploy_key).permit(:key, :title, :can_push)
end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index fed75396d6e..fa37963dfd4 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -5,7 +5,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :authorize_create_deployment!, only: [:stop]
before_action :authorize_update_environment!, only: [:edit, :update]
before_action :authorize_admin_environment!, only: [:terminal, :terminal_websocket_authorize]
- before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize]
+ before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics]
before_action :verify_api_request!, only: :terminal_websocket_authorize
def index
@@ -109,6 +109,19 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
end
+ def metrics
+ # Currently, this acts as a hint to load the metrics details into the cache
+ # if they aren't there already
+ @metrics = environment.metrics || {}
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: @metrics, status: @metrics.any? ? :ok : :no_content
+ end
+ end
+ end
+
private
def verify_api_request!
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index ee8c30058a1..a8cb07eb67a 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -1,26 +1,22 @@
class Projects::ProtectedBranchesController < Projects::ApplicationController
+ include RepositorySettingsRedirect
# Authorize
before_action :require_non_empty_project
before_action :authorize_admin_project!
before_action :load_protected_branch, only: [:show, :update, :destroy]
- before_action :load_protected_branches, only: [:index]
layout "project_settings"
def index
- @protected_branch = @project.protected_branches.new
- load_gon_index
+ redirect_to_repository_settings(@project)
end
def create
@protected_branch = ::ProtectedBranches::CreateService.new(@project, current_user, protected_branch_params).execute
- if @protected_branch.persisted?
- redirect_to namespace_project_protected_branches_path(@project.namespace, @project)
- else
- load_protected_branches
- load_gon_index
- render :index
+ unless @protected_branch.persisted?
+ flash[:alert] = @protected_branches.errors.full_messages.join(', ').html_safe
end
+ redirect_to_repository_settings(@project)
end
def show
@@ -45,7 +41,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
@protected_branch.destroy
respond_to do |format|
- format.html { redirect_to namespace_project_protected_branches_path }
+ format.html { redirect_to_repository_settings(@project) }
format.js { head :ok }
end
end
@@ -61,24 +57,4 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
merge_access_levels_attributes: [:access_level, :id],
push_access_levels_attributes: [:access_level, :id])
end
-
- def load_protected_branches
- @protected_branches = @project.protected_branches.order(:name).page(params[:page])
- end
-
- def access_levels_options
- {
- push_access_levels: {
- roles: ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } },
- },
- merge_access_levels: {
- roles: ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text, before_divider: true } }
- }
- }
- end
-
- def load_gon_index
- params = { open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } } }
- gon.push(params.merge(access_levels_options))
- end
end
diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb
new file mode 100644
index 00000000000..b6ce4abca45
--- /dev/null
+++ b/app/controllers/projects/settings/repository_controller.rb
@@ -0,0 +1,50 @@
+module Projects
+ module Settings
+ class RepositoryController < Projects::ApplicationController
+ before_action :authorize_admin_project!
+
+ def show
+ @deploy_keys = DeployKeysPresenter
+ .new(@project, current_user: current_user)
+
+ define_protected_branches
+ end
+
+ private
+
+ def define_protected_branches
+ load_protected_branches
+ @protected_branch = @project.protected_branches.new
+ load_gon_index
+ end
+
+ def load_protected_branches
+ @protected_branches = @project.protected_branches.order(:name).page(params[:page])
+ end
+
+ def access_levels_options
+ {
+ push_access_levels: {
+ roles: ProtectedBranch::PushAccessLevel.human_access_levels.map do |id, text|
+ { id: id, text: text, before_divider: true }
+ end
+ },
+ merge_access_levels: {
+ roles: ProtectedBranch::MergeAccessLevel.human_access_levels.map do |id, text|
+ { id: id, text: text, before_divider: true }
+ end
+ }
+ }
+ end
+
+ def open_branches
+ branches = @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } }
+ { open_branches: branches }
+ end
+
+ def load_gon_index
+ gon.push(open_branches.merge(access_levels_options))
+ end
+ end
+ end
+end
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 509f4f412ca..f1bfd574f04 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -14,6 +14,8 @@ class UploadsController < ApplicationController
end
disposition = uploader.image? ? 'inline' : 'attachment'
+
+ expires_in 0.seconds, must_revalidate: true, private: true
send_file uploader.file.path, disposition: disposition
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 4b025669f69..ca326dd0627 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -81,8 +81,8 @@ module ApplicationSettingsHelper
end
def repository_storages_options_for_select
- options = Gitlab.config.repositories.storages.map do |name, path|
- ["#{name} - #{path}", name]
+ options = Gitlab.config.repositories.storages.map do |name, storage|
+ ["#{name} - #{storage['path']}", name]
end
options_for_select(options, @application_setting.repository_storages)
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index f16a63e2178..e9b7cbbad6a 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -74,6 +74,10 @@ module GitlabRoutingHelper
namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
end
+ def environment_metrics_path(environment, *args)
+ metrics_namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
+ end
+
def issue_path(entity, *args)
namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 8ad3851fb9a..18734f1411f 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -50,7 +50,7 @@ module SortingHelper
end
def sort_title_priority
- 'Priority'
+ 'Label priority'
end
def sort_title_oldest_updated
diff --git a/app/models/chat_team.rb b/app/models/chat_team.rb
index 7952141a0d6..c52b6f15913 100644
--- a/app/models/chat_team.rb
+++ b/app/models/chat_team.rb
@@ -1,5 +1,6 @@
class ChatTeam < ActiveRecord::Base
validates :team_id, presence: true
+ validates :namespace, uniqueness: true
belongs_to :namespace
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 77fa19cfe21..3722047251d 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -564,10 +564,35 @@ module Ci
@unscoped_project ||= Project.unscoped.find_by(id: gl_project_id)
end
+ CI_REGISTRY_USER = 'gitlab-ci-token'.freeze
+
def predefined_variables
variables = [
{ key: 'CI', value: 'true', public: true },
{ key: 'GITLAB_CI', value: 'true', public: true },
+ { key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
+ { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
+ { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
+ { key: 'CI_JOB_ID', value: id.to_s, public: true },
+ { key: 'CI_JOB_NAME', value: name, public: true },
+ { key: 'CI_JOB_STAGE', value: stage, public: true },
+ { key: 'CI_JOB_TOKEN', value: token, public: false },
+ { key: 'CI_COMMIT_SHA', value: sha, public: true },
+ { key: 'CI_COMMIT_REF_NAME', value: ref, public: true },
+ { key: 'CI_COMMIT_REF_SLUG', value: ref_slug, public: true },
+ { key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER, public: true },
+ { key: 'CI_REGISTRY_PASSWORD', value: token, public: false },
+ { key: 'CI_REPOSITORY_URL', value: repo_url, public: false }
+ ]
+
+ variables << { key: "CI_COMMIT_TAG", value: ref, public: true } if tag?
+ variables << { key: "CI_PIPELINE_TRIGGERED", value: 'true', public: true } if trigger_request
+ variables << { key: "CI_JOB_MANUAL", value: 'true', public: true } if action?
+ variables.concat(legacy_variables)
+ end
+
+ def legacy_variables
+ variables = [
{ key: 'CI_BUILD_ID', value: id.to_s, public: true },
{ key: 'CI_BUILD_TOKEN', value: token, public: false },
{ key: 'CI_BUILD_REF', value: sha, public: true },
@@ -575,14 +600,12 @@ module Ci
{ key: 'CI_BUILD_REF_NAME', value: ref, public: true },
{ key: 'CI_BUILD_REF_SLUG', value: ref_slug, public: true },
{ key: 'CI_BUILD_NAME', value: name, public: true },
- { key: 'CI_BUILD_STAGE', value: stage, public: true },
- { key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
- { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
- { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }
+ { key: 'CI_BUILD_STAGE', value: stage, public: true }
]
- variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag?
- variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request
- variables << { key: 'CI_BUILD_MANUAL', value: 'true', public: true } if action?
+
+ variables << { key: "CI_BUILD_TAG", value: ref, public: true } if tag?
+ variables << { key: "CI_BUILD_TRIGGERED", value: 'true', public: true } if trigger_request
+ variables << { key: "CI_BUILD_MANUAL", value: 'true', public: true } if action?
variables
end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 1a21b5e52b5..bf33010fd21 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -145,6 +145,14 @@ class Environment < ActiveRecord::Base
project.deployment_service.terminals(self) if has_terminals?
end
+ def has_metrics?
+ project.monitoring_service.present? && available? && last_deployment.present?
+ end
+
+ def metrics
+ project.monitoring_service.metrics(self) if has_metrics?
+ end
+
# An environment name is not necessarily suitable for use in URLs, DNS
# or other third-party contexts, so provide a slugified version. A slug has
# the following properties:
diff --git a/app/models/oauth_access_grant.rb b/app/models/oauth_access_grant.rb
new file mode 100644
index 00000000000..3a997406565
--- /dev/null
+++ b/app/models/oauth_access_grant.rb
@@ -0,0 +1,4 @@
+class OauthAccessGrant < Doorkeeper::AccessGrant
+ belongs_to :resource_owner, class_name: 'User'
+ belongs_to :application, class_name: 'Doorkeeper::Application'
+end
diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb
index 116fb71ac08..b85f5dbaf2e 100644
--- a/app/models/oauth_access_token.rb
+++ b/app/models/oauth_access_token.rb
@@ -1,4 +1,4 @@
-class OauthAccessToken < ActiveRecord::Base
+class OauthAccessToken < Doorkeeper::AccessToken
belongs_to :resource_owner, class_name: 'User'
belongs_to :application, class_name: 'Doorkeeper::Application'
end
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index 22809fe1487..e8b000ddad6 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -14,6 +14,9 @@ class PersonalAccessToken < ActiveRecord::Base
scope :with_impersonation, -> { where(impersonation: true) }
scope :without_impersonation, -> { where(impersonation: false) }
+ validates :scopes, presence: true
+ validate :validate_api_scopes
+
def revoke!
self.revoked = true
self.save
@@ -22,4 +25,12 @@ class PersonalAccessToken < ActiveRecord::Base
def active?
!revoked? && !expired?
end
+
+ protected
+
+ def validate_api_scopes
+ unless scopes.all? { |scope| Gitlab::Auth::API_SCOPES.include?(scope.to_sym) }
+ errors.add :scopes, "can only contain API scopes"
+ end
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 7d211784c3c..8c2dadf4659 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -113,6 +113,7 @@ class Project < ActiveRecord::Base
has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project
has_one :external_wiki_service, dependent: :destroy
has_one :kubernetes_service, dependent: :destroy, inverse_of: :project
+ has_one :prometheus_service, dependent: :destroy, inverse_of: :project
has_one :mock_ci_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
@@ -392,7 +393,7 @@ class Project < ActiveRecord::Base
end
def repository_storage_path
- Gitlab.config.repositories.storages[repository_storage]
+ Gitlab.config.repositories.storages[repository_storage]['path']
end
def team
@@ -771,6 +772,14 @@ class Project < ActiveRecord::Base
@deployment_service ||= deployment_services.reorder(nil).find_by(active: true)
end
+ def monitoring_services
+ services.where(category: :monitoring)
+ end
+
+ def monitoring_service
+ @monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true)
+ end
+
def jira_tracker?
issues_tracker.to_param == 'jira'
end
diff --git a/app/models/project_services/monitoring_service.rb b/app/models/project_services/monitoring_service.rb
new file mode 100644
index 00000000000..ea585721e8f
--- /dev/null
+++ b/app/models/project_services/monitoring_service.rb
@@ -0,0 +1,16 @@
+# Base class for monitoring services
+#
+# These services integrate with a deployment solution like Prometheus
+# to provide additional features for environments.
+class MonitoringService < Service
+ default_value_for :category, 'monitoring'
+
+ def self.supported_events
+ %w()
+ end
+
+ # Environments have a number of metrics
+ def metrics(environment)
+ raise NotImplementedError
+ end
+end
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
new file mode 100644
index 00000000000..375966b9efc
--- /dev/null
+++ b/app/models/project_services/prometheus_service.rb
@@ -0,0 +1,93 @@
+class PrometheusService < MonitoringService
+ include ReactiveCaching
+
+ self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] }
+ self.reactive_cache_lease_timeout = 30.seconds
+ self.reactive_cache_refresh_interval = 30.seconds
+ self.reactive_cache_lifetime = 1.minute
+
+ # Access to prometheus is directly through the API
+ prop_accessor :api_url
+
+ with_options presence: true, if: :activated? do
+ validates :api_url, url: true
+ end
+
+ after_save :clear_reactive_cache!
+
+ def initialize_properties
+ if properties.nil?
+ self.properties = {}
+ end
+ end
+
+ def title
+ 'Prometheus'
+ end
+
+ def description
+ 'Prometheus monitoring'
+ end
+
+ def help
+ 'Retrieves `container_cpu_usage_seconds_total` and `container_memory_usage_bytes` from the configured Prometheus server. An `environment` label is required on each metric to identify the Environment.'
+ end
+
+ def self.to_param
+ 'prometheus'
+ end
+
+ def fields
+ [
+ {
+ type: 'text',
+ name: 'api_url',
+ title: 'API URL',
+ placeholder: 'Prometheus API Base URL, like http://prometheus.example.com/'
+ }
+ ]
+ end
+
+ # Check we can connect to the Prometheus API
+ def test(*args)
+ client.ping
+
+ { success: true, result: 'Checked API endpoint' }
+ rescue Gitlab::PrometheusError => err
+ { success: false, result: err }
+ end
+
+ def metrics(environment)
+ with_reactive_cache(environment.slug) do |data|
+ data
+ end
+ end
+
+ # Cache metrics for specific environment
+ def calculate_reactive_cache(environment_slug)
+ return unless active? && project && !project.pending_delete?
+
+ memory_query = %{sum(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"})/1024/1024}
+ cpu_query = %{sum(rate(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}[2m]))}
+
+ {
+ success: true,
+ metrics: {
+ # Memory used in MB
+ memory_values: client.query_range(memory_query, start: 8.hours.ago),
+ memory_current: client.query(memory_query),
+ # CPU Usage rate in cores.
+ cpu_values: client.query_range(cpu_query, start: 8.hours.ago),
+ cpu_current: client.query(cpu_query)
+ },
+ last_update: Time.now.utc
+ }
+
+ rescue Gitlab::PrometheusError => err
+ { success: false, result: err.message }
+ end
+
+ def client
+ @prometheus ||= Gitlab::Prometheus.new(api_url: api_url)
+ end
+end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index e7cc8d6e083..2a12b36a84d 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -50,10 +50,6 @@ class Repository
end
end
- def self.storages
- Gitlab.config.repositories.storages
- end
-
def initialize(path_with_namespace, project)
@path_with_namespace = path_with_namespace
@project = project
diff --git a/app/models/service.rb b/app/models/service.rb
index 3ef4cbead10..2f75a2e4e7f 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -232,6 +232,7 @@ class Service < ActiveRecord::Base
mattermost
pipelines_email
pivotaltracker
+ prometheus
pushover
redmine
slack_slash_commands
diff --git a/app/presenters/projects/settings/deploy_keys_presenter.rb b/app/presenters/projects/settings/deploy_keys_presenter.rb
new file mode 100644
index 00000000000..86ac513b3c0
--- /dev/null
+++ b/app/presenters/projects/settings/deploy_keys_presenter.rb
@@ -0,0 +1,60 @@
+module Projects
+ module Settings
+ class DeployKeysPresenter < Gitlab::View::Presenter::Simple
+ presents :project
+ delegate :size, to: :enabled_keys, prefix: true
+ delegate :size, to: :available_project_keys, prefix: true
+ delegate :size, to: :available_public_keys, prefix: true
+
+ def new_key
+ @key ||= DeployKey.new
+ end
+
+ def enabled_keys
+ @enabled_keys ||= project.deploy_keys
+ end
+
+ def any_keys_enabled?
+ enabled_keys.any?
+ end
+
+ def available_keys
+ @available_keys ||= current_user.accessible_deploy_keys - enabled_keys
+ end
+
+ def available_project_keys
+ @available_project_keys ||= current_user.project_deploy_keys - enabled_keys
+ end
+
+ def any_available_project_keys_enabled?
+ available_project_keys.any?
+ end
+
+ def key_available?(deploy_key)
+ available_keys.include?(deploy_key)
+ end
+
+ def available_public_keys
+ return @available_public_keys if defined?(@available_public_keys)
+
+ @available_public_keys ||= DeployKey.are_public - enabled_keys
+
+ # Public keys that are already used by another accessible project are already
+ # in @available_project_keys.
+ @available_public_keys -= available_project_keys
+ end
+
+ def any_available_public_keys_enabled?
+ available_public_keys.any?
+ end
+
+ def to_partial_path
+ 'projects/deploy_keys/index'
+ end
+
+ def form_partial_path
+ 'projects/deploy_keys/form'
+ end
+ end
+ end
+end
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index a196561f381..82aa51f9778 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -27,6 +27,7 @@
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
+ = hidden_field_tag :nonce, @pre_auth.nonce
= submit_tag "Authorize", class: "btn btn-success wide pull-left"
= form_tag oauth_authorization_path, method: :delete do
= hidden_field_tag :client_id, @pre_auth.client.uid
@@ -34,4 +35,5 @@
= hidden_field_tag :state, @pre_auth.state
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
+ = hidden_field_tag :nonce, @pre_auth.nonce
= submit_tag "Deny", class: "btn btn-danger prepend-left-10"
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 665725f6862..6f2777d1be6 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -4,18 +4,14 @@
%span
Members
- if can_edit
- = nav_link(controller: :deploy_keys) do
- = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do
+ = nav_link(controller: :repository) do
+ = link_to namespace_project_settings_repository_path(@project.namespace, @project), title: 'Repository' do
%span
- Deploy Keys
+ Repository
= nav_link(controller: :integrations) do
= link_to namespace_project_settings_integrations_path(@project.namespace, @project), title: 'Integrations' do
%span
Integrations
- = nav_link(controller: :protected_branches) do
- = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do
- %span
- Protected Branches
- if @project.feature_available?(:builds, current_user)
= nav_link(controller: :ci_cd) do
diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml
index d1e3cb14022..ec8fc4c9ee8 100644
--- a/app/views/projects/deploy_keys/_deploy_key.html.haml
+++ b/app/views/projects/deploy_keys/_deploy_key.html.haml
@@ -18,7 +18,7 @@
%span.key-created-at
created #{time_ago_with_tooltip(deploy_key.created_at)}
.visible-xs-block.visible-sm-block
- - if @available_keys.include?(deploy_key)
+ - if @deploy_keys.key_available?(deploy_key)
= link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: "btn btn-sm prepend-left-10", method: :put do
Enable
- else
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index c91bb9c255a..1421da72418 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -1,5 +1,5 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input" } do |f|
- = form_errors(@key)
+= form_for [@project.namespace.becomes(Namespace), @project, @deploy_keys.new_key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input" } do |f|
+ = form_errors(@deploy_keys.new_key)
.form-group
= f.label :title, class: "label-light"
= f.text_field :title, class: 'form-control', autofocus: true, required: true
diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml
new file mode 100644
index 00000000000..0cbe9b3275a
--- /dev/null
+++ b/app/views/projects/deploy_keys/_index.html.haml
@@ -0,0 +1,34 @@
+.row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ Deploy Keys
+ %p
+ Deploy keys allow read-only access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
+ .col-lg-9
+ %h5.prepend-top-0
+ Create a new deploy key for this project
+ = render @deploy_keys.form_partial_path
+ .col-lg-9.col-lg-offset-3
+ %hr
+ .col-lg-9.col-lg-offset-3.append-bottom-default.deploy-keys
+ %h5.prepend-top-0
+ Enabled deploy keys for this project (#{@deploy_keys.enabled_keys_size})
+ - if @deploy_keys.any_keys_enabled?
+ %ul.well-list
+ = render partial: 'projects/deploy_keys/deploy_key', collection: @deploy_keys.enabled_keys, as: :deploy_key
+ - else
+ .settings-message.text-center
+ No deploy keys found. Create one with the form above.
+ %h5.prepend-top-default
+ Deploy keys from projects you have access to (#{@deploy_keys.available_project_keys_size})
+ - if @deploy_keys.any_available_project_keys_enabled?
+ %ul.well-list
+ = render partial: 'projects/deploy_keys/deploy_key', collection: @deploy_keys.available_project_keys, as: :deploy_key
+ - else
+ .settings-message.text-center
+ No deploy keys from your projects could be found. Create one with the form above or add existing one below.
+ - if @deploy_keys.any_available_public_keys_enabled?
+ %h5.prepend-top-default
+ Public deploy keys available to any project (#{@deploy_keys.available_public_keys_size})
+ %ul.well-list
+ = render partial: 'projects/deploy_keys/deploy_key', collection: @deploy_keys.available_public_keys, as: :deploy_key
diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml
deleted file mode 100644
index 04fbb37d93f..00000000000
--- a/app/views/projects/deploy_keys/index.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
-- page_title "Deploy Keys"
-
-.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
- %h4.prepend-top-0
- = page_title
- %p
- Deploy keys allow read-only access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
- .col-lg-9
- %h5.prepend-top-0
- Create a new deploy key for this project
- = render "form"
- .col-lg-9.col-lg-offset-3
- %hr
- .col-lg-9.col-lg-offset-3.append-bottom-default.deploy-keys
- %h5.prepend-top-0
- Enabled deploy keys for this project (#{@enabled_keys.size})
- - if @enabled_keys.any?
- %ul.well-list
- = render @enabled_keys
- - else
- .settings-message.text-center
- No deploy keys found. Create one with the form above or add existing one below.
- %h5.prepend-top-default
- Deploy keys from projects you have access to (#{@available_project_keys.size})
- - if @available_project_keys.any?
- %ul.well-list
- = render @available_project_keys
- - else
- .settings-message.text-center
- No deploy keys from your projects could be found. Create one with the form above or add existing one below.
- - if @available_public_keys.any?
- %h5.prepend-top-default
- Public deploy keys available to any project (#{@available_public_keys.size})
- %ul.well-list
- = render @available_public_keys
diff --git a/app/views/projects/environments/_metrics_button.html.haml b/app/views/projects/environments/_metrics_button.html.haml
new file mode 100644
index 00000000000..acbac1869fd
--- /dev/null
+++ b/app/views/projects/environments/_metrics_button.html.haml
@@ -0,0 +1,6 @@
+- environment = local_assigns.fetch(:environment)
+
+- return unless environment.has_metrics? && can?(current_user, :read_environment, environment)
+
+= link_to environment_metrics_path(environment), title: 'See metrics', class: 'btn metrics-button' do
+ = icon('area-chart')
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
new file mode 100644
index 00000000000..f8e94ca98ae
--- /dev/null
+++ b/app/views/projects/environments/metrics.html.haml
@@ -0,0 +1,21 @@
+- @no_container = true
+- page_title "Metrics for environment", @environment.name
+= render "projects/pipelines/head"
+
+%div{ class: container_class }
+ .top-area
+ .row
+ .col-sm-6
+ %h3.page-title
+ Environment:
+ = @environment.name
+
+ .col-sm-6
+ .nav-controls
+ = render 'projects/deployments/actions', deployment: @environment.last_deployment
+ .row
+ .col-sm-12
+ %svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
+ .row
+ .col-sm-12
+ %svg.prometheus-graph{ 'graph-type' => 'memory_values' }
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 7036325fff8..29a98f23b88 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -8,6 +8,7 @@
%h3.page-title= @environment.name
.col-md-3
.nav-controls
+ = render 'projects/environments/metrics_button', environment: @environment
= render 'projects/environments/terminal_button', environment: @environment
= render 'projects/environments/external_url', environment: @environment
- if can?(current_user, :update_environment, @environment)
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
index 04b19a8c5a7..cf0db943865 100644
--- a/app/views/projects/protected_branches/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -23,6 +23,6 @@
- if can_admin_project
%th
%tbody
- = render partial: @protected_branches, locals: { can_admin_project: can_admin_project }
+ = render partial: 'projects/protected_branches/protected_branch', collection: @protected_branches, locals: { can_admin_project: can_admin_project}
= paginate @protected_branches, theme: 'gitlab'
diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml
index e95a3b1b4c3..b8e885b4d9a 100644
--- a/app/views/projects/protected_branches/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml
@@ -10,7 +10,7 @@
= f.label :name, class: 'col-md-2 text-right' do
Branch:
.col-md-10
- = render partial: "dropdown", locals: { f: f }
+ = render partial: "projects/protected_branches/dropdown", locals: { f: f }
.help-block
= link_to 'Wildcards', help_page_path('user/project/protected_branches', anchor: 'wildcard-protected-branches')
such as
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/_index.html.haml
index b3b419bd92d..2d8c519c025 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/_index.html.haml
@@ -1,11 +1,10 @@
-- page_title "Protected branches"
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('protected_branches')
.row.prepend-top-default.append-bottom-default
.col-lg-3
%h4.prepend-top-0
- = page_title
+ Protected Branches
%p Keep stable branches secure and force developers to use merge requests.
%p.prepend-top-20
By default, protected branches are designed to:
@@ -17,6 +16,6 @@
%p.append-bottom-0 Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}.
.col-lg-9
- if can? current_user, :admin_project, @project
- = render 'create_protected_branch'
+ = render 'projects/protected_branches/create_protected_branch'
- = render "branches_list"
+ = render "projects/protected_branches/branches_list"
diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml
index 0193800dedf..b2a6b8469a3 100644
--- a/app/views/projects/protected_branches/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_protected_branch.html.haml
@@ -14,7 +14,7 @@
- else
(branch was removed from repository)
- = render partial: 'update_protected_branch', locals: { protected_branch: protected_branch }
+ = render partial: 'projects/protected_branches/update_protected_branch', locals: { protected_branch: protected_branch }
- if can_admin_project
%td
diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml
new file mode 100644
index 00000000000..95d821f6135
--- /dev/null
+++ b/app/views/projects/settings/repository/show.html.haml
@@ -0,0 +1,4 @@
+- page_title "Repository"
+
+= render @deploy_keys
+= render "projects/protected_branches/index"
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 2fff6b0105d..2cd87895c55 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -3,8 +3,8 @@ class PostReceive
include DedicatedSidekiqQueue
def perform(repo_path, identifier, changes)
- if path = Gitlab.config.repositories.storages.find { |p| repo_path.start_with?(p[1].to_s) }
- repo_path.gsub!(path[1].to_s, "")
+ if repository_storage = Gitlab.config.repositories.storages.find { |p| repo_path.start_with?(p[1]['path'].to_s) }
+ repo_path.gsub!(repository_storage[1]['path'].to_s, "")
else
log("Check gitlab.yml config for correct repositories.storages values. No repository storage path matches \"#{repo_path}\"")
end
diff --git a/changelogs/unreleased/26732-combine-deploy-keys-and-push-rules-and-mirror-repository-and-protect-branches-settings-pages.yml b/changelogs/unreleased/26732-combine-deploy-keys-and-push-rules-and-mirror-repository-and-protect-branches-settings-pages.yml
new file mode 100644
index 00000000000..6fc4615dab8
--- /dev/null
+++ b/changelogs/unreleased/26732-combine-deploy-keys-and-push-rules-and-mirror-repository-and-protect-branches-settings-pages.yml
@@ -0,0 +1,5 @@
+---
+title: Combined deploy keys, push rules, protect branches and mirror repository settings options into a single one called
+ Repository
+merge_request:
+author:
diff --git a/changelogs/unreleased/27568-refactor-very-slow-dropdown-asignee-spec.yml b/changelogs/unreleased/27568-refactor-very-slow-dropdown-asignee-spec.yml
new file mode 100644
index 00000000000..5c738af7704
--- /dev/null
+++ b/changelogs/unreleased/27568-refactor-very-slow-dropdown-asignee-spec.yml
@@ -0,0 +1,4 @@
+---
+title: Refactor dropdown_assignee_spec
+merge_request: 9711
+author: George Andrinopoulos
diff --git a/changelogs/unreleased/27936-make-all-uploads-require-revalidation-on-each-browser-fetch.yml b/changelogs/unreleased/27936-make-all-uploads-require-revalidation-on-each-browser-fetch.yml
new file mode 100644
index 00000000000..adc129d8dca
--- /dev/null
+++ b/changelogs/unreleased/27936-make-all-uploads-require-revalidation-on-each-browser-fetch.yml
@@ -0,0 +1,4 @@
+---
+title: Uploaded files which content can change now require revalidation on each page load
+merge_request: 9453
+author:
diff --git a/changelogs/unreleased/28447-hybrid-repository-storages.yml b/changelogs/unreleased/28447-hybrid-repository-storages.yml
new file mode 100644
index 00000000000..00dfc5781b9
--- /dev/null
+++ b/changelogs/unreleased/28447-hybrid-repository-storages.yml
@@ -0,0 +1,4 @@
+---
+title: Update storage settings to allow extra values per repository storage
+merge_request: 9597
+author:
diff --git a/changelogs/unreleased/feature-openid-connect.yml b/changelogs/unreleased/feature-openid-connect.yml
new file mode 100644
index 00000000000..e84eb7aff86
--- /dev/null
+++ b/changelogs/unreleased/feature-openid-connect.yml
@@ -0,0 +1,4 @@
+---
+title: Implement OpenID Connect identity provider
+merge_request: 8018
+author: Markus Koller
diff --git a/changelogs/unreleased/fix-gb-deprecate-ci-config-types.yml b/changelogs/unreleased/fix-gb-deprecate-ci-config-types.yml
new file mode 100644
index 00000000000..605b5f01d0e
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-deprecate-ci-config-types.yml
@@ -0,0 +1,4 @@
+---
+title: Deprecate usage of `types` configuration entry to describe CI/CD stages
+merge_request: 9766
+author:
diff --git a/changelogs/unreleased/priority-to-label-priority.yml b/changelogs/unreleased/priority-to-label-priority.yml
new file mode 100644
index 00000000000..2d9c58bfd9b
--- /dev/null
+++ b/changelogs/unreleased/priority-to-label-priority.yml
@@ -0,0 +1,4 @@
+---
+title: Rename priority sorting option to label priority
+merge_request:
+author:
diff --git a/changelogs/unreleased/rfr-20170307-change-default-project-number-limit.yml b/changelogs/unreleased/rfr-20170307-change-default-project-number-limit.yml
new file mode 100644
index 00000000000..e799dd3b48d
--- /dev/null
+++ b/changelogs/unreleased/rfr-20170307-change-default-project-number-limit.yml
@@ -0,0 +1,4 @@
+---
+title: Change project count limit from 10 to 100000
+merge_request:
+author:
diff --git a/changelogs/unreleased/zj-variables-build-job.yml b/changelogs/unreleased/zj-variables-build-job.yml
new file mode 100644
index 00000000000..1cb0919f824
--- /dev/null
+++ b/changelogs/unreleased/zj-variables-build-job.yml
@@ -0,0 +1,4 @@
+---
+title: Rename job environment variables to new terminology
+merge_request: 9756
+author:
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index be34a4000fa..720df0cac2d 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -461,7 +461,8 @@ production: &base
# gitlab-shell invokes Dir.pwd inside the repository path and that results
# real path not the symlink.
storages: # You must have at least a `default` storage path.
- default: /home/git/repositories/
+ default:
+ path: /home/git/repositories/
## Backup settings
backup:
@@ -574,7 +575,8 @@ test:
path: tmp/tests/gitlab-satellites/
repositories:
storages:
- default: tmp/tests/repositories/
+ default:
+ path: tmp/tests/repositories/
backup:
path: tmp/tests/backups
gitlab_shell:
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 933844e4ea6..b45d0e23080 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -83,7 +83,7 @@ class Settings < Settingslogic
def base_url(config)
custom_port = on_standard_port?(config) ? nil : ":#{config.port}"
-
+
[
config.protocol,
"://",
@@ -186,7 +186,7 @@ Settings['issues_tracker'] ||= {}
# GitLab
#
Settings['gitlab'] ||= Settingslogic.new({})
-Settings.gitlab['default_projects_limit'] ||= 10
+Settings.gitlab['default_projects_limit'] ||= 100000
Settings.gitlab['default_branch_protection'] ||= 2
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
Settings.gitlab['host'] ||= ENV['GITLAB_HOST'] || 'localhost'
@@ -366,8 +366,13 @@ Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.send(:build_gitlab_shell_s
#
Settings['repositories'] ||= Settingslogic.new({})
Settings.repositories['storages'] ||= {}
-# Setting gitlab_shell.repos_path is DEPRECATED and WILL BE REMOVED in version 9.0
-Settings.repositories.storages['default'] ||= Settings.gitlab_shell['repos_path'] || Settings.gitlab['user_home'] + '/repositories/'
+unless Settings.repositories.storages['default']
+ Settings.repositories.storages['default'] ||= {}
+ # We set the path only if the default storage doesn't exist, in case it exists
+ # but follows the pre-9.0 configuration structure. `6_validations.rb` initializer
+ # will validate all storages and throw a relevant error to the user if necessary.
+ Settings.repositories.storages['default']['path'] ||= Settings.gitlab['user_home'] + '/repositories/'
+end
#
# The repository_downloads_path is used to remove outdated repository
@@ -376,11 +381,11 @@ Settings.repositories.storages['default'] ||= Settings.gitlab_shell['repos_path'
# data-integrity issue. In this case, we sets it to the default
# repository_downloads_path value.
#
-repositories_storages_path = Settings.repositories.storages.values
+repositories_storages = Settings.repositories.storages.values
repository_downloads_path = Settings.gitlab['repository_downloads_path'].to_s.gsub(/\/$/, '')
repository_downloads_full_path = File.expand_path(repository_downloads_path, Settings.gitlab['user_home'])
-if repository_downloads_path.blank? || repositories_storages_path.any? { |path| [repository_downloads_path, repository_downloads_full_path].include?(path.gsub(/\/$/, '')) }
+if repository_downloads_path.blank? || repositories_storages.any? { |rs| [repository_downloads_path, repository_downloads_full_path].include?(rs['path'].gsub(/\/$/, '')) }
Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive')
end
diff --git a/config/initializers/6_validations.rb b/config/initializers/6_validations.rb
index d92f64e1647..abe570f430c 100644
--- a/config/initializers/6_validations.rb
+++ b/config/initializers/6_validations.rb
@@ -4,8 +4,8 @@ end
def find_parent_path(name, path)
parent = Pathname.new(path).realpath.parent
- Gitlab.config.repositories.storages.detect do |n, p|
- name != n && Pathname.new(p).realpath == parent
+ Gitlab.config.repositories.storages.detect do |n, rs|
+ name != n && Pathname.new(rs['path']).realpath == parent
end
end
@@ -16,10 +16,22 @@ end
def validate_storages
storage_validation_error('No repository storage path defined') if Gitlab.config.repositories.storages.empty?
- Gitlab.config.repositories.storages.each do |name, path|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
storage_validation_error("\"#{name}\" is not a valid storage name") unless storage_name_valid?(name)
- parent_name, _parent_path = find_parent_path(name, path)
+ if repository_storage.is_a?(String)
+ error = "#{name} is not a valid storage, because it has no `path` key. " \
+ "It may be configured as:\n\n#{name}:\n path: #{repository_storage}\n\n" \
+ "Refer to gitlab.yml.example for an updated example"
+
+ storage_validation_error(error)
+ end
+
+ if !repository_storage.is_a?(Hash) || repository_storage['path'].nil?
+ storage_validation_error("#{name} is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example")
+ end
+
+ parent_name, _parent_path = find_parent_path(name, repository_storage['path'])
if parent_name
storage_validation_error("#{name} is a nested path of #{parent_name}. Nested paths are not supported for repository storages")
end
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index 88cd0f5f652..a5636765774 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -6,9 +6,14 @@ Doorkeeper.configure do
# This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do
# Put your resource owner authentication logic here.
- # Ensure user is redirected to redirect_uri after login
- session[:user_return_to] = request.fullpath
- current_user || redirect_to(new_user_session_url)
+ if current_user
+ current_user
+ else
+ # Ensure user is redirected to redirect_uri after login
+ session[:user_return_to] = request.fullpath
+ redirect_to(new_user_session_url)
+ nil
+ end
end
resource_owner_from_credentials do |routes|
diff --git a/config/initializers/doorkeeper_openid_connect.rb b/config/initializers/doorkeeper_openid_connect.rb
new file mode 100644
index 00000000000..700ca25b884
--- /dev/null
+++ b/config/initializers/doorkeeper_openid_connect.rb
@@ -0,0 +1,36 @@
+Doorkeeper::OpenidConnect.configure do
+ issuer Gitlab.config.gitlab.url
+
+ jws_private_key Rails.application.secrets.jws_private_key
+
+ resource_owner_from_access_token do |access_token|
+ User.active.find_by(id: access_token.resource_owner_id)
+ end
+
+ auth_time_from_resource_owner do |user|
+ user.current_sign_in_at
+ end
+
+ reauthenticate_resource_owner do |user, return_to|
+ store_location_for user, return_to
+ sign_out user
+ redirect_to new_user_session_url
+ end
+
+ subject do |user|
+ # hash the user's ID with the Rails secret_key_base to avoid revealing it
+ Digest::SHA256.hexdigest "#{user.id}-#{Rails.application.secrets.secret_key_base}"
+ end
+
+ claims do
+ with_options scope: :openid do |o|
+ o.claim(:name) { |user| user.name }
+ o.claim(:nickname) { |user| user.username }
+ o.claim(:email) { |user| user.public_email }
+ o.claim(:email_verified) { |user| true if user.public_email? }
+ o.claim(:website) { |user| user.full_website_url if user.website_url? }
+ o.claim(:profile) { |user| Rails.application.routes.url_helpers.user_url user }
+ o.claim(:picture) { |user| user.avatar_url }
+ end
+ end
+end
diff --git a/config/initializers/rspec_profiling.rb b/config/initializers/rspec_profiling.rb
index 0ef9f51e5cf..ac353d14499 100644
--- a/config/initializers/rspec_profiling.rb
+++ b/config/initializers/rspec_profiling.rb
@@ -1,22 +1,41 @@
-module RspecProfilingConnection
- def establish_connection
- ::RspecProfiling::Collectors::PSQL::Result.establish_connection(ENV['RSPEC_PROFILING_POSTGRES_URL'])
+module RspecProfilingExt
+ module PSQL
+ def establish_connection
+ ::RspecProfiling::Collectors::PSQL::Result.establish_connection(ENV['RSPEC_PROFILING_POSTGRES_URL'])
+ end
end
-end
-module RspecProfilingGitBranchCi
- def branch
- ENV['CI_BUILD_REF_NAME'] || super
+ module Git
+ def branch
+ ENV['CI_BUILD_REF_NAME'] || super
+ end
+ end
+
+ module Run
+ def example_finished(*args)
+ super
+ rescue => err
+ return if @already_logged_example_finished_error
+
+ $stderr.puts "rspec_profiling couldn't collect an example: #{err}. Further warnings suppressed."
+ @already_logged_example_finished_error = true
+ end
+
+ alias_method :example_passed, :example_finished
+ alias_method :example_failed, :example_finished
end
end
if Rails.env.test?
RspecProfiling.configure do |config|
if ENV['RSPEC_PROFILING_POSTGRES_URL']
- RspecProfiling::Collectors::PSQL.prepend(RspecProfilingConnection)
+ RspecProfiling::Collectors::PSQL.prepend(RspecProfilingExt::PSQL)
config.collector = RspecProfiling::Collectors::PSQL
end
end
- RspecProfiling::VCS::Git.prepend(RspecProfilingGitBranchCi) if ENV.has_key?('CI')
+ if ENV.has_key?('CI')
+ RspecProfiling::VCS::Git.prepend(RspecProfilingExt::Git)
+ RspecProfiling::Run.prepend(RspecProfilingExt::Run)
+ end
end
diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb
index 291fa6c0abc..f9c1d2165d3 100644
--- a/config/initializers/secret_token.rb
+++ b/config/initializers/secret_token.rb
@@ -24,7 +24,8 @@ def create_tokens
defaults = {
secret_key_base: file_secret_key || generate_new_secure_token,
otp_key_base: env_secret_key || file_secret_key || generate_new_secure_token,
- db_key_base: generate_new_secure_token
+ db_key_base: generate_new_secure_token,
+ jws_private_key: generate_new_rsa_private_key
}
missing_secrets = set_missing_keys(defaults)
@@ -41,6 +42,10 @@ def generate_new_secure_token
SecureRandom.hex(64)
end
+def generate_new_rsa_private_key
+ OpenSSL::PKey::RSA.new(2048).to_pem
+end
+
def warn_missing_secret(secret)
warn "Missing Rails.application.secrets.#{secret} for #{Rails.env} environment. The secret will be generated and stored in config/secrets.yml."
end
diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml
index 1d728282d90..14d49885fb3 100644
--- a/config/locales/doorkeeper.en.yml
+++ b/config/locales/doorkeeper.en.yml
@@ -60,6 +60,7 @@ en:
scopes:
api: Access your API
read_user: Read user information
+ openid: Authenticate using OpenID Connect
flash:
applications:
diff --git a/config/routes.rb b/config/routes.rb
index 06293316937..1a851da6203 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -22,6 +22,8 @@ Rails.application.routes.draw do
authorizations: 'oauth/authorizations'
end
+ use_doorkeeper_openid_connect
+
# Autocomplete
get '/autocomplete/users' => 'autocomplete#users'
get '/autocomplete/users/:id' => 'autocomplete#user'
diff --git a/config/routes/project.rb b/config/routes/project.rb
index df39c3e200c..44b8ae7aedd 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -159,6 +159,7 @@ constraints(ProjectUrlConstrainer.new) do
member do
post :stop
get :terminal
+ get :metrics
get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', constraints: { format: nil }
end
@@ -328,6 +329,7 @@ constraints(ProjectUrlConstrainer.new) do
resource :members, only: [:show]
resource :ci_cd, only: [:show], controller: 'ci_cd'
resource :integrations, only: [:show]
+ resource :repository, only: [:show], controller: :repository
end
# Since both wiki and repository routing contains wildcard characters
diff --git a/db/migrate/20140502125220_migrate_repo_size.rb b/db/migrate/20140502125220_migrate_repo_size.rb
index e8de7ccf3db..66203486d53 100644
--- a/db/migrate/20140502125220_migrate_repo_size.rb
+++ b/db/migrate/20140502125220_migrate_repo_size.rb
@@ -8,7 +8,7 @@ class MigrateRepoSize < ActiveRecord::Migration
project_data.each do |project|
id = project['id']
namespace_path = project['namespace_path'] || ''
- repos_path = Gitlab.config.gitlab_shell['repos_path'] || Gitlab.config.repositories.storages.default
+ repos_path = Gitlab.config.gitlab_shell['repos_path'] || Gitlab.config.repositories.storages.default['path']
path = File.join(repos_path, namespace_path, project['project_path'] + '.git')
begin
diff --git a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
index 3e1f6b1627d..e5292cfba07 100644
--- a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
+++ b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb
@@ -12,7 +12,7 @@ class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration
end
def repository_storage_path
- Gitlab.config.repositories.storages[repository_storage]
+ Gitlab.config.repositories.storages[repository_storage]['path']
end
def repository_path
diff --git a/db/migrate/20161209165216_create_doorkeeper_openid_connect_tables.rb b/db/migrate/20161209165216_create_doorkeeper_openid_connect_tables.rb
new file mode 100644
index 00000000000..e63d5927f86
--- /dev/null
+++ b/db/migrate/20161209165216_create_doorkeeper_openid_connect_tables.rb
@@ -0,0 +1,37 @@
+class CreateDoorkeeperOpenidConnectTables < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ create_table :oauth_openid_requests do |t|
+ t.integer :access_grant_id, null: false
+ t.string :nonce, null: false
+ end
+
+ if Gitlab::Database.postgresql?
+ # add foreign key without validation to avoid downtime on PostgreSQL,
+ # also see db/post_migrate/20170209140523_validate_foreign_keys_on_oauth_openid_requests.rb
+ execute %q{
+ ALTER TABLE "oauth_openid_requests"
+ ADD CONSTRAINT "fk_oauth_openid_requests_oauth_access_grants_access_grant_id"
+ FOREIGN KEY ("access_grant_id")
+ REFERENCES "oauth_access_grants" ("id")
+ NOT VALID;
+ }
+ else
+ execute %q{
+ ALTER TABLE oauth_openid_requests
+ ADD CONSTRAINT fk_oauth_openid_requests_oauth_access_grants_access_grant_id
+ FOREIGN KEY (access_grant_id)
+ REFERENCES oauth_access_grants (id);
+ }
+ end
+ end
+
+ def down
+ drop_table :oauth_openid_requests
+ end
+end
diff --git a/db/migrate/20161220141214_remove_dot_git_from_group_names.rb b/db/migrate/20161220141214_remove_dot_git_from_group_names.rb
index 241afc6b097..8fb1f9d5e73 100644
--- a/db/migrate/20161220141214_remove_dot_git_from_group_names.rb
+++ b/db/migrate/20161220141214_remove_dot_git_from_group_names.rb
@@ -60,7 +60,7 @@ class RemoveDotGitFromGroupNames < ActiveRecord::Migration
def move_namespace(group_id, path_was, path)
repository_storage_paths = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{group_id}").map do |row|
- Gitlab.config.repositories.storages[row['repository_storage']]
+ Gitlab.config.repositories.storages[row['repository_storage']]['path']
end.compact
# Move the namespace directory in all storages paths used by member projects
diff --git a/db/migrate/20161226122833_remove_dot_git_from_usernames.rb b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb
index a0ce927161f..61dcc8c54f5 100644
--- a/db/migrate/20161226122833_remove_dot_git_from_usernames.rb
+++ b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb
@@ -71,7 +71,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
route_exists = route_exists?(path)
Gitlab.config.repositories.storages.each_value do |storage|
- if route_exists || path_exists?(path, storage)
+ if route_exists || path_exists?(path, storage['path'])
counter += 1
path = "#{base}#{counter}"
@@ -84,7 +84,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration
def move_namespace(namespace_id, path_was, path)
repository_storage_paths = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{namespace_id}").map do |row|
- Gitlab.config.repositories.storages[row['repository_storage']]
+ Gitlab.config.repositories.storages[row['repository_storage']]['path']
end.compact
# Move the namespace directory in all storages paths used by member projects
diff --git a/db/post_migrate/20170209140523_validate_foreign_keys_on_oauth_openid_requests.rb b/db/post_migrate/20170209140523_validate_foreign_keys_on_oauth_openid_requests.rb
new file mode 100644
index 00000000000..e206f9af636
--- /dev/null
+++ b/db/post_migrate/20170209140523_validate_foreign_keys_on_oauth_openid_requests.rb
@@ -0,0 +1,20 @@
+class ValidateForeignKeysOnOauthOpenidRequests < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ if Gitlab::Database.postgresql?
+ execute %q{
+ ALTER TABLE "oauth_openid_requests"
+ VALIDATE CONSTRAINT "fk_oauth_openid_requests_oauth_access_grants_access_grant_id";
+ }
+ end
+ end
+
+ def down
+ # noop
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 14f60999f15..21ab9bf9eab 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -61,6 +61,7 @@ ActiveRecord::Schema.define(version: 20170306170512) do
t.boolean "shared_runners_enabled", default: true, null: false
t.integer "max_artifacts_size", default: 100, null: false
t.string "runners_registration_token"
+ t.integer "max_pages_size", default: 100, null: false
t.boolean "require_two_factor_authentication", default: false
t.integer "two_factor_grace_period", default: 48
t.boolean "metrics_enabled", default: false
@@ -109,7 +110,6 @@ ActiveRecord::Schema.define(version: 20170306170512) do
t.boolean "html_emails_enabled", default: true
t.string "plantuml_url"
t.boolean "plantuml_enabled"
- t.integer "max_pages_size", default: 100, null: false
t.integer "terminal_max_session_time", default: 0, null: false
t.string "default_artifacts_expire_in", default: "0", null: false
t.integer "unique_ips_limit_per_user"
@@ -771,8 +771,8 @@ ActiveRecord::Schema.define(version: 20170306170512) do
t.integer "visibility_level", default: 20, null: false
t.boolean "request_access_enabled", default: false, null: false
t.datetime "deleted_at"
- t.boolean "lfs_enabled"
t.text "description_html"
+ t.boolean "lfs_enabled"
t.integer "parent_id"
end
@@ -878,6 +878,11 @@ ActiveRecord::Schema.define(version: 20170306170512) do
add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree
add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree
+ create_table "oauth_openid_requests", force: :cascade do |t|
+ t.integer "access_grant_id", null: false
+ t.string "nonce", null: false
+ end
+
create_table "pages_domains", force: :cascade do |t|
t.integer "project_id"
t.text "certificate"
@@ -1375,6 +1380,7 @@ ActiveRecord::Schema.define(version: 20170306170512) do
add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade
add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade
add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade
+ add_foreign_key "oauth_openid_requests", "oauth_access_grants", column: "access_grant_id", name: "fk_oauth_openid_requests_oauth_access_grants_access_grant_id"
add_foreign_key "personal_access_tokens", "users"
add_foreign_key "project_authorizations", "projects", on_delete: :cascade
add_foreign_key "project_authorizations", "users", on_delete: :cascade
diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md
index d6aa6101026..55a45119525 100644
--- a/doc/administration/repository_storage_paths.md
+++ b/doc/administration/repository_storage_paths.md
@@ -52,9 +52,12 @@ respectively.
# Paths where repositories can be stored. Give the canonicalized absolute pathname.
# NOTE: REPOS PATHS MUST NOT CONTAIN ANY SYMLINK!!!
storages: # You must have at least a 'default' storage path.
- default: /home/git/repositories
- nfs: /mnt/nfs/repositories
- cephfs: /mnt/cephfs/repositories
+ default:
+ path: /home/git/repositories
+ nfs:
+ path: /mnt/nfs/repositories
+ cephfs:
+ path: /mnt/cephfs/repositories
```
1. [Restart GitLab] for the changes to take effect.
@@ -75,9 +78,9 @@ working, you can remove the `repos_path` line.
```ruby
git_data_dirs({
- "default" => "/var/opt/gitlab/git-data",
- "nfs" => "/mnt/nfs/git-data",
- "cephfs" => "/mnt/cephfs/git-data"
+ "default" => { "path" => "/var/opt/gitlab/git-data" },
+ "nfs" => { "path" => "/mnt/nfs/git-data" },
+ "cephfs" => { "path" => "/mnt/cephfs/git-data" }
})
```
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 38a37cd920c..ad975e2e325 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -20,7 +20,7 @@ Example response:
```json
{
- "default_projects_limit" : 10,
+ "default_projects_limit" : 100000,
"signup_enabled" : true,
"id" : 1,
"default_branch_protection" : 2,
@@ -60,7 +60,7 @@ PUT /application/settings
| Attribute | Type | Required | Description |
| --------- | ---- | :------: | ----------- |
-| `default_projects_limit` | integer | no | Project limit per user. Default is `10` |
+| `default_projects_limit` | integer | no | Project limit per user. Default is `100000` |
| `signup_enabled` | boolean | no | Enable registration. Default is `true`. |
| `signin_enabled` | boolean | no | Enable login via a GitLab account. Default is `true`. |
| `gravatar_enabled` | boolean | no | Enable Gravatar |
@@ -98,7 +98,7 @@ Example response:
```json
{
"id": 1,
- "default_projects_limit": 10,
+ "default_projects_limit": 100000,
"signup_enabled": true,
"signin_enabled": true,
"gravatar_enabled": true,
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index a9e25187b88..4c3e7c4e86e 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -35,17 +35,28 @@ version of Runner required.
| **CI_SERVER_NAME** | all | all | The name of CI server that is used to coordinate jobs |
| **CI_SERVER_VERSION** | all | all | GitLab version that is used to schedule jobs |
| **CI_SERVER_REVISION** | all | all | GitLab revision that is used to schedule jobs |
-| **CI_BUILD_ID** | all | all | The unique id of the current job that GitLab CI uses internally |
-| **CI_BUILD_REF** | all | all | The commit revision for which project is built |
-| **CI_BUILD_TAG** | all | 0.5 | The commit tag name. Present only when building tags. |
-| **CI_BUILD_NAME** | all | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
-| **CI_BUILD_STAGE** | all | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
-| **CI_BUILD_REF_NAME** | all | all | The branch or tag name for which project is built |
-| **CI_BUILD_REF_SLUG** | 8.15 | all | `$CI_BUILD_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
-| **CI_BUILD_REPO** | all | all | The URL to clone the Git repository |
-| **CI_BUILD_TRIGGERED** | all | 0.5 | The flag to indicate that job was [triggered] |
-| **CI_BUILD_MANUAL** | 8.12 | all | The flag to indicate that job was manually started |
-| **CI_BUILD_TOKEN** | all | 1.2 | Token used for authenticating with the GitLab Container Registry |
+| **CI_BUILD_ID** | all | all | The unique id of the current job that GitLab CI uses internally. Deprecated, use CI_JOB_ID |
+| **CI_JOB_ID** | 9.0 | all | The unique id of the current job that GitLab CI uses internally |
+| **CI_BUILD_REF** | all | all | The commit revision for which project is built. Deprecated, use CI_COMMIT_REF |
+| **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built |
+| **CI_BUILD_TAG** | all | 0.5 | The commit tag name. Present only when building tags. Deprecated, use CI_COMMIT_TAG |
+| **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
+| **CI_BUILD_NAME** | all | 0.5 | The name of the job as defined in `.gitlab-ci.yml`. Deprecated, use CI_JOB_NAME |
+| **CI_JOB_NAME** | 9.0 | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
+| **CI_BUILD_STAGE** | all | 0.5 | The name of the stage as defined in `.gitlab-ci.yml`. Deprecated, use CI_JOB_STAGE |
+| **CI_JOB_STAGE** | 9.0 | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
+| **CI_BUILD_REF_NAME** | all | all | The branch or tag name for which project is built. Deprecated, use CI_COMMIT_REF_NAME |
+| **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built |
+| **CI_BUILD_REF_SLUG** | 8.15 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. Deprecated, use CI_COMMIT_REF_SLUG |
+| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
+| **CI_BUILD_REPO** | all | all | The URL to clone the Git repository. Deprecated, use CI_REPOSITORY |
+| **CI_REPOSITORY_URL** | 9.0 | all | The URL to clone the Git repository |
+| **CI_BUILD_TRIGGERED** | all | 0.5 | The flag to indicate that job was [triggered]. Deprecated, use CI_PIPELINE_TRIGGERED |
+| **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] |
+| **CI_BUILD_MANUAL** | 8.12 | all | The flag to indicate that job was manually started. Deprecated, use CI_JOB_MANUAL |
+| **CI_JOB_MANUAL** | 8.12 | all | The flag to indicate that job was manually started |
+| **CI_BUILD_TOKEN** | all | 1.2 | Token used for authenticating with the GitLab Container Registry. Deprecated, use CI_JOB_TOKEN |
+| **CI_JOB_TOKEN** | 9.0 | 1.2 | Token used for authenticating with the GitLab Container Registry |
| **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally |
| **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally |
| **CI_PROJECT_NAME** | 8.10 | 0.5 | The project name that is currently being built |
@@ -66,21 +77,22 @@ version of Runner required.
| **RESTORE_CACHE_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to restore the cache running a job |
| **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the job |
| **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the job |
-
+| **CI_REGISTRY_USER** | 9.0 | all | The username to use to push containers to the GitLab Container Registry |
+| **CI_REGISTRY_PASSWORD** | 9.0 | all | The password to use to push containers to the GitLab Container Registry |
Example values:
```bash
-export CI_BUILD_ID="50"
-export CI_BUILD_REF="1ecfd275763eff1d6b4844ea3168962458c9f27a"
-export CI_BUILD_REF_NAME="master"
-export CI_BUILD_REPO="https://gitab-ci-token:abcde-1234ABCD5678ef@example.com/gitlab-org/gitlab-ce.git"
-export CI_BUILD_TAG="1.0.0"
-export CI_BUILD_NAME="spec:other"
-export CI_BUILD_STAGE="test"
-export CI_BUILD_MANUAL="true"
-export CI_BUILD_TRIGGERED="true"
-export CI_BUILD_TOKEN="abcde-1234ABCD5678ef"
+export CI_JOB_ID="50"
+export CI_COMMIT_SHA="1ecfd275763eff1d6b4844ea3168962458c9f27a"
+export CI_COMMIT_REF_NAME="master"
+export CI_REPOSITORY="https://gitab-ci-token:abcde-1234ABCD5678ef@example.com/gitlab-org/gitlab-ce.git"
+export CI_COMMIT_TAG="1.0.0"
+export CI_JOB_NAME="spec:other"
+export CI_JOB_STAGE="test"
+export CI_JOB_MANUAL="true"
+export CI_JOB_TRIGGERED="true"
+export CI_JOB_TOKEN="abcde-1234ABCD5678ef"
export CI_PIPELINE_ID="1000"
export CI_PROJECT_ID="34"
export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce"
@@ -99,8 +111,30 @@ export CI_SERVER_REVISION="70606bf"
export CI_SERVER_VERSION="8.9.0"
export GITLAB_USER_ID="42"
export GITLAB_USER_EMAIL="user@example.com"
+export CI_REGISTRY_USER="gitlab-ci-token"
+export CI_REGISTRY_PASSWORD="longalfanumstring"
```
+## 9.0 Renaming
+
+To follow conventions of naming across GitLab, and to futher move away from the
+`build` term and toward `job` CI variables have been renamed for the 9.0
+release.
+
+| 8.X name | 9.0 name |
+|----------|----------|
+| CI_BUILD_ID | CI_JOB_ID |
+| CI_BUILD_REF | CI_COMMIT_SHA |
+| CI_BUILD_TAG | CI_COMMIT_TAG |
+| CI_BUILD_REF_NAME | CI_COMMIT_REF_NAME |
+| CI_BUILD_REF_SLUG | CI_COMMIT_REF_SLUG |
+| CI_BUILD_NAME | CI_JOB_NAME |
+| CI_BUILD_STAGE | CI_JOB_STAGE |
+| CI_BUILD_REPO | CI_REPOSITORY |
+| CI_BUILD_TRIGGERED | CI_PIPELINE_TRIGGERED |
+| CI_BUILD_MANUAL | CI_JOB_MANUAL |
+| CI_BUILD_TOKEN | CI_JOB_TOKEN |
+
## `.gitlab-ci.yaml` defined variables
>**Note:**
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index b25ccd4376e..49fa8761e5e 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -70,7 +70,7 @@ There are a few reserved `keywords` that **cannot** be used as job names:
| image | no | Use docker image, covered in [Use Docker](../docker/README.md) |
| services | no | Use docker services, covered in [Use Docker](../docker/README.md) |
| stages | no | Define build stages |
-| types | no | Alias for `stages` |
+| types | no | Alias for `stages` (deprecated) |
| before_script | no | Define commands that run before each job's script |
| after_script | no | Define commands that run after each job's script |
| variables | no | Define build variables |
@@ -130,6 +130,8 @@ There are also two edge cases worth mentioning:
### types
+> Deprecated, and will be removed in 10.0. Use [stages](#stages) instead.
+
Alias for [stages](#stages).
### variables
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 22bdf33443d..e56e58498a6 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -12,6 +12,7 @@ See the documentation below for details on how to configure these services.
- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
- [CAS](cas.md) Configure GitLab to sign in using CAS
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
+- [OpenID Connect](openid_connect_provider.md) Use GitLab as an identity provider
- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
- [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
- [Akismet](akismet.md) Configure Akismet to stop spam
diff --git a/doc/integration/openid_connect_provider.md b/doc/integration/openid_connect_provider.md
new file mode 100644
index 00000000000..56f367d841e
--- /dev/null
+++ b/doc/integration/openid_connect_provider.md
@@ -0,0 +1,47 @@
+# GitLab as OpenID Connect identity provider
+
+This document is about using GitLab as an OpenID Connect identity provider
+to sign in to other services.
+
+## Introduction to OpenID Connect
+
+[OpenID Connect] \(OIC) is a simple identity layer on top of the
+OAuth 2.0 protocol. It allows clients to verify the identity of the end-user
+based on the authentication performed by GitLab, as well as to obtain
+basic profile information about the end-user in an interoperable and
+REST-like manner. OIC performs many of the same tasks as OpenID 2.0,
+but does so in a way that is API-friendly, and usable by native and
+mobile applications.
+
+On the client side, you can use [omniauth-openid-connect] for Rails
+applications, or any of the other available [client implementations].
+
+GitLab's implementation uses the [doorkeeper-openid_connect] gem, refer
+to its README for more details about which parts of the specifications
+are supported.
+
+## Enabling OpenID Connect for OAuth applications
+
+Refer to the [OAuth guide] for basic information on how to set up OAuth
+applications in GitLab. To enable OIC for an application, all you have to do
+is select the `openid` scope in the application settings.
+
+Currently the following user information is shared with clients:
+
+| Claim | Type | Description |
+|:-----------------|:----------|:------------|
+| `sub` | `string` | An opaque token that uniquely identifies the user
+| `auth_time` | `integer` | The timestamp for the user's last authentication
+| `name` | `string` | The user's full name
+| `nickname` | `string` | The user's GitLab username
+| `email` | `string` | The user's public email address
+| `email_verified` | `boolean` | Whether the user's public email address was verified
+| `website` | `string` | URL for the user's website
+| `profile` | `string` | URL for the user's GitLab profile
+| `picture` | `string` | URL for the user's GitLab avatar
+
+[OpenID Connect]: http://openid.net/connect/ "OpenID Connect website"
+[doorkeeper-openid_connect]: https://github.com/doorkeeper-gem/doorkeeper-openid_connect "Doorkeeper::OpenidConnect website"
+[OAuth guide]: oauth_provider.md "GitLab as OAuth2 authentication service provider"
+[omniauth-openid-connect]: https://github.com/jjbohn/omniauth-openid-connect/ "OmniAuth::OpenIDConnect website"
+[client implementations]: http://openid.net/developers/libraries#connect "List of available client implementations"
diff --git a/doc/update/8.17-to-9.0.md b/doc/update/8.17-to-9.0.md
index 7b934ecd87a..4cc8be752c4 100644
--- a/doc/update/8.17-to-9.0.md
+++ b/doc/update/8.17-to-9.0.md
@@ -1,3 +1,66 @@
+#### Configuration changes for repository storages
+
+This version introduces a new configuration structure for repository storages.
+Update your current configuration as follows, replacing with your storages names and paths:
+
+**For installations from source**
+
+1. Update your `gitlab.yml`, from
+
+ ```yaml
+ repositories:
+ storages: # You must have at least a 'default' storage path.
+ default: /home/git/repositories
+ nfs: /mnt/nfs/repositories
+ cephfs: /mnt/cephfs/repositories
+ ```
+
+ to
+
+ ```yaml
+ repositories:
+ storages: # You must have at least a 'default' storage path.
+ default:
+ path: /home/git/repositories
+ nfs:
+ path: /mnt/nfs/repositories
+ cephfs:
+ path: /mnt/cephfs/repositories
+ ```
+
+**For Omnibus installations**
+
+1. Upate your `/etc/gitlab/gitlab.rb`, from
+
+ ```ruby
+ git_data_dirs({
+ "default" => "/var/opt/gitlab/git-data",
+ "nfs" => "/mnt/nfs/git-data",
+ "cephfs" => "/mnt/cephfs/git-data"
+ })
+ ```
+
+ to
+
+ ```ruby
+ git_data_dirs({
+ "default" => { "path" => "/var/opt/gitlab/git-data" },
+ "nfs" => { "path" => "/mnt/nfs/git-data" },
+ "cephfs" => { "path" => "/mnt/cephfs/git-data" }
+ })
+ ```
+
+#### Git configuration
+
+Configure Git to generate packfile bitmaps (introduced in Git 2.0) on
+the GitLab server during `git gc`.
+
+```sh
+cd /home/git/gitlab
+
+sudo -u git -H git config --global repack.writeBitmaps true
+```
+
#### Nginx configuration
Ensure you're still up-to-date with the latest NGINX configuration changes:
@@ -12,7 +75,7 @@ git diff origin/8-17-stable:lib/support/nginx/gitlab-ssl origin/9-0-stable:lib/s
git diff origin/8-17-stable:lib/support/nginx/gitlab origin/9-0-stable:lib/support/nginx/gitlab
```
-If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx
+If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx
configuration as GitLab application no longer handles setting it.
If you are using Apache instead of NGINX please see the updated [Apache templates].
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index 8570c637b36..ba04b03c3cc 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -56,10 +56,10 @@ Feature: Project Active Tab
And no other sub navs should be active
And the active main tab should be Settings
- Scenario: On Project Settings/Deploy Keys
+ Scenario: On Project Settings/Repository
Given I visit my project's settings page
- And I click the "Deploy Keys" tab
- Then the active sub nav should be Deploy Keys
+ And I click the "Repository" tab
+ Then the active sub nav should be Repository
And no other sub navs should be active
And the active main tab should be Settings
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
index d29b22d42ec..f901f4889dd 100644
--- a/features/steps/project/active_tab.rb
+++ b/features/steps/project/active_tab.rb
@@ -31,8 +31,10 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
click_link('Integrations')
end
- step 'I click the "Deploy Keys" tab' do
- click_link('Deploy Keys')
+ step 'I click the "Repository" tab' do
+ page.within '.layout-nav .controls' do
+ click_link('Repository')
+ end
end
step 'I click the "Pages" tab' do
@@ -53,8 +55,8 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
ensure_active_sub_nav('Integrations')
end
- step 'the active sub nav should be Deploy Keys' do
- ensure_active_sub_nav('Deploy Keys')
+ step 'the active sub nav should be Repository' do
+ ensure_active_sub_nav('Repository')
end
step 'the active sub nav should be Pages' do
diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb
index edf78f62f9a..580a19494c2 100644
--- a/features/steps/project/deploy_keys.rb
+++ b/features/steps/project/deploy_keys.rb
@@ -36,7 +36,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should be on deploy keys page' do
- expect(current_path).to eq namespace_project_deploy_keys_path(@project.namespace, @project)
+ expect(current_path).to eq namespace_project_settings_repository_path(@project.namespace, @project)
end
step 'I should see newly created deploy key' do
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 080a6274957..2135a787b11 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -9,11 +9,11 @@ module API
# In addition, they may have a '.git' extension and multiple namespaces
#
# Transform all these cases to 'namespace/project'
- def clean_project_path(project_path, storage_paths = Repository.storages.values)
+ def clean_project_path(project_path, storages = Gitlab.config.repositories.storages.values)
project_path = project_path.sub(/\.git\z/, '')
- storage_paths.each do |storage_path|
- storage_path = File.expand_path(storage_path)
+ storages.each do |storage|
+ storage_path = File.expand_path(storage['path'])
if project_path.start_with?(storage_path)
project_path = project_path.sub(storage_path, '')
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 1cf29d9a1a3..5aa2f5eba7b 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -422,6 +422,14 @@ module API
desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
}
],
+ 'prometheus' => [
+ {
+ required: true,
+ name: :api_url,
+ type: String,
+ desc: 'Prometheus API Base URL, like http://prometheus.example.com/'
+ }
+ ],
'pushover' => [
{
required: true,
@@ -558,6 +566,7 @@ module API
SlackSlashCommandsService,
PipelinesEmailService,
PivotaltrackerService,
+ PrometheusService,
PushoverService,
RedmineService,
SlackService,
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 3c4ba5d50e6..cd745d35e7c 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -68,7 +68,8 @@ module Backup
end
def restore
- Gitlab.config.repositories.storages.each do |name, path|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ path = repository_storage['path']
next unless File.exist?(path)
# Move repos dir to 'repositories.old' dir
@@ -199,7 +200,7 @@ module Backup
private
def repository_storage_paths_args
- Gitlab.config.repositories.storages.values
+ Gitlab.config.repositories.storages.values.map { |rs| rs['path'] }
end
end
end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 6d69efb0bf6..eee5601b0ed 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -2,9 +2,17 @@ module Gitlab
module Auth
MissingPersonalTokenError = Class.new(StandardError)
- SCOPES = [:api, :read_user].freeze
+ # Scopes used for GitLab API access
+ API_SCOPES = [:api, :read_user].freeze
+
+ # Scopes used for OpenID Connect
+ OPENID_SCOPES = [:openid].freeze
+
+ # Default scopes for OAuth applications that don't define their own
DEFAULT_SCOPES = [:api].freeze
- OPTIONAL_SCOPES = SCOPES - DEFAULT_SCOPES
+
+ # Other available scopes
+ OPTIONAL_SCOPES = (API_SCOPES + OPENID_SCOPES - DEFAULT_SCOPES).freeze
class << self
def find_for_git_client(login, password, project:, ip:)
@@ -40,7 +48,7 @@ module Gitlab
Gitlab::LDAP::Authentication.login(login, password)
else
- user if user.valid_password?(password)
+ user if user.active? && user.valid_password?(password)
end
end
end
diff --git a/lib/gitlab/prometheus.rb b/lib/gitlab/prometheus.rb
new file mode 100644
index 00000000000..62239779454
--- /dev/null
+++ b/lib/gitlab/prometheus.rb
@@ -0,0 +1,70 @@
+module Gitlab
+ PrometheusError = Class.new(StandardError)
+
+ # Helper methods to interact with Prometheus network services & resources
+ class Prometheus
+ attr_reader :api_url
+
+ def initialize(api_url:)
+ @api_url = api_url
+ end
+
+ def ping
+ json_api_get('query', query: '1')
+ end
+
+ def query(query)
+ get_result('vector') do
+ json_api_get('query', query: query)
+ end
+ end
+
+ def query_range(query, start: 8.hours.ago)
+ get_result('matrix') do
+ json_api_get('query_range',
+ query: query,
+ start: start.to_f,
+ end: Time.now.utc.to_f,
+ step: 1.minute.to_i)
+ end
+ end
+
+ private
+
+ def json_api_get(type, args = {})
+ get(join_api_url(type, args))
+ rescue Errno::ECONNREFUSED
+ raise PrometheusError, 'Connection refused'
+ end
+
+ def join_api_url(type, args = {})
+ url = URI.parse(api_url)
+ rescue URI::Error
+ raise PrometheusError, "Invalid API URL: #{api_url}"
+ else
+ url.path = [url.path.sub(%r{/+\z}, ''), 'api', 'v1', type].join('/')
+ url.query = args.to_query
+
+ url.to_s
+ end
+
+ def get(url)
+ handle_response(HTTParty.get(url))
+ end
+
+ def handle_response(response)
+ if response.code == 200 && response['status'] == 'success'
+ response['data'] || {}
+ elsif response.code == 400
+ raise PrometheusError, response['error'] || 'Bad data received'
+ else
+ raise PrometheusError, "#{response.code} - #{response.body}"
+ end
+ end
+
+ def get_result(expected_type)
+ data = yield
+ data['result'] if data['resultType'] == expected_type
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 38edd49b6ed..a6f8c4ced5d 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -354,7 +354,8 @@ namespace :gitlab do
def check_repo_base_exists
puts "Repo base directory exists?"
- Gitlab.config.repositories.storages.each do |name, repo_base_path|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ repo_base_path = repository_storage['path']
print "#{name}... "
if File.exist?(repo_base_path)
@@ -378,7 +379,8 @@ namespace :gitlab do
def check_repo_base_is_not_symlink
puts "Repo storage directories are symlinks?"
- Gitlab.config.repositories.storages.each do |name, repo_base_path|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ repo_base_path = repository_storage['path']
print "#{name}... "
unless File.exist?(repo_base_path)
@@ -401,7 +403,8 @@ namespace :gitlab do
def check_repo_base_permissions
puts "Repo paths access is drwxrws---?"
- Gitlab.config.repositories.storages.each do |name, repo_base_path|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ repo_base_path = repository_storage['path']
print "#{name}... "
unless File.exist?(repo_base_path)
@@ -431,7 +434,8 @@ namespace :gitlab do
gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group
puts "Repo paths owned by #{gitlab_shell_ssh_user}:#{gitlab_shell_owner_group}?"
- Gitlab.config.repositories.storages.each do |name, repo_base_path|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ repo_base_path = repository_storage['path']
print "#{name}... "
unless File.exist?(repo_base_path)
@@ -810,8 +814,8 @@ namespace :gitlab do
namespace :repo do
desc "GitLab | Check the integrity of the repositories managed by GitLab"
task check: :environment do
- Gitlab.config.repositories.storages.each do |name, path|
- namespace_dirs = Dir.glob(File.join(path, '*'))
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ namespace_dirs = Dir.glob(File.join(repository_storage['path'], '*'))
namespace_dirs.each do |namespace_dir|
repo_dirs = Dir.glob(File.join(namespace_dir, '*'))
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index daf7382dd02..f76bef5f4bf 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -6,7 +6,8 @@ namespace :gitlab do
remove_flag = ENV['REMOVE']
namespaces = Namespace.pluck(:path)
- Gitlab.config.repositories.storages.each do |name, git_base_path|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ git_base_path = repository_storage['path']
all_dirs = Dir.glob(git_base_path + '/*')
puts git_base_path.color(:yellow)
@@ -47,7 +48,8 @@ namespace :gitlab do
warn_user_is_not_gitlab
move_suffix = "+orphaned+#{Time.now.to_i}"
- Gitlab.config.repositories.storages.each do |name, repo_root|
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ repo_root = repository_storage['path']
# Look for global repos (legacy, depth 1) and normal repos (depth 2)
IO.popen(%W(find #{repo_root} -mindepth 1 -maxdepth 2 -name *.git)) do |find|
find.each_line do |path|
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index 66e7b7685f7..48bd9139ce8 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -11,7 +11,8 @@ namespace :gitlab do
#
desc "GitLab | Import bare repositories from repositories -> storages into GitLab project instance"
task repos: :environment do
- Gitlab.config.repositories.storages.each do |name, git_base_path|
+ Gitlab.config.repositories.storages.each_value do |repository_storage|
+ git_base_path = repository_storage['path']
repos_to_import = Dir.glob(git_base_path + '/**/*.git')
repos_to_import.each do |repo_path|
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index b8dd654b9a9..a2a2db487b7 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -65,8 +65,8 @@ namespace :gitlab do
puts "GitLab Shell".color(:yellow)
puts "Version:\t#{gitlab_shell_version || "unknown".color(:red)}"
puts "Repository storage paths:"
- Gitlab.config.repositories.storages.each do |name, path|
- puts "- #{name}: \t#{path}"
+ Gitlab.config.repositories.storages.each do |name, repository_storage|
+ puts "- #{name}: \t#{repository_storage['path']}"
end
puts "Hooks:\t\t#{Gitlab.config.gitlab_shell.hooks_path}"
puts "Git:\t\t#{Gitlab.config.git.bin_path}"
diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb
index 2a999ad6959..bb755ae689b 100644
--- a/lib/tasks/gitlab/task_helpers.rb
+++ b/lib/tasks/gitlab/task_helpers.rb
@@ -130,8 +130,8 @@ module Gitlab
end
def all_repos
- Gitlab.config.repositories.storages.each do |name, path|
- IO.popen(%W(find #{path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find|
+ Gitlab.config.repositories.storages.each_value do |repository_storage|
+ IO.popen(%W(find #{repository_storage['path']} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find|
find.each_line do |path|
yield path.chomp
end
@@ -140,7 +140,7 @@ module Gitlab
end
def repository_storage_paths_args
- Gitlab.config.repositories.storages.values
+ Gitlab.config.repositories.storages.values.map { |rs| rs['path'] }
end
def user_home
diff --git a/spec/controllers/admin/applications_controller_spec.rb b/spec/controllers/admin/applications_controller_spec.rb
new file mode 100644
index 00000000000..e311b8a63b2
--- /dev/null
+++ b/spec/controllers/admin/applications_controller_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+describe Admin::ApplicationsController do
+ let(:admin) { create(:admin) }
+ let(:application) { create(:oauth_application, owner_id: nil, owner_type: nil) }
+
+ before do
+ sign_in(admin)
+ end
+
+ describe 'GET #new' do
+ it 'renders the application form' do
+ get :new
+
+ expect(response).to render_template :new
+ expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes)
+ end
+ end
+
+ describe 'GET #edit' do
+ it 'renders the application form' do
+ get :edit, id: application.id
+
+ expect(response).to render_template :edit
+ expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes)
+ end
+ end
+
+ describe 'POST #create' do
+ it 'creates the application' do
+ expect do
+ post :create, doorkeeper_application: attributes_for(:application)
+ end.to change { Doorkeeper::Application.count }.by(1)
+
+ application = Doorkeeper::Application.last
+
+ expect(response).to redirect_to(admin_application_path(application))
+ end
+
+ it 'renders the application form on errors' do
+ expect do
+ post :create, doorkeeper_application: attributes_for(:application).merge(redirect_uri: nil)
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to render_template :new
+ expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes)
+ end
+ end
+
+ describe 'PATCH #update' do
+ it 'updates the application' do
+ patch :update, id: application.id, doorkeeper_application: { redirect_uri: 'http://example.com/' }
+
+ expect(response).to redirect_to(admin_application_path(application))
+ expect(application.reload.redirect_uri).to eq 'http://example.com/'
+ end
+
+ it 'renders the application form on errors' do
+ patch :update, id: application.id, doorkeeper_application: { redirect_uri: nil }
+
+ expect(response).to render_template :edit
+ expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes)
+ end
+ end
+end
diff --git a/spec/controllers/profiles/personal_access_tokens_spec.rb b/spec/controllers/profiles/personal_access_tokens_spec.rb
index ae8031a45f6..dfed1de2046 100644
--- a/spec/controllers/profiles/personal_access_tokens_spec.rb
+++ b/spec/controllers/profiles/personal_access_tokens_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Profiles::PersonalAccessTokensController do
let(:user) { create(:user) }
+ let(:token_attributes) { attributes_for(:personal_access_token) }
before { sign_in(user) }
@@ -10,41 +11,26 @@ describe Profiles::PersonalAccessTokensController do
PersonalAccessToken.order(:created_at).last
end
- it "allows creation of a token" do
+ it "allows creation of a token with scopes" do
name = FFaker::Product.brand
+ scopes = %w[api read_user]
- post :create, personal_access_token: { name: name }
+ post :create, personal_access_token: token_attributes.merge(scopes: scopes, name: name)
expect(created_token).not_to be_nil
expect(created_token.name).to eq(name)
- expect(created_token.expires_at).to be_nil
+ expect(created_token.scopes).to eq(scopes)
expect(PersonalAccessToken.active).to include(created_token)
end
it "allows creation of a token with an expiry date" do
expires_at = 5.days.from_now.to_date
- post :create, personal_access_token: { name: FFaker::Product.brand, expires_at: expires_at }
+ post :create, personal_access_token: token_attributes.merge(expires_at: expires_at)
expect(created_token).not_to be_nil
expect(created_token.expires_at).to eq(expires_at)
end
-
- context "scopes" do
- it "allows creation of a token with scopes" do
- post :create, personal_access_token: { name: FFaker::Product.brand, scopes: %w(api read_user) }
-
- expect(created_token).not_to be_nil
- expect(created_token.scopes).to eq(%w(api read_user))
- end
-
- it "allows creation of a token with no scopes" do
- post :create, personal_access_token: { name: FFaker::Product.brand, scopes: [] }
-
- expect(created_token).not_to be_nil
- expect(created_token.scopes).to eq([])
- end
- end
end
describe '#index' do
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index 84d119f1867..83d80b376fb 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -187,6 +187,52 @@ describe Projects::EnvironmentsController do
end
end
+ describe 'GET #metrics' do
+ before do
+ allow(controller).to receive(:environment).and_return(environment)
+ end
+
+ context 'when environment has no metrics' do
+ before do
+ expect(environment).to receive(:metrics).and_return(nil)
+ end
+
+ it 'returns a metrics page' do
+ get :metrics, environment_params
+
+ expect(response).to be_ok
+ end
+
+ context 'when requesting metrics as JSON' do
+ it 'returns a metrics JSON document' do
+ get :metrics, environment_params(format: :json)
+
+ expect(response).to have_http_status(204)
+ expect(json_response).to eq({})
+ end
+ end
+ end
+
+ context 'when environment has some metrics' do
+ before do
+ expect(environment).to receive(:metrics).and_return({
+ success: true,
+ metrics: {},
+ last_update: 42
+ })
+ end
+
+ it 'returns a metrics JSON document' do
+ get :metrics, environment_params(format: :json)
+
+ expect(response).to be_ok
+ expect(json_response['success']).to be(true)
+ expect(json_response['metrics']).to eq({})
+ expect(json_response['last_update']).to eq(42)
+ end
+ end
+ end
+
def environment_params(opts = {})
opts.reverse_merge(namespace_id: project.namespace,
project_id: project,
diff --git a/spec/controllers/projects/settings/repository_controller_spec.rb b/spec/controllers/projects/settings/repository_controller_spec.rb
new file mode 100644
index 00000000000..f73471f8ca8
--- /dev/null
+++ b/spec/controllers/projects/settings/repository_controller_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Projects::Settings::RepositoryController do
+ let(:project) { create(:project_empty_repo, :public) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+ end
+
+ describe 'GET show' do
+ it 'renders show with 200 status code' do
+ get :show, namespace_id: project.namespace, project_id: project
+
+ expect(response).to have_http_status(200)
+ expect(response).to render_template(:show)
+ end
+ end
+end
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index c9584ddf18c..f67d26da0ac 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -1,4 +1,9 @@
require 'spec_helper'
+shared_examples 'content not cached without revalidation' do
+ it 'ensures content will not be cached without revalidation' do
+ expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate')
+ end
+end
describe UploadsController do
let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
@@ -50,6 +55,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'image.png'
+ response
+ end
+ end
end
end
@@ -59,6 +71,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'image.png'
+ response
+ end
+ end
end
end
@@ -76,6 +95,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png'
+ response
+ end
+ end
end
context "when signed in" do
@@ -88,6 +114,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png'
+ response
+ end
+ end
end
end
@@ -133,6 +166,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'image.png'
+ response
+ end
+ end
end
end
@@ -157,6 +197,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png'
+ response
+ end
+ end
end
context "when signed in" do
@@ -169,6 +216,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png'
+ response
+ end
+ end
end
end
@@ -205,6 +259,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'image.png'
+ response
+ end
+ end
end
end
@@ -234,6 +295,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+ response
+ end
+ end
end
context "when signed in" do
@@ -246,6 +314,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+ response
+ end
+ end
end
end
@@ -291,6 +366,13 @@ describe UploadsController do
expect(response).to have_http_status(200)
end
+
+ it_behaves_like 'content not cached without revalidation' do
+ subject do
+ get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'image.png'
+ response
+ end
+ end
end
end
diff --git a/spec/factories/chat_teams.rb b/spec/factories/chat_teams.rb
new file mode 100644
index 00000000000..82f44fa3d15
--- /dev/null
+++ b/spec/factories/chat_teams.rb
@@ -0,0 +1,9 @@
+FactoryGirl.define do
+ factory :chat_team, class: ChatTeam do
+ sequence :team_id do |n|
+ "abcdefghijklm#{n}"
+ end
+
+ namespace factory: :group
+ end
+end
diff --git a/spec/factories/oauth_access_grants.rb b/spec/factories/oauth_access_grants.rb
new file mode 100644
index 00000000000..543b3e99274
--- /dev/null
+++ b/spec/factories/oauth_access_grants.rb
@@ -0,0 +1,11 @@
+FactoryGirl.define do
+ factory :oauth_access_grant do
+ resource_owner_id { create(:user).id }
+ application
+ token { Doorkeeper::OAuth::Helpers::UniqueToken.generate }
+ expires_in 2.hours
+
+ redirect_uri { application.redirect_uri }
+ scopes { application.scopes }
+ end
+end
diff --git a/spec/factories/oauth_access_tokens.rb b/spec/factories/oauth_access_tokens.rb
index ccf02d0719b..a46bc1d8ce8 100644
--- a/spec/factories/oauth_access_tokens.rb
+++ b/spec/factories/oauth_access_tokens.rb
@@ -2,6 +2,7 @@ FactoryGirl.define do
factory :oauth_access_token do
resource_owner
application
- token '123456'
+ token { Doorkeeper::OAuth::Helpers::UniqueToken.generate }
+ scopes { application.scopes }
end
end
diff --git a/spec/factories/oauth_applications.rb b/spec/factories/oauth_applications.rb
index d116a573830..86cdc208268 100644
--- a/spec/factories/oauth_applications.rb
+++ b/spec/factories/oauth_applications.rb
@@ -1,7 +1,7 @@
FactoryGirl.define do
factory :oauth_application, class: 'Doorkeeper::Application', aliases: [:application] do
name { FFaker::Name.name }
- uid { FFaker::Name.name }
+ uid { Doorkeeper::OAuth::Helpers::UniqueToken.generate }
redirect_uri { FFaker::Internet.uri('http') }
owner
owner_type 'User'
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 70c65bc693a..c6f91e05d83 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -195,4 +195,15 @@ FactoryGirl.define do
factory :kubernetes_project, parent: :empty_project do
kubernetes_service
end
+
+ factory :prometheus_project, parent: :empty_project do
+ after :create do |project|
+ project.create_prometheus_service(
+ active: true,
+ properties: {
+ api_url: 'https://prometheus.example.com'
+ }
+ )
+ end
+ end
end
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 93763f092fb..ede6aa0c201 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -1,25 +1,16 @@
require 'rails_helper'
-describe 'Dropdown assignee', js: true, feature: true do
- include WaitForAjax
-
+describe 'Dropdown assignee', :feature, :js do
let!(:project) { create(:empty_project) }
let!(:user) { create(:user, name: 'administrator', username: 'root') }
let!(:user_john) { create(:user, name: 'John', username: 'th0mas') }
let!(:user_jacob) { create(:user, name: 'Jacob', username: 'otter32') }
let(:filtered_search) { find('.filtered-search') }
let(:js_dropdown_assignee) { '#js-dropdown-assignee' }
-
- def send_keys_to_filtered_search(input)
- input.split("").each do |i|
- filtered_search.send_keys(i)
- sleep 5
- wait_for_ajax
- end
- end
+ let(:filter_dropdown) { find("#{js_dropdown_assignee} .filter-dropdown") }
def dropdown_assignee_size
- page.all('#js-dropdown-assignee .filter-dropdown .filter-dropdown-item').size
+ filter_dropdown.all('.filter-dropdown-item').size
end
def click_assignee(text)
@@ -56,63 +47,80 @@ describe 'Dropdown assignee', js: true, feature: true do
end
it 'should hide loading indicator when loaded' do
- send_keys_to_filtered_search('assignee:')
+ filtered_search.set('assignee:')
- expect(page).not_to have_css('#js-dropdown-assignee .filter-dropdown-loading')
+ expect(find(js_dropdown_assignee)).to have_css('.filter-dropdown-loading')
+ expect(find(js_dropdown_assignee)).not_to have_css('.filter-dropdown-loading')
end
it 'should load all the assignees when opened' do
- send_keys_to_filtered_search('assignee:')
+ filtered_search.set('assignee:')
expect(dropdown_assignee_size).to eq(3)
end
it 'shows current user at top of dropdown' do
- send_keys_to_filtered_search('assignee:')
+ filtered_search.set('assignee:')
- expect(first('#js-dropdown-assignee .filter-dropdown li')).to have_content(user.name)
+ expect(filter_dropdown.first('.filter-dropdown-item')).to have_content(user.name)
end
end
describe 'filtering' do
before do
- send_keys_to_filtered_search('assignee:')
+ filtered_search.set('assignee:')
+
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_john.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
end
it 'filters by name' do
- send_keys_to_filtered_search('j')
+ filtered_search.send_keys('j')
- expect(dropdown_assignee_size).to eq(2)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_john.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user.name)
end
it 'filters by case insensitive name' do
- send_keys_to_filtered_search('J')
+ filtered_search.send_keys('J')
- expect(dropdown_assignee_size).to eq(2)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_john.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user.name)
end
it 'filters by username with symbol' do
- send_keys_to_filtered_search('@ot')
+ filtered_search.send_keys('@ot')
- expect(dropdown_assignee_size).to eq(2)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user_john.name)
end
it 'filters by case insensitive username with symbol' do
- send_keys_to_filtered_search('@OT')
+ filtered_search.send_keys('@OT')
- expect(dropdown_assignee_size).to eq(2)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user_john.name)
end
it 'filters by username without symbol' do
- send_keys_to_filtered_search('ot')
+ filtered_search.send_keys('ot')
- expect(dropdown_assignee_size).to eq(2)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user_john.name)
end
it 'filters by case insensitive username without symbol' do
- send_keys_to_filtered_search('OT')
+ filtered_search.send_keys('OT')
- expect(dropdown_assignee_size).to eq(2)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user_jacob.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_content(user.name)
+ expect(find("#{js_dropdown_assignee} .filter-dropdown")).to have_no_content(user_john.name)
end
end
@@ -129,7 +137,7 @@ describe 'Dropdown assignee', js: true, feature: true do
end
it 'fills in the assignee username when the assignee has been filtered' do
- send_keys_to_filtered_search('roo')
+ filtered_search.send_keys('roo')
click_assignee(user.name)
expect(page).to have_css(js_dropdown_assignee, visible: false)
@@ -173,7 +181,7 @@ describe 'Dropdown assignee', js: true, feature: true do
describe 'caching requests' do
it 'caches requests after the first load' do
filtered_search.set('assignee')
- send_keys_to_filtered_search(':')
+ filtered_search.send_keys(':')
initial_size = dropdown_assignee_size
expect(initial_size).to be > 0
@@ -182,7 +190,7 @@ describe 'Dropdown assignee', js: true, feature: true do
project.team << [new_user, :master]
find('.filtered-search-input-container .clear-search').click
filtered_search.set('assignee')
- send_keys_to_filtered_search(':')
+ filtered_search.send_keys(':')
expect(dropdown_assignee_size).to eq(initial_size)
end
diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb
new file mode 100644
index 00000000000..ee925e811e1
--- /dev/null
+++ b/spec/features/projects/environments/environment_metrics_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+feature 'Environment > Metrics', :feature do
+ include PrometheusHelpers
+
+ given(:user) { create(:user) }
+ given(:project) { create(:prometheus_project) }
+ given(:pipeline) { create(:ci_pipeline, project: project) }
+ given(:build) { create(:ci_build, pipeline: pipeline) }
+ given(:environment) { create(:environment, project: project) }
+ given(:current_time) { Time.now.utc }
+
+ background do
+ project.add_developer(user)
+ create(:deployment, environment: environment, deployable: build)
+ stub_all_prometheus_requests(environment.slug)
+
+ login_as(user)
+ visit_environment(environment)
+ end
+
+ around do |example|
+ Timecop.freeze(current_time) { example.run }
+ end
+
+ context 'with deployments and related deployable present' do
+ scenario 'shows metrics' do
+ click_link('See metrics')
+
+ expect(page).to have_css('svg.prometheus-graph')
+ end
+ end
+
+ def visit_environment(environment)
+ visit namespace_project_environment_path(environment.project.namespace,
+ environment.project,
+ environment)
+ end
+end
diff --git a/spec/features/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 65373e3f77d..e2d16e0830a 100644
--- a/spec/features/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -37,13 +37,7 @@ feature 'Environment', :feature do
scenario 'does show deployment SHA' do
expect(page).to have_link(deployment.short_sha)
- end
-
- scenario 'does not show a re-deploy button for deployment without build' do
expect(page).not_to have_link('Re-deploy')
- end
-
- scenario 'does not show terminal button' do
expect(page).not_to have_terminal_button
end
end
@@ -58,13 +52,7 @@ feature 'Environment', :feature do
scenario 'does show build name' do
expect(page).to have_link("#{build.name} (##{build.id})")
- end
-
- scenario 'does show re-deploy button' do
expect(page).to have_link('Re-deploy')
- end
-
- scenario 'does not show terminal button' do
expect(page).not_to have_terminal_button
end
@@ -117,9 +105,6 @@ feature 'Environment', :feature do
it 'displays a web terminal' do
expect(page).to have_selector('#terminal')
- end
-
- it 'displays a link to the environment external url' do
expect(page).to have_link(nil, href: environment.external_url)
end
end
@@ -147,10 +132,6 @@ feature 'Environment', :feature do
on_stop: 'close_app')
end
- scenario 'does show stop button' do
- expect(page).to have_link('Stop')
- end
-
scenario 'does allow to stop environment' do
click_link('Stop')
diff --git a/spec/features/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 25f31b423b8..25f31b423b8 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index 7414ce21f59..de3c6eceb82 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -32,7 +32,7 @@ feature 'Issue prioritization', feature: true do
visit namespace_project_issues_path(project.namespace, project, sort: 'priority')
# Ensure we are indicating that issues are sorted by priority
- expect(page).to have_selector('.dropdown-toggle', text: 'Priority')
+ expect(page).to have_selector('.dropdown-toggle', text: 'Label priority')
page.within('.issues-holder') do
issue_titles = all('.issues-list .issue-title-text').map(&:text)
@@ -70,7 +70,7 @@ feature 'Issue prioritization', feature: true do
login_as user
visit namespace_project_issues_path(project.namespace, project, sort: 'priority')
- expect(page).to have_selector('.dropdown-toggle', text: 'Priority')
+ expect(page).to have_selector('.dropdown-toggle', text: 'Label priority')
page.within('.issues-holder') do
issue_titles = all('.issues-list .issue-title-text').map(&:text)
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 24af062d763..1a66d1a6a1e 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -110,6 +110,20 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_denied_for(:external) }
end
+ describe "GET /:project_path/settings/repository" do
+ subject { namespace_project_settings_repository_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for(:admin) }
+ it { is_expected.to be_allowed_for(:owner).of(project) }
+ it { is_expected.to be_allowed_for(:master).of(project) }
+ it { is_expected.to be_denied_for(:developer).of(project) }
+ it { is_expected.to be_denied_for(:reporter).of(project) }
+ it { is_expected.to be_denied_for(:guest).of(project) }
+ it { is_expected.to be_denied_for(:user) }
+ it { is_expected.to be_denied_for(:visitor) }
+ it { is_expected.to be_denied_for(:external) }
+ end
+
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index c511dcfa18e..ad3bd60a313 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -110,6 +110,20 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_denied_for(:external) }
end
+ describe "GET /:project_path/settings/repository" do
+ subject { namespace_project_settings_repository_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for(:admin) }
+ it { is_expected.to be_allowed_for(:owner).of(project) }
+ it { is_expected.to be_allowed_for(:master).of(project) }
+ it { is_expected.to be_denied_for(:developer).of(project) }
+ it { is_expected.to be_denied_for(:reporter).of(project) }
+ it { is_expected.to be_denied_for(:guest).of(project) }
+ it { is_expected.to be_denied_for(:user) }
+ it { is_expected.to be_denied_for(:external) }
+ it { is_expected.to be_denied_for(:visitor) }
+ end
+
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore'))}
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index d8cc012c27e..e06aab4e0b2 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -110,6 +110,20 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_denied_for(:external) }
end
+ describe "GET /:project_path/settings/repository" do
+ subject { namespace_project_settings_repository_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for(:admin) }
+ it { is_expected.to be_allowed_for(:owner).of(project) }
+ it { is_expected.to be_allowed_for(:master).of(project) }
+ it { is_expected.to be_denied_for(:developer).of(project) }
+ it { is_expected.to be_denied_for(:reporter).of(project) }
+ it { is_expected.to be_denied_for(:guest).of(project) }
+ it { is_expected.to be_denied_for(:user) }
+ it { is_expected.to be_denied_for(:visitor) }
+ it { is_expected.to be_denied_for(:external) }
+ end
+
describe "GET /:project_path/pipelines" do
subject { namespace_project_pipelines_path(project.namespace, project) }
diff --git a/spec/features/todos/todos_sorting_spec.rb b/spec/features/todos/todos_sorting_spec.rb
index fec28c55d30..4d5bd476301 100644
--- a/spec/features/todos/todos_sorting_spec.rb
+++ b/spec/features/todos/todos_sorting_spec.rb
@@ -56,8 +56,8 @@ describe "Dashboard > User sorts todos", feature: true do
expect(results_list.all('p')[4]).to have_content("merge_request_1")
end
- it "sorts by priority" do
- click_link "Priority"
+ it "sorts by label priority" do
+ click_link "Label priority"
results_list = page.find('.todos-list')
expect(results_list.all('p')[0]).to have_content("issue_3")
@@ -85,8 +85,8 @@ describe "Dashboard > User sorts todos", feature: true do
visit dashboard_todos_path
end
- it "doesn't mix issues and merge requests priorities" do
- click_link "Priority"
+ it "doesn't mix issues and merge requests label priorities" do
+ click_link "Label priority"
results_list = page.find('.todos-list')
expect(results_list.all('p')[0]).to have_content("issue_1")
diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb
index baab30f482f..cf182e6d221 100644
--- a/spec/initializers/6_validations_spec.rb
+++ b/spec/initializers/6_validations_spec.rb
@@ -14,7 +14,7 @@ describe '6_validations', lib: true do
context 'with correct settings' do
before do
- mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/d')
+ mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c' }, 'bar' => { 'path' => 'tmp/tests/paths/a/b/d' })
end
it 'passes through' do
@@ -24,7 +24,7 @@ describe '6_validations', lib: true do
context 'with invalid storage names' do
before do
- mock_storages('name with spaces' => 'tmp/tests/paths/a/b/c')
+ mock_storages('name with spaces' => { 'path' => 'tmp/tests/paths/a/b/c' })
end
it 'throws an error' do
@@ -34,7 +34,7 @@ describe '6_validations', lib: true do
context 'with nested storage paths' do
before do
- mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/c/d')
+ mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c' }, 'bar' => { 'path' => 'tmp/tests/paths/a/b/c/d' })
end
it 'throws an error' do
@@ -44,7 +44,7 @@ describe '6_validations', lib: true do
context 'with similar but un-nested storage paths' do
before do
- mock_storages('foo' => 'tmp/tests/paths/a/b/c', 'bar' => 'tmp/tests/paths/a/b/c2')
+ mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c' }, 'bar' => { 'path' => 'tmp/tests/paths/a/b/c2' })
end
it 'passes through' do
@@ -52,6 +52,26 @@ describe '6_validations', lib: true do
end
end
+ context 'with incomplete settings' do
+ before do
+ mock_storages('foo' => {})
+ end
+
+ it 'throws an error suggesting the user to update its settings' do
+ expect { validate_storages }.to raise_error('foo is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example. Please fix this in your gitlab.yml before starting GitLab.')
+ end
+ end
+
+ context 'with deprecated settings structure' do
+ before do
+ mock_storages('foo' => 'tmp/tests/paths/a/b/c')
+ end
+
+ it 'throws an error suggesting the user to update its settings' do
+ expect { validate_storages }.to raise_error("foo is not a valid storage, because it has no `path` key. It may be configured as:\n\nfoo:\n path: tmp/tests/paths/a/b/c\n\nRefer to gitlab.yml.example for an updated example. Please fix this in your gitlab.yml before starting GitLab.")
+ end
+ end
+
def mock_storages(storages)
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
diff --git a/spec/initializers/doorkeeper_spec.rb b/spec/initializers/doorkeeper_spec.rb
new file mode 100644
index 00000000000..74bdbb01166
--- /dev/null
+++ b/spec/initializers/doorkeeper_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+require_relative '../../config/initializers/doorkeeper'
+
+describe Doorkeeper.configuration do
+ describe '#default_scopes' do
+ it 'matches Gitlab::Auth::DEFAULT_SCOPES' do
+ expect(subject.default_scopes).to eq Gitlab::Auth::DEFAULT_SCOPES
+ end
+ end
+
+ describe '#optional_scopes' do
+ it 'matches Gitlab::Auth::OPTIONAL_SCOPES' do
+ expect(subject.optional_scopes).to eq Gitlab::Auth::OPTIONAL_SCOPES
+ end
+ end
+
+ describe '#resource_owner_authenticator' do
+ subject { controller.instance_exec(&Doorkeeper.configuration.authenticate_resource_owner) }
+
+ let(:controller) { double }
+
+ before do
+ allow(controller).to receive(:current_user).and_return(current_user)
+ allow(controller).to receive(:session).and_return({})
+ allow(controller).to receive(:request).and_return(OpenStruct.new(fullpath: '/return-path'))
+ allow(controller).to receive(:redirect_to)
+ allow(controller).to receive(:new_user_session_url).and_return('/login')
+ end
+
+ context 'with a user present' do
+ let(:current_user) { create(:user) }
+
+ it 'returns the user' do
+ expect(subject).to eq current_user
+ end
+
+ it 'does not redirect' do
+ expect(controller).not_to receive(:redirect_to)
+
+ subject
+ end
+
+ it 'does not store the return path' do
+ subject
+
+ expect(controller.session).not_to include :user_return_to
+ end
+ end
+
+ context 'without a user present' do
+ let(:current_user) { nil }
+
+ # NOTE: this is required for doorkeeper-openid_connect
+ it 'returns nil' do
+ expect(subject).to eq nil
+ end
+
+ it 'redirects to the login form' do
+ expect(controller).to receive(:redirect_to).with('/login')
+
+ subject
+ end
+
+ it 'stores the return path' do
+ subject
+
+ expect(controller.session[:user_return_to]).to eq '/return-path'
+ end
+ end
+ end
+end
diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb
index ad7f032d1e5..65c97da2efd 100644
--- a/spec/initializers/secret_token_spec.rb
+++ b/spec/initializers/secret_token_spec.rb
@@ -6,6 +6,9 @@ describe 'create_tokens', lib: true do
let(:secrets) { ActiveSupport::OrderedOptions.new }
+ HEX_KEY = /\h{128}/
+ RSA_KEY = /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m
+
before do
allow(File).to receive(:write)
allow(File).to receive(:delete)
@@ -15,7 +18,7 @@ describe 'create_tokens', lib: true do
allow(self).to receive(:exit)
end
- context 'setting secret_key_base and otp_key_base' do
+ context 'setting secret keys' do
context 'when none of the secrets exist' do
before do
stub_env('SECRET_KEY_BASE', nil)
@@ -24,19 +27,29 @@ describe 'create_tokens', lib: true do
allow(self).to receive(:warn_missing_secret)
end
- it 'generates different secrets for secret_key_base, otp_key_base, and db_key_base' do
+ it 'generates different hashes for secret_key_base, otp_key_base, and db_key_base' do
create_tokens
keys = secrets.values_at(:secret_key_base, :otp_key_base, :db_key_base)
expect(keys.uniq).to eq(keys)
- expect(keys.map(&:length)).to all(eq(128))
+ expect(keys).to all(match(HEX_KEY))
+ end
+
+ it 'generates an RSA key for jws_private_key' do
+ create_tokens
+
+ keys = secrets.values_at(:jws_private_key)
+
+ expect(keys.uniq).to eq(keys)
+ expect(keys).to all(match(RSA_KEY))
end
it 'warns about the secrets to add to secrets.yml' do
expect(self).to receive(:warn_missing_secret).with('secret_key_base')
expect(self).to receive(:warn_missing_secret).with('otp_key_base')
expect(self).to receive(:warn_missing_secret).with('db_key_base')
+ expect(self).to receive(:warn_missing_secret).with('jws_private_key')
create_tokens
end
@@ -48,6 +61,7 @@ describe 'create_tokens', lib: true do
expect(new_secrets['secret_key_base']).to eq(secrets.secret_key_base)
expect(new_secrets['otp_key_base']).to eq(secrets.otp_key_base)
expect(new_secrets['db_key_base']).to eq(secrets.db_key_base)
+ expect(new_secrets['jws_private_key']).to eq(secrets.jws_private_key)
end
create_tokens
@@ -63,6 +77,7 @@ describe 'create_tokens', lib: true do
context 'when the other secrets all exist' do
before do
secrets.db_key_base = 'db_key_base'
+ secrets.jws_private_key = 'jws_private_key'
allow(File).to receive(:exist?).with('.secret').and_return(true)
allow(File).to receive(:read).with('.secret').and_return('file_key')
@@ -73,6 +88,7 @@ describe 'create_tokens', lib: true do
stub_env('SECRET_KEY_BASE', 'env_key')
secrets.secret_key_base = 'secret_key_base'
secrets.otp_key_base = 'otp_key_base'
+ secrets.jws_private_key = 'jws_private_key'
end
it 'does not issue a warning' do
@@ -98,6 +114,7 @@ describe 'create_tokens', lib: true do
before do
secrets.secret_key_base = 'secret_key_base'
secrets.otp_key_base = 'otp_key_base'
+ secrets.jws_private_key = 'jws_private_key'
end
it 'does not write any files' do
@@ -112,6 +129,7 @@ describe 'create_tokens', lib: true do
expect(secrets.secret_key_base).to eq('secret_key_base')
expect(secrets.otp_key_base).to eq('otp_key_base')
expect(secrets.db_key_base).to eq('db_key_base')
+ expect(secrets.jws_private_key).to eq('jws_private_key')
end
it 'deletes the .secret file' do
@@ -135,6 +153,7 @@ describe 'create_tokens', lib: true do
expect(new_secrets['secret_key_base']).to eq('file_key')
expect(new_secrets['otp_key_base']).to eq('file_key')
expect(new_secrets['db_key_base']).to eq('db_key_base')
+ expect(new_secrets['jws_private_key']).to eq('jws_private_key')
end
create_tokens
diff --git a/spec/javascripts/fixtures/environments/metrics.html.haml b/spec/javascripts/fixtures/environments/metrics.html.haml
new file mode 100644
index 00000000000..483063fb889
--- /dev/null
+++ b/spec/javascripts/fixtures/environments/metrics.html.haml
@@ -0,0 +1,12 @@
+%div
+ .top-area
+ .row
+ .col-sm-6
+ %h3.page-title
+ Metrics for environment
+ .row
+ .col-sm-12
+ %svg.prometheus-graph{ 'graph-type' => 'cpu_values' }
+ .row
+ .col-sm-12
+ %svg.prometheus-graph{ 'graph-type' => 'memory_values' } \ No newline at end of file
diff --git a/spec/javascripts/monitoring/prometheus_graph_spec.js b/spec/javascripts/monitoring/prometheus_graph_spec.js
new file mode 100644
index 00000000000..823b4bab7fc
--- /dev/null
+++ b/spec/javascripts/monitoring/prometheus_graph_spec.js
@@ -0,0 +1,78 @@
+import 'jquery';
+import es6Promise from 'es6-promise';
+import '~/lib/utils/common_utils';
+import PrometheusGraph from '~/monitoring/prometheus_graph';
+import { prometheusMockData } from './prometheus_mock_data';
+
+es6Promise.polyfill();
+
+describe('PrometheusGraph', () => {
+ const fixtureName = 'static/environments/metrics.html.raw';
+ const prometheusGraphContainer = '.prometheus-graph';
+ const prometheusGraphContents = `${prometheusGraphContainer}[graph-type=cpu_values]`;
+
+ preloadFixtures(fixtureName);
+
+ beforeEach(() => {
+ loadFixtures(fixtureName);
+ this.prometheusGraph = new PrometheusGraph();
+ const self = this;
+ const fakeInit = (metricsResponse) => {
+ self.prometheusGraph.transformData(metricsResponse);
+ self.prometheusGraph.createGraph();
+ };
+ spyOn(this.prometheusGraph, 'init').and.callFake(fakeInit);
+ });
+
+ it('initializes graph properties', () => {
+ // Test for the measurements
+ expect(this.prometheusGraph.margin).toBeDefined();
+ expect(this.prometheusGraph.marginLabelContainer).toBeDefined();
+ expect(this.prometheusGraph.originalWidth).toBeDefined();
+ expect(this.prometheusGraph.originalHeight).toBeDefined();
+ expect(this.prometheusGraph.height).toBeDefined();
+ expect(this.prometheusGraph.width).toBeDefined();
+ expect(this.prometheusGraph.backOffRequestCounter).toBeDefined();
+ // Test for the graph properties (colors, radius, etc.)
+ expect(this.prometheusGraph.graphSpecificProperties).toBeDefined();
+ expect(this.prometheusGraph.commonGraphProperties).toBeDefined();
+ });
+
+ it('transforms the data', () => {
+ this.prometheusGraph.init(prometheusMockData.metrics);
+ expect(this.prometheusGraph.data).toBeDefined();
+ expect(this.prometheusGraph.data.cpu_values.length).toBe(121);
+ expect(this.prometheusGraph.data.memory_values.length).toBe(121);
+ });
+
+ it('creates two graphs', () => {
+ this.prometheusGraph.init(prometheusMockData.metrics);
+ expect($(prometheusGraphContainer).length).toBe(2);
+ });
+
+ describe('Graph contents', () => {
+ beforeEach(() => {
+ this.prometheusGraph.init(prometheusMockData.metrics);
+ });
+
+ it('has axis, an area, a line and a overlay', () => {
+ const $graphContainer = $(prometheusGraphContents).find('.x-axis').parent();
+ expect($graphContainer.find('.x-axis')).toBeDefined();
+ expect($graphContainer.find('.y-axis')).toBeDefined();
+ expect($graphContainer.find('.prometheus-graph-overlay')).toBeDefined();
+ expect($graphContainer.find('.metric-line')).toBeDefined();
+ expect($graphContainer.find('.metric-area')).toBeDefined();
+ });
+
+ it('has legends, labels and an extra axis that labels the metrics', () => {
+ const $prometheusGraphContents = $(prometheusGraphContents);
+ const $axisLabelContainer = $(prometheusGraphContents).find('.label-x-axis-line').parent();
+ expect($prometheusGraphContents.find('.label-x-axis-line')).toBeDefined();
+ expect($prometheusGraphContents.find('.label-y-axis-line')).toBeDefined();
+ expect($prometheusGraphContents.find('.label-axis-text')).toBeDefined();
+ expect($prometheusGraphContents.find('.rect-axis-text')).toBeDefined();
+ expect($axisLabelContainer.find('rect').length).toBe(2);
+ expect($axisLabelContainer.find('text').length).toBe(4);
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/prometheus_mock_data.js b/spec/javascripts/monitoring/prometheus_mock_data.js
new file mode 100644
index 00000000000..1cdc14faaa8
--- /dev/null
+++ b/spec/javascripts/monitoring/prometheus_mock_data.js
@@ -0,0 +1,1014 @@
+/* eslint-disable import/prefer-default-export*/
+export const prometheusMockData = {
+ status: 200,
+ metrics: {
+ success: true,
+ metrics: {
+ memory_values: [
+ {
+ metric: {
+ },
+ values: [
+ [
+ 1488462917.256,
+ '10.12890625',
+ ],
+ [
+ 1488462977.256,
+ '10.140625',
+ ],
+ [
+ 1488463037.256,
+ '10.140625',
+ ],
+ [
+ 1488463097.256,
+ '10.14453125',
+ ],
+ [
+ 1488463157.256,
+ '10.1484375',
+ ],
+ [
+ 1488463217.256,
+ '10.15625',
+ ],
+ [
+ 1488463277.256,
+ '10.15625',
+ ],
+ [
+ 1488463337.256,
+ '10.15625',
+ ],
+ [
+ 1488463397.256,
+ '10.1640625',
+ ],
+ [
+ 1488463457.256,
+ '10.171875',
+ ],
+ [
+ 1488463517.256,
+ '10.171875',
+ ],
+ [
+ 1488463577.256,
+ '10.171875',
+ ],
+ [
+ 1488463637.256,
+ '10.18359375',
+ ],
+ [
+ 1488463697.256,
+ '10.1953125',
+ ],
+ [
+ 1488463757.256,
+ '10.203125',
+ ],
+ [
+ 1488463817.256,
+ '10.20703125',
+ ],
+ [
+ 1488463877.256,
+ '10.20703125',
+ ],
+ [
+ 1488463937.256,
+ '10.20703125',
+ ],
+ [
+ 1488463997.256,
+ '10.20703125',
+ ],
+ [
+ 1488464057.256,
+ '10.2109375',
+ ],
+ [
+ 1488464117.256,
+ '10.2109375',
+ ],
+ [
+ 1488464177.256,
+ '10.2109375',
+ ],
+ [
+ 1488464237.256,
+ '10.2109375',
+ ],
+ [
+ 1488464297.256,
+ '10.21484375',
+ ],
+ [
+ 1488464357.256,
+ '10.22265625',
+ ],
+ [
+ 1488464417.256,
+ '10.22265625',
+ ],
+ [
+ 1488464477.256,
+ '10.2265625',
+ ],
+ [
+ 1488464537.256,
+ '10.23046875',
+ ],
+ [
+ 1488464597.256,
+ '10.23046875',
+ ],
+ [
+ 1488464657.256,
+ '10.234375',
+ ],
+ [
+ 1488464717.256,
+ '10.234375',
+ ],
+ [
+ 1488464777.256,
+ '10.234375',
+ ],
+ [
+ 1488464837.256,
+ '10.234375',
+ ],
+ [
+ 1488464897.256,
+ '10.234375',
+ ],
+ [
+ 1488464957.256,
+ '10.234375',
+ ],
+ [
+ 1488465017.256,
+ '10.23828125',
+ ],
+ [
+ 1488465077.256,
+ '10.23828125',
+ ],
+ [
+ 1488465137.256,
+ '10.2421875',
+ ],
+ [
+ 1488465197.256,
+ '10.2421875',
+ ],
+ [
+ 1488465257.256,
+ '10.2421875',
+ ],
+ [
+ 1488465317.256,
+ '10.2421875',
+ ],
+ [
+ 1488465377.256,
+ '10.2421875',
+ ],
+ [
+ 1488465437.256,
+ '10.2421875',
+ ],
+ [
+ 1488465497.256,
+ '10.2421875',
+ ],
+ [
+ 1488465557.256,
+ '10.2421875',
+ ],
+ [
+ 1488465617.256,
+ '10.2421875',
+ ],
+ [
+ 1488465677.256,
+ '10.2421875',
+ ],
+ [
+ 1488465737.256,
+ '10.2421875',
+ ],
+ [
+ 1488465797.256,
+ '10.24609375',
+ ],
+ [
+ 1488465857.256,
+ '10.25',
+ ],
+ [
+ 1488465917.256,
+ '10.25390625',
+ ],
+ [
+ 1488465977.256,
+ '9.98828125',
+ ],
+ [
+ 1488466037.256,
+ '9.9921875',
+ ],
+ [
+ 1488466097.256,
+ '9.9921875',
+ ],
+ [
+ 1488466157.256,
+ '9.99609375',
+ ],
+ [
+ 1488466217.256,
+ '10',
+ ],
+ [
+ 1488466277.256,
+ '10.00390625',
+ ],
+ [
+ 1488466337.256,
+ '10.0078125',
+ ],
+ [
+ 1488466397.256,
+ '10.01171875',
+ ],
+ [
+ 1488466457.256,
+ '10.0234375',
+ ],
+ [
+ 1488466517.256,
+ '10.02734375',
+ ],
+ [
+ 1488466577.256,
+ '10.02734375',
+ ],
+ [
+ 1488466637.256,
+ '10.03125',
+ ],
+ [
+ 1488466697.256,
+ '10.03125',
+ ],
+ [
+ 1488466757.256,
+ '10.03125',
+ ],
+ [
+ 1488466817.256,
+ '10.03125',
+ ],
+ [
+ 1488466877.256,
+ '10.03125',
+ ],
+ [
+ 1488466937.256,
+ '10.03125',
+ ],
+ [
+ 1488466997.256,
+ '10.03125',
+ ],
+ [
+ 1488467057.256,
+ '10.0390625',
+ ],
+ [
+ 1488467117.256,
+ '10.0390625',
+ ],
+ [
+ 1488467177.256,
+ '10.04296875',
+ ],
+ [
+ 1488467237.256,
+ '10.05078125',
+ ],
+ [
+ 1488467297.256,
+ '10.05859375',
+ ],
+ [
+ 1488467357.256,
+ '10.06640625',
+ ],
+ [
+ 1488467417.256,
+ '10.06640625',
+ ],
+ [
+ 1488467477.256,
+ '10.0703125',
+ ],
+ [
+ 1488467537.256,
+ '10.07421875',
+ ],
+ [
+ 1488467597.256,
+ '10.0859375',
+ ],
+ [
+ 1488467657.256,
+ '10.0859375',
+ ],
+ [
+ 1488467717.256,
+ '10.09765625',
+ ],
+ [
+ 1488467777.256,
+ '10.1015625',
+ ],
+ [
+ 1488467837.256,
+ '10.10546875',
+ ],
+ [
+ 1488467897.256,
+ '10.10546875',
+ ],
+ [
+ 1488467957.256,
+ '10.125',
+ ],
+ [
+ 1488468017.256,
+ '10.13671875',
+ ],
+ [
+ 1488468077.256,
+ '10.1484375',
+ ],
+ [
+ 1488468137.256,
+ '10.15625',
+ ],
+ [
+ 1488468197.256,
+ '10.16796875',
+ ],
+ [
+ 1488468257.256,
+ '10.171875',
+ ],
+ [
+ 1488468317.256,
+ '10.171875',
+ ],
+ [
+ 1488468377.256,
+ '10.171875',
+ ],
+ [
+ 1488468437.256,
+ '10.171875',
+ ],
+ [
+ 1488468497.256,
+ '10.171875',
+ ],
+ [
+ 1488468557.256,
+ '10.171875',
+ ],
+ [
+ 1488468617.256,
+ '10.171875',
+ ],
+ [
+ 1488468677.256,
+ '10.17578125',
+ ],
+ [
+ 1488468737.256,
+ '10.17578125',
+ ],
+ [
+ 1488468797.256,
+ '10.265625',
+ ],
+ [
+ 1488468857.256,
+ '10.19921875',
+ ],
+ [
+ 1488468917.256,
+ '10.19921875',
+ ],
+ [
+ 1488468977.256,
+ '10.19921875',
+ ],
+ [
+ 1488469037.256,
+ '10.19921875',
+ ],
+ [
+ 1488469097.256,
+ '10.19921875',
+ ],
+ [
+ 1488469157.256,
+ '10.203125',
+ ],
+ [
+ 1488469217.256,
+ '10.43359375',
+ ],
+ [
+ 1488469277.256,
+ '10.20703125',
+ ],
+ [
+ 1488469337.256,
+ '10.2109375',
+ ],
+ [
+ 1488469397.256,
+ '10.22265625',
+ ],
+ [
+ 1488469457.256,
+ '10.21484375',
+ ],
+ [
+ 1488469517.256,
+ '10.21484375',
+ ],
+ [
+ 1488469577.256,
+ '10.21484375',
+ ],
+ [
+ 1488469637.256,
+ '10.22265625',
+ ],
+ [
+ 1488469697.256,
+ '10.234375',
+ ],
+ [
+ 1488469757.256,
+ '10.234375',
+ ],
+ [
+ 1488469817.256,
+ '10.234375',
+ ],
+ [
+ 1488469877.256,
+ '10.2421875',
+ ],
+ [
+ 1488469937.256,
+ '10.25',
+ ],
+ [
+ 1488469997.256,
+ '10.25390625',
+ ],
+ [
+ 1488470057.256,
+ '10.26171875',
+ ],
+ [
+ 1488470117.256,
+ '10.2734375',
+ ],
+ ],
+ },
+ ],
+ memory_current: [
+ {
+ metric: {
+ },
+ value: [
+ 1488470117.737,
+ '10.2734375',
+ ],
+ },
+ ],
+ cpu_values: [
+ {
+ metric: {
+ },
+ values: [
+ [
+ 1488462918.15,
+ '0.0002996458625058103',
+ ],
+ [
+ 1488462978.15,
+ '0.0002652382333333314',
+ ],
+ [
+ 1488463038.15,
+ '0.0003485461333333421',
+ ],
+ [
+ 1488463098.15,
+ '0.0003420421999999886',
+ ],
+ [
+ 1488463158.15,
+ '0.00023107150000001297',
+ ],
+ [
+ 1488463218.15,
+ '0.00030463981666664826',
+ ],
+ [
+ 1488463278.15,
+ '0.0002477177833333677',
+ ],
+ [
+ 1488463338.15,
+ '0.00026936656666665115',
+ ],
+ [
+ 1488463398.15,
+ '0.000406264750000022',
+ ],
+ [
+ 1488463458.15,
+ '0.00029592802026561453',
+ ],
+ [
+ 1488463518.15,
+ '0.00023426999683316343',
+ ],
+ [
+ 1488463578.15,
+ '0.0003057080666666915',
+ ],
+ [
+ 1488463638.15,
+ '0.0003408470500000149',
+ ],
+ [
+ 1488463698.15,
+ '0.00025497336666665166',
+ ],
+ [
+ 1488463758.15,
+ '0.0003009282833333534',
+ ],
+ [
+ 1488463818.15,
+ '0.0003119383499999924',
+ ],
+ [
+ 1488463878.15,
+ '0.00028719019999998705',
+ ],
+ [
+ 1488463938.15,
+ '0.000327864749999988',
+ ],
+ [
+ 1488463998.15,
+ '0.0002514917333333422',
+ ],
+ [
+ 1488464058.15,
+ '0.0003614651166666742',
+ ],
+ [
+ 1488464118.15,
+ '0.0003221668000000122',
+ ],
+ [
+ 1488464178.15,
+ '0.00023323083333330884',
+ ],
+ [
+ 1488464238.15,
+ '0.00028531499475009274',
+ ],
+ [
+ 1488464298.15,
+ '0.0002627695294921391',
+ ],
+ [
+ 1488464358.15,
+ '0.00027145463333333453',
+ ],
+ [
+ 1488464418.15,
+ '0.00025669488333335266',
+ ],
+ [
+ 1488464478.15,
+ '0.00022307761666665965',
+ ],
+ [
+ 1488464538.15,
+ '0.0003307265833333517',
+ ],
+ [
+ 1488464598.15,
+ '0.0002817050666666709',
+ ],
+ [
+ 1488464658.15,
+ '0.00022357458333332285',
+ ],
+ [
+ 1488464718.15,
+ '0.00032648590000000275',
+ ],
+ [
+ 1488464778.15,
+ '0.00028410750000000816',
+ ],
+ [
+ 1488464838.15,
+ '0.0003038076999999954',
+ ],
+ [
+ 1488464898.15,
+ '0.00037568226666667335',
+ ],
+ [
+ 1488464958.15,
+ '0.00020160354999999202',
+ ],
+ [
+ 1488465018.15,
+ '0.0003229403333333399',
+ ],
+ [
+ 1488465078.15,
+ '0.00033516069999999236',
+ ],
+ [
+ 1488465138.15,
+ '0.0003365978333333371',
+ ],
+ [
+ 1488465198.15,
+ '0.00020262178333331585',
+ ],
+ [
+ 1488465258.15,
+ '0.00040567498333331876',
+ ],
+ [
+ 1488465318.15,
+ '0.00029114155000001436',
+ ],
+ [
+ 1488465378.15,
+ '0.0002498841000000122',
+ ],
+ [
+ 1488465438.15,
+ '0.00027296763333331715',
+ ],
+ [
+ 1488465498.15,
+ '0.0002958794000000135',
+ ],
+ [
+ 1488465558.15,
+ '0.0002922354666666867',
+ ],
+ [
+ 1488465618.15,
+ '0.00034186624999999653',
+ ],
+ [
+ 1488465678.15,
+ '0.0003397984166666627',
+ ],
+ [
+ 1488465738.15,
+ '0.0002658284166666469',
+ ],
+ [
+ 1488465798.15,
+ '0.00026221139999999346',
+ ],
+ [
+ 1488465858.15,
+ '0.00029467960000001034',
+ ],
+ [
+ 1488465918.15,
+ '0.0002634141333333358',
+ ],
+ [
+ 1488465978.15,
+ '0.0003202958333333209',
+ ],
+ [
+ 1488466038.15,
+ '0.00037890760000000394',
+ ],
+ [
+ 1488466098.15,
+ '0.00023453356666666518',
+ ],
+ [
+ 1488466158.15,
+ '0.0002866827333333433',
+ ],
+ [
+ 1488466218.15,
+ '0.0003335935499999998',
+ ],
+ [
+ 1488466278.15,
+ '0.00022787131666666125',
+ ],
+ [
+ 1488466338.15,
+ '0.00033821938333333064',
+ ],
+ [
+ 1488466398.15,
+ '0.00029233375000001043',
+ ],
+ [
+ 1488466458.15,
+ '0.00026562758333333514',
+ ],
+ [
+ 1488466518.15,
+ '0.0003142600999999819',
+ ],
+ [
+ 1488466578.15,
+ '0.00027392178333333444',
+ ],
+ [
+ 1488466638.15,
+ '0.00028178598333334173',
+ ],
+ [
+ 1488466698.15,
+ '0.0002463400666666911',
+ ],
+ [
+ 1488466758.15,
+ '0.00040234373333332125',
+ ],
+ [
+ 1488466818.15,
+ '0.00023677453333332822',
+ ],
+ [
+ 1488466878.15,
+ '0.00030852703333333523',
+ ],
+ [
+ 1488466938.15,
+ '0.0003582272833333455',
+ ],
+ [
+ 1488466998.15,
+ '0.0002176380833332973',
+ ],
+ [
+ 1488467058.15,
+ '0.00026180203333335447',
+ ],
+ [
+ 1488467118.15,
+ '0.00027862966666667436',
+ ],
+ [
+ 1488467178.15,
+ '0.0002769731166666567',
+ ],
+ [
+ 1488467238.15,
+ '0.0002832899166666477',
+ ],
+ [
+ 1488467298.15,
+ '0.0003446533500000311',
+ ],
+ [
+ 1488467358.15,
+ '0.0002691345999999761',
+ ],
+ [
+ 1488467418.15,
+ '0.000284919933333357',
+ ],
+ [
+ 1488467478.15,
+ '0.0002396026166666528',
+ ],
+ [
+ 1488467538.15,
+ '0.00035625295000002075',
+ ],
+ [
+ 1488467598.15,
+ '0.00036759816666664946',
+ ],
+ [
+ 1488467658.15,
+ '0.00030326608333333855',
+ ],
+ [
+ 1488467718.15,
+ '0.00023584972418043393',
+ ],
+ [
+ 1488467778.15,
+ '0.00025744508892115107',
+ ],
+ [
+ 1488467838.15,
+ '0.00036737541666663395',
+ ],
+ [
+ 1488467898.15,
+ '0.00034325741666666094',
+ ],
+ [
+ 1488467958.15,
+ '0.00026390046666667407',
+ ],
+ [
+ 1488468018.15,
+ '0.0003302534500000102',
+ ],
+ [
+ 1488468078.15,
+ '0.00035243794999999527',
+ ],
+ [
+ 1488468138.15,
+ '0.00020149738333333407',
+ ],
+ [
+ 1488468198.15,
+ '0.0003183469666666679',
+ ],
+ [
+ 1488468258.15,
+ '0.0003835329166666845',
+ ],
+ [
+ 1488468318.15,
+ '0.0002485075333333124',
+ ],
+ [
+ 1488468378.15,
+ '0.0003011457166666768',
+ ],
+ [
+ 1488468438.15,
+ '0.00032242785497684965',
+ ],
+ [
+ 1488468498.15,
+ '0.0002659713747457531',
+ ],
+ [
+ 1488468558.15,
+ '0.0003476860333333202',
+ ],
+ [
+ 1488468618.15,
+ '0.00028336403333334794',
+ ],
+ [
+ 1488468678.15,
+ '0.00017132354999998728',
+ ],
+ [
+ 1488468738.15,
+ '0.0003001915833333276',
+ ],
+ [
+ 1488468798.15,
+ '0.0003025715666666725',
+ ],
+ [
+ 1488468858.15,
+ '0.0003012370166666815',
+ ],
+ [
+ 1488468918.15,
+ '0.00030203619999997025',
+ ],
+ [
+ 1488468978.15,
+ '0.0002804355000000314',
+ ],
+ [
+ 1488469038.15,
+ '0.00033194884999998564',
+ ],
+ [
+ 1488469098.15,
+ '0.00025201496666665455',
+ ],
+ [
+ 1488469158.15,
+ '0.0002777531500000189',
+ ],
+ [
+ 1488469218.15,
+ '0.0003314885833333392',
+ ],
+ [
+ 1488469278.15,
+ '0.0002234891422095589',
+ ],
+ [
+ 1488469338.15,
+ '0.000349117355867791',
+ ],
+ [
+ 1488469398.15,
+ '0.0004036731333333303',
+ ],
+ [
+ 1488469458.15,
+ '0.00024553911666667835',
+ ],
+ [
+ 1488469518.15,
+ '0.0003056456833333184',
+ ],
+ [
+ 1488469578.15,
+ '0.0002618737166666681',
+ ],
+ [
+ 1488469638.15,
+ '0.00022972643333331414',
+ ],
+ [
+ 1488469698.15,
+ '0.0003713522500000307',
+ ],
+ [
+ 1488469758.15,
+ '0.00018322576666666515',
+ ],
+ [
+ 1488469818.15,
+ '0.00034534762753952466',
+ ],
+ [
+ 1488469878.15,
+ '0.00028200510008501677',
+ ],
+ [
+ 1488469938.15,
+ '0.0002773708499999768',
+ ],
+ [
+ 1488469998.15,
+ '0.00027547160000001013',
+ ],
+ [
+ 1488470058.15,
+ '0.00031713610000000023',
+ ],
+ [
+ 1488470118.15,
+ '0.00035276853333332525',
+ ],
+ ],
+ },
+ ],
+ cpu_current: [
+ {
+ metric: {
+ },
+ value: [
+ 1488470118.566,
+ '0.00035276853333332525',
+ ],
+ },
+ ],
+ last_update: '2017-03-02T15:55:18.981Z',
+ },
+ },
+};
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 939e8cb3a56..03c4879ed6f 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -3,6 +3,24 @@ require 'spec_helper'
describe Gitlab::Auth, lib: true do
let(:gl_auth) { described_class }
+ describe 'constants' do
+ it 'API_SCOPES contains all scopes for API access' do
+ expect(subject::API_SCOPES).to eq [:api, :read_user]
+ end
+
+ it 'OPENID_SCOPES contains all scopes for OpenID Connect' do
+ expect(subject::OPENID_SCOPES).to eq [:openid]
+ end
+
+ it 'DEFAULT_SCOPES contains all default scopes' do
+ expect(subject::DEFAULT_SCOPES).to eq [:api]
+ end
+
+ it 'OPTIONAL_SCOPES contains all non-default scopes' do
+ expect(subject::OPTIONAL_SCOPES).to eq [:read_user, :openid]
+ end
+ end
+
describe 'find_for_git_client' do
context 'build token' do
subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: project, ip: 'ip') }
@@ -222,6 +240,18 @@ describe Gitlab::Auth, lib: true do
end
end
+ it "does not find user in blocked state" do
+ user.block
+
+ expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
+ end
+
+ it "does not find user in ldap_blocked state" do
+ user.ldap_block
+
+ expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
+ end
+
context "with ldap enabled" do
before do
allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 1a1280e5198..e47956a365f 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -136,6 +136,7 @@ project:
- slack_slash_commands_service
- irker_service
- pivotaltracker_service
+- prometheus_service
- hipchat_service
- flowdock_service
- assembla_service
diff --git a/spec/lib/gitlab/prometheus_spec.rb b/spec/lib/gitlab/prometheus_spec.rb
new file mode 100644
index 00000000000..280264188e2
--- /dev/null
+++ b/spec/lib/gitlab/prometheus_spec.rb
@@ -0,0 +1,143 @@
+require 'spec_helper'
+
+describe Gitlab::Prometheus, lib: true do
+ include PrometheusHelpers
+
+ subject { described_class.new(api_url: 'https://prometheus.example.com') }
+
+ describe '#ping' do
+ it 'issues a "query" request to the API endpoint' do
+ req_stub = stub_prometheus_request(prometheus_query_url('1'), body: prometheus_value_body('vector'))
+
+ expect(subject.ping).to eq({ "resultType" => "vector", "result" => [{ "metric" => {}, "value" => [1488772511.004, "0.000041021495238095323"] }] })
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ # This shared examples expect:
+ # - query_url: A query URL
+ # - execute_query: A query call
+ shared_examples 'failure response' do
+ context 'when request returns 400 with an error message' do
+ it 'raises a Gitlab::PrometheusError error' do
+ req_stub = stub_prometheus_request(query_url, status: 400, body: { error: 'bar!' })
+
+ expect { execute_query }
+ .to raise_error(Gitlab::PrometheusError, 'bar!')
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when request returns 400 without an error message' do
+ it 'raises a Gitlab::PrometheusError error' do
+ req_stub = stub_prometheus_request(query_url, status: 400)
+
+ expect { execute_query }
+ .to raise_error(Gitlab::PrometheusError, 'Bad data received')
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when request returns 500' do
+ it 'raises a Gitlab::PrometheusError error' do
+ req_stub = stub_prometheus_request(query_url, status: 500, body: { message: 'FAIL!' })
+
+ expect { execute_query }
+ .to raise_error(Gitlab::PrometheusError, '500 - {"message":"FAIL!"}')
+ expect(req_stub).to have_been_requested
+ end
+ end
+ end
+
+ describe '#query' do
+ let(:prometheus_query) { prometheus_cpu_query('env-slug') }
+ let(:query_url) { prometheus_query_url(prometheus_query) }
+
+ context 'when request returns vector results' do
+ it 'returns data from the API call' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_value_body('vector'))
+
+ expect(subject.query(prometheus_query)).to eq [{ "metric" => {}, "value" => [1488772511.004, "0.000041021495238095323"] }]
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when request returns matrix results' do
+ it 'returns nil' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_value_body('matrix'))
+
+ expect(subject.query(prometheus_query)).to be_nil
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when request returns no data' do
+ it 'returns []' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_empty_body('vector'))
+
+ expect(subject.query(prometheus_query)).to be_empty
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ it_behaves_like 'failure response' do
+ let(:execute_query) { subject.query(prometheus_query) }
+ end
+ end
+
+ describe '#query_range' do
+ let(:prometheus_query) { prometheus_memory_query('env-slug') }
+ let(:query_url) { prometheus_query_range_url(prometheus_query) }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ context 'when a start time is passed' do
+ let(:query_url) { prometheus_query_range_url(prometheus_query, start: 2.hours.ago) }
+
+ it 'passed it in the requested URL' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('vector'))
+
+ subject.query_range(prometheus_query, start: 2.hours.ago)
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when request returns vector results' do
+ it 'returns nil' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('vector'))
+
+ expect(subject.query_range(prometheus_query)).to be_nil
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when request returns matrix results' do
+ it 'returns data from the API call' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_values_body('matrix'))
+
+ expect(subject.query_range(prometheus_query)).to eq([
+ {
+ "metric" => {},
+ "values" => [[1488758662.506, "0.00002996364761904785"], [1488758722.506, "0.00003090239047619091"]]
+ }
+ ])
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'when request returns no data' do
+ it 'returns []' do
+ req_stub = stub_prometheus_request(query_url, body: prometheus_empty_body('matrix'))
+
+ expect(subject.query_range(prometheus_query)).to be_empty
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ it_behaves_like 'failure response' do
+ let(:execute_query) { subject.query_range(prometheus_query) }
+ end
+ end
+end
diff --git a/spec/models/chat_team_spec.rb b/spec/models/chat_team_spec.rb
index 1aab161ec13..5283561a83f 100644
--- a/spec/models/chat_team_spec.rb
+++ b/spec/models/chat_team_spec.rb
@@ -1,9 +1,14 @@
require 'spec_helper'
describe ChatTeam, type: :model do
+ subject { create(:chat_team) }
+
# Associations
it { is_expected.to belong_to(:namespace) }
+ # Validations
+ it { is_expected.to validate_uniqueness_of(:namespace) }
+
# Fields
it { is_expected.to respond_to(:name) }
it { is_expected.to respond_to(:team_id) }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 2db42a94077..fd6ea2d6722 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -345,11 +345,11 @@ describe Ci::Build, :models do
describe '#expanded_environment_name' do
subject { build.expanded_environment_name }
- context 'when environment uses $CI_BUILD_REF_NAME' do
+ context 'when environment uses $CI_COMMIT_REF_NAME' do
let(:build) do
create(:ci_build,
ref: 'master',
- environment: 'review/$CI_BUILD_REF_NAME')
+ environment: 'review/$CI_COMMIT_REF_NAME')
end
it { is_expected.to eq('review/master') }
@@ -915,7 +915,7 @@ describe Ci::Build, :models do
end
context 'referenced with a variable' do
- let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-$CI_BUILD_REF_NAME") }
+ let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-$CI_COMMIT_REF_NAME") }
it { is_expected.to eq(@environment) }
end
@@ -1286,23 +1286,25 @@ describe Ci::Build, :models do
[
{ key: 'CI', value: 'true', public: true },
{ key: 'GITLAB_CI', value: 'true', public: true },
- { key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
- { key: 'CI_BUILD_TOKEN', value: build.token, public: false },
- { key: 'CI_BUILD_REF', value: build.sha, public: true },
- { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true },
- { key: 'CI_BUILD_REF_NAME', value: 'master', public: true },
- { key: 'CI_BUILD_REF_SLUG', value: 'master', public: true },
- { key: 'CI_BUILD_NAME', value: 'test', public: true },
- { key: 'CI_BUILD_STAGE', value: 'test', public: true },
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
{ key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
+ { key: 'CI_JOB_ID', value: build.id.to_s, public: true },
+ { key: 'CI_JOB_NAME', value: 'test', public: true },
+ { key: 'CI_JOB_STAGE', value: 'test', public: true },
+ { key: 'CI_JOB_TOKEN', value: build.token, public: false },
+ { key: 'CI_COMMIT_SHA', value: build.sha, public: true },
+ { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true },
+ { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true },
{ key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: project.path, public: true },
{ key: 'CI_PROJECT_PATH', value: project.full_path, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true },
- { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }
+ { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true },
+ { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
+ { key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false },
+ { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false },
]
end
@@ -1317,7 +1319,7 @@ describe Ci::Build, :models do
build.yaml_variables = []
end
- it { is_expected.to eq(predefined_variables) }
+ it { is_expected.to include(*predefined_variables) }
end
context 'when build has user' do
@@ -1355,7 +1357,7 @@ describe Ci::Build, :models do
end
let(:manual_variable) do
- { key: 'CI_BUILD_MANUAL', value: 'true', public: true }
+ { key: 'CI_JOB_MANUAL', value: 'true', public: true }
end
it { is_expected.to include(manual_variable) }
@@ -1363,7 +1365,7 @@ describe Ci::Build, :models do
context 'when build is for tag' do
let(:tag_variable) do
- { key: 'CI_BUILD_TAG', value: 'master', public: true }
+ { key: 'CI_COMMIT_TAG', value: 'master', public: true }
end
before do
@@ -1392,7 +1394,7 @@ describe Ci::Build, :models do
{ key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false }
end
let(:predefined_trigger_variable) do
- { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true }
+ { key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true }
end
before do
@@ -1416,7 +1418,7 @@ describe Ci::Build, :models do
context 'when config is not found' do
let(:config) { nil }
- it { is_expected.to eq(predefined_variables) }
+ it { is_expected.to include(*predefined_variables) }
end
context 'when config does not have a questioned job' do
@@ -1428,7 +1430,7 @@ describe Ci::Build, :models do
})
end
- it { is_expected.to eq(predefined_variables) }
+ it { is_expected.to include(*predefined_variables) }
end
context 'when config has variables' do
@@ -1446,7 +1448,8 @@ describe Ci::Build, :models do
[{ key: 'KEY', value: 'value', public: true }]
end
- it { is_expected.to eq(predefined_variables + variables) }
+ it { is_expected.to include(*predefined_variables) }
+ it { is_expected.to include(*variables) }
end
end
end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index dce18f008f8..b4305e92812 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -271,7 +271,11 @@ describe Environment, models: true do
context 'when the environment is unavailable' do
let(:project) { create(:kubernetes_project) }
- before { environment.stop }
+
+ before do
+ environment.stop
+ end
+
it { is_expected.to be_falsy }
end
end
@@ -281,20 +285,85 @@ describe Environment, models: true do
subject { environment.terminals }
context 'when the environment has terminals' do
- before { allow(environment).to receive(:has_terminals?).and_return(true) }
+ before do
+ allow(environment).to receive(:has_terminals?).and_return(true)
+ end
it 'returns the terminals from the deployment service' do
- expect(project.deployment_service).
- to receive(:terminals).with(environment).
- and_return(:fake_terminals)
+ expect(project.deployment_service)
+ .to receive(:terminals).with(environment)
+ .and_return(:fake_terminals)
is_expected.to eq(:fake_terminals)
end
end
context 'when the environment does not have terminals' do
- before { allow(environment).to receive(:has_terminals?).and_return(false) }
- it { is_expected.to eq(nil) }
+ before do
+ allow(environment).to receive(:has_terminals?).and_return(false)
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#has_metrics?' do
+ subject { environment.has_metrics? }
+
+ context 'when the enviroment is available' do
+ context 'with a deployment service' do
+ let(:project) { create(:prometheus_project) }
+
+ context 'and a deployment' do
+ let!(:deployment) { create(:deployment, environment: environment) }
+ it { is_expected.to be_truthy }
+ end
+
+ context 'but no deployments' do
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ context 'without a monitoring service' do
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ context 'when the environment is unavailable' do
+ let(:project) { create(:prometheus_project) }
+
+ before do
+ environment.stop
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#metrics' do
+ let(:project) { create(:prometheus_project) }
+ subject { environment.metrics }
+
+ context 'when the environment has metrics' do
+ before do
+ allow(environment).to receive(:has_metrics?).and_return(true)
+ end
+
+ it 'returns the metrics from the deployment service' do
+ expect(project.monitoring_service)
+ .to receive(:metrics).with(environment)
+ .and_return(:fake_metrics)
+
+ is_expected.to eq(:fake_metrics)
+ end
+ end
+
+ context 'when the environment does not have metrics' do
+ before do
+ allow(environment).to receive(:has_metrics?).and_return(false)
+ end
+
+ it { is_expected.to be_nil }
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 7525a1b79ee..757f3921450 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -165,7 +165,7 @@ describe Namespace, models: true do
describe :rm_dir do
let!(:project) { create(:empty_project, namespace: namespace) }
- let!(:path) { File.join(Gitlab.config.repositories.storages.default, namespace.full_path) }
+ let!(:path) { File.join(Gitlab.config.repositories.storages.default['path'], namespace.full_path) }
it "removes its dirs when deleted" do
namespace.destroy
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index 7c9f4aad836..823623d96fa 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -34,4 +34,28 @@ describe PersonalAccessToken, models: true do
expect(active_personal_access_token).to be_active
end
end
+
+ context "validations" do
+ let(:personal_access_token) { build(:personal_access_token) }
+
+ it "requires at least one scope" do
+ personal_access_token.scopes = []
+
+ expect(personal_access_token).not_to be_valid
+ expect(personal_access_token.errors[:scopes].first).to eq "can't be blank"
+ end
+
+ it "allows creating a token with API scopes" do
+ personal_access_token.scopes = [:api, :read_user]
+
+ expect(personal_access_token).to be_valid
+ end
+
+ it "rejects creating a token with non-API scopes" do
+ personal_access_token.scopes = [:openid, :api]
+
+ expect(personal_access_token).not_to be_valid
+ expect(personal_access_token.errors[:scopes].first).to eq "can only contain API scopes"
+ end
+ end
end
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
new file mode 100644
index 00000000000..d15079b686b
--- /dev/null
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -0,0 +1,104 @@
+require 'spec_helper'
+
+describe PrometheusService, models: true, caching: true do
+ include PrometheusHelpers
+ include ReactiveCachingHelpers
+
+ let(:project) { create(:prometheus_project) }
+ let(:service) { project.prometheus_service }
+
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before { subject.active = true }
+
+ it { is_expected.to validate_presence_of(:api_url) }
+ end
+
+ context 'when service is inactive' do
+ before { subject.active = false }
+
+ it { is_expected.not_to validate_presence_of(:api_url) }
+ end
+ end
+
+ describe '#test' do
+ let!(:req_stub) { stub_prometheus_request(prometheus_query_url('1'), body: prometheus_value_body('vector')) }
+
+ context 'success' do
+ it 'reads the discovery endpoint' do
+ expect(service.test[:success]).to be_truthy
+ expect(req_stub).to have_been_requested
+ end
+ end
+
+ context 'failure' do
+ let!(:req_stub) { stub_prometheus_request(prometheus_query_url('1'), status: 404) }
+
+ it 'fails to read the discovery endpoint' do
+ expect(service.test[:success]).to be_falsy
+ expect(req_stub).to have_been_requested
+ end
+ end
+ end
+
+ describe '#metrics' do
+ let(:environment) { build_stubbed(:environment, slug: 'env-slug') }
+ subject { service.metrics(environment) }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ context 'with valid data' do
+ before do
+ stub_reactive_cache(service, prometheus_data, 'env-slug')
+ end
+
+ it 'returns reactive data' do
+ is_expected.to eq(prometheus_data)
+ end
+ end
+ end
+
+ describe '#calculate_reactive_cache' do
+ let(:environment) { build_stubbed(:environment, slug: 'env-slug') }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ subject do
+ service.calculate_reactive_cache(environment.slug)
+ end
+
+ context 'when service is inactive' do
+ before do
+ service.active = false
+ end
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when Prometheus responds with valid data' do
+ before do
+ stub_all_prometheus_requests(environment.slug)
+ end
+
+ it { expect(subject.to_json).to eq(prometheus_data.to_json) }
+ end
+
+ [404, 500].each do |status|
+ context "when Prometheus responds with #{status}" do
+ before do
+ stub_all_prometheus_requests(environment.slug, status: status, body: 'QUERY FAILED!')
+ end
+
+ it { is_expected.to eq(success: false, result: %(#{status} - "QUERY FAILED!")) }
+ end
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 84bdcbe8e59..e120e21af06 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -179,7 +179,7 @@ describe Project, models: true do
let(:project2) { build(:empty_project, repository_storage: 'missing') }
before do
- storages = { 'custom' => 'tmp/tests/custom_repositories' }
+ storages = { 'custom' => { 'path' => 'tmp/tests/custom_repositories' } }
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
@@ -381,7 +381,7 @@ describe Project, models: true do
before do
FileUtils.mkdir('tmp/tests/custom_repositories')
- storages = { 'custom' => 'tmp/tests/custom_repositories' }
+ storages = { 'custom' => { 'path' => 'tmp/tests/custom_repositories' } }
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
@@ -947,8 +947,8 @@ describe Project, models: true do
before do
storages = {
- 'default' => 'tmp/tests/repositories',
- 'picked' => 'tmp/tests/repositories',
+ 'default' => { 'path' => 'tmp/tests/repositories' },
+ 'picked' => { 'path' => 'tmp/tests/repositories' },
}
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
diff --git a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
new file mode 100644
index 00000000000..6443f86b6a1
--- /dev/null
+++ b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Projects::Settings::DeployKeysPresenter do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let(:deploy_key) { create(:deploy_key, public: true) }
+
+ let!(:deploy_keys_project) do
+ create(:deploy_keys_project, project: project, deploy_key: deploy_key)
+ end
+
+ subject(:presenter) do
+ described_class.new(project, current_user: user)
+ end
+
+ it 'inherits from Gitlab::View::Presenter::Simple' do
+ expect(described_class.superclass).to eq(Gitlab::View::Presenter::Simple)
+ end
+
+ describe '#enabled_keys' do
+ it 'returns currently enabled keys' do
+ expect(presenter.enabled_keys).to eq [deploy_keys_project.deploy_key]
+ end
+
+ it 'does not contain enabled_keys inside available_keys' do
+ expect(presenter.available_keys).not_to include deploy_key
+ end
+
+ it 'returns the enabled_keys size' do
+ expect(presenter.enabled_keys_size).to eq(1)
+ end
+
+ it 'returns true if there is any enabled_keys' do
+ expect(presenter.any_keys_enabled?).to eq(true)
+ end
+ end
+
+ describe '#available_keys/#available_project_keys' do
+ let(:other_deploy_key) { create(:another_deploy_key) }
+
+ before do
+ project_key = create(:deploy_keys_project, deploy_key: other_deploy_key)
+ project_key.project.add_developer(user)
+ end
+
+ it 'returns the current available_keys' do
+ expect(presenter.available_keys).not_to be_empty
+ end
+
+ it 'returns the current available_project_keys' do
+ expect(presenter.available_project_keys).not_to be_empty
+ end
+
+ it 'returns false if any available_project_keys are enabled' do
+ expect(presenter.any_available_project_keys_enabled?).to eq(true)
+ end
+
+ it 'returns the available_project_keys size' do
+ expect(presenter.available_project_keys_size).to eq(1)
+ end
+
+ it 'shows if there is an available key' do
+ expect(presenter.key_available?(deploy_key)).to eq(false)
+ end
+ end
+end
diff --git a/spec/requests/api/api_internal_helpers_spec.rb b/spec/requests/api/api_internal_helpers_spec.rb
index be4bc39ada2..f5265ea60ff 100644
--- a/spec/requests/api/api_internal_helpers_spec.rb
+++ b/spec/requests/api/api_internal_helpers_spec.rb
@@ -21,7 +21,7 @@ describe ::API::Helpers::InternalHelpers do
# Relative and absolute storage paths, with and without trailing /
['.', './', Dir.pwd, Dir.pwd + '/'].each do |storage_path|
context "storage path is #{storage_path}" do
- subject { clean_project_path(project_path, [storage_path]) }
+ subject { clean_project_path(project_path, [{ 'path' => storage_path }]) }
it { is_expected.to eq(expected) }
end
diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb
index 2974875510a..f6fd567eca5 100644
--- a/spec/requests/api/doorkeeper_access_spec.rb
+++ b/spec/requests/api/doorkeeper_access_spec.rb
@@ -39,4 +39,22 @@ describe API::API, api: true do
end
end
end
+
+ describe "when user is blocked" do
+ it "returns authentication error" do
+ user.block
+ get api("/user"), access_token: token.token
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ describe "when user is ldap_blocked" do
+ it "returns authentication error" do
+ user.ldap_block
+ get api("/user"), access_token: token.token
+
+ expect(response).to have_http_status(401)
+ end
+ end
end
diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb
index 7e2cc50e591..367225df717 100644
--- a/spec/requests/api/oauth_tokens_spec.rb
+++ b/spec/requests/api/oauth_tokens_spec.rb
@@ -29,5 +29,27 @@ describe API::API, api: true do
expect(json_response['access_token']).not_to be_nil
end
end
+
+ context "when user is blocked" do
+ it "does not create an access token" do
+ user = create(:user)
+ user.block
+
+ request_oauth_token(user)
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context "when user is ldap_blocked" do
+ it "does not create an access token" do
+ user = create(:user)
+ user.ldap_block
+
+ request_oauth_token(user)
+
+ expect(response).to have_http_status(401)
+ end
+ end
end
end
diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb
index 794e2b5c04d..28fab2011a5 100644
--- a/spec/requests/api/session_spec.rb
+++ b/spec/requests/api/session_spec.rb
@@ -87,5 +87,23 @@ describe API::Session, api: true do
expect(response).to have_http_status(400)
end
end
+
+ context "when user is blocked" do
+ it "returns authentication error" do
+ user.block
+ post api("/session"), email: user.username, password: user.password
+
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context "when user is ldap_blocked" do
+ it "returns authentication error" do
+ user.ldap_block
+ post api("/session"), email: user.username, password: user.password
+
+ expect(response).to have_http_status(401)
+ end
+ end
end
end
diff --git a/spec/requests/api/v3/services_spec.rb b/spec/requests/api/v3/services_spec.rb
index 7e8c8753d02..3a760a8f25c 100644
--- a/spec/requests/api/v3/services_spec.rb
+++ b/spec/requests/api/v3/services_spec.rb
@@ -6,7 +6,9 @@ describe API::V3::Services, api: true do
let(:user) { create(:user) }
let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) }
- Service.available_services_names.each do |service|
+ available_services = Service.available_services_names
+ available_services.delete('prometheus')
+ available_services.each do |service|
describe "DELETE /projects/:id/services/#{service.dasherize}" do
include_context service
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 87786e85621..006d6a6af1c 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -221,12 +221,20 @@ describe 'Git HTTP requests', lib: true do
end
context "when the user is blocked" do
- it "responds with status 404" do
+ it "responds with status 401" do
user.block
project.team << [user, :master]
download(path, env) do |response|
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ it "responds with status 401 for unknown projects (no project existence information leak)" do
+ user.block
+
+ download('doesnt/exist.git', env) do |response|
+ expect(response).to have_http_status(401)
end
end
end
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
new file mode 100644
index 00000000000..5206634bca5
--- /dev/null
+++ b/spec/requests/openid_connect_spec.rb
@@ -0,0 +1,134 @@
+require 'spec_helper'
+
+describe 'OpenID Connect requests' do
+ include ApiHelpers
+
+ let(:user) { create :user }
+ let(:access_grant) { create :oauth_access_grant, application: application, resource_owner_id: user.id }
+ let(:access_token) { create :oauth_access_token, application: application, resource_owner_id: user.id }
+
+ def request_access_token
+ login_as user
+
+ post '/oauth/token',
+ grant_type: 'authorization_code',
+ code: access_grant.token,
+ redirect_uri: application.redirect_uri,
+ client_id: application.uid,
+ client_secret: application.secret
+ end
+
+ def request_user_info
+ get '/oauth/userinfo', nil, 'Authorization' => "Bearer #{access_token.token}"
+ end
+
+ def hashed_subject
+ Digest::SHA256.hexdigest("#{user.id}-#{Rails.application.secrets.secret_key_base}")
+ end
+
+ context 'Application without OpenID scope' do
+ let(:application) { create :oauth_application, scopes: 'api' }
+
+ it 'token response does not include an ID token' do
+ request_access_token
+
+ expect(json_response).to include 'access_token'
+ expect(json_response).not_to include 'id_token'
+ end
+
+ it 'userinfo response is unauthorized' do
+ request_user_info
+
+ expect(response).to have_http_status 403
+ expect(response.body).to be_blank
+ end
+ end
+
+ context 'Application with OpenID scope' do
+ let(:application) { create :oauth_application, scopes: 'openid' }
+
+ it 'token response includes an ID token' do
+ request_access_token
+
+ expect(json_response).to include 'id_token'
+ end
+
+ context 'UserInfo payload' do
+ let(:user) do
+ create(
+ :user,
+ name: 'Alice',
+ username: 'alice',
+ emails: [private_email, public_email],
+ email: private_email.email,
+ public_email: public_email.email,
+ website_url: 'https://example.com',
+ avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png"),
+ )
+ end
+
+ let(:public_email) { build :email, email: 'public@example.com' }
+ let(:private_email) { build :email, email: 'private@example.com' }
+
+ it 'includes all user information' do
+ request_user_info
+
+ expect(json_response).to eq({
+ 'sub' => hashed_subject,
+ 'name' => 'Alice',
+ 'nickname' => 'alice',
+ 'email' => 'public@example.com',
+ 'email_verified' => true,
+ 'website' => 'https://example.com',
+ 'profile' => 'http://localhost/alice',
+ 'picture' => "http://localhost/uploads/user/avatar/#{user.id}/dk.png",
+ })
+ end
+ end
+
+ context 'ID token payload' do
+ before do
+ request_access_token
+ @payload = JSON::JWT.decode(json_response['id_token'], :skip_verification)
+ end
+
+ it 'includes the Gitlab root URL' do
+ expect(@payload['iss']).to eq Gitlab.config.gitlab.url
+ end
+
+ it 'includes the hashed user ID' do
+ expect(@payload['sub']).to eq hashed_subject
+ end
+
+ it 'includes the time of the last authentication' do
+ expect(@payload['auth_time']).to eq user.current_sign_in_at.to_i
+ end
+
+ it 'does not include any unknown properties' do
+ expect(@payload.keys).to eq %w[iss sub aud exp iat auth_time]
+ end
+ end
+
+ context 'when user is blocked' do
+ it 'returns authentication error' do
+ access_grant
+ user.block
+
+ expect do
+ request_access_token
+ end.to throw_symbol :warden
+ end
+ end
+
+ context 'when user is ldap_blocked' do
+ it 'returns authentication error' do
+ access_grant
+ user.ldap_block
+
+ expect do
+ request_access_token
+ end.to throw_symbol :warden
+ end
+ end
+ end
+end
diff --git a/spec/routing/openid_connect_spec.rb b/spec/routing/openid_connect_spec.rb
new file mode 100644
index 00000000000..2c3bc08f1a1
--- /dev/null
+++ b/spec/routing/openid_connect_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+# oauth_discovery_keys GET /oauth/discovery/keys(.:format) doorkeeper/openid_connect/discovery#keys
+# oauth_discovery_provider GET /.well-known/openid-configuration(.:format) doorkeeper/openid_connect/discovery#provider
+# oauth_discovery_webfinger GET /.well-known/webfinger(.:format) doorkeeper/openid_connect/discovery#webfinger
+describe Doorkeeper::OpenidConnect::DiscoveryController, 'routing' do
+ it "to #provider" do
+ expect(get('/.well-known/openid-configuration')).to route_to('doorkeeper/openid_connect/discovery#provider')
+ end
+
+ it "to #webfinger" do
+ expect(get('/.well-known/webfinger')).to route_to('doorkeeper/openid_connect/discovery#webfinger')
+ end
+
+ it "to #keys" do
+ expect(get('/oauth/discovery/keys')).to route_to('doorkeeper/openid_connect/discovery#keys')
+ end
+end
+
+# oauth_userinfo GET /oauth/userinfo(.:format) doorkeeper/openid_connect/userinfo#show
+# POST /oauth/userinfo(.:format) doorkeeper/openid_connect/userinfo#show
+describe Doorkeeper::OpenidConnect::UserinfoController, 'routing' do
+ it "to #show" do
+ expect(get('/oauth/userinfo')).to route_to('doorkeeper/openid_connect/userinfo#show')
+ end
+
+ it "to #show" do
+ expect(post('/oauth/userinfo')).to route_to('doorkeeper/openid_connect/userinfo#show')
+ end
+end
diff --git a/spec/support/prometheus_helpers.rb b/spec/support/prometheus_helpers.rb
new file mode 100644
index 00000000000..a52d8f37d14
--- /dev/null
+++ b/spec/support/prometheus_helpers.rb
@@ -0,0 +1,117 @@
+module PrometheusHelpers
+ def prometheus_memory_query(environment_slug)
+ %{sum(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"})/1024/1024}
+ end
+
+ def prometheus_cpu_query(environment_slug)
+ %{sum(rate(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}[2m]))}
+ end
+
+ def prometheus_query_url(prometheus_query)
+ query = { query: prometheus_query }.to_query
+
+ "https://prometheus.example.com/api/v1/query?#{query}"
+ end
+
+ def prometheus_query_range_url(prometheus_query, start: 8.hours.ago)
+ query = {
+ query: prometheus_query,
+ start: start.to_f,
+ end: Time.now.utc.to_f,
+ step: 1.minute.to_i
+ }.to_query
+
+ "https://prometheus.example.com/api/v1/query_range?#{query}"
+ end
+
+ def stub_prometheus_request(url, body: {}, status: 200)
+ WebMock.stub_request(:get, url)
+ .to_return({
+ status: status,
+ headers: { 'Content-Type' => 'application/json' },
+ body: body.to_json
+ })
+ end
+
+ def stub_all_prometheus_requests(environment_slug, body: nil, status: 200)
+ stub_prometheus_request(
+ prometheus_query_url(prometheus_memory_query(environment_slug)),
+ status: status,
+ body: body || prometheus_value_body
+ )
+ stub_prometheus_request(
+ prometheus_query_range_url(prometheus_memory_query(environment_slug)),
+ status: status,
+ body: body || prometheus_values_body
+ )
+ stub_prometheus_request(
+ prometheus_query_url(prometheus_cpu_query(environment_slug)),
+ status: status,
+ body: body || prometheus_value_body
+ )
+ stub_prometheus_request(
+ prometheus_query_range_url(prometheus_cpu_query(environment_slug)),
+ status: status,
+ body: body || prometheus_values_body
+ )
+ end
+
+ def prometheus_data(last_update: Time.now.utc)
+ {
+ success: true,
+ metrics: {
+ memory_values: prometheus_values_body('matrix').dig(:data, :result),
+ memory_current: prometheus_value_body('vector').dig(:data, :result),
+ cpu_values: prometheus_values_body('matrix').dig(:data, :result),
+ cpu_current: prometheus_value_body('vector').dig(:data, :result)
+ },
+ last_update: last_update
+ }
+ end
+
+ def prometheus_empty_body(type)
+ {
+ "status": "success",
+ "data": {
+ "resultType": type,
+ "result": []
+ }
+ }
+ end
+
+ def prometheus_value_body(type = 'vector')
+ {
+ "status": "success",
+ "data": {
+ "resultType": type,
+ "result": [
+ {
+ "metric": {},
+ "value": [
+ 1488772511.004,
+ "0.000041021495238095323"
+ ]
+ }
+ ]
+ }
+ }
+ end
+
+ def prometheus_values_body(type = 'matrix')
+ {
+ "status": "success",
+ "data": {
+ "resultType": type,
+ "result": [
+ {
+ "metric": {},
+ "values": [
+ [1488758662.506, "0.00002996364761904785"],
+ [1488758722.506, "0.00003090239047619091"]
+ ]
+ }
+ ]
+ }
+ }
+ end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index c3aa3ef44c2..f1d226b6ae3 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -143,7 +143,7 @@ module TestEnv
end
def repos_path
- Gitlab.config.repositories.storages.default
+ Gitlab.config.repositories.storages.default['path']
end
def backup_path
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index dfbfbd05f43..10458966cb9 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -227,8 +227,8 @@ describe 'gitlab:app namespace rake task' do
FileUtils.mkdir('tmp/tests/default_storage')
FileUtils.mkdir('tmp/tests/custom_storage')
storages = {
- 'default' => 'tmp/tests/default_storage',
- 'custom' => 'tmp/tests/custom_storage'
+ 'default' => { 'path' => 'tmp/tests/default_storage' },
+ 'custom' => { 'path' => 'tmp/tests/custom_storage' }
}
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 5919b99a6ed..7bcb5521202 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -105,6 +105,6 @@ describe PostReceive do
end
def pwd(project)
- File.join(Gitlab.config.repositories.storages.default, project.path_with_namespace)
+ File.join(Gitlab.config.repositories.storages.default['path'], project.path_with_namespace)
end
end