Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-10-05 21:13:27 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-05 21:13:27 +0300
commitad41744a177d11ead3268b1ec706e9c26f593060 (patch)
tree3c06d26fa5577a484e6c096a2b6b28ee14461b23
parenta84626f13d61d190b2db5e44caf71b22fc541276 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/dast.gitlab-ci.yml211
-rw-r--r--app/assets/javascripts/access_tokens/index.js2
-rw-r--r--app/assets/javascripts/comment_type_toggle.js71
-rw-r--r--app/assets/javascripts/deprecated_notes.js73
-rw-r--r--app/assets/javascripts/notes/components/comment_type_dropdown.vue8
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue11
-rw-r--r--app/controllers/groups/dependency_proxy_for_containers_controller.rb46
-rw-r--r--app/helpers/workhorse_helper.rb9
-rw-r--r--app/services/ci/pipelines/add_job_service.rb10
-rw-r--r--app/uploaders/dependency_proxy/file_uploader.rb1
-rw-r--r--app/views/clusters/clusters/show.html.haml7
-rw-r--r--app/views/shared/notes/_comment_button.html.haml29
-rw-r--r--config/feature_flags/development/ci_pipeline_add_job_with_lock.yml8
-rw-r--r--config/feature_flags/development/dependency_proxy_workhorse.yml8
-rw-r--r--config/feature_flags/development/use_rate_limiting_store_for_application_rate_limiter.yml8
-rw-r--r--config/routes/group.rb2
-rw-r--r--doc/development/snowplow/index.md228
-rw-r--r--doc/user/infrastructure/clusters/connect/index.md5
-rw-r--r--doc/user/packages/composer_repository/index.md1
-rw-r--r--doc/user/project/integrations/webhooks.md110
-rw-r--r--lib/gitlab/application_rate_limiter.rb10
-rw-r--r--lib/gitlab/middleware/multipart.rb1
-rw-r--r--lib/gitlab/rack_attack/instrumented_cache_store.rb20
-rw-r--r--lib/gitlab/redis/rate_limiting.rb4
-rw-r--r--lib/gitlab/workhorse.rb12
-rw-r--r--locale/gitlab.pot24
-rw-r--r--package.json2
-rw-r--r--spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb176
-rw-r--r--spec/controllers/projects/pipeline_schedules_controller_spec.rb2
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb2
-rw-r--r--spec/controllers/projects_controller_spec.rb8
-rw-r--r--spec/features/groups/dependency_proxy_for_containers_spec.rb108
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb4
-rw-r--r--spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap2
-rw-r--r--spec/frontend/comment_type_toggle_spec.js169
-rw-r--r--spec/frontend/notes/components/comment_type_dropdown_spec.js14
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap2
-rw-r--r--spec/lib/gitlab/application_rate_limiter_spec.rb122
-rw-r--r--spec/lib/gitlab/middleware/multipart/handler_spec.rb1
-rw-r--r--spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb13
-rw-r--r--spec/lib/gitlab/rate_limit_helpers_spec.rb2
-rw-r--r--spec/services/ci/pipelines/add_job_service_spec.rb13
-rw-r--r--spec/services/web_hook_service_spec.rb2
-rw-r--r--spec/support/shared_examples/features/discussion_comments_shared_example.rb30
-rw-r--r--workhorse/internal/dependencyproxy/dependencyproxy.go125
-rw-r--r--workhorse/internal/dependencyproxy/dependencyproxy_test.go98
-rw-r--r--workhorse/internal/upstream/routes.go11
-rw-r--r--workhorse/main_test.go98
-rw-r--r--yarn.lock18
49 files changed, 570 insertions, 1371 deletions
diff --git a/.gitlab/ci/dast.gitlab-ci.yml b/.gitlab/ci/dast.gitlab-ci.yml
index 309714f8739..f4e30830185 100644
--- a/.gitlab/ci/dast.gitlab-ci.yml
+++ b/.gitlab/ci/dast.gitlab-ci.yml
@@ -10,29 +10,21 @@
variables:
DAST_USERNAME_FIELD: "user[login]"
DAST_PASSWORD_FIELD: "user[password]"
+ DAST_SUBMIT_FIELD: "commit"
DAST_FULL_SCAN_ENABLED: "true"
- DAST_SPIDER_MINS: 0
- # TBD pin to a version
- DAST_VERSION: 1.22.1
+ DAST_VERSION: 2
+ GIT_STRATEGY: none
# -Xmx is used to set the JVM memory to 6GB to prevent DAST OutOfMemoryError.
DAST_ZAP_CLI_OPTIONS: "-Xmx6144m"
- DAST_RULES: "41,42,43,10027,10032,10041,10042,10045,10047,10052,10053,10057,10061,10096,10097,10104,10106,20012,20014,20015,20016,20017,20018,40019,40020,40021,40024,40025,40027,40029,40032,90001,90019,10109,10026,10028,10029,10030,10031,10033,10034,10035,10036,10038,10039,10043,10044,10048,10050,10051,10058,10062,10095,10107,10108,30003,40013,40022,40023,40028,90021,90023,90024,90025,90027,90028,10003,50003,0,2,3,6,7,10010,10011,10015,10017,10019,10020,10021,10023,10024,10025,10037,10040,10054,10055,10056,10098,10105,10202,20019,30001,30002,40003,40008,40009,40012,40014,40016,40017,40018,50000,50001,90011,90020,90022,90033"
before_script:
- 'export DAST_WEBSITE="${DAST_WEBSITE:-$(cat environment_url.txt)}"'
- 'export DAST_AUTH_URL="${DAST_WEBSITE}/users/sign_in"'
- 'export DAST_PASSWORD="${REVIEW_APPS_ROOT_PASSWORD}"'
- # Below three lines can be removed once https://gitlab.com/gitlab-org/gitlab/-/issues/230687 is fixed
- - mkdir -p /zap/xml
- - 'sed -i "84 s/true/false/" /zap/xml/config.xml'
- - cat /zap/xml/config.xml
# Help pages are excluded from scan as they are static pages.
# profile/two_factor_auth is excluded from scan to prevent 2FA from being turned on from user profile, which will reduce coverage.
- - 'export DAST_AUTH_EXCLUDE_URLS="${DAST_WEBSITE}/help/.*,${DAST_WEBSITE}/profile/two_factor_auth,${DAST_WEBSITE}/users/sign_out"'
+ - 'DAST_EXCLUDE_URLS="${DAST_WEBSITE}/help/.*,${DAST_WEBSITE}/-/profile/two_factor_auth,${DAST_WEBSITE}/users/sign_out"'
# Exclude the automatically generated monitoring project from being tested due to https://gitlab.com/gitlab-org/gitlab/-/issues/260362
- - 'DAST_AUTH_EXCLUDE_URLS="${DAST_AUTH_EXCLUDE_URLS},https://.*\.gitlab-review\.app/gitlab-instance-(administrators-)?[a-zA-Z0-9]{8}/.*"'
- - enable_rule () { read all_rules; rule=$1; echo $all_rules | sed -r "s/(,)?$rule(,)?/\1-1\2/" ; }
- # Sort ids in DAST_RULES ascendingly, which is required when using DAST_RULES as argument to enable_rule
- - 'DAST_RULES=$(echo $DAST_RULES | tr "," "\n" | sort -n | paste -sd ",")'
+ - 'export DAST_EXCLUDE_URLS="${DAST_EXCLUDE_URLS},${DAST_WEBSITE}/gitlab-instance-.*"'
needs: ["review-deploy"]
stage: dast
# Default job timeout set to 90m and dast rules needs 2h to so that it won't timeout.
@@ -47,159 +39,152 @@
expire_in: 1 week # GitLab-specific
# DAST scan with a subset of Release scan rules.
-DAST-fullscan-ruleset1:
+# ZAP rule details can be found at https://www.zaproxy.org/docs/alerts/
+
+# 10019, 10021 Missing security headers
+# 10023, 10024, 10025, 10037 Information Disclosure
+# 10040 Secure Pages Include Mixed Content
+# 10055 CSP
+# 10056 X-Debug-Token Information Leak
+# Duration: 14 minutes 20 seconds
+
+dast:secureHeaders-csp-infoLeak:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user1"
+ DAST_ONLY_INCLUDE_RULES: "10019,10021,10023,10024,10025,10037,10040,10055,10056"
script:
- - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 10019 | enable_rule 10020 | enable_rule 10021 | enable_rule 10023 | enable_rule 10024 | enable_rule 10025 | enable_rule 10037 | enable_rule 10040 | enable_rule 10054 | enable_rule 10055 | enable_rule 10056)
- - echo $DAST_EXCLUDE_RULES
- - /analyze -t $DAST_WEBSITE -d
+ - /analyze
-# DAST scan with a subset of Release scan rules.
-DAST-fullscan-ruleset2:
+# 90023 XML External Entity Attack
+# Duration: 41 minutes 20 seconds
+# 90019 Server Side Code Injection
+# Duration: 34 minutes 31 seconds
+dast:XXE-SrvSideInj:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user2"
+ DAST_ONLY_INCLUDE_RULES: "90023,90019"
script:
- - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 90011 | enable_rule 90020 | enable_rule 90022 | enable_rule 90033)
- - echo $DAST_EXCLUDE_RULES
- - /analyze -t $DAST_WEBSITE -d
+ - /analyze
-# DAST scan with a subset of Release scan rules.
-DAST-fullscan-ruleset3:
+# 0 Directory Browsing
+# 2 Private IP Disclosure
+# 3 Session ID in URL Rewrite
+# 7 Remote File Inclusion
+# Duration: 63 minutes 43 seconds
+# 90034 Cloud Metadata Potentially Exposed
+# Duration: 13 minutes 48 seconds
+# 90022 Application Error Disclosure
+# Duration: 12 minutes 7 seconds
+dast:infoLeak-fileInc-DirBrowsing:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user3"
+ DAST_ONLY_INCLUDE_RULES: "0,2,3,7,90034,90022"
script:
- - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 40016 | enable_rule 40017 | enable_rule 50000 | enable_rule 50001)
- - echo $DAST_EXCLUDE_RULES
- - /analyze -t $DAST_WEBSITE -d
+ - /analyze
-# DAST scan with a subset of Release scan rules.
-DAST-fullscan-ruleset4:
+# 10010 Cookie No HttpOnly Flag
+# 10011 Cookie Without Secure Flag
+# 10017 Cross-Domain JavaScript Source File Inclusion
+# 10029 Cookie Poisoning
+# 90033 Loosely Scoped Cookie
+# 10054 Cookie Without SameSite Attribute
+# Duration: 13 minutes 23 seconds
+dast:insecureCookie:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user4"
+ DAST_ONLY_INCLUDE_RULES: "10010,10011,10017,10029,90033,10054"
script:
- - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 0 | enable_rule 2 | enable_rule 3 | enable_rule 7 )
- - echo $DAST_EXCLUDE_RULES
- - /analyze -t $DAST_WEBSITE -d
+ - /analyze
-# DAST scan with a subset of Release scan rules.
-DAST-fullscan-ruleset5:
- extends:
- - .dast_conf
- variables:
- DAST_USERNAME: "user5"
- script:
- - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 10010 | enable_rule 10011 | enable_rule 10017 | enable_rule 10019)
- - echo $DAST_EXCLUDE_RULES
- - /analyze -t $DAST_WEBSITE -d
-
-# DAST scan with a subset of Release scan rules.
-DAST-fullscan-ruleset6:
- extends:
- - .dast_conf
- variables:
- DAST_USERNAME: "user6"
- script:
- - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 30001 | enable_rule 40009)
- - echo $DAST_EXCLUDE_RULES
- - /analyze -t $DAST_WEBSITE -d
-# Enable when https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39749 is fixed
-# DAST scan with a subset of Beta scan rules.
-# DAST-fullscan-ruleset7:
-# extends:
-# - .dast_conf
-# variables:
-# DAST_USERNAME: "user7"
-# script:
-# - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 10098 | enable_rule 10105 | enable_rule 10202 | enable_rule 30002 | enable_rule 40003 | enable_rule 40008 | enable_rule 40009)
-# - echo $DAST_EXCLUDE_RULES
-# - /analyze -t $DAST_WEBSITE -d
+# 20012 Anti-CSRF Tokens Check
+# 10202 Absence of Anti-CSRF Tokens
+# https://gitlab.com/gitlab-com/gl-security/appsec/appsec-team/-/issues/192
-# Enable when https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39749 is fixed
-# Below jobs runs DAST scans with one time consuming scan rule. These scan rules are disabled in above jobs so that those jobs won't timeout.
-# DAST scan with rule - 20019 External Redirect
-# DAST-fullscan-rule-20019:
+# Commented because of lot of FP's
+# dast:csrfTokenCheck:
# extends:
# - .dast_conf
# variables:
-# DAST_USERNAME: "user8"
+# DAST_USERNAME: "user6"
+# DAST_ONLY_INCLUDE_RULES: "20012,10202"
# script:
-# - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 20019)
-# - echo $DAST_EXCLUDE_RULES
-# - /analyze -t $DAST_WEBSITE -d
+# - /analyze
-# Enable when https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39749 is fixed
-# DAST scan with rule - 10107 Httpoxy - Proxy Header Misuse - Active/beta
-# DAST-fullscan-rule-10107:
-# extends:
-# - .dast_conf
-# variables:
-# DAST_USERNAME: "user9"
-# script:
-# - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 10107)
-# - echo $DAST_EXCLUDE_RULES
-# - /analyze -t $DAST_WEBSITE -d
+# 10098 Cross-Domain Misconfiguration
+# 10105 Weak Authentication Method
+# 40003 CRLF Injection
+# 40008 Parameter Tampering
+# Duration: 71 minutes 15 seconds
+dast:corsMisconfig-weakauth-crlfInj:
+ extends:
+ - .dast_conf
+ variables:
+ DAST_USERNAME: "user5"
+ DAST_ONLY_INCLUDE_RULES: "10098,10105,40003,40008"
+ script:
+ - /analyze
-# DAST scan with rule - 90020 Remote OS Command Injection
-DAST-fullscan-rule-90020:
+# 20019 External Redirect
+# 20014 HTTP Parameter Pollution
+# Duration: 46 minutes 12 seconds
+dast:extRedirect-paramPollution:
extends:
- .dast_conf
variables:
- DAST_USERNAME: "user10"
+ DAST_USERNAME: "user6"
+ DAST_ONLY_INCLUDE_RULES: "20019,20014"
script:
- - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 90020)
- - echo $DAST_EXCLUDE_RULES
- - /analyze -t $DAST_WEBSITE -d
+ - /analyze
-# DAST scan with rule - 40018 SQL Injection - Active/release
-DAST-fullscan-rule-40018:
+# 40022 SQL Injection - PostgreSQL
+# Duration: 53 minutes 59 seconds
+dast:sqlInjection:
extends:
- .dast_conf
variables:
- DAST_USERNAME: "user11"
+ DAST_USERNAME: "user7"
+ DAST_ONLY_INCLUDE_RULES: "40022"
script:
- - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 40018)
- - echo $DAST_EXCLUDE_RULES
- - /analyze -t $DAST_WEBSITE -d
+ - /analyze
-# DAST scan with rule - 40014 Cross Site Scripting (Persistent) - Active/release
-DAST-fullscan-rule-40014:
+# 40014 Cross Site Scripting (Persistent)
+# Duration: 21 minutes 50 seconds
+dast:xss-persistent:
extends:
- .dast_conf
variables:
- DAST_USERNAME: "user12"
+ DAST_USERNAME: "user8"
+ DAST_ONLY_INCLUDE_RULES: "40014"
script:
- - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 40014)
- - echo $DAST_EXCLUDE_RULES
- - /analyze -t $DAST_WEBSITE -d
+ - /analyze
-# DAST scan with rule - 6 Path travesal
-DAST-fullscan-rule-6:
+# 40012 Cross Site Scripting (Reflected)
+# Duration: 73 minutes 15 seconds
+dast:xss-reflected:
extends:
- .dast_conf
variables:
- DAST_USERNAME: "user13"
+ DAST_USERNAME: "user9"
+ DAST_ONLY_INCLUDE_RULES: "40012"
script:
- - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 6)
- - echo $DAST_EXCLUDE_RULES
- - /analyze -t $DAST_WEBSITE -d
+ - /analyze
-# DAST scan with rule - 40012 Cross Site Scripting (Reflected)
-DAST-fullscan-rule-40012:
+# 40013 Session Fixation
+# Duration: 44 minutes 25 seconds
+dast:sessionFixation:
extends:
- .dast_conf
variables:
- DAST_USERNAME: "user14"
+ DAST_USERNAME: "user10"
+ DAST_ONLY_INCLUDE_RULES: "40013"
script:
- - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 40012)
- - echo $DAST_EXCLUDE_RULES
- - /analyze -t $DAST_WEBSITE -d
+ - /analyze
diff --git a/app/assets/javascripts/access_tokens/index.js b/app/assets/javascripts/access_tokens/index.js
index 7f5f0403de6..2cd3a8f12ee 100644
--- a/app/assets/javascripts/access_tokens/index.js
+++ b/app/assets/javascripts/access_tokens/index.js
@@ -49,7 +49,7 @@ export const initProjectsField = () => {
{ default: createDefaultClient },
]) => {
const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(),
+ defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
});
Vue.use(VueApollo);
diff --git a/app/assets/javascripts/comment_type_toggle.js b/app/assets/javascripts/comment_type_toggle.js
deleted file mode 100644
index 2fcd40a901d..00000000000
--- a/app/assets/javascripts/comment_type_toggle.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import DropLab from './droplab/drop_lab';
-import ISetter from './droplab/plugins/input_setter';
-
-// Todo: Remove this when fixing issue in input_setter plugin
-const InputSetter = { ...ISetter };
-
-class CommentTypeToggle {
- constructor(opts = {}) {
- this.dropdownTrigger = opts.dropdownTrigger;
- this.dropdownList = opts.dropdownList;
- this.noteTypeInput = opts.noteTypeInput;
- this.submitButton = opts.submitButton;
- this.closeButton = opts.closeButton;
- this.reopenButton = opts.reopenButton;
- }
-
- initDroplab() {
- this.droplab = new DropLab();
-
- const config = this.setConfig();
-
- this.droplab.init(this.dropdownTrigger, this.dropdownList, [InputSetter], config);
- }
-
- setConfig() {
- const config = {
- InputSetter: [
- {
- input: this.noteTypeInput,
- valueAttribute: 'data-value',
- },
- {
- input: this.submitButton,
- valueAttribute: 'data-submit-text',
- },
- ],
- };
-
- if (this.closeButton) {
- config.InputSetter.push(
- {
- input: this.closeButton,
- valueAttribute: 'data-close-text',
- },
- {
- input: this.closeButton,
- valueAttribute: 'data-close-text',
- inputAttribute: 'data-alternative-text',
- },
- );
- }
-
- if (this.reopenButton) {
- config.InputSetter.push(
- {
- input: this.reopenButton,
- valueAttribute: 'data-reopen-text',
- },
- {
- input: this.reopenButton,
- valueAttribute: 'data-reopen-text',
- inputAttribute: 'data-alternative-text',
- },
- );
- }
-
- return config;
- }
-}
-
-export default CommentTypeToggle;
diff --git a/app/assets/javascripts/deprecated_notes.js b/app/assets/javascripts/deprecated_notes.js
index a42b50edb8a..4ab3f140b61 100644
--- a/app/assets/javascripts/deprecated_notes.js
+++ b/app/assets/javascripts/deprecated_notes.js
@@ -19,9 +19,10 @@ import Vue from 'vue';
import '~/lib/utils/jquery_at_who';
import AjaxCache from '~/lib/utils/ajax_cache';
import syntaxHighlight from '~/syntax_highlight';
+import CommentTypeDropdown from '~/notes/components/comment_type_dropdown.vue';
+import * as constants from '~/notes/constants';
import Autosave from './autosave';
import loadAwardsHandler from './awards_handler';
-import CommentTypeToggle from './comment_type_toggle';
import createFlash from './flash';
import { defaultAutocompleteConfig } from './gfm_auto_complete';
import GLForm from './gl_form';
@@ -128,7 +129,13 @@ export default class Notes {
this.$wrapperEl.on('click', '.js-note-edit', this.showEditForm.bind(this));
this.$wrapperEl.on('click', '.note-edit-cancel', this.cancelEdit);
// Reopen and close actions for Issue/MR combined with note form submit
- this.$wrapperEl.on('click', '.js-comment-submit-button', this.postComment);
+ this.$wrapperEl.on(
+ 'click',
+ // this oddly written selector needs to match the old style (input with class) as
+ // well as the new DOM styling from the Vue-based note form
+ 'input.js-comment-submit-button, .js-comment-submit-button > button:first-child',
+ this.postComment,
+ );
this.$wrapperEl.on('click', '.js-comment-save-button', this.updateComment);
this.$wrapperEl.on('keyup input', '.js-note-text', this.updateTargetButtons);
// resolve a discussion
@@ -201,23 +208,39 @@ export default class Notes {
}
static initCommentTypeToggle(form) {
- const dropdownTrigger = form.querySelector('.js-comment-type-dropdown .dropdown-toggle');
- const dropdownList = form.querySelector('.js-comment-type-dropdown .dropdown-menu');
+ const el = form.querySelector('.js-comment-type-dropdown');
+ const { noteableName } = el.dataset;
const noteTypeInput = form.querySelector('#note_type');
- const submitButton = form.querySelector('.js-comment-type-dropdown .js-comment-submit-button');
- const closeButton = form.querySelector('.js-note-target-close');
- const reopenButton = form.querySelector('.js-note-target-reopen');
-
- const commentTypeToggle = new CommentTypeToggle({
- dropdownTrigger,
- dropdownList,
- noteTypeInput,
- submitButton,
- closeButton,
- reopenButton,
- });
+ const formHasContent = form.querySelector('.js-note-text').value.trim().length > 0;
- commentTypeToggle.initDroplab();
+ form.commentTypeComponent = new Vue({
+ el,
+ data() {
+ return {
+ noteType: constants.COMMENT,
+ disabled: !formHasContent,
+ };
+ },
+ render(createElement) {
+ return createElement(CommentTypeDropdown, {
+ props: {
+ noteType: this.noteType,
+ noteableDisplayName: noteableName,
+ disabled: this.disabled,
+ },
+ on: {
+ change: (arg) => {
+ this.noteType = arg;
+ if (this.noteType === constants.DISCUSSION) {
+ noteTypeInput.value = constants.DISCUSSION_NOTE;
+ } else {
+ noteTypeInput.value = '';
+ }
+ },
+ },
+ });
+ },
+ });
}
keydownNoteText(e) {
@@ -1107,6 +1130,7 @@ export default class Notes {
const form = textarea.parents('form');
const reopenbtn = form.find('.js-note-target-reopen');
const closebtn = form.find('.js-note-target-close');
+ const commentTypeComponent = form.get(0)?.commentTypeComponent;
if (textarea.val().trim().length > 0) {
reopentext = reopenbtn.attr('data-alternative-text');
@@ -1123,6 +1147,9 @@ export default class Notes {
if (closebtn.is(':not(.btn-comment-and-close)')) {
closebtn.addClass('btn-comment-and-close');
}
+ if (commentTypeComponent) {
+ commentTypeComponent.disabled = false;
+ }
} else {
reopentext = reopenbtn.data('originalText');
closetext = closebtn.data('originalText');
@@ -1138,6 +1165,9 @@ export default class Notes {
if (closebtn.is('.btn-comment-and-close')) {
closebtn.removeClass('btn-comment-and-close');
}
+ if (commentTypeComponent) {
+ commentTypeComponent.disabled = true;
+ }
}
}
@@ -1308,9 +1338,6 @@ export default class Notes {
}
cleanForm($form) {
- // Remove JS classes that are not needed here
- $form.find('.js-comment-type-dropdown').removeClass('btn-group');
-
// Remove dropdown
$form.find('.dropdown-menu').remove();
@@ -1505,6 +1532,8 @@ export default class Notes {
const $submitBtn = $(e.target);
$submitBtn.prop('disabled', true);
let $form = $submitBtn.parents('form');
+ const commentTypeComponent = $form.get(0)?.commentTypeComponent;
+ if (commentTypeComponent) commentTypeComponent.disabled = true;
const $closeBtn = $form.find('.js-note-target-close');
const isDiscussionNote =
$submitBtn.parent().find('li.droplab-item-selected').attr('id') === 'discussion';
@@ -1584,6 +1613,8 @@ export default class Notes {
const note = res.data;
$submitBtn.prop('disabled', false);
+ if (commentTypeComponent) commentTypeComponent.disabled = false;
+
// Submission successful! remove placeholder
$notesContainer.find(`#${noteUniqueId}`).remove();
@@ -1662,6 +1693,8 @@ export default class Notes {
// Submission failed, remove placeholder note and show Flash error message
$notesContainer.find(`#${noteUniqueId}`).remove();
$submitBtn.prop('disabled', false);
+ if (commentTypeComponent) commentTypeComponent.disabled = false;
+
const blurEvent = new CustomEvent('blur.imageDiff', {
detail: e,
});
diff --git a/app/assets/javascripts/notes/components/comment_type_dropdown.vue b/app/assets/javascripts/notes/components/comment_type_dropdown.vue
index 663a912999d..30ea5d3532e 100644
--- a/app/assets/javascripts/notes/components/comment_type_dropdown.vue
+++ b/app/assets/javascripts/notes/components/comment_type_dropdown.vue
@@ -96,7 +96,11 @@ export default {
data-track-action="click_button"
@click="$emit('click')"
>
- <gl-dropdown-item is-check-item :is-checked="isNoteTypeComment" @click="setNoteTypeToComment">
+ <gl-dropdown-item
+ is-check-item
+ :is-checked="isNoteTypeComment"
+ @click.stop.prevent="setNoteTypeToComment"
+ >
<strong>{{ $options.i18n.submitButton.comment }}</strong>
<p class="gl-m-0">{{ commentDescription }}</p>
</gl-dropdown-item>
@@ -105,7 +109,7 @@ export default {
is-check-item
:is-checked="isNoteTypeDiscussion"
data-qa-selector="discussion_menu_item"
- @click="setNoteTypeToDiscussion"
+ @click.stop.prevent="setNoteTypeToDiscussion"
>
<strong>{{ $options.i18n.submitButton.startThread }}</strong>
<p class="gl-m-0">{{ startDiscussionDescription }}</p>
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 4e686ce8719..0925195d4bb 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -1,10 +1,16 @@
<script>
-import { GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
+import {
+ GlIcon,
+ GlLoadingIcon,
+ GlTooltipDirective,
+ GlSafeHtmlDirective as SafeHtml,
+} from '@gitlab/ui';
import { mapActions } from 'vuex';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import UserNameWithStatus from '../../sidebar/components/assignees/user_name_with_status.vue';
export default {
+ safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
components: {
timeAgoTooltip,
GitlabTeamMemberBadge: () =>
@@ -14,6 +20,7 @@ export default {
UserNameWithStatus,
},
directives: {
+ SafeHtml,
GlTooltip: GlTooltipDirective,
},
props: {
@@ -165,10 +172,10 @@ export default {
<span
v-if="authorStatus"
ref="authorStatus"
+ v-safe-html:[$options.safeHtmlConfig]="authorStatus"
v-on="
authorStatusHasTooltip ? { mouseenter: removeEmojiTitle, mouseleave: addEmojiTitle } : {}
"
- v-html="authorStatus /* eslint-disable-line vue/no-v-html */"
></span>
<span class="text-nowrap author-username">
<a
diff --git a/app/controllers/groups/dependency_proxy_for_containers_controller.rb b/app/controllers/groups/dependency_proxy_for_containers_controller.rb
index e19b8ae35f8..f7dc552bd3e 100644
--- a/app/controllers/groups/dependency_proxy_for_containers_controller.rb
+++ b/app/controllers/groups/dependency_proxy_for_containers_controller.rb
@@ -5,15 +5,11 @@ class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy
include DependencyProxy::GroupAccess
include SendFileUpload
include ::PackagesHelper # for event tracking
- include WorkhorseRequest
before_action :ensure_group
- before_action :ensure_token_granted!, only: [:blob, :manifest]
+ before_action :ensure_token_granted!
before_action :ensure_feature_enabled!
- before_action :verify_workhorse_api!, only: [:authorize_upload_blob, :upload_blob]
- skip_before_action :verify_authenticity_token, only: [:authorize_upload_blob, :upload_blob]
-
attr_reader :token
feature_category :dependency_proxy
@@ -42,8 +38,6 @@ class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy
end
def blob
- return blob_via_workhorse if Feature.enabled?(:dependency_proxy_workhorse, group, default_enabled: :yaml)
-
result = DependencyProxy::FindOrCreateBlobService
.new(group, image, token, params[:sha]).execute
@@ -56,47 +50,11 @@ class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy
end
end
- def authorize_upload_blob
- set_workhorse_internal_api_content_type
-
- render json: DependencyProxy::FileUploader.workhorse_authorize(has_length: false)
- end
-
- def upload_blob
- @group.dependency_proxy_blobs.create!(
- file_name: blob_file_name,
- file: params[:file],
- size: params[:file].size
- )
-
- event_name = tracking_event_name(object_type: :blob, from_cache: false)
- track_package_event(event_name, :dependency_proxy, namespace: group, user: auth_user)
-
- head :ok
- end
-
private
- def blob_via_workhorse
- blob = @group.dependency_proxy_blobs.find_by_file_name(blob_file_name)
-
- if blob.present?
- event_name = tracking_event_name(object_type: :blob, from_cache: true)
- track_package_event(event_name, :dependency_proxy, namespace: group, user: auth_user)
-
- send_upload(blob.file)
- else
- send_dependency(token, DependencyProxy::Registry.blob_url(image, params[:sha]), blob_file_name)
- end
- end
-
- def blob_file_name
- @blob_file_name ||= params[:sha].sub('sha256:', '') + '.gz'
- end
-
def group
strong_memoize(:group) do
- Group.find_by_full_path(params[:group_id], follow_redirects: true)
+ Group.find_by_full_path(params[:group_id], follow_redirects: request.get?)
end
end
diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb
index 4862282bc73..8785c4cdcbb 100644
--- a/app/helpers/workhorse_helper.rb
+++ b/app/helpers/workhorse_helper.rb
@@ -41,15 +41,6 @@ module WorkhorseHelper
head :ok
end
- def send_dependency(token, url, filename)
- headers.store(*Gitlab::Workhorse.send_dependency(token, url))
- headers['Content-Disposition'] =
- ActionDispatch::Http::ContentDisposition.format(disposition: 'attachment', filename: filename)
- headers['Content-Type'] = 'application/gzip'
-
- head :ok
- end
-
def set_workhorse_internal_api_content_type
headers['Content-Type'] = Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
end
diff --git a/app/services/ci/pipelines/add_job_service.rb b/app/services/ci/pipelines/add_job_service.rb
index 53536b6fdf9..703bb22fb5d 100644
--- a/app/services/ci/pipelines/add_job_service.rb
+++ b/app/services/ci/pipelines/add_job_service.rb
@@ -16,15 +16,7 @@ module Ci
def execute!(job, &block)
assign_pipeline_attributes(job)
- if Feature.enabled?(:ci_pipeline_add_job_with_lock, pipeline.project, default_enabled: :yaml)
- in_lock("ci:pipelines:#{pipeline.id}:add-job", ttl: LOCK_TIMEOUT, sleep_sec: LOCK_SLEEP, retries: LOCK_RETRIES) do
- Ci::Pipeline.transaction do
- yield(job)
-
- job.update_older_statuses_retried!
- end
- end
- else
+ in_lock("ci:pipelines:#{pipeline.id}:add-job", ttl: LOCK_TIMEOUT, sleep_sec: LOCK_SLEEP, retries: LOCK_RETRIES) do
Ci::Pipeline.transaction do
yield(job)
diff --git a/app/uploaders/dependency_proxy/file_uploader.rb b/app/uploaders/dependency_proxy/file_uploader.rb
index f0222d4cf06..5154f180454 100644
--- a/app/uploaders/dependency_proxy/file_uploader.rb
+++ b/app/uploaders/dependency_proxy/file_uploader.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
class DependencyProxy::FileUploader < GitlabUploader
- extend Workhorse::UploadPath
include ObjectStorage::Concern
before :cache, :set_content_type
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index 0a482f1eb01..cfeceb4f463 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -24,11 +24,10 @@
.js-serverless-survey-banner{ data: { user_name: current_user.name, user_email: current_user.email } }
- .d-flex.my-3
- %p.badge.badge-light.p-2.mr-2
+ %h4.gl-my-5
+ = @cluster.name
+ %span.badge.badge-info.badge-pill.gl-badge.md.gl-vertical-align-middle
= cluster_type_label(@cluster.cluster_type)
- %h4.m-0
- = @cluster.name
= render 'banner'
diff --git a/app/views/shared/notes/_comment_button.html.haml b/app/views/shared/notes/_comment_button.html.haml
index d0a2d97df0f..3e880a36e29 100644
--- a/app/views/shared/notes/_comment_button.html.haml
+++ b/app/views/shared/notes/_comment_button.html.haml
@@ -1,31 +1,4 @@
- noteable_name = @note.noteable.human_class_name
-.float-left.btn-group.gl-sm-mr-3.droplab-dropdown.comment-type-dropdown.js-comment-type-dropdown
+.js-comment-type-dropdown.float-left.gl-sm-mr-3{ data: { noteable_name: noteable_name } }
%input.btn.gl-button.btn-confirm.js-comment-button.js-comment-submit-button{ type: 'submit', value: _('Comment'), data: { qa_selector: 'comment_button' } }
-
- - if @note.can_be_discussion_note?
- = button_tag type: 'button', class: 'gl-button btn dropdown-toggle btn-confirm js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => _('Open comment type dropdown') do
- = sprite_icon('chevron-down')
-
- %ul#resolvable-comment-menu.dropdown-menu.dropdown-open-top{ data: { dropdown: true } }
- %li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => _('Comment'), 'close-text' => _("Comment & close %{noteable_name}") % { noteable_name: noteable_name }, 'reopen-text' => _("Comment & reopen %{noteable_name}") % { noteable_name: noteable_name } } }
- %button.btn.gl-button.btn-default-tertiary
- = sprite_icon('check', css_class: 'icon')
- .description
- %strong= _("Comment")
- %p
- = _("Add a general comment to this %{noteable_name}.") % { noteable_name: noteable_name }
-
- %li.divider.droplab-item-ignore
-
- %li#discussion{ data: { value: 'DiscussionNote', 'submit-text' => _('Start thread'), 'close-text' => _("Start thread & close %{noteable_name}") % { noteable_name: noteable_name }, 'reopen-text' => _("Start thread & reopen %{noteable_name}") % { noteable_name: noteable_name } } }
- %button.btn.gl-button.btn-default-tertiary
- = sprite_icon('check', css_class: 'icon')
- .description
- %strong= _("Start thread")
- %p
- = succeed '.' do
- - if @note.noteable.supports_resolvable_notes?
- = _('Discuss a specific suggestion or question that needs to be resolved')
- - else
- = _('Discuss a specific suggestion or question')
diff --git a/config/feature_flags/development/ci_pipeline_add_job_with_lock.yml b/config/feature_flags/development/ci_pipeline_add_job_with_lock.yml
deleted file mode 100644
index 6a708013ca5..00000000000
--- a/config/feature_flags/development/ci_pipeline_add_job_with_lock.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: ci_pipeline_add_job_with_lock
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65754
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337628
-milestone: '14.2'
-type: development
-group: group::pipeline authoring
-default_enabled: true
diff --git a/config/feature_flags/development/dependency_proxy_workhorse.yml b/config/feature_flags/development/dependency_proxy_workhorse.yml
deleted file mode 100644
index a3545d32cd5..00000000000
--- a/config/feature_flags/development/dependency_proxy_workhorse.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: dependency_proxy_workhorse
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68157
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339639
-milestone: '14.3'
-type: development
-group: group::source code
-default_enabled: false
diff --git a/config/feature_flags/development/use_rate_limiting_store_for_application_rate_limiter.yml b/config/feature_flags/development/use_rate_limiting_store_for_application_rate_limiter.yml
new file mode 100644
index 00000000000..073da6de563
--- /dev/null
+++ b/config/feature_flags/development/use_rate_limiting_store_for_application_rate_limiter.yml
@@ -0,0 +1,8 @@
+---
+name: use_rate_limiting_store_for_application_rate_limiter
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71196
+rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1249
+milestone: '14.4'
+type: development
+group: group::scalability
+default_enabled: false
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 803249f8861..ef31b639d33 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -146,7 +146,5 @@ scope format: false do
constraints image: Gitlab::PathRegex.container_image_regex, sha: Gitlab::PathRegex.container_image_blob_sha_regex do
get 'v2/*group_id/dependency_proxy/containers/*image/manifests/*tag' => 'groups/dependency_proxy_for_containers#manifest' # rubocop:todo Cop/PutGroupRoutesUnderScope
get 'v2/*group_id/dependency_proxy/containers/*image/blobs/:sha' => 'groups/dependency_proxy_for_containers#blob' # rubocop:todo Cop/PutGroupRoutesUnderScope
- post 'v2/*group_id/dependency_proxy/containers/*image/blobs/:sha/upload/authorize' => 'groups/dependency_proxy_for_containers#authorize_upload_blob' # rubocop:todo Cop/PutGroupRoutesUnderScope
- post 'v2/*group_id/dependency_proxy/containers/*image/blobs/:sha/upload' => 'groups/dependency_proxy_for_containers#upload_blob' # rubocop:todo Cop/PutGroupRoutesUnderScope
end
end
diff --git a/doc/development/snowplow/index.md b/doc/development/snowplow/index.md
index 7f3eae580c8..a5c4938afb1 100644
--- a/doc/development/snowplow/index.md
+++ b/doc/development/snowplow/index.md
@@ -24,14 +24,14 @@ More useful links:
Snowplow is an enterprise-grade marketing and Product Intelligence platform which helps track the way users engage with our website and application.
-[Snowplow](https://snowplowanalytics.com) consists of the following loosely-coupled sub-systems:
+[Snowplow](https://snowplowanalytics.com) consists of several loosely-coupled sub-systems:
-- **Trackers** fire Snowplow events. Snowplow has 12 trackers, covering web, mobile, desktop, server, and IoT.
-- **Collectors** receive Snowplow events from trackers. We have three different event collectors, synchronizing events either to Amazon S3, Apache Kafka, or Amazon Kinesis.
-- **Enrich** cleans up the raw Snowplow events, enriches them and puts them into storage. We have an Hadoop-based enrichment process, and a Kinesis-based or Kafka-based process.
+- **Trackers** fire Snowplow events. Snowplow has twelve trackers that cover web, mobile, desktop, server, and IoT.
+- **Collectors** receive Snowplow events from trackers. We have three different event collectors that synchronize events to Amazon S3, Apache Kafka, or Amazon Kinesis.
+- **Enrich** cleans up raw Snowplow events, enriches them, and puts them into storage. There is a Hadoop-based enrichment process, and a Kinesis-based or Kafka-based process.
- **Storage** is where the Snowplow events live. We store the Snowplow events in a flat file structure on S3, and in the Redshift and PostgreSQL databases.
-- **Data modeling** is where event-level data is joined with other data sets and aggregated into smaller data sets, and business logic is applied. This produces a clean set of tables which make it easier to perform analysis on the data. We have data models for Redshift and Looker.
-- **Analytics** are performed on the Snowplow events or on the aggregate tables.
+- **Data modeling** is where event-level data joins other data sets and aggregates into smaller data sets, and business logic is applied. This produces a clean set of tables for data analysis. We have data models for Redshift and Looker.
+- **Analytics** are performed on Snowplow events or on aggregate tables.
![snowplow_flow](../img/snowplow_flow.png)
@@ -101,15 +101,16 @@ sequenceDiagram
## Structured event taxonomy
-When adding new click events, we should add them in a way that's internally consistent. If we don't, it is difficult to perform analysis across features because each feature captures events differently.
+Click events must be consistent. If each feature captures events differently, it can be difficult
+to perform analysis.
-The current method provides several attributes that are sent on each click event. Please try to follow these guidelines when specifying events to capture:
+Each click event provides attributes that describe the event.
-| attribute | type | required | description |
+| Attribute | Type | Required | Description |
| --------- | ------- | -------- | ----------- |
-| category | text | true | The page or backend area of the application. Unless infeasible, please use the Rails page attribute by default in the frontend, and namespace + class name on the backend. |
-| action | text | true | The action the user is taking, or aspect that's being instrumented. The first word should always describe the action or aspect: clicks should be `click`, activations should be `activate`, creations should be `create`, etc. Use underscores to describe what was acted on; for example, activating a form field would be `activate_form_input`. An interface action like clicking on a dropdown would be `click_dropdown`, while a behavior like creating a project record from the backend would be `create_project` |
-| label | text | false | The specific element or object to act on. This can be one of the following: the label of the element (for example, a tab labeled 'Create from template' for `create_from_template`), a unique identifier if no text is available (for example, `groups_dropdown_close` for closing the Groups dropdown in the top bar), or the name or title attribute of a record being created. |
+| category | text | true | The page or backend section of the application. Unless infeasible, use the Rails page attribute by default in the frontend, and namespace + class name on the backend. |
+| action | text | true | The action the user takes, or aspect that's being instrumented. The first word must describe the action or aspect. For example, clicks must be `click`, activations must be `activate`, creations must be `create`. Use underscores to describe what was acted on. For example, activating a form field is `activate_form_input`, an interface action like clicking on a dropdown is `click_dropdown`, a behavior like creating a project record from the backend is `create_project`. |
+| label | text | false | The specific element or object to act on. This can be one of the following: the label of the element, for example, a tab labeled 'Create from template' for `create_from_template`; a unique identifier if no text is available, for example, `groups_dropdown_close` for closing the Groups dropdown in the top bar; or the name or title attribute of a record being created. |
| property | text | false | Any additional property of the element, or object being acted on. |
| value | decimal | false | Describes a numeric value or something directly related to the event. This could be the value of an input. For example, `10` when clicking `internal` visibility. |
@@ -154,29 +155,31 @@ LIMIT 20
### Web-specific parameters
-Snowplow JS adds many [web-specific parameters](https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/snowplow-tracker-protocol/#Web-specific_parameters) to all web events by default.
+Snowplow JS adds [web-specific parameters](https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/snowplow-tracker-protocol/#Web-specific_parameters) to all web events by default.
-## Implement Snowplow JS (Frontend) tracking
+## Snowplow JavaScript frontend tracking
-GitLab provides `Tracking`, an interface that wraps the [Snowplow JavaScript Tracker](https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/) for tracking custom events. The simplest way to use it is to add `data-` attributes to clickable elements and dropdowns. There is also a Vue mixin (exposing a `track` method), and the static method `Tracking.event`. Each of these requires at minimum a `category` and an `action`. You can provide additional [Structured event taxonomy](#structured-event-taxonomy) properties along with an `extra` object that accepts key-value pairs.
+GitLab provides a `Tracking` interface that wraps the [Snowplow JavaScript tracker](https://docs.snowplowanalytics.com/docs/collecting-data/collecting-from-own-applications/javascript-trackers/) to track custom events. For the recommended implementation type, see [Usage recommendations](#usage-recommendations).
-| field | type | default value | description |
+Tracking implementations must have an `action` and a `category`. You can provide additional [structured event taxonomy](#structured-event-taxonomy) categories with an `extra` object that accepts key-value pairs.
+
+| Field | Type | Default value | Description |
|:-----------|:-------|:---------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `category` | string | `document.body.dataset.page` | Page or subsection of a page that events are being captured within. |
-| `action` | string | generic | Action the user is taking. Clicks should be `click` and activations should be `activate`, so for example, focusing a form field would be `activate_form_input`, and clicking a button would be `click_button`. |
-| `data` | object | `{}` | Additional data such as `label`, `property`, `value`, `context` (as described in our [Structured event taxonomy](#structured-event-taxonomy)), and `extra` (key-value pairs object). |
+| `category` | string | `document.body.dataset.page` | Page or subsection of a page in which events are captured. |
+| `action` | string | generic | Action the user is taking. Clicks must be `click` and activations must be `activate`. For example, focusing a form field is `activate_form_input`, and clicking a button is `click_button`. |
+| `data` | object | `{}` | Additional data such as `label`, `property`, `value`, `context` as described in [Structured event taxonomy](#structured-event-taxonomy), and `extra` (key-value pairs object). |
### Usage recommendations
-- Use [data attributes](#tracking-with-data-attributes) on HTML elements that emits either the `click`, `show.bs.dropdown`, or `hide.bs.dropdown` events.
-- Use the [Vue mixin](#tracking-within-vue-components) when tracking custom events, or if the supported events for data attributes are not propagating.
-- Use the [Tracking class directly](#tracking-in-raw-javascript) when tracking on raw JS files.
+- Use [data attributes](#implement-data-attribute-tracking) on HTML elements that emit `click`, `show.bs.dropdown`, or `hide.bs.dropdown` events.
+- Use the [Vue mixin](#implement-vue-component-tracking) for tracking custom events, or if the supported events for data attributes are not propagating.
+- Use the [tracking class](#implement-raw-javascript-tracking) when tracking raw JavaScript files.
-### Tracking with data attributes
+### Implement data attribute tracking
-When working in HAML (or Vue templates) we can add `data-track-*` attributes to elements of interest. All elements that have a `data-track-action` attribute automatically have event tracking bound on clicks. You can provide extra data as a valid JSON string using `data-track-extra`.
+To implement tracking for HAML or Vue templates, add a [`data-track` attribute](#data-track-attributes) to the element.
-Below is an example of `data-track-*` attributes assigned to a button:
+The following example shows `data-track-*` attributes assigned to a button:
```haml
%button.btn{ data: { track: { action: "click_button", label: "template_preview", property: "my-template" } } }
@@ -191,19 +194,21 @@ Below is an example of `data-track-*` attributes assigned to a button:
/>
```
-Event listeners are bound at the document level to handle click events on or within elements with these data attributes. This allows them to be properly handled on re-rendering and changes to the DOM. Note that because of the way these events are bound, click events should not be stopped from propagating up the DOM tree. If click events are being stopped from propagating, you must implement your own listeners and follow the instructions in [Tracking within Vue components](#tracking-within-vue-components) or [Tracking in raw JavaScript](#tracking-in-raw-javascript).
-
-Below is a list of supported `data-track-*` attributes:
+#### `data-track` attributes
-| attribute | required | description |
+| Attribute | Required | Description |
|:----------------------|:---------|:------------|
-| `data-track-action` | true | Action the user is taking. Clicks must be prepended with `click` and activations must be prepended with `activate`. For example, focusing a form field would be `activate_form_input` and clicking a button would be `click_button`. Replaces `data-track-event`, which was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/290962) in GitLab 13.11. |
-| `data-track-label` | false | The `label` as described in our [Structured event taxonomy](#structured-event-taxonomy). |
-| `data-track-property` | false | The `property` as described in our [Structured event taxonomy](#structured-event-taxonomy). |
-| `data-track-value` | false | The `value` as described in our [Structured event taxonomy](#structured-event-taxonomy). If omitted, this is the element's `value` property or `undefined`. For checkboxes, the default value is the element's checked attribute or `0` when unchecked. |
+| `data-track-action` | true | Action the user is taking. Clicks must be prepended with `click` and activations must be prepended with `activate`. For example, focusing a form field is `activate_form_input` and clicking a button is `click_button`. Replaces `data-track-event`, which was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/290962) in GitLab 13.11. |
+| `data-track-label` | false | The `label` as described in [structured event taxonomy](#structured-event-taxonomy). |
+| `data-track-property` | false | The `property` as described in [structured event taxonomy](#structured-event-taxonomy). |
+| `data-track-value` | false | The `value` as described in [structured event taxonomy](#structured-event-taxonomy). If omitted, this is the element's `value` property or `undefined`. For checkboxes, the default value is the element's checked attribute or `0` when unchecked. |
| `data-track-extra` | false | A key-value pairs object passed as a valid JSON string. This is added to the `extra` property in our [`gitlab_standard`](#gitlab_standard) schema. |
| `data-track-context` | false | The `context` as described in our [Structured event taxonomy](#structured-event-taxonomy). |
+#### Event listeners
+
+Event listeners are bound at the document level to handle click events in elements with data attributes. This allows them to be handled on re-rendering and changes to the DOM. Because of the way these events are bound, click events should not stop from propagating up the DOM tree. If click events are stopped from propagating, you must implement listeners and follow the instructions in [Implement Vue component tracking](#implement-vue-component-tracking) or [Implement raw JavaScript tracking](#implement-raw-javascript-tracking).
+
#### Available helpers
```ruby
@@ -228,36 +233,96 @@ Be careful, as this behavior can be confused with the `ActionView` helper method
= link_to explore_groups_path, title: _("Explore"), data: { track_label: "explore_groups", track_action: "click_button" }
```
-### Tracking within Vue components
+### Implement Vue component tracking
-There's a tracking Vue mixin that can be used in components if more complex tracking is required. To use it, first import the `Tracking` library and request a mixin.
+For custom event tracking, use a Vue `mixin` in components. Vue `mixin` exposes the `Tracking.event` static method and the `track` method called from components or templates. You can specify tracking options in `data` or `computed`. These options override any defaults and allow the values to be dynamic from props or based on state.
-```javascript
-import Tracking from '~/tracking';
-const trackingMixin = Tracking.mixin({ label: 'right_sidebar' });
-```
+Default options are passed when an event is tracked from the component. If you don't specify an option, the default `document.body.dataset.page` is used. The default options are:
-You can provide default options that are passed along whenever an event is tracked from within your component. For example, if all events in a component should be tracked with a given `label`, you can provide one at this time. Available defaults are `category`, `label`, `property`, and `value`. If no category is specified, `document.body.dataset.page` is used as the default.
+- `category`
+- `label`
+- `property`
+- `value`
-You can then use the mixin normally in your component with the `mixin` Vue declaration. The mixin also provides the ability to specify tracking options in `data` or `computed`. These override any defaults and allow the values to be dynamic from props, or based on state.
+To implement Vue component tracking:
-```javascript
-export default {
- mixins: [trackingMixin],
- // ...[component implementation]...
- data() {
- return {
- expanded: false,
- tracking: {
- label: 'left_sidebar',
+1. Import the `Tracking` library and request a `mixin`:
+
+ ```javascript
+ import Tracking from '~/tracking';
+ const trackingMixin = Tracking.mixin;
+ ```
+
+1. Provide categories to track the event from the component. For example, to track all events in a component with a label, use the `label` category:
+
+ ```javascript
+ import Tracking from '~/tracking';
+ const trackingMixin = Tracking.mixin({ label: 'right_sidebar' });
+ ```
+
+1. In the component, declare the Vue `mixin`.
+
+ ```javascript
+ export default {
+ mixins: [trackingMixin],
+ // ...[component implementation]...
+ data() {
+ return {
+ expanded: false,
+ tracking: {
+ label: 'left_sidebar',
+ },
+ };
},
};
- },
-};
-```
+ ```
+
+1. To receive event data as a tracking object or computed property:
+ - Declare it in the `data` function. Use a `tracking` object when default event properties are dynamic or provided at runtime:
+
+ ```javascript
+ export default {
+ name: 'RightSidebar',
+ mixins: [Tracking.mixin()],
+ data() {
+ return {
+ tracking: {
+ label: 'right_sidebar',
+ // category: '',
+ // property: '',
+ // value: '',
+ // experiment: '',
+ // extra: {},
+ },
+ };
+ },
+ };
+ ```
+
+ - Declare it in the event data in the `track` function. This object merges with any previously provided options:
+
+ ```javascript
+ this.track('click_button', {
+ label: 'right_sidebar',
+ });
+ ```
+
+1. Optional. Use the `track` method in a template:
+
+ ```html
+ <template>
+ <div>
+ <button data-testid="toggle" @click="toggle">Toggle</button>
+
+ <div v-if="expanded">
+ <p>Hello world!</p>
+ <button @click="track('click_action')">Track another event</button>
+ </div>
+ </div>
+ </template>
+ ```
-The mixin provides a `track` method that can be called from within the template,
-or from component methods. An example of the whole implementation might look like this:
+#### Implementation example
```javascript
export default {
@@ -278,53 +343,6 @@ export default {
};
```
-The event data can be provided with a `tracking` object, declared in the `data` function,
-or as a `computed property`. A `tracking` object is convenient when the default
-event properties are dynamic or provided at runtime.
-
-```javascript
-export default {
- name: 'RightSidebar',
- mixins: [Tracking.mixin()],
- data() {
- return {
- tracking: {
- label: 'right_sidebar',
- // category: '',
- // property: '',
- // value: '',
- // experiment: '',
- // extra: {},
- },
- };
- },
-};
-```
-
-The event data can be provided directly in the `track` function as well.
-This object merges with any previously provided options.
-
-```javascript
-this.track('click_button', {
- label: 'right_sidebar',
-});
-```
-
-Lastly, if needed within the template, you can use the `track` method directly as well.
-
-```html
-<template>
- <div>
- <button data-testid="toggle" @click="toggle">Toggle</button>
-
- <div v-if="expanded">
- <p>Hello world!</p>
- <button @click="track('click_action')">Track another event</button>
- </div>
- </div>
-</template>
-```
-
#### Testing example
```javascript
@@ -352,9 +370,11 @@ describe('RightSidebar.vue', () => {
});
```
-### Tracking in raw JavaScript
+### Implement raw JavaScript tracking
+
+To call custom event tracking and instrumentation directly from the JavaScript file, call the `Tracking.event` static function.
-Custom event tracking and instrumentation can be added by directly calling the `Tracking.event` static function. The following example demonstrates tracking a click on a button by calling `Tracking.event` manually.
+The following example demonstrates tracking a click on a button by manually calling `Tracking.event`.
```javascript
import Tracking from '~/tracking';
diff --git a/doc/user/infrastructure/clusters/connect/index.md b/doc/user/infrastructure/clusters/connect/index.md
index 189b7f4135f..87cd58a519c 100644
--- a/doc/user/infrastructure/clusters/connect/index.md
+++ b/doc/user/infrastructure/clusters/connect/index.md
@@ -76,11 +76,14 @@ version. The range of supported versions is based on the evaluation of:
GitLab supports the following Kubernetes versions, and you can upgrade your
Kubernetes version to any supported version at any time:
+- 1.20 (support ends on July 22, 2022)
- 1.19 (support ends on February 22, 2022)
- 1.18 (support ends on November 22, 2021)
- 1.17 (support ends on September 22, 2021)
-Some GitLab features may support versions outside the range provided here.
+[Adding support to other versions of Kubernetes is managed under this epic](https://gitlab.com/groups/gitlab-org/-/epics/4827).
+
+Some GitLab features may support versions outside the range provided here.
## Cluster levels
diff --git a/doc/user/packages/composer_repository/index.md b/doc/user/packages/composer_repository/index.md
index 1a53dc4dfac..a07a71cebf3 100644
--- a/doc/user/packages/composer_repository/index.md
+++ b/doc/user/packages/composer_repository/index.md
@@ -125,6 +125,7 @@ You can publish a Composer package to the Package Registry as part of your CI/CD
deploy:
stage: deploy
script:
+ - apk add curl
- 'curl --header "Job-Token: $CI_JOB_TOKEN" --data tag=<tag> "${CI_API_V4_URL}/projects/$CI_PROJECT_ID/packages/composer"'
```
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 44225ac2921..e2f51ad5506 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -6,46 +6,70 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Webhooks **(FREE)**
-Project webhooks allow you to trigger a percent-encoded URL if, for example, new code is pushed or
-a new issue is created. You can configure webhooks to listen for specific events
-like pushes, issues or merge requests. GitLab sends a POST request with data
-to the webhook URL.
+[Webhooks](https://en.wikipedia.org/wiki/Webhook) are custom HTTP callbacks
+that you define. They are usually triggered by an
+event, such as pushing code to a repository or posting a comment on a blog.
+When the event occurs, the source app makes an HTTP request to the URI
+configured for the webhook. The action to take may be anything. For example,
+you can use webhooks to:
+
+- Trigger continuous integration (CI) jobs, update external issue trackers,
+ update a backup mirror, or deploy to your production server.
+- Send a notification to
+ [Slack](https://api.slack.com/incoming-webhooks) every time a job fails.
+- [Integrate with Twilio to be notified via SMS](https://www.datadoghq.com/blog/send-alerts-sms-customizable-webhooks-twilio/)
+ every time an issue is created for a specific project or group in GitLab.
+- [Automatically assign labels to merge requests](https://about.gitlab.com/blog/2016/08/19/applying-gitlab-labels-automatically/).
-You usually need to set up your own [webhook receiver](#example-webhook-receiver)
+You can configure webhook settings in GitLab for a [project](#project-webhooks-in-gitlab)
+or a [group](#group-webhooks).
+Usually, you set up your own [webhook receiver](#example-webhook-receiver)
to receive information from GitLab and send it to another app, according to your requirements.
-We already have a [built-in receiver](slack.md)
-for sending [Slack](https://api.slack.com/incoming-webhooks) notifications _per project_.
+We have a [built-in receiver](slack.md)
+for sending [Slack](https://api.slack.com/incoming-webhooks) notifications per project.
-## Overview
+GitLab.com enforces [webhook limits](../../../user/gitlab_com/index.md#webhooks),
+including:
-[Webhooks](https://en.wikipedia.org/wiki/Webhook) are "_user-defined HTTP
-callbacks_". They are usually triggered by some
-event, such as pushing code to a repository or a comment being posted to a blog.
-When that event occurs, the source app makes an HTTP request to the URI
-configured for the webhook. The action taken may be anything.
-Common uses are to trigger builds with continuous integration systems or to
-notify bug tracking systems.
+- The maximum number of webhooks and their size, both per project and per group.
+- The number of webhook calls per minute.
-Webhooks can be used to update an external issue tracker, trigger CI jobs,
-update a backup mirror, or even deploy to your production server.
+## Project webhooks in GitLab
-Webhooks are available:
+You can configure your project to trigger a percent-encoded webhook URL
+when an event occurs. For example, when new code is pushed or
+a new issue is created. You can configure a webhook to listen for specific [events](#events).
+GitLab sends a POST request with data to the webhook URL.
-- Per project, at a project's **Settings > Webhooks** menu. **(FREE)**
-- Additionally per group, at a group's **Settings > Webhooks** menu. **(PREMIUM)**
+### Validate payloads by using a secret token
-GitLab.com enforces various [webhook limits](../../../user/gitlab_com/index.md#webhooks), including:
+You can specify a secret token to validate received payloads.
+The token is sent with the hook request in the
+`X-Gitlab-Token` HTTP header. Your webhook endpoint can check the token to verify
+that the request is legitimate.
-- The maximum number of webhooks and their size, both per project, and per group.
-- The number of webhook calls per minute.
+### Verify an SSL certificate
-## Possible uses for webhooks
+By default, the SSL certificate of the webhook endpoint is verified based on
+an internal list of Certificate Authorities. This means the certificate cannot
+be self-signed.
-- You can set up a webhook in GitLab to send a notification to
- [Slack](https://api.slack.com/incoming-webhooks) every time a job fails.
-- You can [integrate with Twilio to be notified via SMS](https://www.datadoghq.com/blog/send-alerts-sms-customizable-webhooks-twilio/)
- every time an issue is created for a specific project or group within GitLab
-- You can use them to [automatically assign labels to merge requests](https://about.gitlab.com/blog/2016/08/19/applying-gitlab-labels-automatically/).
+You can turn off SSL verification in the [webhook settings](#configure-a-webhook)
+in your GitLab projects.
+
+### Filter push events by branch
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/20338) in GitLab 11.3.
+
+Push events can be filtered by branch using a branch name or wildcard pattern
+to limit which push events are sent to your webhook endpoint. By default,
+all push events are sent to your webhook endpoint. You can configure branch filtering
+in the [webhook settings](#configure-a-webhook) in your project.
+
+## Group webhooks **(PREMIUM)**
+
+You can configure a webhook for a group to ensure all projects in the group
+receive the same webhook settings.
## Webhook endpoint tips
@@ -62,27 +86,17 @@ GitLab webhooks, keep in mind the following:
you are writing a low-level hook this is important to remember.
- GitLab ignores the HTTP status code returned by your endpoint.
-## Secret token
-
-If you specify a secret token, it is sent with the hook request in the
-`X-Gitlab-Token` HTTP header. Your webhook endpoint can check that to verify
-that the request is legitimate.
+## Configure a webhook
-## SSL verification
+You can configure a webhook for a group or a project.
-By default, the SSL certificate of the webhook endpoint is verified based on
-an internal list of Certificate Authorities. This means the certificate cannot
-be self-signed.
-
-You can turn this off in the webhook settings in your GitLab projects.
-
-## Branch filtering
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/20338) in GitLab 11.3.
-
-Push events can be filtered by branch using a branch name or wildcard pattern
-to limit which push events are sent to your webhook endpoint. By default the
-field is blank causing all push events to be sent to your webhook endpoint.
+1. In your project or group, on the left sidebar, select **Settings > Webhooks**.
+1. In **URL**, enter the URL of the webhook endpoint.
+ The URL must be percentage-encoded, if necessary.
+1. In **Secret token**, enter the [secret token](#validate-payloads-by-using-a-secret-token) to validate payloads.
+1. In the **Trigger** section, select the [events](#events) to trigger the webhook.
+1. Optional. Clear the **Enable SSL verification** checkbox to disable [SSL verification](#verify-an-ssl-certificate).
+1. Select **Add webhook**.
## Events
diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb
index f91a56a0cd2..3c76daad4dd 100644
--- a/lib/gitlab/application_rate_limiter.rb
+++ b/lib/gitlab/application_rate_limiter.rb
@@ -73,7 +73,7 @@ module Gitlab
value = 0
interval_value = interval || interval(key)
- Gitlab::Redis::Cache.with do |redis|
+ cache_store.with do |redis|
cache_key = action_key(key, scope)
value = redis.incr(cache_key)
redis.expire(cache_key, interval_value) if value == 1
@@ -109,6 +109,14 @@ module Gitlab
private
+ def cache_store
+ if ::Feature.enabled?(:use_rate_limiting_store_for_application_rate_limiter, default_enabled: :yaml)
+ ::Gitlab::Redis::RateLimiting
+ else
+ ::Gitlab::Redis::Cache
+ end
+ end
+
def threshold(key)
value = rate_limit_value_by_key(key, :threshold)
diff --git a/lib/gitlab/middleware/multipart.rb b/lib/gitlab/middleware/multipart.rb
index a047015e54f..49be3ffc839 100644
--- a/lib/gitlab/middleware/multipart.rb
+++ b/lib/gitlab/middleware/multipart.rb
@@ -158,7 +158,6 @@ module Gitlab
::Gitlab.config.uploads.storage_path,
::JobArtifactUploader.workhorse_upload_path,
::LfsObjectUploader.workhorse_upload_path,
- ::DependencyProxy::FileUploader.workhorse_upload_path,
File.join(Rails.root, 'public/uploads/tmp')
] + package_allowed_paths
end
diff --git a/lib/gitlab/rack_attack/instrumented_cache_store.rb b/lib/gitlab/rack_attack/instrumented_cache_store.rb
index 8cf9082384f..b84e41685b5 100644
--- a/lib/gitlab/rack_attack/instrumented_cache_store.rb
+++ b/lib/gitlab/rack_attack/instrumented_cache_store.rb
@@ -2,9 +2,10 @@
module Gitlab
module RackAttack
- # This class is a proxy for all Redis calls made by RackAttack. All the
- # calls are instrumented, then redirected to ::Rails.cache. This class
- # instruments the standard interfaces of ActiveRecord::Cache defined in
+ # This class is a proxy for all Redis calls made by RackAttack. All
+ # the calls are instrumented, then redirected to the underlying
+ # store (in `.store). This class instruments the standard interfaces
+ # of ActiveRecord::Cache defined in
# https://github.com/rails/rails/blob/v6.0.3.1/activesupport/lib/active_support/cache.rb#L315
#
# For more information, please see
@@ -14,7 +15,18 @@ module Gitlab
delegate :silence!, :mute, to: :@upstream_store
- def initialize(upstream_store: ::Rails.cache, notifier: ActiveSupport::Notifications)
+ # Clean up in https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1249
+ def self.store
+ if ENV['USE_RATE_LIMITING_STORE_FOR_RACK_ATTACK'] == '1'
+ Gitlab::AuthLogger.info(message: 'Rack::Attack using rate limiting store')
+ ::Gitlab::Redis::RateLimiting.cache_store
+ else
+ Gitlab::AuthLogger.info(message: 'Rack::Attack using cache store')
+ ::Rails.cache
+ end
+ end
+
+ def initialize(upstream_store: self.class.store, notifier: ActiveSupport::Notifications)
@upstream_store = upstream_store
@notifier = notifier
end
diff --git a/lib/gitlab/redis/rate_limiting.rb b/lib/gitlab/redis/rate_limiting.rb
index 44681b4d5be..4ae1d55e4ce 100644
--- a/lib/gitlab/redis/rate_limiting.rb
+++ b/lib/gitlab/redis/rate_limiting.rb
@@ -7,6 +7,10 @@ module Gitlab
def self.config_fallback
Cache
end
+
+ def self.cache_store
+ @cache_store ||= ActiveSupport::Cache::RedisCacheStore.new(redis: pool, namespace: Cache::CACHE_NAMESPACE)
+ end
end
end
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index c5a99d4b93b..0f33c3aa68e 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -169,18 +169,6 @@ module Gitlab
]
end
- def send_dependency(token, url)
- params = {
- 'Header' => { Authorization: ["Bearer #{token}"] },
- 'Url' => url
- }
-
- [
- SEND_DATA_HEADER,
- "send-dependency:#{encode(params)}"
- ]
- end
-
def channel_websocket(channel)
details = {
'Channel' => {
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f8dff9c507b..5184145ef0e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1931,9 +1931,6 @@ msgstr ""
msgid "Add a general comment to this %{noteableDisplayName}."
msgstr ""
-msgid "Add a general comment to this %{noteable_name}."
-msgstr ""
-
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
@@ -8269,12 +8266,6 @@ msgstr ""
msgid "Comment"
msgstr ""
-msgid "Comment & close %{noteable_name}"
-msgstr ""
-
-msgid "Comment & reopen %{noteable_name}"
-msgstr ""
-
msgid "Comment & resolve thread"
msgstr ""
@@ -11987,12 +11978,6 @@ msgstr ""
msgid "Discover|Upgrade now"
msgstr ""
-msgid "Discuss a specific suggestion or question"
-msgstr ""
-
-msgid "Discuss a specific suggestion or question that needs to be resolved"
-msgstr ""
-
msgid "Discuss a specific suggestion or question that needs to be resolved."
msgstr ""
@@ -23839,9 +23824,6 @@ msgstr ""
msgid "Open a CLI and connect to the cluster you want to install the Agent in. Use this installation method to minimize any manual steps. The token is already included in the command."
msgstr ""
-msgid "Open comment type dropdown"
-msgstr ""
-
msgid "Open epics"
msgstr ""
@@ -32216,12 +32198,6 @@ msgstr ""
msgid "Start thread"
msgstr ""
-msgid "Start thread & close %{noteable_name}"
-msgstr ""
-
-msgid "Start thread & reopen %{noteable_name}"
-msgstr ""
-
msgid "Start your Free Ultimate Trial"
msgstr ""
diff --git a/package.json b/package.json
index c2fa1824653..87d8dd7d674 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "1.213.0",
"@gitlab/tributejs": "1.0.0",
- "@gitlab/ui": "32.14.0",
+ "@gitlab/ui": "32.15.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "6.1.4-1",
"@rails/ujs": "6.1.4-1",
diff --git a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb
index fa402d556c7..7415c2860c8 100644
--- a/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb
+++ b/spec/controllers/groups/dependency_proxy_for_containers_controller_spec.rb
@@ -5,7 +5,6 @@ require 'spec_helper'
RSpec.describe Groups::DependencyProxyForContainersController do
include HttpBasicAuthHelpers
include DependencyProxyHelpers
- include WorkhorseHelpers
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:group) { create(:group, :private) }
@@ -243,9 +242,16 @@ RSpec.describe Groups::DependencyProxyForContainersController do
end
describe 'GET #blob' do
- let(:blob) { create(:dependency_proxy_blob, group: group) }
+ let_it_be(:blob) { create(:dependency_proxy_blob) }
let(:blob_sha) { blob.file_name.sub('.gz', '') }
+ let(:blob_response) { { status: :success, blob: blob, from_cache: false } }
+
+ before do
+ allow_next_instance_of(DependencyProxy::FindOrCreateBlobService) do |instance|
+ allow(instance).to receive(:execute).and_return(blob_response)
+ end
+ end
subject { get_blob }
@@ -258,31 +264,40 @@ RSpec.describe Groups::DependencyProxyForContainersController do
it_behaves_like 'without permission'
it_behaves_like 'feature flag disabled with private group'
- context 'a valid user' do
+ context 'remote blob request fails' do
+ let(:blob_response) do
+ {
+ status: :error,
+ http_status: 400,
+ message: ''
+ }
+ end
+
before do
group.add_guest(user)
end
- it_behaves_like 'a successful blob pull'
- it_behaves_like 'a package tracking event', described_class.name, 'pull_blob_from_cache'
+ it 'proxies status from the remote blob request', :aggregate_failures do
+ subject
- context 'when cache entry does not exist' do
- let(:blob_sha) { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4' }
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(response.body).to be_empty
+ end
+ end
- it 'returns Workhorse send-dependency instructions' do
- subject
+ context 'a valid user' do
+ before do
+ group.add_guest(user)
+ end
- send_data_type, send_data = workhorse_send_data
- header, url = send_data.values_at('Header', 'Url')
+ it_behaves_like 'a successful blob pull'
+ it_behaves_like 'a package tracking event', described_class.name, 'pull_blob'
- expect(send_data_type).to eq('send-dependency')
- expect(header).to eq("Authorization" => ["Bearer abcd1234"])
- expect(url).to eq(DependencyProxy::Registry.blob_url('alpine', blob_sha))
- expect(response.headers['Content-Type']).to eq('application/gzip')
- expect(response.headers['Content-Disposition']).to eq(
- ActionDispatch::Http::ContentDisposition.format(disposition: 'attachment', filename: blob.file_name)
- )
- end
+ context 'with a cache entry' do
+ let(:blob_response) { { status: :success, blob: blob, from_cache: true } }
+
+ it_behaves_like 'returning response status', :success
+ it_behaves_like 'a package tracking event', described_class.name, 'pull_blob_from_cache'
end
end
@@ -304,74 +319,6 @@ RSpec.describe Groups::DependencyProxyForContainersController do
it_behaves_like 'a successful blob pull'
end
end
-
- context 'when dependency_proxy_workhorse disabled' do
- let(:blob_response) { { status: :success, blob: blob, from_cache: false } }
-
- before do
- stub_feature_flags(dependency_proxy_workhorse: false)
-
- allow_next_instance_of(DependencyProxy::FindOrCreateBlobService) do |instance|
- allow(instance).to receive(:execute).and_return(blob_response)
- end
- end
-
- context 'remote blob request fails' do
- let(:blob_response) do
- {
- status: :error,
- http_status: 400,
- message: ''
- }
- end
-
- before do
- group.add_guest(user)
- end
-
- it 'proxies status from the remote blob request', :aggregate_failures do
- subject
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(response.body).to be_empty
- end
- end
-
- context 'a valid user' do
- before do
- group.add_guest(user)
- end
-
- it_behaves_like 'a successful blob pull'
- it_behaves_like 'a package tracking event', described_class.name, 'pull_blob'
-
- context 'with a cache entry' do
- let(:blob_response) { { status: :success, blob: blob, from_cache: true } }
-
- it_behaves_like 'returning response status', :success
- it_behaves_like 'a package tracking event', described_class.name, 'pull_blob_from_cache'
- end
- end
-
- context 'a valid deploy token' do
- let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
- let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
-
- it_behaves_like 'a successful blob pull'
-
- context 'pulling from a subgroup' do
- let_it_be_with_reload(:parent_group) { create(:group) }
- let_it_be_with_reload(:group) { create(:group, parent: parent_group) }
-
- before do
- parent_group.create_dependency_proxy_setting!(enabled: true)
- group_deploy_token.update_column(:group_id, parent_group.id)
- end
-
- it_behaves_like 'a successful blob pull'
- end
- end
- end
end
it_behaves_like 'not found when disabled'
@@ -381,61 +328,6 @@ RSpec.describe Groups::DependencyProxyForContainersController do
end
end
- describe 'GET #authorize_upload_blob' do
- let(:blob_sha) { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4' }
-
- subject(:authorize_upload_blob) do
- request.headers.merge!(workhorse_internal_api_request_header)
-
- get :authorize_upload_blob, params: { group_id: group.to_param, image: 'alpine', sha: blob_sha }
- end
-
- it_behaves_like 'without permission'
-
- context 'with a valid user' do
- before do
- group.add_guest(user)
- end
-
- it 'sends Workhorse file upload instructions', :aggregate_failures do
- authorize_upload_blob
-
- expect(response.headers['Content-Type']).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- expect(json_response['TempPath']).to eq(DependencyProxy::FileUploader.workhorse_local_upload_path)
- end
- end
- end
-
- describe 'GET #upload_blob' do
- let(:blob_sha) { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4' }
- let(:file) { fixture_file_upload("spec/fixtures/dependency_proxy/#{blob_sha}.gz", 'application/gzip') }
-
- subject do
- request.headers.merge!(workhorse_internal_api_request_header)
-
- get :upload_blob, params: {
- group_id: group.to_param,
- image: 'alpine',
- sha: blob_sha,
- file: file
- }
- end
-
- it_behaves_like 'without permission'
-
- context 'with a valid user' do
- before do
- group.add_guest(user)
-
- expect_next_found_instance_of(Group) do |instance|
- expect(instance).to receive_message_chain(:dependency_proxy_blobs, :create!)
- end
- end
-
- it_behaves_like 'a package tracking event', described_class.name, 'pull_blob'
- end
- end
-
def enable_dependency_proxy
group.create_dependency_proxy_setting!(enabled: true)
end
diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
index 27a3e95896a..d86f38c1f0b 100644
--- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb
+++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
@@ -397,7 +397,7 @@ RSpec.describe Projects::PipelineSchedulesController do
end
end
- describe 'POST #play', :clean_gitlab_redis_cache do
+ describe 'POST #play', :clean_gitlab_redis_rate_limiting do
let(:ref) { 'master' }
before do
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 2c25c7e20ea..a81173ccaac 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -84,7 +84,7 @@ RSpec.describe Projects::RawController do
include_examples 'single Gitaly request'
end
- context 'when the endpoint receives requests above the limit', :clean_gitlab_redis_cache do
+ context 'when the endpoint receives requests above the limit', :clean_gitlab_redis_rate_limiting do
let(:file_path) { 'master/README.md' }
before do
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 2bb5fad9231..d502670d276 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -1384,12 +1384,12 @@ RSpec.describe ProjectsController do
end
end
- context 'when the endpoint receives requests above the limit', :clean_gitlab_redis_cache do
+ context 'when the endpoint receives requests above the limit', :clean_gitlab_redis_rate_limiting do
include_examples 'rate limits project export endpoint'
end
end
- describe '#download_export', :clean_gitlab_redis_cache do
+ describe '#download_export', :clean_gitlab_redis_rate_limiting do
let(:action) { :download_export }
context 'object storage enabled' do
@@ -1424,7 +1424,7 @@ RSpec.describe ProjectsController do
end
end
- context 'when the endpoint receives requests above the limit', :clean_gitlab_redis_cache do
+ context 'when the endpoint receives requests above the limit', :clean_gitlab_redis_rate_limiting do
before do
allow(Gitlab::ApplicationRateLimiter)
.to receive(:increment)
@@ -1496,7 +1496,7 @@ RSpec.describe ProjectsController do
end
end
- context 'when the endpoint receives requests above the limit', :clean_gitlab_redis_cache do
+ context 'when the endpoint receives requests above the limit', :clean_gitlab_redis_rate_limiting do
include_examples 'rate limits project export endpoint'
end
end
diff --git a/spec/features/groups/dependency_proxy_for_containers_spec.rb b/spec/features/groups/dependency_proxy_for_containers_spec.rb
deleted file mode 100644
index a4cd6d0f503..00000000000
--- a/spec/features/groups/dependency_proxy_for_containers_spec.rb
+++ /dev/null
@@ -1,108 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'Group Dependency Proxy for containers', :js do
- include DependencyProxyHelpers
-
- include_context 'file upload requests helpers'
-
- let_it_be(:user) { create(:user) }
- let_it_be(:group) { create(:group) }
- let_it_be(:sha) { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4' }
- let_it_be(:content) { fixture_file_upload("spec/fixtures/dependency_proxy/#{sha}.gz").read }
-
- let(:image) { 'alpine' }
- let(:url) { capybara_url("/v2/#{group.full_path}/dependency_proxy/containers/#{image}/blobs/sha256:#{sha}") }
- let(:token) { 'token' }
- let(:headers) { { 'Authorization' => "Bearer #{build_jwt(user).encoded}" } }
-
- subject do
- HTTParty.get(url, headers: headers)
- end
-
- def run_server(handler)
- default_server = Capybara.server
-
- Capybara.server = Capybara.servers[:puma]
- server = Capybara::Server.new(handler)
- server.boot
- server
- ensure
- Capybara.server = default_server
- end
-
- let_it_be(:external_server) do
- handler = lambda do |env|
- if env['REQUEST_PATH'] == '/token'
- [200, {}, [{ token: 'token' }.to_json]]
- else
- [200, {}, [content]]
- end
- end
-
- run_server(handler)
- end
-
- before do
- stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
- stub_config(dependency_proxy: { enabled: true })
- group.add_developer(user)
-
- stub_const("DependencyProxy::Registry::AUTH_URL", external_server.base_url)
- stub_const("DependencyProxy::Registry::LIBRARY_URL", external_server.base_url)
- end
-
- shared_examples 'responds with the file' do
- it 'sends file' do
- expect(subject.code).to eq(200)
- expect(subject.body).to eq(content)
- expect(subject.headers.to_h).to include(
- "content-type" => ["application/gzip"],
- "content-disposition" => ["attachment; filename=\"#{sha}.gz\"; filename*=UTF-8''#{sha}.gz"],
- "content-length" => ["32"]
- )
- end
- end
-
- shared_examples 'caches the file' do
- it 'caches the file' do
- expect { subject }.to change {
- group.dependency_proxy_blobs.count
- }.from(0).to(1)
-
- expect(subject.code).to eq(200)
- expect(group.dependency_proxy_blobs.first.file.read).to eq(content)
- end
- end
-
- context 'fetching a blob' do
- context 'when the blob is cached for the group' do
- let!(:dependency_proxy_blob) { create(:dependency_proxy_blob, group: group) }
-
- it_behaves_like 'responds with the file'
-
- context 'dependency_proxy_workhorse feature flag disabled' do
- before do
- stub_feature_flags({ dependency_proxy_workhorse: false })
- end
-
- it_behaves_like 'responds with the file'
- end
- end
- end
-
- context 'when the blob must be downloaded' do
- it_behaves_like 'responds with the file'
- it_behaves_like 'caches the file'
-
- context 'dependency_proxy_workhorse feature flag disabled' do
- before do
- stub_feature_flags({ dependency_proxy_workhorse: false })
- end
-
- it_behaves_like 'responds with the file'
- it_behaves_like 'caches the file'
- end
- end
-end
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index e03f71c5352..fc88cd9205c 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -70,8 +70,8 @@ RSpec.describe 'Comments on personal snippets', :js do
context 'when submitting a note' do
it 'shows a valid form' do
is_expected.to have_css('.js-main-target-form', visible: true, count: 1)
- expect(find('.js-main-target-form .js-comment-button').value)
- .to eq('Comment')
+ expect(find('.js-main-target-form .js-comment-button button', match: :first))
+ .to have_content('Comment')
page.within('.js-main-target-form') do
expect(page).not_to have_link('Cancel')
diff --git a/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap b/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap
index 721507586e0..42d81900911 100644
--- a/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap
+++ b/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap
@@ -33,7 +33,7 @@ exports[`Remove cluster confirmation modal renders splitbutton with modal includ
<span
class="sr-only"
>
- Toggle Dropdown
+ Toggle dropdown
</span>
</button>
<ul
diff --git a/spec/frontend/comment_type_toggle_spec.js b/spec/frontend/comment_type_toggle_spec.js
deleted file mode 100644
index 06dbfac1803..00000000000
--- a/spec/frontend/comment_type_toggle_spec.js
+++ /dev/null
@@ -1,169 +0,0 @@
-import CommentTypeToggle from '~/comment_type_toggle';
-import DropLab from '~/droplab/drop_lab';
-import InputSetter from '~/droplab/plugins/input_setter';
-
-describe('CommentTypeToggle', () => {
- const testContext = {};
-
- describe('class constructor', () => {
- beforeEach(() => {
- testContext.dropdownTrigger = {};
- testContext.dropdownList = {};
- testContext.noteTypeInput = {};
- testContext.submitButton = {};
- testContext.closeButton = {};
-
- testContext.commentTypeToggle = new CommentTypeToggle({
- dropdownTrigger: testContext.dropdownTrigger,
- dropdownList: testContext.dropdownList,
- noteTypeInput: testContext.noteTypeInput,
- submitButton: testContext.submitButton,
- closeButton: testContext.closeButton,
- });
- });
-
- it('should set .dropdownTrigger', () => {
- expect(testContext.commentTypeToggle.dropdownTrigger).toBe(testContext.dropdownTrigger);
- });
-
- it('should set .dropdownList', () => {
- expect(testContext.commentTypeToggle.dropdownList).toBe(testContext.dropdownList);
- });
-
- it('should set .noteTypeInput', () => {
- expect(testContext.commentTypeToggle.noteTypeInput).toBe(testContext.noteTypeInput);
- });
-
- it('should set .submitButton', () => {
- expect(testContext.commentTypeToggle.submitButton).toBe(testContext.submitButton);
- });
-
- it('should set .closeButton', () => {
- expect(testContext.commentTypeToggle.closeButton).toBe(testContext.closeButton);
- });
-
- it('should set .reopenButton', () => {
- expect(testContext.commentTypeToggle.reopenButton).toBe(testContext.reopenButton);
- });
- });
-
- describe('initDroplab', () => {
- beforeEach(() => {
- testContext.commentTypeToggle = {
- dropdownTrigger: {},
- dropdownList: {},
- noteTypeInput: {},
- submitButton: {},
- closeButton: {},
- setConfig: () => {},
- };
- testContext.config = {};
-
- jest.spyOn(DropLab.prototype, 'init').mockImplementation();
- jest.spyOn(DropLab.prototype, 'constructor').mockImplementation();
-
- jest.spyOn(testContext.commentTypeToggle, 'setConfig').mockReturnValue(testContext.config);
-
- CommentTypeToggle.prototype.initDroplab.call(testContext.commentTypeToggle);
- });
-
- it('should instantiate a DropLab instance and set .droplab', () => {
- expect(testContext.commentTypeToggle.droplab instanceof DropLab).toBe(true);
- });
-
- it('should call .setConfig', () => {
- expect(testContext.commentTypeToggle.setConfig).toHaveBeenCalled();
- });
-
- it('should call DropLab.prototype.init', () => {
- expect(DropLab.prototype.init).toHaveBeenCalledWith(
- testContext.commentTypeToggle.dropdownTrigger,
- testContext.commentTypeToggle.dropdownList,
- [InputSetter],
- testContext.config,
- );
- });
- });
-
- describe('setConfig', () => {
- describe('if no .closeButton is provided', () => {
- beforeEach(() => {
- testContext.commentTypeToggle = {
- dropdownTrigger: {},
- dropdownList: {},
- noteTypeInput: {},
- submitButton: {},
- reopenButton: {},
- };
-
- testContext.setConfig = CommentTypeToggle.prototype.setConfig.call(
- testContext.commentTypeToggle,
- );
- });
-
- it('should not add .closeButton related InputSetter config', () => {
- expect(testContext.setConfig).toEqual({
- InputSetter: [
- {
- input: testContext.commentTypeToggle.noteTypeInput,
- valueAttribute: 'data-value',
- },
- {
- input: testContext.commentTypeToggle.submitButton,
- valueAttribute: 'data-submit-text',
- },
- {
- input: testContext.commentTypeToggle.reopenButton,
- valueAttribute: 'data-reopen-text',
- },
- {
- input: testContext.commentTypeToggle.reopenButton,
- valueAttribute: 'data-reopen-text',
- inputAttribute: 'data-alternative-text',
- },
- ],
- });
- });
- });
-
- describe('if no .reopenButton is provided', () => {
- beforeEach(() => {
- testContext.commentTypeToggle = {
- dropdownTrigger: {},
- dropdownList: {},
- noteTypeInput: {},
- submitButton: {},
- closeButton: {},
- };
-
- testContext.setConfig = CommentTypeToggle.prototype.setConfig.call(
- testContext.commentTypeToggle,
- );
- });
-
- it('should not add .reopenButton related InputSetter config', () => {
- expect(testContext.setConfig).toEqual({
- InputSetter: [
- {
- input: testContext.commentTypeToggle.noteTypeInput,
- valueAttribute: 'data-value',
- },
- {
- input: testContext.commentTypeToggle.submitButton,
- valueAttribute: 'data-submit-text',
- },
- {
- input: testContext.commentTypeToggle.closeButton,
- valueAttribute: 'data-close-text',
- },
- {
- input: testContext.commentTypeToggle.closeButton,
- valueAttribute: 'data-close-text',
- inputAttribute: 'data-alternative-text',
- },
- ],
- });
- });
- });
- });
-});
diff --git a/spec/frontend/notes/components/comment_type_dropdown_spec.js b/spec/frontend/notes/components/comment_type_dropdown_spec.js
index 5e1cb813369..8ac6144e5c8 100644
--- a/spec/frontend/notes/components/comment_type_dropdown_spec.js
+++ b/spec/frontend/notes/components/comment_type_dropdown_spec.js
@@ -47,8 +47,18 @@ describe('CommentTypeDropdown component', () => {
it('Should emit `change` event when clicking on an alternate dropdown option', () => {
mountComponent({ props: { noteType: constants.DISCUSSION } });
- findCommentDropdownOption().vm.$emit('click');
- findDiscussionDropdownOption().vm.$emit('click');
+ const event = {
+ type: 'click',
+ stopPropagation: jest.fn(),
+ preventDefault: jest.fn(),
+ };
+
+ findCommentDropdownOption().vm.$emit('click', event);
+ findDiscussionDropdownOption().vm.$emit('click', event);
+
+ // ensure the native events don't trigger anything
+ expect(event.stopPropagation).toHaveBeenCalledTimes(2);
+ expect(event.preventDefault).toHaveBeenCalledTimes(2);
expect(wrapper.emitted('change')[0]).toEqual([constants.COMMENT]);
expect(wrapper.emitted('change').length).toEqual(1);
diff --git a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
index cc512dca2eb..44b4c0398cd 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
@@ -35,6 +35,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
<b-form-input-stub
class="gl-form-input"
debounce="0"
+ formatter="[Function]"
readonly="true"
type="text"
value="ssh://foo.bar"
@@ -78,6 +79,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
<b-form-input-stub
class="gl-form-input"
debounce="0"
+ formatter="[Function]"
readonly="true"
type="text"
value="http://foo.bar"
diff --git a/spec/lib/gitlab/application_rate_limiter_spec.rb b/spec/lib/gitlab/application_rate_limiter_spec.rb
index 2525b1ce41e..1661dec44de 100644
--- a/spec/lib/gitlab/application_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/application_rate_limiter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::ApplicationRateLimiter do
let(:redis) { double('redis') }
let(:user) { create(:user) }
let(:project) { create(:project) }
@@ -20,7 +20,6 @@ RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_cache do
subject { described_class }
before do
- allow(Gitlab::Redis::Cache).to receive(:with).and_yield(redis)
allow(described_class).to receive(:rate_limits).and_return(rate_limits)
end
@@ -49,73 +48,94 @@ RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_cache do
end
end
- context 'when the key is an array of only ActiveRecord models' do
- let(:scope) { [user, project] }
+ # Clean up in https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1249
+ shared_examples 'rate limiting' do
+ context 'when the key is an array of only ActiveRecord models' do
+ let(:scope) { [user, project] }
- let(:cache_key) do
- "application_rate_limiter:test_action:user:#{user.id}:project:#{project.id}"
+ let(:cache_key) do
+ "application_rate_limiter:test_action:user:#{user.id}:project:#{project.id}"
+ end
+
+ it_behaves_like 'action rate limiter'
end
- it_behaves_like 'action rate limiter'
- end
+ context 'when they key a combination of ActiveRecord models and strings' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:commit) { project.repository.commit }
+ let(:path) { 'app/controllers/groups_controller.rb' }
+ let(:scope) { [project, commit, path] }
- context 'when they key a combination of ActiveRecord models and strings' do
- let(:project) { create(:project, :public, :repository) }
- let(:commit) { project.repository.commit }
- let(:path) { 'app/controllers/groups_controller.rb' }
- let(:scope) { [project, commit, path] }
+ let(:cache_key) do
+ "application_rate_limiter:test_action:project:#{project.id}:commit:#{commit.sha}:#{path}"
+ end
- let(:cache_key) do
- "application_rate_limiter:test_action:project:#{project.id}:commit:#{commit.sha}:#{path}"
+ it_behaves_like 'action rate limiter'
end
- it_behaves_like 'action rate limiter'
- end
-
- describe '#log_request' do
- let(:file_path) { 'master/README.md' }
- let(:type) { :raw_blob_request_limit }
- let(:fullpath) { "/#{project.full_path}/raw/#{file_path}" }
+ describe '#log_request' do
+ let(:file_path) { 'master/README.md' }
+ let(:type) { :raw_blob_request_limit }
+ let(:fullpath) { "/#{project.full_path}/raw/#{file_path}" }
- let(:request) do
- double('request', ip: '127.0.0.1', request_method: 'GET', fullpath: fullpath)
- end
+ let(:request) do
+ double('request', ip: '127.0.0.1', request_method: 'GET', fullpath: fullpath)
+ end
- let(:base_attributes) do
- {
- message: 'Application_Rate_Limiter_Request',
- env: type,
- remote_ip: '127.0.0.1',
- request_method: 'GET',
- path: fullpath
- }
- end
+ let(:base_attributes) do
+ {
+ message: 'Application_Rate_Limiter_Request',
+ env: type,
+ remote_ip: '127.0.0.1',
+ request_method: 'GET',
+ path: fullpath
+ }
+ end
- context 'without a current user' do
- let(:current_user) { nil }
+ context 'without a current user' do
+ let(:current_user) { nil }
- it 'logs information to auth.log' do
- expect(Gitlab::AuthLogger).to receive(:error).with(base_attributes).once
+ it 'logs information to auth.log' do
+ expect(Gitlab::AuthLogger).to receive(:error).with(base_attributes).once
- subject.log_request(request, type, current_user)
+ subject.log_request(request, type, current_user)
+ end
end
- end
- context 'with a current_user' do
- let(:current_user) { create(:user) }
+ context 'with a current_user' do
+ let(:current_user) { create(:user) }
- let(:attributes) do
- base_attributes.merge({
- user_id: current_user.id,
- username: current_user.username
- })
- end
+ let(:attributes) do
+ base_attributes.merge({
+ user_id: current_user.id,
+ username: current_user.username
+ })
+ end
- it 'logs information to auth.log' do
- expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once
+ it 'logs information to auth.log' do
+ expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once
- subject.log_request(request, type, current_user)
+ subject.log_request(request, type, current_user)
+ end
end
end
end
+
+ context 'when use_rate_limiting_store_for_application_rate_limiter is enabled' do
+ before do
+ stub_feature_flags(use_rate_limiting_store_for_application_rate_limiter: true)
+ allow(Gitlab::Redis::RateLimiting).to receive(:with).and_yield(redis)
+ end
+
+ it_behaves_like 'rate limiting'
+ end
+
+ context 'when use_rate_limiting_store_for_application_rate_limiter is disabled' do
+ before do
+ stub_feature_flags(use_rate_limiting_store_for_application_rate_limiter: false)
+ allow(Gitlab::Redis::Cache).to receive(:with).and_yield(redis)
+ end
+
+ it_behaves_like 'rate limiting'
+ end
end
diff --git a/spec/lib/gitlab/middleware/multipart/handler_spec.rb b/spec/lib/gitlab/middleware/multipart/handler_spec.rb
index 53b59b042e2..aac3f00defe 100644
--- a/spec/lib/gitlab/middleware/multipart/handler_spec.rb
+++ b/spec/lib/gitlab/middleware/multipart/handler_spec.rb
@@ -16,7 +16,6 @@ RSpec.describe Gitlab::Middleware::Multipart::Handler do
::Gitlab.config.uploads.storage_path,
::JobArtifactUploader.workhorse_upload_path,
::LfsObjectUploader.workhorse_upload_path,
- ::DependencyProxy::FileUploader.workhorse_upload_path,
File.join(Rails.root, 'public/uploads/tmp')
]
end
diff --git a/spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb b/spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb
index bd167ee2e3e..c98af0fc320 100644
--- a/spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb
+++ b/spec/lib/gitlab/rack_attack/instrumented_cache_store_spec.rb
@@ -86,4 +86,17 @@ RSpec.describe Gitlab::RackAttack::InstrumentedCacheStore do
test_proc.call(subject)
end
end
+
+ # Remove in https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1249
+ describe '.store' do
+ it 'uses the rate limiting store when USE_RATE_LIMITING_STORE_FOR_RACK_ATTACK is set' do
+ stub_env('USE_RATE_LIMITING_STORE_FOR_RACK_ATTACK', '1')
+
+ expect(described_class.store).to eq(Gitlab::Redis::RateLimiting.cache_store)
+ end
+
+ it 'uses the cache store' do
+ expect(described_class.store).to eq(Rails.cache)
+ end
+ end
end
diff --git a/spec/lib/gitlab/rate_limit_helpers_spec.rb b/spec/lib/gitlab/rate_limit_helpers_spec.rb
index d583c8e58fb..ad0e2de1448 100644
--- a/spec/lib/gitlab/rate_limit_helpers_spec.rb
+++ b/spec/lib/gitlab/rate_limit_helpers_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::RateLimitHelpers, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::RateLimitHelpers, :clean_gitlab_redis_rate_limiting do
let(:limiter_class) do
Class.new do
include ::Gitlab::RateLimitHelpers
diff --git a/spec/services/ci/pipelines/add_job_service_spec.rb b/spec/services/ci/pipelines/add_job_service_spec.rb
index 3a77d26dd9e..709a840c644 100644
--- a/spec/services/ci/pipelines/add_job_service_spec.rb
+++ b/spec/services/ci/pipelines/add_job_service_spec.rb
@@ -77,19 +77,6 @@ RSpec.describe Ci::Pipelines::AddJobService do
expect(execute).to be_success
expect(execute.payload[:job]).to eq(job)
end
-
- context 'when the FF ci_pipeline_add_job_with_lock is disabled' do
- before do
- stub_feature_flags(ci_pipeline_add_job_with_lock: false)
- end
-
- it 'does not use exclusive lock' do
- expect(Gitlab::ExclusiveLease).not_to receive(:new).with(lock_key, timeout: lock_timeout)
-
- expect(execute).to be_success
- expect(execute.payload[:job]).to eq(job)
- end
- end
end
end
end
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index f9fa46a4fc8..2aebd2adab9 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -392,7 +392,7 @@ RSpec.describe WebHookService do
end
end
- context 'when the hook is throttled (via Redis)', :clean_gitlab_redis_cache do
+ context 'when the hook is throttled (via Redis)', :clean_gitlab_redis_rate_limiting do
before do
# Set a high interval to avoid intermittent failures in CI
allow(Gitlab::ApplicationRateLimiter).to receive(:rate_limits).and_return(
diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
index 318ba67b9e9..6c06cbf9082 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -3,9 +3,9 @@
RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name|
let(:form_selector) { '.js-main-target-form' }
let(:dropdown_selector) { "#{form_selector} .comment-type-dropdown" }
- let(:toggle_selector) { "#{dropdown_selector} .dropdown-toggle" }
+ let(:toggle_selector) { "#{dropdown_selector} .gl-dropdown-toggle" }
let(:menu_selector) { "#{dropdown_selector} .dropdown-menu" }
- let(:submit_selector) { "#{form_selector} .js-comment-submit-button" }
+ let(:submit_selector) { "#{form_selector} .js-comment-submit-button > button:first-child" }
let(:close_selector) { "#{form_selector} .btn-comment-and-close" }
let(:comments_selector) { '.timeline > .note.timeline-entry:not(.being-posted)' }
let(:comment) { 'My comment' }
@@ -43,13 +43,11 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name
expect(items.first).to have_content 'Comment'
expect(items.first).to have_content "Add a general comment to this #{resource_name}."
- expect(items.first).to have_selector '[data-testid="check-icon"]'
- expect(items.first['class']).to match 'droplab-item-selected'
+ expect(items.first).to have_selector '[data-testid="dropdown-item-checkbox"]'
expect(items.last).to have_content 'Start thread'
expect(items.last).to have_content "Discuss a specific suggestion or question#{' that needs to be resolved' if resource_name == 'merge request'}."
- expect(items.last).not_to have_selector '[data-testid="check-icon"]'
- expect(items.last['class']).not_to match 'droplab-item-selected'
+ expect(items.last).not_to have_selector '[data-testid="dropdown-item-checkbox"]'
end
it 'closes the menu when clicking the toggle or body' do
@@ -75,14 +73,14 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name
expect(find(dropdown_selector)).to have_content 'Comment'
find(toggle_selector).click
- execute_script("document.querySelector('#{menu_selector} .divider').click()")
+ execute_script("document.querySelector('#{menu_selector} .dropdown-divider').click()")
else
execute_script("document.querySelector('#{menu_selector}').click()")
expect(page).to have_selector menu_selector
expect(find(dropdown_selector)).to have_content 'Comment'
- execute_script("document.querySelector('#{menu_selector} .divider').click()")
+ execute_script("document.querySelector('#{menu_selector} .dropdown-divider').click()")
expect(page).to have_selector menu_selector
end
@@ -97,7 +95,7 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name
end
it 'updates the submit button text and closes the dropdown' do
- expect(find(submit_selector).value).to eq 'Start thread'
+ expect(find(submit_selector).text).to eq 'Start thread'
expect(page).not_to have_selector menu_selector
end
@@ -137,12 +135,10 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name
items = all("#{menu_selector} li")
expect(items.first).to have_content 'Comment'
- expect(items.first).not_to have_selector '[data-testid="check-icon"]'
- expect(items.first['class']).not_to match 'droplab-item-selected'
+ expect(items.first).not_to have_selector '[data-testid="dropdown-item-checkbox"]'
expect(items.last).to have_content 'Start thread'
- expect(items.last).to have_selector '[data-testid="check-icon"]'
- expect(items.last['class']).to match 'droplab-item-selected'
+ expect(items.last).to have_selector '[data-testid="dropdown-item-checkbox"]'
end
describe 'when selecting "Comment"' do
@@ -153,7 +149,7 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name
it 'updates the submit button text and closes the dropdown' do
button = find(submit_selector)
- expect(button.value).to eq 'Comment'
+ expect(button.text).to eq 'Comment'
expect(page).not_to have_selector menu_selector
end
@@ -166,12 +162,10 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name
aggregate_failures do
expect(items.first).to have_content 'Comment'
- expect(items.first).to have_selector '[data-testid="check-icon"]'
- expect(items.first['class']).to match 'droplab-item-selected'
+ expect(items.first).to have_selector '[data-testid="dropdown-item-checkbox"]'
expect(items.last).to have_content 'Start thread'
- expect(items.last).not_to have_selector '[data-testid="check-icon"]'
- expect(items.last['class']).not_to match 'droplab-item-selected'
+ expect(items.last).not_to have_selector '[data-testid="dropdown-item-checkbox"]'
end
end
end
diff --git a/workhorse/internal/dependencyproxy/dependencyproxy.go b/workhorse/internal/dependencyproxy/dependencyproxy.go
deleted file mode 100644
index ebc310ca7f6..00000000000
--- a/workhorse/internal/dependencyproxy/dependencyproxy.go
+++ /dev/null
@@ -1,125 +0,0 @@
-package dependencyproxy
-
-import (
- "context"
- "fmt"
- "io"
- "net"
- "net/http"
- "time"
-
- "gitlab.com/gitlab-org/labkit/correlation"
- "gitlab.com/gitlab-org/labkit/log"
- "gitlab.com/gitlab-org/labkit/tracing"
-
- "gitlab.com/gitlab-org/gitlab/workhorse/internal/helper"
- "gitlab.com/gitlab-org/gitlab/workhorse/internal/senddata"
-)
-
-// httpTransport defines a http.Transport with values
-// that are more restrictive than for http.DefaultTransport,
-// they define shorter TLS Handshake, and more aggressive connection closing
-// to prevent the connection hanging and reduce FD usage
-var httpTransport = tracing.NewRoundTripper(correlation.NewInstrumentedRoundTripper(&http.Transport{
- Proxy: http.ProxyFromEnvironment,
- DialContext: (&net.Dialer{
- Timeout: 30 * time.Second,
- KeepAlive: 10 * time.Second,
- }).DialContext,
- MaxIdleConns: 2,
- IdleConnTimeout: 30 * time.Second,
- TLSHandshakeTimeout: 10 * time.Second,
- ExpectContinueTimeout: 10 * time.Second,
- ResponseHeaderTimeout: 30 * time.Second,
-}))
-
-var httpClient = &http.Client{
- Transport: httpTransport,
-}
-
-type Injector struct {
- senddata.Prefix
- uploadHandler http.Handler
-}
-
-type entryParams struct {
- Url string
- Header http.Header
-}
-
-type nullResponseWriter struct {
- header http.Header
- status int
-}
-
-func (nullResponseWriter) Write(p []byte) (int, error) {
- return len(p), nil
-}
-
-func (w *nullResponseWriter) Header() http.Header {
- return w.header
-}
-
-func (w *nullResponseWriter) WriteHeader(status int) {
- if w.status == 0 {
- w.status = status
- }
-}
-
-func NewInjector() *Injector {
- return &Injector{
- Prefix: "send-dependency:",
- }
-}
-
-func (p *Injector) SetUploadHandler(uploadHandler http.Handler) {
- p.uploadHandler = uploadHandler
-}
-
-func (p *Injector) Inject(w http.ResponseWriter, r *http.Request, sendData string) {
- dependencyResponse, err := p.fetchUrl(r.Context(), sendData)
- if err != nil {
- helper.Fail500(w, r, err)
- return
- }
- defer dependencyResponse.Body.Close()
- if dependencyResponse.StatusCode >= 400 {
- w.WriteHeader(dependencyResponse.StatusCode)
- io.Copy(w, dependencyResponse.Body)
- return
- }
-
- teeReader := io.TeeReader(dependencyResponse.Body, w)
- saveFileRequest, err := http.NewRequestWithContext(r.Context(), "POST", r.URL.String()+"/upload", teeReader)
- if err != nil {
- helper.Fail500(w, r, fmt.Errorf("dependency proxy: failed to create request: %w", err))
- }
- saveFileRequest.Header = helper.HeaderClone(r.Header)
- saveFileRequest.ContentLength = dependencyResponse.ContentLength
-
- w.Header().Del("Content-Length")
-
- nrw := &nullResponseWriter{header: http.Header{}}
- p.uploadHandler.ServeHTTP(nrw, saveFileRequest)
-
- if nrw.status != http.StatusOK {
- fields := log.Fields{"code": nrw.status}
-
- helper.Fail500WithFields(nrw, r, fmt.Errorf("dependency proxy: failed to upload file"), fields)
- }
-}
-
-func (p *Injector) fetchUrl(ctx context.Context, sendData string) (*http.Response, error) {
- var params entryParams
- if err := p.Unpack(&params, sendData); err != nil {
- return nil, fmt.Errorf("dependency proxy: unpack sendData: %v", err)
- }
-
- r, err := http.NewRequestWithContext(ctx, "GET", params.Url, nil)
- if err != nil {
- return nil, fmt.Errorf("dependency proxy: failed to fetch dependency: %v", err)
- }
- r.Header = params.Header
-
- return httpClient.Do(r)
-}
diff --git a/workhorse/internal/dependencyproxy/dependencyproxy_test.go b/workhorse/internal/dependencyproxy/dependencyproxy_test.go
deleted file mode 100644
index 395ca58f90e..00000000000
--- a/workhorse/internal/dependencyproxy/dependencyproxy_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package dependencyproxy
-
-import (
- "encoding/base64"
- "io"
- "net/http"
- "net/http/httptest"
- "strconv"
- "testing"
-
- "github.com/stretchr/testify/require"
-)
-
-type fakeUploadHandler struct {
- request *http.Request
- body []byte
- handler func(w http.ResponseWriter, r *http.Request)
-}
-
-func (f *fakeUploadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- f.request = r
-
- f.body, _ = io.ReadAll(r.Body)
-
- f.handler(w, r)
-}
-
-func TestSuccessfullRequest(t *testing.T) {
- content := []byte("result")
- originResourceServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Length", strconv.Itoa(len(content)))
- w.Write(content)
- }))
-
- uploadHandler := &fakeUploadHandler{
- handler: func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(200)
- },
- }
-
- injector := NewInjector()
- injector.SetUploadHandler(uploadHandler)
-
- response := makeRequest(injector, `{"Token": "token", "Url": "`+originResourceServer.URL+`/url"}`)
-
- require.Equal(t, "/target/upload", uploadHandler.request.URL.Path)
- require.Equal(t, int64(6), uploadHandler.request.ContentLength)
-
- require.Equal(t, content, uploadHandler.body)
-
- require.Equal(t, 200, response.Code)
- require.Equal(t, string(content), response.Body.String())
-}
-
-func TestIncorrectSendData(t *testing.T) {
- response := makeRequest(NewInjector(), "")
-
- require.Equal(t, 500, response.Code)
- require.Equal(t, "Internal server error\n", response.Body.String())
-}
-
-func TestIncorrectSendDataUrl(t *testing.T) {
- response := makeRequest(NewInjector(), `{"Token": "token", "Url": "url"}`)
-
- require.Equal(t, 500, response.Code)
- require.Equal(t, "Internal server error\n", response.Body.String())
-}
-
-func TestFailedOriginServer(t *testing.T) {
- originResourceServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(404)
- w.Write([]byte("Not found"))
- }))
-
- uploadHandler := &fakeUploadHandler{
- handler: func(w http.ResponseWriter, r *http.Request) {
- require.FailNow(t, "the error response must not be uploaded")
- },
- }
-
- injector := NewInjector()
- injector.SetUploadHandler(uploadHandler)
-
- response := makeRequest(injector, `{"Token": "token", "Url": "`+originResourceServer.URL+`/url"}`)
-
- require.Equal(t, 404, response.Code)
- require.Equal(t, "Not found", response.Body.String())
-}
-
-func makeRequest(injector *Injector, data string) *httptest.ResponseRecorder {
- w := httptest.NewRecorder()
- r := httptest.NewRequest("GET", "/target", nil)
-
- sendData := base64.StdEncoding.EncodeToString([]byte(data))
- injector.Inject(w, r, sendData)
-
- return w
-}
diff --git a/workhorse/internal/upstream/routes.go b/workhorse/internal/upstream/routes.go
index 9e92393dcaa..8c85c5144e5 100644
--- a/workhorse/internal/upstream/routes.go
+++ b/workhorse/internal/upstream/routes.go
@@ -16,7 +16,6 @@ import (
"gitlab.com/gitlab-org/gitlab/workhorse/internal/builds"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/channel"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/config"
- "gitlab.com/gitlab-org/gitlab/workhorse/internal/dependencyproxy"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/git"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/helper"
"gitlab.com/gitlab-org/gitlab/workhorse/internal/imageresizer"
@@ -171,7 +170,7 @@ func (ro *routeEntry) isMatch(cleanedPath string, req *http.Request) bool {
return ok
}
-func buildProxy(backend *url.URL, version string, rt http.RoundTripper, cfg config.Config, dependencyProxyInjector *dependencyproxy.Injector) http.Handler {
+func buildProxy(backend *url.URL, version string, rt http.RoundTripper, cfg config.Config) http.Handler {
proxier := proxypkg.NewProxy(backend, version, rt)
return senddata.SendData(
@@ -184,7 +183,6 @@ func buildProxy(backend *url.URL, version string, rt http.RoundTripper, cfg conf
artifacts.SendEntry,
sendurl.SendURL,
imageresizer.NewResizer(cfg),
- dependencyProxyInjector,
)
}
@@ -195,8 +193,7 @@ func buildProxy(backend *url.URL, version string, rt http.RoundTripper, cfg conf
func configureRoutes(u *upstream) {
api := u.APIClient
static := &staticpages.Static{DocumentRoot: u.DocumentRoot, Exclude: staticExclude}
- dependencyProxyInjector := dependencyproxy.NewInjector()
- proxy := buildProxy(u.Backend, u.Version, u.RoundTripper, u.Config, dependencyProxyInjector)
+ proxy := buildProxy(u.Backend, u.Version, u.RoundTripper, u.Config)
cableProxy := proxypkg.NewProxy(u.CableBackend, u.Version, u.CableRoundTripper)
assetsNotFoundHandler := NotFoundUnless(u.DevelopmentMode, proxy)
@@ -210,7 +207,7 @@ func configureRoutes(u *upstream) {
}
signingTripper := secret.NewRoundTripper(u.RoundTripper, u.Version)
- signingProxy := buildProxy(u.Backend, u.Version, signingTripper, u.Config, dependencyProxyInjector)
+ signingProxy := buildProxy(u.Backend, u.Version, signingTripper, u.Config)
preparers := createUploadPreparers(u.Config)
uploadPath := path.Join(u.DocumentRoot, "uploads/tmp")
@@ -218,8 +215,6 @@ func configureRoutes(u *upstream) {
ciAPIProxyQueue := queueing.QueueRequests("ci_api_job_requests", uploadAccelerateProxy, u.APILimit, u.APIQueueLimit, u.APIQueueTimeout)
ciAPILongPolling := builds.RegisterHandler(ciAPIProxyQueue, redis.WatchKey, u.APICILongPollingDuration)
- dependencyProxyInjector.SetUploadHandler(upload.BodyUploader(api, signingProxy, preparers.packages))
-
// Serve static files or forward the requests
defaultUpstream := static.ServeExisting(
u.URLPrefix,
diff --git a/workhorse/main_test.go b/workhorse/main_test.go
index f90a07f1d7d..6e61e2fc65a 100644
--- a/workhorse/main_test.go
+++ b/workhorse/main_test.go
@@ -934,101 +934,3 @@ func TestHealthChecksUnreachable(t *testing.T) {
})
}
}
-
-func TestDependencyProxyInjector(t *testing.T) {
- token := "token"
- bodyLength := 4096 * 12
- expectedBody := strings.Repeat("p", bodyLength)
-
- testCases := []struct {
- desc string
- contentLength int
- readSize int
- finalizeHandler func(*testing.T, http.ResponseWriter)
- }{
- {
- desc: "the uploading successfully finalized",
- contentLength: bodyLength,
- readSize: bodyLength,
- finalizeHandler: func(t *testing.T, w http.ResponseWriter) {
- w.WriteHeader(200)
- },
- }, {
- desc: "the uploading failed",
- contentLength: bodyLength,
- readSize: bodyLength,
- finalizeHandler: func(t *testing.T, w http.ResponseWriter) {
- w.WriteHeader(500)
- },
- }, {
- desc: "the origin resource server returns partial response",
- contentLength: bodyLength + 1000,
- readSize: bodyLength,
- finalizeHandler: func(t *testing.T, _ http.ResponseWriter) {
- t.Fatal("partial file must not be saved")
- },
- }, {
- desc: "a user does not read the whole file",
- contentLength: bodyLength,
- readSize: bodyLength - 1000,
- finalizeHandler: func(t *testing.T, _ http.ResponseWriter) {
- t.Fatal("partial file must not be saved")
- },
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.desc, func(t *testing.T) {
- originResource := "/origin_resource"
-
- originResourceServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- require.Equal(t, originResource, r.URL.String())
-
- w.Header().Set("Content-Length", strconv.Itoa(tc.contentLength))
-
- _, err := io.WriteString(w, expectedBody)
- require.NoError(t, err)
- }))
- defer originResourceServer.Close()
-
- originResourceUrl := originResourceServer.URL + originResource
-
- ts := testhelper.TestServerWithHandler(regexp.MustCompile(`.`), func(w http.ResponseWriter, r *http.Request) {
- switch r.URL.String() {
- case "/base":
- params := `{"Url": "` + originResourceUrl + `", "Token": "` + token + `"}`
- w.Header().Set("Gitlab-Workhorse-Send-Data", `send-dependency:`+base64.URLEncoding.EncodeToString([]byte(params)))
- case "/base/upload/authorize":
- w.Header().Set("Content-Type", api.ResponseContentType)
- _, err := fmt.Fprintf(w, `{"TempPath":"%s"}`, scratchDir)
- require.NoError(t, err)
- case "/base/upload":
- tc.finalizeHandler(t, w)
- default:
- t.Fatalf("unexpected request: %s", r.URL)
- }
- })
- defer ts.Close()
-
- ws := startWorkhorseServer(ts.URL)
- defer ws.Close()
-
- req, err := http.NewRequest("GET", ws.URL+"/base", nil)
- require.NoError(t, err)
-
- resp, err := http.DefaultClient.Do(req)
- require.NoError(t, err)
- defer resp.Body.Close()
-
- body := make([]byte, tc.readSize)
- _, err = io.ReadFull(resp.Body, body)
- require.NoError(t, err)
-
- require.NoError(t, resp.Body.Close()) // Client closes connection
- ws.Close() // Wait for server handler to return
-
- require.Equal(t, 200, resp.StatusCode, "status code")
- require.Equal(t, expectedBody[0:tc.readSize], string(body), "response body")
- })
- }
-}
diff --git a/yarn.lock b/yarn.lock
index 20dc6127116..65e1d7a9420 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -914,13 +914,13 @@
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
-"@gitlab/ui@32.14.0":
- version "32.14.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.14.0.tgz#91b9346490077c1e6db808256bb0da7a092fd82a"
- integrity sha512-GnxTqoyZdAMse8IBvLJ7l8HPDHiJhuUO0h7rnVjDdrU4uHGCmDLBRUXE9cjtI4e0AM2UTkvBv+BhswJ2kUCELw==
+"@gitlab/ui@32.15.0":
+ version "32.15.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.15.0.tgz#292518f1c52ef22d73cfded9d6f9f6d48a47efee"
+ integrity sha512-n7SwTA5Je+s/66cTXBhRdlxiJILGA+mXX6aevUFXzFvTn4a4yVb1wx4mmaMA/EB3vUxb8+u/z61CYZ4pd5JIbw==
dependencies:
"@babel/standalone" "^7.0.0"
- bootstrap-vue "2.18.1"
+ bootstrap-vue "2.19.0"
copy-to-clipboard "^3.0.8"
dompurify "^2.3.3"
echarts "^4.9.0"
@@ -2749,10 +2749,10 @@ boolbase@^1.0.0:
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
-bootstrap-vue@2.18.1:
- version "2.18.1"
- resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.18.1.tgz#4378d26b713d4255b45b42b3f852f6fa0a11d400"
- integrity sha512-oYKAhEnNuCxtF4gxsdLzijhQpFk7UYPvzhwZvUbnPbZ1eWu2dsc3+fRkY9PMowt5OJRtuVb7ov3lpsI2fraYsA==
+bootstrap-vue@2.19.0:
+ version "2.19.0"
+ resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.19.0.tgz#5019df48251e552a5c34da57fc97dabebd53b02f"
+ integrity sha512-IjAXUSrRU5Qu9x3uwUcoj6LtysKbCVeWoJOsODyI/WokStUr95M+tTIajXUjIrB/Nsk0fS+RNvZnm2sWeNFrhg==
dependencies:
"@nuxt/opencollective" "^0.3.2"
bootstrap ">=4.5.3 <5.0.0"