diff options
54 files changed, 603 insertions, 476 deletions
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 0d73092cfba..0720ea3e056 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -233,22 +233,3 @@ qa-frontend-node:latest: extends: .qa-frontend-node image: node:latest allow_failure: true - -jsdoc: - extends: - - .default-tags - - .default-retry - - .default-cache - - .except-docs - variables: - SETUP_DB: "false" - stage: post-test - dependencies: ["compile-assets", "compile-assets pull-cache"] - script: - - date - - yarn run jsdoc || true # ignore exit code - artifacts: - name: jsdoc - expire_in: 31d - paths: - - jsdoc/ diff --git a/.gitlab/ci/pages.gitlab-ci.yml b/.gitlab/ci/pages.gitlab-ci.yml index 5d13a72e224..2de09753cca 100644 --- a/.gitlab/ci/pages.gitlab-ci.yml +++ b/.gitlab/ci/pages.gitlab-ci.yml @@ -9,7 +9,7 @@ pages: - master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ee stage: pages - dependencies: ["coverage", "karma", "gitlab:assets:compile", "jsdoc"] + dependencies: ["coverage", "karma", "gitlab:assets:compile"] script: - mv public/ .public/ - mkdir public/ @@ -18,7 +18,6 @@ pages: - mv webpack-report/ public/webpack-report/ || true - cp .public/assets/application-*.css public/application.css || true - cp .public/assets/application-*.css.gz public/application.css.gz || true - - mv jsdoc/ public/jsdoc/ || true artifacts: paths: - public diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml index 8628e1e0a14..9c021b23db6 100644 --- a/.gitlab/ci/qa.gitlab-ci.yml +++ b/.gitlab/ci/qa.gitlab-ci.yml @@ -19,6 +19,8 @@ package-and-qa-manual: except: refs: - master + - /(^docs[\/-].+|.+-docs$)/ + - /(^qa[\/-].*|.*-qa$)/ when: manual needs: ["build-qa-image", "gitlab:assets:compile pull-cache"] diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index be147d72f71..f1f8ff6e862 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -218,7 +218,7 @@ Lint/UriEscapeUnescape: - 'app/models/project_services/drone_ci_service.rb' - 'spec/lib/google_api/auth_spec.rb' - 'spec/requests/api/files_spec.rb' - - 'spec/requests/api/internal_spec.rb' + - 'spec/requests/api/internal/base_spec.rb' # Offense count: 1 # Configuration parameters: CheckForMethodsWithNoSideEffects. diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index 9069b35db9a..086340105b7 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -3,7 +3,7 @@ /* global ListMilestone */ /* global ListAssignee */ -import Vue from 'vue'; +import axios from '~/lib/utils/axios_utils'; import './label'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import IssueProject from './project'; @@ -133,7 +133,7 @@ class ListIssue { } const projectPath = this.project ? this.project.path : ''; - return Vue.http.patch(`${this.path}.json`, data).then(({ body = {} } = {}) => { + return axios.patch(`${this.path}.json`, data).then(({ data: body = {} } = {}) => { /** * Since post implementation of Scoped labels, server can reject * same key-ed labels. To keep the UI and server Model consistent, diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue index aa50fd8ff62..8d2dac47ff2 100644 --- a/app/assets/javascripts/groups/components/app.vue +++ b/app/assets/javascripts/groups/components/app.vue @@ -95,10 +95,8 @@ export default { if (updatePagination) { this.updatePagination(res.headers); } - - return res; + return res.data; }) - .then(res => res.json()) .catch(() => { this.isLoading = false; $.scrollTo(0); @@ -190,11 +188,10 @@ export default { this.targetGroup.isBeingRemoved = true; this.service .leaveGroup(this.targetGroup.leavePath) - .then(res => res.json()) .then(res => { $.scrollTo(0); this.store.removeGroup(this.targetGroup, this.targetParentGroup); - Flash(res.notice, 'notice'); + Flash(res.data.notice, 'notice'); }) .catch(err => { let message = COMMON_STR.FAILURE; diff --git a/app/assets/javascripts/groups/service/groups_service.js b/app/assets/javascripts/groups/service/groups_service.js index b79ba291463..790b581a7c0 100644 --- a/app/assets/javascripts/groups/service/groups_service.js +++ b/app/assets/javascripts/groups/service/groups_service.js @@ -1,40 +1,39 @@ -import Vue from 'vue'; -import '../../vue_shared/vue_resource_interceptor'; +import axios from '~/lib/utils/axios_utils'; export default class GroupsService { constructor(endpoint) { - this.groups = Vue.resource(endpoint); + this.endpoint = endpoint; } getGroups(parentId, page, filterGroups, sort, archived) { - const data = {}; + const params = {}; if (parentId) { - data.parent_id = parentId; + params.parent_id = parentId; } else { // Do not send the following param for sub groups if (page) { - data.page = page; + params.page = page; } if (filterGroups) { - data.filter = filterGroups; + params.filter = filterGroups; } if (sort) { - data.sort = sort; + params.sort = sort; } if (archived) { - data.archived = archived; + params.archived = archived; } } - return this.groups.get(data); + return axios.get(this.endpoint, { params }); } // eslint-disable-next-line class-methods-use-this leaveGroup(endpoint) { - return Vue.http.delete(endpoint); + return axios.delete(endpoint); } } diff --git a/app/models/board.rb b/app/models/board.rb index 50b6ca9b70f..b5d07f1b282 100644 --- a/app/models/board.rb +++ b/app/models/board.rb @@ -19,6 +19,7 @@ class Board < ApplicationRecord def parent @parent ||= group || project end + alias_method :resource_parent, :parent def group_board? group_id.present? diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index eefe9f00836..a998d9b7e1b 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -277,6 +277,10 @@ module Issuable end end + def resource_parent + project + end + def milestone_available? project_id == milestone&.project_id || project.ancestors_upto.compact.include?(milestone&.group) end diff --git a/app/models/event.rb b/app/models/event.rb index 392d7368033..52d54be39a9 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -51,6 +51,7 @@ class Event < ApplicationRecord belongs_to :author, class_name: "User" belongs_to :project + belongs_to :group belongs_to :target, -> { # If the association for "target" defines an "author" association we want to diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 101e963ea29..cb87b46a31d 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -260,6 +260,7 @@ class Milestone < ApplicationRecord def parent group || project end + alias_method :resource_parent, :parent def group_milestone? group_id.present? diff --git a/app/models/note.rb b/app/models/note.rb index ebd13675dc9..0d024b0a25c 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -477,6 +477,7 @@ class Note < ApplicationRecord def parent project end + alias_method :resource_parent, :parent private diff --git a/app/models/todo.rb b/app/models/todo.rb index 1ec04189482..f7f30aed832 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -146,6 +146,7 @@ class Todo < ApplicationRecord def parent project end + alias_method :resource_parent, :parent def unmergeable? action == UNMERGEABLE diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb index e7464fd9d5f..39266a6c961 100644 --- a/app/services/event_create_service.rb +++ b/app/services/event_create_service.rb @@ -95,16 +95,23 @@ class EventCreateService private def create_record_event(record, current_user, status) - create_event(record.project, current_user, status, target_id: record.id, target_type: record.class.name) + create_event(record.resource_parent, current_user, status, target_id: record.id, target_type: record.class.name) end - def create_event(project, current_user, status, attributes = {}) + def create_event(resource_parent, current_user, status, attributes = {}) attributes.reverse_merge!( - project: project, action: status, author_id: current_user.id ) + resource_parent_attr = case resource_parent + when Project + :project + when Group + :group + end + attributes[resource_parent_attr] = resource_parent if resource_parent_attr + Event.create!(attributes) end end diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml index 04b77fb987a..1d5d90593ae 100644 --- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml +++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml @@ -1,3 +1,14 @@ +- has_base_domain = @project.all_clusters.any? { |cluster| cluster.base_domain && !cluster.base_domain.empty? } + +- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe +- link_end = '</a>'.html_safe + +- kubernetes_cluster_path = help_page_path('user/project/clusters/index') +- kubernetes_cluster_link_start = link_start % { url: kubernetes_cluster_path } + +- base_domain_path = help_page_path('user/project/clusters/index', anchor: 'base-domain') +- base_domain_link_start = link_start % { url: base_domain_path } + .row .col-lg-12 = form_for @project, url: project_settings_ci_cd_path(@project, anchor: 'autodevops-settings') do |f| @@ -19,9 +30,10 @@ .card-footer.js-extra-settings{ class: auto_devops_enabled || 'hidden' } - if @project.all_clusters.empty? %p.settings-message.text-center - - kubernetes_cluster_link = help_page_path('user/project/clusters/index') - - kubernetes_cluster_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: kubernetes_cluster_link } - = s_('CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly.').html_safe % { kubernetes_cluster_start: kubernetes_cluster_start, kubernetes_cluster_end: '</a>'.html_safe } + = s_('CICD|You must add a %{kubernetes_cluster_link_start}Kubernetes cluster integration%{link_end} to this project with a domain in order for your deployment strategy to work correctly.').html_safe % { kubernetes_cluster_link_start: kubernetes_cluster_link_start, link_end: link_end } + - elsif !has_base_domain + %p.settings-message.text-center + = s_('CICD|You must add a %{base_domain_link_start}base domain%{link_end} to your %{kubernetes_cluster_link_start}Kubernetes cluster%{link_end} in order for your deployment strategy to work.').html_safe % { base_domain_link_start: base_domain_link_start, kubernetes_cluster_link_start: kubernetes_cluster_link_start, link_end: link_end } %label.prepend-top-10 %strong= s_('CICD|Deployment strategy') .form-check diff --git a/app/views/shared/empty_states/_priority_labels.html.haml b/app/views/shared/empty_states/_priority_labels.html.haml index bba3475d244..a93f6e4c795 100644 --- a/app/views/shared/empty_states/_priority_labels.html.haml +++ b/app/views/shared/empty_states/_priority_labels.html.haml @@ -1,4 +1,6 @@ .text-center .svg-content.qa-label-svg = image_tag 'illustrations/priority_labels.svg' - %p Star labels to start sorting by priority + - if can?(current_user, :admin_label, @project) + %p + = _("Star labels to start sorting by priority") diff --git a/changelogs/unreleased/change-prioritized-labels-empty-state-message.yml b/changelogs/unreleased/change-prioritized-labels-empty-state-message.yml new file mode 100644 index 00000000000..d5df889d15c --- /dev/null +++ b/changelogs/unreleased/change-prioritized-labels-empty-state-message.yml @@ -0,0 +1,5 @@ +--- +title: Change prioritized labels empty state message +merge_request: 32338 +author: Lee Tickett +type: other diff --git a/changelogs/unreleased/je-add-cluster-domain-warning.yml b/changelogs/unreleased/je-add-cluster-domain-warning.yml new file mode 100644 index 00000000000..e7d244f730f --- /dev/null +++ b/changelogs/unreleased/je-add-cluster-domain-warning.yml @@ -0,0 +1,5 @@ +--- +title: Add cluster domain warning +merge_request: 32260 +author: +type: changed diff --git a/changelogs/unreleased/remove-vue-resource-from-group-service.yml b/changelogs/unreleased/remove-vue-resource-from-group-service.yml new file mode 100644 index 00000000000..771d301cabf --- /dev/null +++ b/changelogs/unreleased/remove-vue-resource-from-group-service.yml @@ -0,0 +1,5 @@ +--- +title: Remove vue resource from group service +merge_request: +author: Lee Tickett +type: other diff --git a/changelogs/unreleased/remove-vue-resource-from-issue.yml b/changelogs/unreleased/remove-vue-resource-from-issue.yml new file mode 100644 index 00000000000..b2de1b0913a --- /dev/null +++ b/changelogs/unreleased/remove-vue-resource-from-issue.yml @@ -0,0 +1,5 @@ +--- +title: Remove vue resource from issue +merge_request: 32421 +author: Lee Tickett +type: other diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 20b1020e025..6dcaefc05d5 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -431,6 +431,7 @@ production: &base # key: config/registry.key # path: shared/registry # issuer: gitlab-issuer + # notification_secret: '' # only set it when you use Geo replication feature without built-in Registry # Add notification settings if you plan to use Geo Replication for the registry # notifications: diff --git a/db/migrate/20190826100605_add_group_column_to_events.rb b/db/migrate/20190826100605_add_group_column_to_events.rb new file mode 100644 index 00000000000..cd7b2b1d96a --- /dev/null +++ b/db/migrate/20190826100605_add_group_column_to_events.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddGroupColumnToEvents < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def change + add_reference :events, :group, index: true, foreign_key: { to_table: :namespaces, on_delete: :cascade } + end +end diff --git a/db/schema.rb b/db/schema.rb index 0f535e4d674..f2d6f70217b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1310,9 +1310,11 @@ ActiveRecord::Schema.define(version: 2019_09_02_160015) do t.datetime_with_timezone "updated_at", null: false t.integer "action", limit: 2, null: false t.string "target_type" + t.bigint "group_id" t.index ["action"], name: "index_events_on_action" t.index ["author_id", "project_id"], name: "index_events_on_author_id_and_project_id" t.index ["created_at", "author_id"], name: "analytics_index_events_on_created_at_and_author_id" + t.index ["group_id"], name: "index_events_on_group_id" t.index ["project_id", "created_at"], name: "index_events_on_project_id_and_created_at" t.index ["project_id", "id"], name: "index_events_on_project_id_and_id" t.index ["target_type", "target_id"], name: "index_events_on_target_type_and_target_id" @@ -3838,6 +3840,7 @@ ActiveRecord::Schema.define(version: 2019_09_02_160015) do add_foreign_key "epics", "users", column: "assignee_id", name: "fk_dccd3f98fc", on_delete: :nullify add_foreign_key "epics", "users", column: "author_id", name: "fk_3654b61b03", on_delete: :cascade add_foreign_key "epics", "users", column: "closed_by_id", name: "fk_aa5798e761", on_delete: :nullify + add_foreign_key "events", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "events", "projects", on_delete: :cascade add_foreign_key "events", "users", column: "author_id", name: "fk_edfd187b6f", on_delete: :cascade add_foreign_key "fork_network_members", "fork_networks", on_delete: :cascade diff --git a/doc/administration/logs.md b/doc/administration/logs.md index 9b1efb610f8..4c43a434817 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -341,9 +341,10 @@ installations from source. ## `database_load_balancing.log` -Introduced in GitLab 12.3 for observability of [Database Load -Balancing](https://docs.gitlab.com/ee/administration/database_load_balancing.html) -when enabled. This file lives in -`/var/log/gitlab/gitlab-rails/database_load_balancing.log` for Omnibus GitLab -packages or in `/home/git/gitlab/log/database_load_balancing.log` for -installations from source. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/15442) in GitLab 12.3. + +This log is used for observability of [Database Load Balancing](database_load_balancing.md). +It is stored at: + +- `/var/log/gitlab/gitlab-rails/database_load_balancing.log` for Omnibus GitLab packages. +- `/home/git/gitlab/log/database_load_balancing.log` for installations from source. diff --git a/doc/ci/environments.md b/doc/ci/environments.md index f6c47a99712..c2d444cb1d6 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -675,9 +675,10 @@ fetch line: fetch = +refs/environments/*:refs/remotes/origin/environments/* ``` -### Scoping environments with specs **(PREMIUM)** +### Scoping environments with specs -> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2112) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.4. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2112) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.4. +> - [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30779) to Core in Gitlab 12.2. You can limit the environment scope of a variable by defining which environments it can be available for. diff --git a/doc/ci/large_repositories/index.md b/doc/ci/large_repositories/index.md index 29d649ad717..b1359537fca 100644 --- a/doc/ci/large_repositories/index.md +++ b/doc/ci/large_repositories/index.md @@ -84,7 +84,7 @@ Fork workflow from GitLab Runner's perspective is stored as a separate repositor with separate worktree. That means that GitLab Runner cannot optimize the usage of worktrees and you might have to instruct GitLab Runner to use that. -In such cases, ideally you want to make the GitLab Runner executor be used only used only +In such cases, ideally you want to make the GitLab Runner executor be used only for the given project and not shared across different projects to make this process more efficient. diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 5a15b907da0..438b7c03b51 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -401,7 +401,7 @@ Once you set them, they will be available for all subsequent pipelines. You can limit the environment scope of a variable by [defining which environments][envs] it can be available for. -To learn more about about scoping environments, see [Scoping environments with specs](../environments.md#scoping-environments-with-specs-premium). +To learn more about about scoping environments, see [Scoping environments with specs](../environments.md#scoping-environments-with-specs). ### Deployment environment variables diff --git a/doc/development/database_review.md b/doc/development/database_review.md index 367a481ee11..157c64b514c 100644 --- a/doc/development/database_review.md +++ b/doc/development/database_review.md @@ -38,6 +38,7 @@ A Merge Request author's role is to: - If database review is needed, add the ~database label. - Use the [database changes](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/merge_request_templates/Database%20changes.md) merge request template, or include the appropriate items in the MR description. +- [Prepare the merge request for a database review](#how-to-prepare-the-merge-request-for-a-database-review). A database **reviewer**'s role is to: @@ -68,7 +69,7 @@ make sure you have applied the ~database label and rerun the `danger-review` CI job, or pick someone from the [`@gl-database` team](https://gitlab.com/groups/gl-database/-/group_members). -### How to prepare for speedy database reviews +### How to prepare the merge request for a database review In order to make reviewing easier and therefore faster, please consider preparing a comment and details for a database reviewer: diff --git a/doc/user/instance/clusters/index.md b/doc/user/instance/clusters/index.md index f557dcf4b3c..cb1bfc69826 100644 --- a/doc/user/instance/clusters/index.md +++ b/doc/user/instance/clusters/index.md @@ -19,4 +19,4 @@ GitLab will try match to clusters in the following order: - Instance level To be selected, the cluster must be enabled and -match the [environment selector](../../../ci/environments.md#scoping-environments-with-specs-premium). +match the [environment selector](../../../ci/environments.md#scoping-environments-with-specs). diff --git a/doc/user/project/operations/feature_flags.md b/doc/user/project/operations/feature_flags.md index 28248ad3696..39ca1bd0c77 100644 --- a/doc/user/project/operations/feature_flags.md +++ b/doc/user/project/operations/feature_flags.md @@ -63,15 +63,15 @@ For example, you may not want to enable a feature flag on production until your first confirmed that the feature is working correctly on testing environments. To handle these situations, you can enable a feature flag on a particular environment -with [Environment specs](../../../ci/environments.md#scoping-environments-with-specs-premium). +with [Environment specs](../../../ci/environments.md#scoping-environments-with-specs). You can define multiple specs per flag so that you can control your feature flag more granularly. To define specs for each environment: 1. Navigate to your project's **Operations > Feature Flags**. 1. Click on the **New Feature Flag** button or edit an existing flag. -1. Set the status of the default [spec](../../../ci/environments.md#scoping-environments-with-specs-premium) (`*`). Choose a rollout strategy. This status and rollout strategy combination will be used for _all_ environments. -1. If you want to enable/disable the feature on a specific environment, create a new [spec](../../../ci/environments.md#scoping-environments-with-specs-premium) and type the environment name. +1. Set the status of the default [spec](../../../ci/environments.md#scoping-environments-with-specs) (`*`). Choose a rollout strategy. This status and rollout strategy combination will be used for _all_ environments. +1. If you want to enable/disable the feature on a specific environment, create a new [spec](../../../ci/environments.md#scoping-environments-with-specs) and type the environment name. 1. Set the status and rollout strategy of the additional spec. This status and rollout strategy combination takes precedence over the default spec since we always use the most specific match available. 1. Click **Create feature flag** or **Update feature flag**. diff --git a/lib/api/api.rb b/lib/api/api.rb index 219ed45eff6..aa6a67d817a 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -118,7 +118,7 @@ module API mount ::API::GroupContainerRepositories mount ::API::GroupVariables mount ::API::ImportGithub - mount ::API::Internal + mount ::API::Internal::Base mount ::API::Issues mount ::API::JobArtifacts mount ::API::Jobs diff --git a/lib/api/internal.rb b/lib/api/internal.rb deleted file mode 100644 index 088ea5bd79a..00000000000 --- a/lib/api/internal.rb +++ /dev/null @@ -1,294 +0,0 @@ -# frozen_string_literal: true - -module API - # Internal access API - class Internal < Grape::API - before { authenticate_by_gitlab_shell_token! } - - helpers ::API::Helpers::InternalHelpers - helpers ::Gitlab::Identifier - - UNKNOWN_CHECK_RESULT_ERROR = 'Unknown check result'.freeze - - helpers do - def response_with_status(code: 200, success: true, message: nil, **extra_options) - status code - { status: success, message: message }.merge(extra_options).compact - end - - def lfs_authentication_url(project) - # This is a separate method so that EE can alter its behaviour more - # easily. - project.http_url_to_repo - end - end - - namespace 'internal' do - # Check if git command is allowed for project - # - # Params: - # key_id - ssh key id for Git over SSH - # user_id - user id for Git over HTTP or over SSH in keyless SSH CERT mode - # username - user name for Git over SSH in keyless SSH cert mode - # protocol - Git access protocol being used, e.g. HTTP or SSH - # project - project full_path (not path on disk) - # action - git action (git-upload-pack or git-receive-pack) - # changes - changes as "oldrev newrev ref", see Gitlab::ChangesList - # rubocop: disable CodeReuse/ActiveRecord - post "/allowed" do - # Stores some Git-specific env thread-safely - env = parse_env - Gitlab::Git::HookEnv.set(gl_repository, env) if project - - actor = - if params[:key_id] - Key.find_by(id: params[:key_id]) - elsif params[:user_id] - User.find_by(id: params[:user_id]) - elsif params[:username] - UserFinder.new(params[:username]).find_by_username - end - - protocol = params[:protocol] - - actor.update_last_used_at if actor.is_a?(Key) - user = - if actor.is_a?(Key) - actor.user - else - actor - end - - access_checker_klass = repo_type.access_checker_class - access_checker = access_checker_klass.new(actor, project, - protocol, authentication_abilities: ssh_authentication_abilities, - namespace_path: namespace_path, project_path: project_path, - redirected_path: redirected_path) - - check_result = begin - result = access_checker.check(params[:action], params[:changes]) - @project ||= access_checker.project - result - rescue Gitlab::GitAccess::UnauthorizedError => e - break response_with_status(code: 401, success: false, message: e.message) - rescue Gitlab::GitAccess::TimeoutError => e - break response_with_status(code: 503, success: false, message: e.message) - rescue Gitlab::GitAccess::NotFoundError => e - break response_with_status(code: 404, success: false, message: e.message) - end - - log_user_activity(actor) - - case check_result - when ::Gitlab::GitAccessResult::Success - payload = { - gl_repository: gl_repository, - gl_project_path: gl_project_path, - gl_id: Gitlab::GlId.gl_id(user), - gl_username: user&.username, - git_config_options: [], - gitaly: gitaly_payload(params[:action]), - gl_console_messages: check_result.console_messages - } - - # Custom option for git-receive-pack command - receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i - if receive_max_input_size > 0 - payload[:git_config_options] << "receive.maxInputSize=#{receive_max_input_size.megabytes}" - end - - response_with_status(**payload) - when ::Gitlab::GitAccessResult::CustomAction - response_with_status(code: 300, message: check_result.message, payload: check_result.payload) - else - response_with_status(code: 500, success: false, message: UNKNOWN_CHECK_RESULT_ERROR) - end - end - # rubocop: enable CodeReuse/ActiveRecord - - # rubocop: disable CodeReuse/ActiveRecord - post "/lfs_authenticate" do - status 200 - - if params[:key_id] - actor = Key.find(params[:key_id]) - actor.update_last_used_at - elsif params[:user_id] - actor = User.find_by(id: params[:user_id]) - raise ActiveRecord::RecordNotFound.new("No such user id!") unless actor - else - raise ActiveRecord::RecordNotFound.new("No key_id or user_id passed!") - end - - Gitlab::LfsToken - .new(actor) - .authentication_payload(lfs_authentication_url(project)) - end - # rubocop: enable CodeReuse/ActiveRecord - - get "/merge_request_urls" do - merge_request_urls - end - - # - # Get a ssh key using the fingerprint - # - # rubocop: disable CodeReuse/ActiveRecord - get "/authorized_keys" do - fingerprint = params.fetch(:fingerprint) do - Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint - end - key = Key.find_by(fingerprint: fingerprint) - not_found!("Key") if key.nil? - present key, with: Entities::SSHKey - end - # rubocop: enable CodeReuse/ActiveRecord - - # - # Discover user by ssh key, user id or username - # - # rubocop: disable CodeReuse/ActiveRecord - get "/discover" do - if params[:key_id] - key = Key.find(params[:key_id]) - user = key.user - elsif params[:user_id] - user = User.find_by(id: params[:user_id]) - elsif params[:username] - user = UserFinder.new(params[:username]).find_by_username - end - - present user, with: Entities::UserSafe - end - # rubocop: enable CodeReuse/ActiveRecord - - get "/check" do - { - api_version: API.version, - gitlab_version: Gitlab::VERSION, - gitlab_rev: Gitlab.revision, - redis: redis_ping - } - end - - get "/broadcast_messages" do - if messages = BroadcastMessage.current - present messages, with: Entities::BroadcastMessage - else - [] - end - end - - get "/broadcast_message" do - if message = BroadcastMessage.current&.last - present message, with: Entities::BroadcastMessage - else - {} - end - end - - # rubocop: disable CodeReuse/ActiveRecord - post '/two_factor_recovery_codes' do - status 200 - - if params[:key_id] - key = Key.find_by(id: params[:key_id]) - - if key - key.update_last_used_at - else - break { 'success' => false, 'message' => 'Could not find the given key' } - end - - if key.is_a?(DeployKey) - break { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' } - end - - user = key.user - - unless user - break { success: false, message: 'Could not find a user for the given key' } - end - elsif params[:user_id] - user = User.find_by(id: params[:user_id]) - - unless user - break { success: false, message: 'Could not find the given user' } - end - end - - unless user.two_factor_enabled? - break { success: false, message: 'Two-factor authentication is not enabled for this user' } - end - - codes = nil - - ::Users::UpdateService.new(current_user, user: user).execute! do |user| - codes = user.generate_otp_backup_codes! - end - - { success: true, recovery_codes: codes } - end - # rubocop: enable CodeReuse/ActiveRecord - - post '/pre_receive' do - status 200 - - reference_counter_increased = Gitlab::ReferenceCounter.new(params[:gl_repository]).increase - - { reference_counter_increased: reference_counter_increased } - end - - post "/notify_post_receive" do - status 200 - - # TODO: Re-enable when Gitaly is processing the post-receive notification - # return unless Gitlab::GitalyClient.enabled? - # - # begin - # repository = wiki? ? project.wiki.repository : project.repository - # Gitlab::GitalyClient::NotificationService.new(repository.raw_repository).post_receive - # rescue GRPC::Unavailable => e - # render_api_error!(e, 500) - # end - end - - post '/post_receive' do - status 200 - - response = Gitlab::InternalPostReceive::Response.new - user = identify(params[:identifier]) - project = Gitlab::GlRepository.parse(params[:gl_repository]).first - push_options = Gitlab::PushOptions.new(params[:push_options]) - - response.reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease - - PostReceive.perform_async(params[:gl_repository], params[:identifier], - params[:changes], push_options.as_json) - - mr_options = push_options.get(:merge_request) - if mr_options.present? - message = process_mr_push_options(mr_options, project, user, params[:changes]) - response.add_alert_message(message) - end - - broadcast_message = BroadcastMessage.current&.last&.message - response.add_alert_message(broadcast_message) - - response.add_merge_request_urls(merge_request_urls) - - # A user is not guaranteed to be returned; an orphaned write deploy - # key could be used - if user - redirect_message = Gitlab::Checks::ProjectMoved.fetch_message(user.id, project.id) - project_created_message = Gitlab::Checks::ProjectCreated.fetch_message(user.id, project.id) - - response.add_basic_message(redirect_message) - response.add_basic_message(project_created_message) - end - - present response, with: Entities::InternalPostReceive::Response - end - end - end -end diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb new file mode 100644 index 00000000000..622032b8355 --- /dev/null +++ b/lib/api/internal/base.rb @@ -0,0 +1,296 @@ +# frozen_string_literal: true + +module API + # Internal access API + module Internal + class Base < Grape::API + before { authenticate_by_gitlab_shell_token! } + + helpers ::API::Helpers::InternalHelpers + helpers ::Gitlab::Identifier + + UNKNOWN_CHECK_RESULT_ERROR = 'Unknown check result'.freeze + + helpers do + def response_with_status(code: 200, success: true, message: nil, **extra_options) + status code + { status: success, message: message }.merge(extra_options).compact + end + + def lfs_authentication_url(project) + # This is a separate method so that EE can alter its behaviour more + # easily. + project.http_url_to_repo + end + end + + namespace 'internal' do + # Check if git command is allowed for project + # + # Params: + # key_id - ssh key id for Git over SSH + # user_id - user id for Git over HTTP or over SSH in keyless SSH CERT mode + # username - user name for Git over SSH in keyless SSH cert mode + # protocol - Git access protocol being used, e.g. HTTP or SSH + # project - project full_path (not path on disk) + # action - git action (git-upload-pack or git-receive-pack) + # changes - changes as "oldrev newrev ref", see Gitlab::ChangesList + # rubocop: disable CodeReuse/ActiveRecord + post "/allowed" do + # Stores some Git-specific env thread-safely + env = parse_env + Gitlab::Git::HookEnv.set(gl_repository, env) if project + + actor = + if params[:key_id] + Key.find_by(id: params[:key_id]) + elsif params[:user_id] + User.find_by(id: params[:user_id]) + elsif params[:username] + UserFinder.new(params[:username]).find_by_username + end + + protocol = params[:protocol] + + actor.update_last_used_at if actor.is_a?(Key) + user = + if actor.is_a?(Key) + actor.user + else + actor + end + + access_checker_klass = repo_type.access_checker_class + access_checker = access_checker_klass.new(actor, project, + protocol, authentication_abilities: ssh_authentication_abilities, + namespace_path: namespace_path, project_path: project_path, + redirected_path: redirected_path) + + check_result = begin + result = access_checker.check(params[:action], params[:changes]) + @project ||= access_checker.project + result + rescue Gitlab::GitAccess::UnauthorizedError => e + break response_with_status(code: 401, success: false, message: e.message) + rescue Gitlab::GitAccess::TimeoutError => e + break response_with_status(code: 503, success: false, message: e.message) + rescue Gitlab::GitAccess::NotFoundError => e + break response_with_status(code: 404, success: false, message: e.message) + end + + log_user_activity(actor) + + case check_result + when ::Gitlab::GitAccessResult::Success + payload = { + gl_repository: gl_repository, + gl_project_path: gl_project_path, + gl_id: Gitlab::GlId.gl_id(user), + gl_username: user&.username, + git_config_options: [], + gitaly: gitaly_payload(params[:action]), + gl_console_messages: check_result.console_messages + } + + # Custom option for git-receive-pack command + receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i + if receive_max_input_size > 0 + payload[:git_config_options] << "receive.maxInputSize=#{receive_max_input_size.megabytes}" + end + + response_with_status(**payload) + when ::Gitlab::GitAccessResult::CustomAction + response_with_status(code: 300, message: check_result.message, payload: check_result.payload) + else + response_with_status(code: 500, success: false, message: UNKNOWN_CHECK_RESULT_ERROR) + end + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + post "/lfs_authenticate" do + status 200 + + if params[:key_id] + actor = Key.find(params[:key_id]) + actor.update_last_used_at + elsif params[:user_id] + actor = User.find_by(id: params[:user_id]) + raise ActiveRecord::RecordNotFound.new("No such user id!") unless actor + else + raise ActiveRecord::RecordNotFound.new("No key_id or user_id passed!") + end + + Gitlab::LfsToken + .new(actor) + .authentication_payload(lfs_authentication_url(project)) + end + # rubocop: enable CodeReuse/ActiveRecord + + get "/merge_request_urls" do + merge_request_urls + end + + # + # Get a ssh key using the fingerprint + # + # rubocop: disable CodeReuse/ActiveRecord + get "/authorized_keys" do + fingerprint = params.fetch(:fingerprint) do + Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint + end + key = Key.find_by(fingerprint: fingerprint) + not_found!("Key") if key.nil? + present key, with: Entities::SSHKey + end + # rubocop: enable CodeReuse/ActiveRecord + + # + # Discover user by ssh key, user id or username + # + # rubocop: disable CodeReuse/ActiveRecord + get "/discover" do + if params[:key_id] + key = Key.find(params[:key_id]) + user = key.user + elsif params[:user_id] + user = User.find_by(id: params[:user_id]) + elsif params[:username] + user = UserFinder.new(params[:username]).find_by_username + end + + present user, with: Entities::UserSafe + end + # rubocop: enable CodeReuse/ActiveRecord + + get "/check" do + { + api_version: API.version, + gitlab_version: Gitlab::VERSION, + gitlab_rev: Gitlab.revision, + redis: redis_ping + } + end + + get "/broadcast_messages" do + if messages = BroadcastMessage.current + present messages, with: Entities::BroadcastMessage + else + [] + end + end + + get "/broadcast_message" do + if message = BroadcastMessage.current&.last + present message, with: Entities::BroadcastMessage + else + {} + end + end + + # rubocop: disable CodeReuse/ActiveRecord + post '/two_factor_recovery_codes' do + status 200 + + if params[:key_id] + key = Key.find_by(id: params[:key_id]) + + if key + key.update_last_used_at + else + break { 'success' => false, 'message' => 'Could not find the given key' } + end + + if key.is_a?(DeployKey) + break { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' } + end + + user = key.user + + unless user + break { success: false, message: 'Could not find a user for the given key' } + end + elsif params[:user_id] + user = User.find_by(id: params[:user_id]) + + unless user + break { success: false, message: 'Could not find the given user' } + end + end + + unless user.two_factor_enabled? + break { success: false, message: 'Two-factor authentication is not enabled for this user' } + end + + codes = nil + + ::Users::UpdateService.new(current_user, user: user).execute! do |user| + codes = user.generate_otp_backup_codes! + end + + { success: true, recovery_codes: codes } + end + # rubocop: enable CodeReuse/ActiveRecord + + post '/pre_receive' do + status 200 + + reference_counter_increased = Gitlab::ReferenceCounter.new(params[:gl_repository]).increase + + { reference_counter_increased: reference_counter_increased } + end + + post "/notify_post_receive" do + status 200 + + # TODO: Re-enable when Gitaly is processing the post-receive notification + # return unless Gitlab::GitalyClient.enabled? + # + # begin + # repository = wiki? ? project.wiki.repository : project.repository + # Gitlab::GitalyClient::NotificationService.new(repository.raw_repository).post_receive + # rescue GRPC::Unavailable => e + # render_api_error!(e, 500) + # end + end + + post '/post_receive' do + status 200 + + response = Gitlab::InternalPostReceive::Response.new + user = identify(params[:identifier]) + project = Gitlab::GlRepository.parse(params[:gl_repository]).first + push_options = Gitlab::PushOptions.new(params[:push_options]) + + response.reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease + + PostReceive.perform_async(params[:gl_repository], params[:identifier], + params[:changes], push_options.as_json) + + mr_options = push_options.get(:merge_request) + if mr_options.present? + message = process_mr_push_options(mr_options, project, user, params[:changes]) + response.add_alert_message(message) + end + + broadcast_message = BroadcastMessage.current&.last&.message + response.add_alert_message(broadcast_message) + + response.add_merge_request_urls(merge_request_urls) + + # A user is not guaranteed to be returned; an orphaned write deploy + # key could be used + if user + redirect_message = Gitlab::Checks::ProjectMoved.fetch_message(user.id, project.id) + project_created_message = Gitlab::Checks::ProjectCreated.fetch_message(user.id, project.id) + + response.add_basic_message(redirect_message) + response.add_basic_message(project_created_message) + end + + present response, with: Entities::InternalPostReceive::Response + end + end + end + end +end diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb index 5d1b40e3bff..def36dc8529 100644 --- a/lib/api/release/links.rb +++ b/lib/api/release/links.rb @@ -5,7 +5,7 @@ module API class Links < Grape::API include PaginationParams - RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS + RELEASE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS .merge(tag_name: API::NO_SLASH_URL_PART_REGEX) before { authorize! :read_release, user_project } @@ -17,7 +17,7 @@ module API params do requires :tag_name, type: String, desc: 'The name of the tag', as: :tag end - resource 'releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do + resource 'releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do resource :assets do desc 'Get a list of links of a release' do detail 'This feature was introduced in GitLab 11.7.' diff --git a/lib/api/releases.rb b/lib/api/releases.rb index 11a9a085068..5a31581c4da 100644 --- a/lib/api/releases.rb +++ b/lib/api/releases.rb @@ -4,7 +4,7 @@ module API class Releases < Grape::API include PaginationParams - RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS + RELEASE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS .merge(tag_name: API::NO_SLASH_URL_PART_REGEX) before { authorize_read_releases! } @@ -33,7 +33,7 @@ module API params do requires :tag_name, type: String, desc: 'The name of the tag', as: :tag end - get ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do + get ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do authorize_download_code! present release, with: Entities::Release, current_user: current_user @@ -82,7 +82,7 @@ module API optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready.' optional :milestone, type: String, desc: 'The title of the related milestone' end - put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do + put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do authorize_update_release! result = ::Releases::UpdateService @@ -103,7 +103,7 @@ module API params do requires :tag_name, type: String, desc: 'The name of the tag', as: :tag end - delete ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do + delete ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do authorize_destroy_release! result = ::Releases::DestroyService diff --git a/lib/gitlab/jwt_authenticatable.rb b/lib/gitlab/jwt_authenticatable.rb new file mode 100644 index 00000000000..1270a148e8d --- /dev/null +++ b/lib/gitlab/jwt_authenticatable.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Gitlab + module JwtAuthenticatable + # Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32 + # bytes https://tools.ietf.org/html/rfc4868#section-2.6 + SECRET_LENGTH = 32 + + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + include Gitlab::Utils::StrongMemoize + + def decode_jwt_for_issuer(issuer, encoded_message) + JWT.decode( + encoded_message, + secret, + true, + { iss: issuer, verify_iss: true, algorithm: 'HS256' } + ) + end + + def secret + strong_memoize(:secret) do + Base64.strict_decode64(File.read(secret_path).chomp).tap do |bytes| + raise "#{secret_path} does not contain #{SECRET_LENGTH} bytes" if bytes.length != SECRET_LENGTH + end + end + end + + def write_secret + bytes = SecureRandom.random_bytes(SECRET_LENGTH) + File.open(secret_path, 'w:BINARY', 0600) do |f| + f.chmod(0600) # If the file already existed, the '0600' passed to 'open' above was a no-op. + f.write(Base64.strict_encode64(bytes)) + end + end + end + end +end diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 29087d26007..139ec6e384a 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -15,9 +15,7 @@ module Gitlab ALLOWED_GIT_HTTP_ACTIONS = %w[git_receive_pack git_upload_pack info_refs].freeze DETECT_HEADER = 'Gitlab-Workhorse-Detect-Content-Type'.freeze - # Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32 - # bytes https://tools.ietf.org/html/rfc4868#section-2.6 - SECRET_LENGTH = 32 + include JwtAuthenticatable class << self def git_http_ok(repository, repo_type, user, action, show_all_refs: false) @@ -187,34 +185,12 @@ module Gitlab path.readable? ? path.read.chomp : 'unknown' end - def secret - @secret ||= begin - bytes = Base64.strict_decode64(File.read(secret_path).chomp) - raise "#{secret_path} does not contain #{SECRET_LENGTH} bytes" if bytes.length != SECRET_LENGTH - - bytes - end - end - - def write_secret - bytes = SecureRandom.random_bytes(SECRET_LENGTH) - File.open(secret_path, 'w:BINARY', 0600) do |f| - f.chmod(0600) # If the file already existed, the '0600' passed to 'open' above was a no-op. - f.write(Base64.strict_encode64(bytes)) - end - end - def verify_api_request!(request_headers) decode_jwt(request_headers[INTERNAL_API_REQUEST_HEADER]) end def decode_jwt(encoded_message) - JWT.decode( - encoded_message, - secret, - true, - { iss: 'gitlab-workhorse', verify_iss: true, algorithm: 'HS256' } - ) + decode_jwt_for_issuer('gitlab-workhorse', encoded_message) end def secret_path diff --git a/lib/peek/views/active_record.rb b/lib/peek/views/active_record.rb index bbc9f11e90f..1bb3ddb964a 100644 --- a/lib/peek/views/active_record.rb +++ b/lib/peek/views/active_record.rb @@ -5,15 +5,15 @@ module Peek class ActiveRecord < DetailedView DEFAULT_THRESHOLDS = { calls: 100, - duration: 3, - individual_call: 1 + duration: 3000, + individual_call: 1000 }.freeze THRESHOLDS = { production: { calls: 100, - duration: 15, - individual_call: 5 + duration: 15000, + individual_call: 5000 } }.freeze diff --git a/lib/peek/views/gitaly.rb b/lib/peek/views/gitaly.rb index f669feae254..7dc00b16cc0 100644 --- a/lib/peek/views/gitaly.rb +++ b/lib/peek/views/gitaly.rb @@ -5,15 +5,15 @@ module Peek class Gitaly < DetailedView DEFAULT_THRESHOLDS = { calls: 30, - duration: 1, - individual_call: 0.5 + duration: 1000, + individual_call: 500 }.freeze THRESHOLDS = { production: { calls: 30, - duration: 1, - individual_call: 0.5 + duration: 1000, + individual_call: 500 } }.freeze @@ -24,7 +24,7 @@ module Peek private def duration - ::Gitlab::GitalyClient.query_time + ::Gitlab::GitalyClient.query_time * 1000 end def calls diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a5619079988..629daccf006 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1960,7 +1960,10 @@ msgstr "" msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found." msgstr "" -msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly." +msgid "CICD|You must add a %{base_domain_link_start}base domain%{link_end} to your %{kubernetes_cluster_link_start}Kubernetes cluster%{link_end} in order for your deployment strategy to work." +msgstr "" + +msgid "CICD|You must add a %{kubernetes_cluster_link_start}Kubernetes cluster integration%{link_end} to this project with a domain in order for your deployment strategy to work correctly." msgstr "" msgid "CICD|group enabled" @@ -10879,6 +10882,9 @@ msgstr "" msgid "Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging." msgstr "" +msgid "Star labels to start sorting by priority" +msgstr "" + msgid "Star toggle failed. Try again later." msgstr "" diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index 26db4d05906..a9549171b54 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -164,7 +164,7 @@ function create_application_secret() { function download_chart() { echoinfo "Downloading the GitLab chart..." true - curl -o gitlab.tar.bz2 "https://gitlab.com/gitlab-org/charts/gitlab/-/archive/${GITLAB_HELM_CHART_REF}/gitlab-${GITLAB_HELM_CHART_REF}.tar.bz2" + curl --location -o gitlab.tar.bz2 "https://gitlab.com/gitlab-org/charts/gitlab/-/archive/${GITLAB_HELM_CHART_REF}/gitlab-${GITLAB_HELM_CHART_REF}.tar.bz2" tar -xjf gitlab.tar.bz2 cd "gitlab-${GITLAB_HELM_CHART_REF}" diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index bb7abe52eae..05e6ea1394d 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -1,6 +1,6 @@ /* global ListIssue */ -import Vue from 'vue'; +import axios from '~/lib/utils/axios_utils'; import '~/boards/models/label'; import '~/boards/models/assignee'; import '~/boards/models/issue'; @@ -175,7 +175,7 @@ describe('Issue model', () => { describe('update', () => { it('passes assignee ids when there are assignees', done => { - spyOn(Vue.http, 'patch').and.callFake((url, data) => { + spyOn(axios, 'patch').and.callFake((url, data) => { expect(data.issue.assignee_ids).toEqual([1]); done(); return Promise.resolve(); @@ -185,7 +185,7 @@ describe('Issue model', () => { }); it('passes assignee ids of [0] when there are no assignees', done => { - spyOn(Vue.http, 'patch').and.callFake((url, data) => { + spyOn(axios, 'patch').and.callFake((url, data) => { expect(data.issue.assignee_ids).toEqual([0]); done(); return Promise.resolve(); diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js index 31873311e16..23b2564d3f9 100644 --- a/spec/javascripts/groups/components/app_spec.js +++ b/spec/javascripts/groups/components/app_spec.js @@ -1,3 +1,4 @@ +import '~/flash'; import $ from 'jquery'; import Vue from 'vue'; @@ -333,7 +334,7 @@ describe('AppComponent', () => { it('hides modal confirmation leave group and remove group item from tree', done => { const notice = `You left the "${childGroupItem.fullName}" group.`; - spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ notice })); + spyOn(vm.service, 'leaveGroup').and.returnValue(Promise.resolve({ data: { notice } })); spyOn(vm.store, 'removeGroup').and.callThrough(); spyOn(window, 'Flash'); spyOn($, 'scrollTo'); diff --git a/spec/javascripts/groups/service/groups_service_spec.js b/spec/javascripts/groups/service/groups_service_spec.js index 339e5131615..45db962a1ef 100644 --- a/spec/javascripts/groups/service/groups_service_spec.js +++ b/spec/javascripts/groups/service/groups_service_spec.js @@ -1,11 +1,8 @@ -import Vue from 'vue'; -import VueResource from 'vue-resource'; +import axios from '~/lib/utils/axios_utils'; import GroupsService from '~/groups/service/groups_service'; import { mockEndpoint, mockParentGroupItem } from '../mock_data'; -Vue.use(VueResource); - describe('GroupsService', () => { let service; @@ -15,8 +12,8 @@ describe('GroupsService', () => { describe('getGroups', () => { it('should return promise for `GET` request on provided endpoint', () => { - spyOn(service.groups, 'get').and.stub(); - const queryParams = { + spyOn(axios, 'get').and.stub(); + const params = { page: 2, filter: 'git', sort: 'created_asc', @@ -25,21 +22,21 @@ describe('GroupsService', () => { service.getGroups(55, 2, 'git', 'created_asc', true); - expect(service.groups.get).toHaveBeenCalledWith({ parent_id: 55 }); + expect(axios.get).toHaveBeenCalledWith(mockEndpoint, { params: { parent_id: 55 } }); service.getGroups(null, 2, 'git', 'created_asc', true); - expect(service.groups.get).toHaveBeenCalledWith(queryParams); + expect(axios.get).toHaveBeenCalledWith(mockEndpoint, { params }); }); }); describe('leaveGroup', () => { it('should return promise for `DELETE` request on provided endpoint', () => { - spyOn(Vue.http, 'delete').and.stub(); + spyOn(axios, 'delete').and.stub(); service.leaveGroup(mockParentGroupItem.leavePath); - expect(Vue.http.delete).toHaveBeenCalledWith(mockParentGroupItem.leavePath); + expect(axios.delete).toHaveBeenCalledWith(mockParentGroupItem.leavePath); }); }); }); diff --git a/spec/lib/gitlab/favicon_spec.rb b/spec/lib/gitlab/favicon_spec.rb index 23de12f12f8..63c26e29d73 100644 --- a/spec/lib/gitlab/favicon_spec.rb +++ b/spec/lib/gitlab/favicon_spec.rb @@ -1,14 +1,16 @@ require 'spec_helper' RSpec.describe Gitlab::Favicon, :request_store do + include RailsHelpers + describe '.main' do it 'defaults to favicon.png' do - allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production')) + stub_rails_env('production') expect(described_class.main).to match_asset_path '/assets/favicon.png' end it 'has blue favicon for development', unless: Gitlab.ee? do - allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development')) + stub_rails_env('development') expect(described_class.main).to match_asset_path '/assets/favicon-blue.png' end @@ -24,7 +26,7 @@ RSpec.describe Gitlab::Favicon, :request_store do context 'asset host' do before do - allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production')) + stub_rails_env('production') end it 'returns a relative url when the asset host is not configured' do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index c6aa4a2482c..47ba7eff8ed 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -26,6 +26,7 @@ issues: events: - author - project +- group - target - push_event_payload notes: diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index f0545176a90..516e62c4728 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -33,6 +33,7 @@ Event: - target_type - target_id - project_id +- group_id - created_at - updated_at - action diff --git a/spec/lib/gitlab/jwt_authenticatable_spec.rb b/spec/lib/gitlab/jwt_authenticatable_spec.rb new file mode 100644 index 00000000000..0c1c491b308 --- /dev/null +++ b/spec/lib/gitlab/jwt_authenticatable_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::JwtAuthenticatable do + let(:test_class) do + Class.new do + include Gitlab::JwtAuthenticatable + + def self.secret_path + Rails.root.join('tmp', 'tests', '.jwt_shared_secret') + end + end + end + + before do + begin + File.delete(test_class.secret_path) + rescue Errno::ENOENT + end + + test_class.write_secret + end + + describe '.secret' do + subject(:secret) { test_class.secret } + + it 'returns 32 bytes' do + expect(secret).to be_a(String) + expect(secret.length).to eq(32) + expect(secret.encoding).to eq(Encoding::ASCII_8BIT) + end + + it 'accepts a trailing newline' do + File.open(test_class.secret_path, 'a') { |f| f.write "\n" } + + expect(secret.length).to eq(32) + end + + it 'raises an exception if the secret file cannot be read' do + File.delete(test_class.secret_path) + + expect { secret }.to raise_exception(Errno::ENOENT) + end + + it 'raises an exception if the secret file contains the wrong number of bytes' do + File.truncate(test_class.secret_path, 0) + + expect { secret }.to raise_exception(RuntimeError) + end + end + + describe '.write_secret' do + it 'uses mode 0600' do + expect(File.stat(test_class.secret_path).mode & 0777).to eq(0600) + end + + it 'writes base64 data' do + bytes = Base64.strict_decode64(File.read(test_class.secret_path)) + + expect(bytes).not_to be_empty + end + end + + describe '.decode_jwt_for_issuer' do + let(:payload) { { 'iss' => 'test_issuer' } } + + it 'accepts a correct header' do + encoded_message = JWT.encode(payload, test_class.secret, 'HS256') + + expect { test_class.decode_jwt_for_issuer('test_issuer', encoded_message) }.not_to raise_error + end + + it 'raises an error when the JWT is not signed' do + encoded_message = JWT.encode(payload, nil, 'none') + + expect { test_class.decode_jwt_for_issuer('test_issuer', encoded_message) }.to raise_error(JWT::DecodeError) + end + + it 'raises an error when the header is signed with the wrong secret' do + encoded_message = JWT.encode(payload, 'wrongsecret', 'HS256') + + expect { test_class.decode_jwt_for_issuer('test_issuer', encoded_message) }.to raise_error(JWT::DecodeError) + end + + it 'raises an error when the issuer is incorrect' do + payload['iss'] = 'somebody else' + encoded_message = JWT.encode(payload, test_class.secret, 'HS256') + + expect { test_class.decode_jwt_for_issuer('test_issuer', encoded_message) }.to raise_error(JWT::DecodeError) + end + end +end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 98421cd12d3..88bc5034da5 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -200,57 +200,6 @@ describe Gitlab::Workhorse do end end - describe ".secret" do - subject { described_class.secret } - - before do - described_class.instance_variable_set(:@secret, nil) - described_class.write_secret - end - - it 'returns 32 bytes' do - expect(subject).to be_a(String) - expect(subject.length).to eq(32) - expect(subject.encoding).to eq(Encoding::ASCII_8BIT) - end - - it 'accepts a trailing newline' do - File.open(described_class.secret_path, 'a') { |f| f.write "\n" } - expect(subject.length).to eq(32) - end - - it 'raises an exception if the secret file cannot be read' do - File.delete(described_class.secret_path) - expect { subject }.to raise_exception(Errno::ENOENT) - end - - it 'raises an exception if the secret file contains the wrong number of bytes' do - File.truncate(described_class.secret_path, 0) - expect { subject }.to raise_exception(RuntimeError) - end - end - - describe ".write_secret" do - let(:secret_path) { described_class.secret_path } - before do - begin - File.delete(secret_path) - rescue Errno::ENOENT - end - - described_class.write_secret - end - - it 'uses mode 0600' do - expect(File.stat(secret_path).mode & 0777).to eq(0600) - end - - it 'writes base64 data' do - bytes = Base64.strict_decode64(File.read(secret_path)) - expect(bytes).not_to be_empty - end - end - describe '#verify_api_request!' do let(:header_key) { described_class::INTERNAL_API_REQUEST_HEADER } let(:payload) { { 'iss' => 'gitlab-workhorse' } } diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index 1fc363460ae..74d4b12a070 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require 'fast_spec_helper' - -require_dependency 'gitlab' +require 'spec_helper' describe Gitlab do + include RailsHelpers + describe '.root' do it 'returns the root path of the app' do expect(described_class.root).to eq(Pathname.new(File.expand_path('../..', __dir__))) @@ -113,7 +113,7 @@ describe Gitlab do it 'is true when dev env' do allow(described_class).to receive_messages(com?: false, org?: false) - allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development')) + stub_rails_env('development') expect(described_class.dev_env_org_or_com?).to eq true end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal/base_spec.rb index c94f6d22e74..a56527073c7 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::Internal do +describe API::Internal::Base do set(:user) { create(:user) } let(:key) { create(:key, user: user) } set(:project) { create(:project, :repository, :wiki_repo) } diff --git a/spec/support/helpers/rails_helpers.rb b/spec/support/helpers/rails_helpers.rb new file mode 100644 index 00000000000..e1875b2fb15 --- /dev/null +++ b/spec/support/helpers/rails_helpers.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module RailsHelpers + def stub_rails_env(env_name) + allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new(env_name)) + end +end diff --git a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb index ff2d491539b..697e44be065 100644 --- a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb +++ b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb @@ -17,10 +17,16 @@ describe 'projects/settings/ci_cd/_autodevops_form' do context 'when the project has an available kubernetes cluster' do let!(:cluster) { create(:cluster, cluster_type: :project_type, projects: [project]) } - it 'does not show a warning message' do + it 'does not show a warning message about Kubernetes cluster' do render expect(rendered).not_to have_text('You must add a Kubernetes cluster') end + + it 'shows a warning message about base domain' do + render + + expect(rendered).to have_text('You must add a base domain to your Kubernetes cluster in order for your deployment strategy to work.') + end end end diff --git a/yarn.lock b/yarn.lock index 4cf3a9584f1..bd8f9014131 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4909,7 +4909,7 @@ exports-loader@^0.7.0: express@^4.16.2, express@^4.16.3: version "4.16.3" - resolved "http://registry.npmjs.org/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" + resolved "https://registry.npmjs.org/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" integrity sha1-avilAjUNsyRuzEvs9rWjTSL37VM= dependencies: accepts "~1.3.5" @@ -8304,7 +8304,7 @@ mixin-deep@^1.2.0: mkdirp@0.5.x, mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" - resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= dependencies: minimist "0.0.8" |