diff options
56 files changed, 755 insertions, 265 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6853b100ed7..0c06df88b3f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -36,10 +36,21 @@ workflow: - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^release-tools\/\d+\.\d+\.\d+-rc\d+$/ && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^[\d-]+-stable(-ee)?$/ && $CI_PROJECT_PATH == "gitlab-org/gitlab"' when: never # For merged result pipelines, set $QA_IMAGE, since $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is only available for merged result pipelines. - - if: '$CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"' + # AND + # For merge requests running exclusively in Ruby 3.0 + - if: '($CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train") && $CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby3/' variables: QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}" - # Also run (detached) merge request pipelines. + RUBY_VERSION: "3.0" + # For merged result pipelines, set $QA_IMAGE, since $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is only available for merged result pipelines. + - if: '($CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train")' + variables: + QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}" + # For merge requests running exclusively in Ruby 3.0 + - if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby3/' + variables: + RUBY_VERSION: "3.0" + # For (detached) merge request pipelines. - if: '$CI_MERGE_REQUEST_IID' # For the maintenance scheduled pipelines, we set specific variables. - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule" && $SCHEDULE_TYPE == "maintenance"' diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 0f3e2b1fcda..d55d5e0cf44 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -929,16 +929,18 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /app/workers/authorized_projects_worker.rb @gitlab-org/manage/authentication-and-authorization /app/workers/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization /config/feature_flags/development/application_settings_tokens_optional_encryption.yml @gitlab-org/manage/authentication-and-authorization +/config/feature_flags/development/async_only_project_authorizations_refresh.yml @gitlab-org/manage/authentication-and-authorization /config/feature_flags/development/enforce_auth_checks_on_uploads.yml @gitlab-org/manage/authentication-and-authorization /config/feature_flags/development/forti_authenticator.yml @gitlab-org/manage/authentication-and-authorization /config/feature_flags/development/forti_token_cloud.yml @gitlab-org/manage/authentication-and-authorization /config/feature_flags/development/groups_tokens_optional_encryption.yml @gitlab-org/manage/authentication-and-authorization -/config/feature_flags/development/omniauth_initializer_fullhost_proc.yml @gitlab-org/manage/authentication-and-authorization /config/feature_flags/development/omniauth_login_minimal_scopes.yml @gitlab-org/manage/authentication-and-authorization /config/feature_flags/development/personal_access_tokens_scoped_to_projects.yml @gitlab-org/manage/authentication-and-authorization /config/feature_flags/development/projects_tokens_optional_encryption.yml @gitlab-org/manage/authentication-and-authorization /config/feature_flags/development/refresh_authorizations_via_affected_projects_on_group_membership.yml @gitlab-org/manage/authentication-and-authorization +/config/feature_flags/development/skip_group_share_unlink_auth_refresh.yml @gitlab-org/manage/authentication-and-authorization /config/feature_flags/development/specialized_worker_for_group_lock_update_auth_recalculation.yml @gitlab-org/manage/authentication-and-authorization +/config/feature_flags/development/update_oauth_registration_flow.yml @gitlab-org/manage/authentication-and-authorization /config/feature_flags/development/webauthn.yml @gitlab-org/manage/authentication-and-authorization /config/feature_flags/ops/block_password_auth_for_saml_users.yml @gitlab-org/manage/authentication-and-authorization /config/initializers/01_secret_token.rb @gitlab-org/manage/authentication-and-authorization @@ -975,6 +977,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /ee/app/helpers/ee/access_tokens_helper.rb @gitlab-org/manage/authentication-and-authorization /ee/app/helpers/ee/auth_helper.rb @gitlab-org/manage/authentication-and-authorization /ee/app/helpers/ee/personal_access_tokens_helper.rb @gitlab-org/manage/authentication-and-authorization +/ee/app/models/concerns/password_complexity.rb @gitlab-org/manage/authentication-and-authorization /ee/app/models/ee/personal_access_token.rb @gitlab-org/manage/authentication-and-authorization /ee/app/models/ee/project_authorization.rb @gitlab-org/manage/authentication-and-authorization /ee/app/models/scim_oauth_access_token.rb @gitlab-org/manage/authentication-and-authorization @@ -984,6 +987,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /ee/app/services/ee/resource_access_tokens/ @gitlab-org/manage/authentication-and-authorization /ee/app/services/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization /ee/app/services/security/token_revocation_service.rb @gitlab-org/manage/authentication-and-authorization +/ee/app/validators/password/ @gitlab-org/manage/authentication-and-authorization /ee/app/views/admin/application_settings/_personal_access_token_expiration_policy.html.haml @gitlab-org/manage/authentication-and-authorization /ee/app/views/credentials_inventory_mailer/personal_access_token_revoked_email.html.haml @gitlab-org/manage/authentication-and-authorization /ee/app/views/credentials_inventory_mailer/personal_access_token_revoked_email.text.haml @gitlab-org/manage/authentication-and-authorization @@ -996,6 +1000,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /ee/app/views/shared/credentials_inventory/_project_access_tokens.html.haml @gitlab-org/manage/authentication-and-authorization /ee/app/views/shared/credentials_inventory/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization /ee/app/views/shared/credentials_inventory/project_access_tokens/ @gitlab-org/manage/authentication-and-authorization +/ee/app/workers/auth/ @gitlab-org/manage/authentication-and-authorization /ee/app/workers/personal_access_tokens/ @gitlab-org/manage/authentication-and-authorization /ee/config/routes/oauth.rb @gitlab-org/manage/authentication-and-authorization /ee/lib/ee/gitlab/auth/ @gitlab-org/manage/authentication-and-authorization @@ -1012,6 +1017,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab /lib/api/entities/impersonation_token.rb @gitlab-org/manage/authentication-and-authorization /lib/api/entities/impersonation_token_with_token.rb @gitlab-org/manage/authentication-and-authorization /lib/api/entities/personal_access_token.rb @gitlab-org/manage/authentication-and-authorization +/lib/api/entities/personal_access_token_with_details.rb @gitlab-org/manage/authentication-and-authorization /lib/api/entities/personal_access_token_with_token.rb @gitlab-org/manage/authentication-and-authorization /lib/api/entities/resource_access_token.rb @gitlab-org/manage/authentication-and-authorization /lib/api/entities/resource_access_token_with_token.rb @gitlab-org/manage/authentication-and-authorization diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 6c3909c9e78..c9929f3c2e0 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -49,6 +49,9 @@ .if-merge-request-targeting-stable-branch: &if-merge-request-targeting-stable-branch if: '$CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^[\d-]+-stable(-ee)?$/' +.if-merge-request-labels-run-in-ruby3: &if-merge-request-labels-run-in-ruby3 + if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby3/' + .if-merge-request-labels-as-if-foss: &if-merge-request-labels-as-if-foss if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-as-if-foss/' @@ -1789,6 +1792,10 @@ - <<: *if-default-refs changes: *code-backstage-patterns +.setup:rules:verify-ruby-2.7: + rules: + - <<: *if-merge-request-labels-run-in-ruby3 + .setup:rules:verify-tests-yml: rules: - <<: *if-not-ee diff --git a/.gitlab/ci/setup.gitlab-ci.yml b/.gitlab/ci/setup.gitlab-ci.yml index 505caeec837..2da397aaab8 100644 --- a/.gitlab/ci/setup.gitlab-ci.yml +++ b/.gitlab/ci/setup.gitlab-ci.yml @@ -23,13 +23,19 @@ cache gems: - .default-retry needs: [] -dont-interrupt-me: - extends: .setup:rules:dont-interrupt-me - stage: sync +.absolutely-minimal-job: + extends: + - .minimal-job image: ${GITLAB_DEPENDENCY_PROXY}alpine:edge - interruptible: false variables: GIT_STRATEGY: none + +dont-interrupt-me: + extends: + - .absolutely-minimal-job + - .setup:rules:dont-interrupt-me + stage: sync + interruptible: false script: - echo "This jobs makes sure this pipeline won't be interrupted! See https://docs.gitlab.com/ee/ci/yaml/#interruptible." @@ -57,6 +63,15 @@ no-jh-check: script: - scripts/no-dir-check jh +verify-ruby-2.7: + extends: + - .absolutely-minimal-job + - .setup:rules:verify-ruby-2.7 + stage: prepare + script: + - echo 'Please remove label ~"pipeline:run-in-ruby3" so we do test against Ruby 2.7 (default version) before merging the merge request' + - exit 1 + verify-tests-yml: extends: - .setup:rules:verify-tests-yml @@ -70,8 +85,8 @@ verify-tests-yml: verify-approvals: extends: + - .minimal-job - .setup:rules:jh-contribution - needs: [] script: - source scripts/utils.sh - install_gitlab_gem diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue index 33e5c61f4fc..3c22844434d 100644 --- a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue +++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue @@ -1,5 +1,15 @@ <script> -import { GlForm, GlIcon, GlLink, GlButton, GlSprintf, GlAlert } from '@gitlab/ui'; +import { + GlForm, + GlIcon, + GlLink, + GlButton, + GlSprintf, + GlAlert, + GlFormGroup, + GlFormInput, + GlFormSelect, +} from '@gitlab/ui'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import axios from '~/lib/utils/axios_utils'; import csrf from '~/lib/utils/csrf'; @@ -76,9 +86,12 @@ export default { }, components: { GlAlert, + GlIcon, GlForm, + GlFormGroup, + GlFormInput, + GlFormSelect, GlSprintf, - GlIcon, GlLink, GlButton, MarkdownField, @@ -311,155 +324,151 @@ export default { name="wiki[last_commit_sha]" :value="pageInfo.lastCommitSha" /> - <div class="form-group row"> - <div class="col-sm-2 col-form-label"> - <label class="control-label-full-width" for="wiki_title">{{ - $options.i18n.title.label - }}</label> - </div> - <div class="col-sm-10"> - <input - id="wiki_title" - v-model="title" - name="wiki[title]" - type="text" - class="form-control" - data-qa-selector="wiki_title_textbox" - :required="true" - :autofocus="!pageInfo.persisted" - :placeholder="$options.i18n.title.placeholder" - @input="updateCommitMessage" - /> - <span class="gl-display-inline-block gl-max-w-full gl-mt-2 gl-text-gray-600"> - <gl-icon class="gl-mr-n1" name="bulb" /> - {{ titleHelpText }} - <gl-link :href="helpPath" target="_blank"> - {{ $options.i18n.title.helpText.learnMore }} - </gl-link> - </span> - </div> - </div> - <div class="form-group row"> - <div class="col-sm-2 col-form-label"> - <label class="control-label-full-width" for="wiki_format">{{ - $options.i18n.format.label - }}</label> + + <div class="row"> + <div class="col-sm-9"> + <gl-form-group :label="$options.i18n.title.label" label-for="wiki_title"> + <template #description> + <gl-icon class="gl-mr-n1" name="bulb" /> + {{ titleHelpText }} + <gl-link :href="helpPath" target="_blank"> + {{ $options.i18n.title.helpText.learnMore }} + </gl-link> + </template> + + <gl-form-input + id="wiki_title" + v-model="title" + name="wiki[title]" + type="text" + class="form-control" + data-qa-selector="wiki_title_textbox" + :required="true" + :autofocus="!pageInfo.persisted" + :placeholder="$options.i18n.title.placeholder" + @input="updateCommitMessage" + /> + </gl-form-group> </div> - <div class="col-sm-10"> - <select - id="wiki_format" - v-model="format" - class="form-control" - name="wiki[format]" - :disabled="isContentEditorActive" - > - <option v-for="(key, label) of formatOptions" :key="key" :value="key"> - {{ label }} - </option> - </select> + + <div class="col-sm-3 row-sm-10"> + <gl-form-group :label="$options.i18n.format.label" label-for="wiki_format"> + <gl-form-select + id="wiki_format" + v-model="format" + name="wiki[format]" + :disabled="isContentEditorActive" + class="form-control" + :value="formatOptions.Markdown" + > + <option v-for="(key, label) of formatOptions" :key="key" :value="key"> + {{ label }} + </option> + </gl-form-select> + </gl-form-group> </div> </div> - <div class="form-group row" data-testid="wiki-form-content-fieldset"> - <div class="col-sm-2 col-form-label"> - <label class="control-label-full-width" for="wiki_content">{{ - $options.i18n.content.label - }}</label> - </div> - <div class="col-sm-10"> - <div v-if="isMarkdownFormat" class="gl-display-flex gl-justify-content-end gl-mb-3"> - <gl-button - data-testid="toggle-editing-mode-button" - data-qa-selector="editing_mode_button" - :data-qa-mode="toggleEditingModeButtonText" - variant="link" - @click="toggleEditingMode" - >{{ toggleEditingModeButtonText }}</gl-button - > - </div> - <local-storage-sync - storage-key="gl-wiki-content-editor-enabled" - :value="useContentEditor" - @input="setUseContentEditor" - /> - <markdown-field - v-if="!isContentEditorActive" - :markdown-preview-path="pageInfo.markdownPreviewPath" - :can-attach-file="true" - :enable-autocomplete="true" - :textarea-value="content" - :markdown-docs-path="pageInfo.markdownHelpPath" - :uploads-path="pageInfo.uploadsPath" - :enable-preview="isMarkdownFormat" - class="bordered-box" - > - <template #textarea> - <textarea - id="wiki_content" - ref="textarea" - v-model="content" - name="wiki[content]" - class="note-textarea js-gfm-input js-autosize markdown-area" - dir="auto" - data-supports-quick-actions="false" - data-qa-selector="wiki_content_textarea" - :autofocus="pageInfo.persisted" - :aria-label="$options.i18n.content.label" - :placeholder="$options.i18n.content.placeholder" - @input="handleContentChange" + + <div class="row" data-testid="wiki-form-content-fieldset"> + <div class="col-sm-12 row-sm-5"> + <gl-form-group> + <div v-if="isMarkdownFormat" class="gl-display-flex gl-justify-content-end gl-mb-3"> + <gl-button + data-testid="toggle-editing-mode-button" + data-qa-selector="editing_mode_button" + :data-qa-mode="toggleEditingModeButtonText" + variant="link" + @click="toggleEditingMode" + >{{ toggleEditingModeButtonText }}</gl-button > - </textarea> - </template> - </markdown-field> - <div v-if="isContentEditorActive"> - <content-editor - :render-markdown="renderMarkdown" - :uploads-path="pageInfo.uploadsPath" - @initialized="loadInitialContent" - @change="handleContentEditorChange" + </div> + <local-storage-sync + storage-key="gl-wiki-content-editor-enabled" + :value="useContentEditor" + @input="setUseContentEditor" /> - <input id="wiki_content" v-model.trim="content" type="hidden" name="wiki[content]" /> - </div> - - <div class="clearfix"></div> - <div class="error-alert"></div> - - <div class="form-text gl-text-gray-600"> - <gl-sprintf v-if="displayWikiSpecificMarkdownHelp" :message="$options.i18n.linksHelpText"> - <template #linkExample - ><code>{{ linkExample }}</code></template + <markdown-field + v-if="!isContentEditorActive" + :markdown-preview-path="pageInfo.markdownPreviewPath" + :can-attach-file="true" + :enable-autocomplete="true" + :textarea-value="content" + :markdown-docs-path="pageInfo.markdownHelpPath" + :uploads-path="pageInfo.uploadsPath" + :enable-preview="isMarkdownFormat" + class="bordered-box" + > + <template #textarea> + <textarea + id="wiki_content" + ref="textarea" + v-model="content" + name="wiki[content]" + class="note-textarea js-gfm-input js-autosize markdown-area" + dir="auto" + data-supports-quick-actions="false" + data-qa-selector="wiki_content_textarea" + :autofocus="pageInfo.persisted" + :aria-label="$options.i18n.content.label" + :placeholder="$options.i18n.content.placeholder" + @input="handleContentChange" + > + </textarea> + </template> + </markdown-field> + <div v-if="isContentEditorActive"> + <content-editor + :render-markdown="renderMarkdown" + :uploads-path="pageInfo.uploadsPath" + @initialized="loadInitialContent" + @change="handleContentEditorChange" + /> + <input id="wiki_content" v-model.trim="content" type="hidden" name="wiki[content]" /> + </div> + + <div class="clearfix"></div> + <div class="error-alert"></div> + + <div class="form-text gl-text-gray-600"> + <gl-sprintf + v-if="displayWikiSpecificMarkdownHelp" + :message="$options.i18n.linksHelpText" > - <template - #link="// eslint-disable-next-line vue/no-template-shadow + <template #linkExample> + <code>{{ linkExample }}</code> + </template> + <template + #link="// eslint-disable-next-line vue/no-template-shadow { content }" - ><gl-link - :href="wikiSpecificMarkdownHelpPath" - target="_blank" - data-testid="wiki-markdown-help-link" - >{{ content }}</gl-link - ></template - > - </gl-sprintf> - </div> + ><gl-link + :href="wikiSpecificMarkdownHelpPath" + target="_blank" + data-testid="wiki-markdown-help-link" + >{{ content }}</gl-link + ></template + > + </gl-sprintf> + </div> + </gl-form-group> </div> </div> - <div class="form-group row"> - <div class="col-sm-2 col-form-label"> - <label class="control-label-full-width" for="wiki_message">{{ - $options.i18n.commitMessage.label - }}</label> - </div> - <div class="col-sm-10"> - <input - id="wiki_message" - v-model.trim="commitMessage" - name="wiki[message]" - type="text" - class="form-control" - data-qa-selector="wiki_message_textbox" - :placeholder="$options.i18n.commitMessage.label" - /> + + <div class="row"> + <div class="col-sm-12 row-sm-5"> + <gl-form-group :label="$options.i18n.commitMessage.label" label-for="wiki_message"> + <gl-form-input + id="wiki_message" + v-model.trim="commitMessage" + name="wiki[message]" + type="text" + class="form-control" + data-qa-selector="wiki_message_textbox" + :placeholder="$options.i18n.commitMessage.label" + /> + </gl-form-group> </div> </div> + <div class="form-actions"> <gl-button category="primary" diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 5bb06cdbb4a..4a24624528a 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -450,9 +450,14 @@ class Namespace < ApplicationRecord end def pages_virtual_domain + cache = if Feature.enabled?(:cache_pages_domain_api, root_ancestor) + ::Gitlab::Pages::CacheControl.for_namespace(root_ancestor.id) + end + Pages::VirtualDomain.new( - all_projects_with_pages.includes(:route, :project_feature, pages_metadatum: :pages_deployment), - trim_prefix: full_path + projects: all_projects_with_pages.includes(:route, :project_feature, pages_metadatum: :pages_deployment), + trim_prefix: full_path, + cache: cache ) end diff --git a/app/models/pages/virtual_domain.rb b/app/models/pages/virtual_domain.rb index 497f67993ae..119cc7fc166 100644 --- a/app/models/pages/virtual_domain.rb +++ b/app/models/pages/virtual_domain.rb @@ -2,8 +2,9 @@ module Pages class VirtualDomain - def initialize(projects, trim_prefix: nil, domain: nil) + def initialize(projects:, cache: nil, trim_prefix: nil, domain: nil) @projects = projects + @cache = cache @trim_prefix = trim_prefix @domain = domain end @@ -27,8 +28,12 @@ module Pages paths.sort_by(&:prefix).reverse end + def cache_key + @cache_key ||= cache&.cache_key + end + private - attr_reader :projects, :trim_prefix, :domain + attr_reader :projects, :trim_prefix, :domain, :cache end end diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 93119bbff1f..9e93bff4acf 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -209,7 +209,15 @@ class PagesDomain < ApplicationRecord def pages_virtual_domain return unless pages_deployed? - Pages::VirtualDomain.new([project], domain: self) + cache = if Feature.enabled?(:cache_pages_domain_api, project.root_namespace) + ::Gitlab::Pages::CacheControl.for_project(project.id) + end + + Pages::VirtualDomain.new( + projects: [project], + domain: self, + cache: cache + ) end def clear_auto_ssl_failure diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml index 749584a7044..bd98003a804 100644 --- a/app/views/notify/closed_merge_request_email.html.haml +++ b/app/views/notify/closed_merge_request_email.html.haml @@ -1,3 +1,4 @@ %p - Merge request #{merge_request_reference_link(@merge_request)} - was closed by #{sanitize_name(@updated_by.name)} + - mr_link = merge_request_reference_link(@merge_request) + - closed_by = sanitize_name(@updated_by.name) + = s_('Notify|Merge request %{mr_link} was closed by %{closed_by}').html_safe % { mr_link: mr_link, closed_by: closed_by } diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index d4612a65c07..6e9f0e8894c 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -2704,6 +2704,15 @@ :weight: 1 :idempotent: false :tags: [] +- :name: pages_invalidate_domain_cache + :worker_name: Pages::InvalidateDomainCacheWorker + :feature_category: :pages + :has_external_dependencies: false + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: pages_transfer :worker_name: PagesTransferWorker :feature_category: :pages diff --git a/app/workers/pages/invalidate_domain_cache_worker.rb b/app/workers/pages/invalidate_domain_cache_worker.rb new file mode 100644 index 00000000000..63b6f5c05b5 --- /dev/null +++ b/app/workers/pages/invalidate_domain_cache_worker.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Pages + class InvalidateDomainCacheWorker + include Gitlab::EventStore::Subscriber + + idempotent! + + feature_category :pages + + def handle_event(event) + if event.data[:project_id] + ::Gitlab::Pages::CacheControl + .for_project(event.data[:project_id]) + .clear_cache + end + + if event.data[:root_namespace_id] + ::Gitlab::Pages::CacheControl + .for_namespace(event.data[:root_namespace_id]) + .clear_cache + end + end + end +end diff --git a/config/feature_flags/development/about_your_company_registration_flow.yml b/config/feature_flags/development/about_your_company_registration_flow.yml index 424de7a9ba2..63c1e42972a 100644 --- a/config/feature_flags/development/about_your_company_registration_flow.yml +++ b/config/feature_flags/development/about_your_company_registration_flow.yml @@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83345 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/355909 milestone: '14.10' type: development -group: group::adoption +group: group::acquisition default_enabled: false diff --git a/config/feature_flags/development/cache_pages_domain_api.yml b/config/feature_flags/development/cache_pages_domain_api.yml new file mode 100644 index 00000000000..409497aa22d --- /dev/null +++ b/config/feature_flags/development/cache_pages_domain_api.yml @@ -0,0 +1,8 @@ +--- +name: cache_pages_domain_api +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88956 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/364127 +milestone: '15.2' +type: development +group: group::editor +default_enabled: false diff --git a/config/feature_flags/development/subgroups_approval_rules.yml b/config/feature_flags/development/subgroups_approval_rules.yml new file mode 100644 index 00000000000..13f4e872b4f --- /dev/null +++ b/config/feature_flags/development/subgroups_approval_rules.yml @@ -0,0 +1,8 @@ +--- +name: subgroups_approval_rules +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91598 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366741 +milestone: '15.2' +type: development +group: group::source code +default_enabled: false diff --git a/config/feature_flags/development/update_oauth_registration_flow.yml b/config/feature_flags/development/update_oauth_registration_flow.yml index 5dd565e2143..7b066ebf9ab 100644 --- a/config/feature_flags/development/update_oauth_registration_flow.yml +++ b/config/feature_flags/development/update_oauth_registration_flow.yml @@ -1,8 +1,8 @@ --- name: update_oauth_registration_flow introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85871 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363030 -milestone: '15.1' +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/364342 +milestone: '15.2' type: development -group: group::adoption +group: group::acquisition default_enabled: false diff --git a/config/feature_flags/experiment/combined_registration.yml b/config/feature_flags/experiment/combined_registration.yml index 1be740c7141..0b867353946 100644 --- a/config/feature_flags/experiment/combined_registration.yml +++ b/config/feature_flags/experiment/combined_registration.yml @@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67614 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/285533 milestone: '14.3' type: experiment -group: group::adoption +group: group::acquisition default_enabled: false diff --git a/config/feature_flags/experiment/invite_members_in_side_nav.yml b/config/feature_flags/experiment/invite_members_in_side_nav.yml index 7968a885374..1cb8d6d2b0a 100644 --- a/config/feature_flags/experiment/invite_members_in_side_nav.yml +++ b/config/feature_flags/experiment/invite_members_in_side_nav.yml @@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70451 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/342951 milestone: '14.5' type: experiment -group: group::expansion +group: group::acquisition default_enabled: false diff --git a/config/feature_flags/experiment/null_hypothesis.yml b/config/feature_flags/experiment/null_hypothesis.yml index 8ac76809842..bf1526fa950 100644 --- a/config/feature_flags/experiment/null_hypothesis.yml +++ b/config/feature_flags/experiment/null_hypothesis.yml @@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45840 rollout_issue_url: milestone: '13.7' type: experiment -group: group::adoption +group: group::acquisition default_enabled: false diff --git a/config/feature_flags/experiment/pql_three_cta_test.yml b/config/feature_flags/experiment/pql_three_cta_test.yml index f65d3080c05..aa46c51d7ed 100644 --- a/config/feature_flags/experiment/pql_three_cta_test.yml +++ b/config/feature_flags/experiment/pql_three_cta_test.yml @@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74054 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349799 milestone: '14.7' type: experiment -group: group::conversion +group: group::acquisition default_enabled: false diff --git a/config/feature_flags/experiment/security_reports_mr_widget_prompt.yml b/config/feature_flags/experiment/security_reports_mr_widget_prompt.yml index ca6d17b1720..16cc3d890d7 100644 --- a/config/feature_flags/experiment/security_reports_mr_widget_prompt.yml +++ b/config/feature_flags/experiment/security_reports_mr_widget_prompt.yml @@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70086 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340436 milestone: '14.3' type: experiment -group: group::adoption +group: group::acquisition default_enabled: false diff --git a/config/feature_flags/experiment/video_tutorials_continuous_onboarding.yml b/config/feature_flags/experiment/video_tutorials_continuous_onboarding.yml index 6dc3f798f63..e047a4e9682 100644 --- a/config/feature_flags/experiment/video_tutorials_continuous_onboarding.yml +++ b/config/feature_flags/experiment/video_tutorials_continuous_onboarding.yml @@ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82274 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351916 milestone: '14.9' type: experiment -group: group::adoption +group: group::acquisition default_enabled: false diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index ce4640e65e4..0f7f40dfd56 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -327,6 +327,8 @@ - 1 - - pages_domain_verification - 1 +- - pages_invalidate_domain_cache + - 1 - - pages_transfer - 1 - - personal_access_tokens diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md index e222997df31..2bf1e5a315a 100644 --- a/doc/development/pipelines.md +++ b/doc/development/pipelines.md @@ -275,6 +275,19 @@ rather than from the default branch `main-jh`. NOTE: For now, CI will try to fetch the branch on the [GitLab JH mirror](https://gitlab.com/gitlab-org/gitlab-jh-mirrors/gitlab), so it might take some time for the new JH branch to propagate to the mirror. +## Ruby 3.0 jobs + +You can add the `pipeline:run-in-ruby3` label to the merge request to switch +the Ruby version used for running the whole test suite to 3.0. When you do +this, the test suite will no longer run in Ruby 2.7 (default), and an +additional job `verify-ruby-2.7` will also run and always fail to remind us to +remove the label and run in Ruby 2.7 before merging the merge request. + +This should let us: + +- Test changes for Ruby 3.0 +- Make sure it will not break anything when it's merged into the default branch + ## `undercover` RSpec test > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74859) in GitLab 14.6. diff --git a/doc/operations/metrics/img/prometheus_integration_alerts.png b/doc/operations/metrics/img/prometheus_integration_alerts.png Binary files differdeleted file mode 100644 index 609c5e5196c..00000000000 --- a/doc/operations/metrics/img/prometheus_integration_alerts.png +++ /dev/null diff --git a/doc/user/project/issues/img/turn_off_confidentiality_v15_0.png b/doc/user/project/issues/img/turn_off_confidentiality_v15_0.png Binary files differdeleted file mode 100644 index 37cbe0f4fd9..00000000000 --- a/doc/user/project/issues/img/turn_off_confidentiality_v15_0.png +++ /dev/null diff --git a/doc/user/project/issues/img/turn_on_confidentiality_v15_0.png b/doc/user/project/issues/img/turn_on_confidentiality_v15_0.png Binary files differdeleted file mode 100644 index 498867d1933..00000000000 --- a/doc/user/project/issues/img/turn_on_confidentiality_v15_0.png +++ /dev/null diff --git a/doc/user/project/settings/img/cve_id_request_toggle.png b/doc/user/project/settings/img/cve_id_request_toggle.png Binary files differdeleted file mode 100644 index 53ec804922c..00000000000 --- a/doc/user/project/settings/img/cve_id_request_toggle.png +++ /dev/null diff --git a/lib/api/internal/pages.rb b/lib/api/internal/pages.rb index 8eaeeae26c2..20ca7038471 100644 --- a/lib/api/internal/pages.rb +++ b/lib/api/internal/pages.rb @@ -54,7 +54,15 @@ module API virtual_domain = host.pages_virtual_domain no_content! unless virtual_domain - present virtual_domain, with: Entities::Internal::Pages::VirtualDomain + if virtual_domain.cache_key.present? + # Cache context is not added to make it easier to expire the cache with + # Gitlab::Pages::CacheControl + present_cached virtual_domain, + cache_context: nil, + with: Entities::Internal::Pages::VirtualDomain + else + present virtual_domain, with: Entities::Internal::Pages::VirtualDomain + end end end end diff --git a/lib/gitlab/ci/templates/Rust.gitlab-ci.yml b/lib/gitlab/ci/templates/Rust.gitlab-ci.yml index 390f0bb8061..dafe3ca7bc7 100644 --- a/lib/gitlab/ci/templates/Rust.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Rust.gitlab-ci.yml @@ -26,3 +26,14 @@ test:cargo: script: - rustc --version && cargo --version # Print version info for debugging - cargo test --workspace --verbose + +# Optional: Use a third party library to generate gitlab junit reports +# test:junit-report: +# script: +# Should be specified in Cargo.toml +# - cargo install junitify +# - cargo test -- --format=json -Z unstable-options --report-time | junitify --out $CI_PROJECT_DIR/tests/ +# artifacts: +# when: always +# reports: +# junit: $CI_PROJECT_DIR/tests/*.xml diff --git a/lib/gitlab/error_tracking.rb b/lib/gitlab/error_tracking.rb index 35c1a1e73cf..83920182da4 100644 --- a/lib/gitlab/error_tracking.rb +++ b/lib/gitlab/error_tracking.rb @@ -20,7 +20,10 @@ module Gitlab ::Gitlab::ErrorTracking::Processor::SidekiqProcessor, ::Gitlab::ErrorTracking::Processor::GrpcErrorProcessor, ::Gitlab::ErrorTracking::Processor::ContextPayloadProcessor, - ::Gitlab::ErrorTracking::Processor::SanitizeErrorMessageProcessor + ::Gitlab::ErrorTracking::Processor::SanitizeErrorMessageProcessor, + # IMPORTANT: this processor must stay at the bottom, right before + # sending the event to Sentry. + ::Gitlab::ErrorTracking::Processor::SanitizerProcessor ].freeze class << self diff --git a/lib/gitlab/error_tracking/processor/sanitizer_processor.rb b/lib/gitlab/error_tracking/processor/sanitizer_processor.rb new file mode 100644 index 00000000000..e6114f8e206 --- /dev/null +++ b/lib/gitlab/error_tracking/processor/sanitizer_processor.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Gitlab + module ErrorTracking + module Processor + module SanitizerProcessor + SANITIZED_HTTP_HEADERS = %w[Authorization Private-Token Job-Token].freeze + SANITIZED_ATTRIBUTES = %i[user contexts extra tags].freeze + + # This processor removes sensitive fields or headers from the event + # before sending. Sentry versions above 4.0 don't support + # sanitized_fields and sanitized_http_headers anymore. The official + # document recommends using before_send instead. + # + # For more information, please visit: + # https://docs.sentry.io/platforms/ruby/guides/rails/configuration/filtering/#using-beforesend + def self.call(event) + # Raven::Event instances don't need this processing. + return event unless event.is_a?(Sentry::Event) + + if event.request.present? + event.request.cookies = {} + event.request.data = {} + end + + if event.request.present? && event.request.headers.is_a?(Hash) + header_filter = ActiveSupport::ParameterFilter.new(SANITIZED_HTTP_HEADERS) + event.request.headers = header_filter.filter(event.request.headers) + end + + attribute_filter = ActiveSupport::ParameterFilter.new(Rails.application.config.filter_parameters) + SANITIZED_ATTRIBUTES.each do |attribute| + event.send("#{attribute}=", attribute_filter.filter(event.send(attribute))) # rubocop:disable GitlabSecurity/PublicSend + end + + if event.request.present? && event.request.query_string.present? + query = Rack::Utils.parse_nested_query(event.request.query_string) + query = attribute_filter.filter(query) + query = Rack::Utils.build_nested_query(query) + event.request.query_string = query + end + + event + end + end + end + end +end diff --git a/lib/gitlab/event_store.rb b/lib/gitlab/event_store.rb index e20ea1c7365..ee0e9e195a7 100644 --- a/lib/gitlab/event_store.rb +++ b/lib/gitlab/event_store.rb @@ -35,6 +35,9 @@ module Gitlab store.subscribe ::MergeRequests::UpdateHeadPipelineWorker, to: ::Ci::PipelineCreatedEvent store.subscribe ::Namespaces::UpdateRootStatisticsWorker, to: ::Projects::ProjectDeletedEvent + + store.subscribe ::Pages::InvalidateDomainCacheWorker, to: ::Pages::PageDeployedEvent + store.subscribe ::Pages::InvalidateDomainCacheWorker, to: ::Pages::PageDeletedEvent end private_class_method :configure! end diff --git a/lib/gitlab/pages/cache_control.rb b/lib/gitlab/pages/cache_control.rb new file mode 100644 index 00000000000..991a1297d03 --- /dev/null +++ b/lib/gitlab/pages/cache_control.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Gitlab + module Pages + class CacheControl + CACHE_KEY_FORMAT = 'pages_domain_for_%{type}_%{id}' + + attr_reader :cache_key + + class << self + def for_project(project_id) + new(type: :project, id: project_id) + end + + def for_namespace(namespace_id) + new(type: :namespace, id: namespace_id) + end + end + + def initialize(type:, id:) + raise(ArgumentError, "type must be :namespace or :project") unless %i[namespace project].include?(type) + + @cache_key = CACHE_KEY_FORMAT % { type: type, id: id } + end + + def clear_cache + Rails.cache.delete(cache_key) + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 61bbb20d19a..eb973319562 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -26446,6 +26446,9 @@ msgstr "" msgid "Notify|Merge request %{merge_request} was merged" msgstr "" +msgid "Notify|Merge request %{mr_link} was closed by %{closed_by}" +msgstr "" + msgid "Notify|Merge request URL: %{merge_request_url}" msgstr "" @@ -40987,9 +40990,6 @@ msgstr "" msgid "Unable to fetch upstream and downstream pipelines." msgstr "" -msgid "Unable to fetch vulnerable projects" -msgstr "" - msgid "Unable to find Jira project to import data from." msgstr "" diff --git a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js index fc490bfb774..a5db10d106d 100644 --- a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js +++ b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js @@ -1,5 +1,5 @@ import { nextTick } from 'vue'; -import { GlAlert, GlButton } from '@gitlab/ui'; +import { GlAlert, GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui'; import { mount, shallowMount } from '@vue/test-utils'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; @@ -107,6 +107,8 @@ describe('WikiForm', () => { GlAlert, GlButton, LocalStorageSync: stubComponent(LocalStorageSync), + GlFormInput, + GlFormGroup, }, }), ); @@ -132,7 +134,7 @@ describe('WikiForm', () => { `( 'updates the commit message to $message when title is $title and persisted=$persisted', async ({ title, message, persisted }) => { - createWrapper({ persisted }); + createWrapper({ persisted, mountFn: mount }); await findTitle().setValue(title); @@ -141,7 +143,7 @@ describe('WikiForm', () => { ); it('sets the commit message to "Update My page" when the page first loads when persisted', async () => { - createWrapper({ persisted: true }); + createWrapper({ persisted: true, mountFn: mount }); await nextTick(); @@ -161,7 +163,7 @@ describe('WikiForm', () => { ${'asciidoc'} | ${false} | ${'hides'} ${'org'} | ${false} | ${'hides'} `('$action preview in the markdown field when format is $format', async ({ format, enabled }) => { - createWrapper(); + createWrapper({ mountFn: mount }); await setFormat(format); @@ -258,7 +260,7 @@ describe('WikiForm', () => { `( "when title='$title', content='$content', then the button is $buttonState'", async ({ title, content, disabledAttr }) => { - createWrapper(); + createWrapper({ mountFn: mount }); await findTitle().setValue(title); await findContent().setValue(content); @@ -295,7 +297,7 @@ describe('WikiForm', () => { describe('toggle editing mode control', () => { beforeEach(() => { - createWrapper(); + createWrapper({ mountFn: mount }); }); it.each` diff --git a/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb b/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb index 2878d72210e..68c29bad287 100644 --- a/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb +++ b/spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb @@ -38,7 +38,11 @@ RSpec.describe Gitlab::DatabaseImporters::InstanceAdministrators::CreateGroup do end end - context 'with application settings and admin users', :do_not_mock_admin_mode_setting do + context( + 'with application settings and admin users', + :do_not_mock_admin_mode_setting, + :do_not_stub_snowplow_by_default + ) do let(:group) { result[:group] } let(:application_setting) { Gitlab::CurrentSettings.current_application_settings } diff --git a/spec/lib/gitlab/error_tracking/processor/sanitizer_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/sanitizer_processor_spec.rb new file mode 100644 index 00000000000..9673bfc5cd3 --- /dev/null +++ b/spec/lib/gitlab/error_tracking/processor/sanitizer_processor_spec.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::ErrorTracking::Processor::SanitizerProcessor, :sentry do + describe '.call' do + let(:event) { Sentry.get_current_client.event_from_exception(exception) } + let(:result_hash) { described_class.call(event).to_hash } + + before do + data.each do |key, value| + event.send("#{key}=", value) + end + end + + after do + Sentry.get_current_scope.clear + end + + context 'when event attributes contains sensitive information' do + let(:exception) { RuntimeError.new } + let(:data) do + { + contexts: { + jwt: 'abcdef', + controller: 'GraphController#execute' + }, + tags: { + variables: %w[some sensitive information'], + deep_hash: { + sharedSecret: 'secret123' + } + }, + user: { + email: 'a@a.com', + password: 'nobodyknows' + }, + extra: { + issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/-/issues/1', + my_token: '[FILTERED]', + another_token: '[FILTERED]' + } + } + end + + it 'filters sensitive attributes' do + expect_next_instance_of(ActiveSupport::ParameterFilter) do |instance| + expect(instance).to receive(:filter).exactly(4).times.and_call_original + end + + expect(result_hash).to include( + contexts: { + jwt: '[FILTERED]', + controller: 'GraphController#execute' + }, + tags: { + variables: '[FILTERED]', + deep_hash: { + sharedSecret: '[FILTERED]' + } + }, + user: { + email: 'a@a.com', + password: '[FILTERED]' + }, + extra: { + issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/-/issues/1', + my_token: '[FILTERED]', + another_token: '[FILTERED]' + } + ) + end + end + + context 'when request contains sensitive information' do + let(:exception) { RuntimeError.new } + let(:data) { {} } + + before do + event.rack_env = { + 'HTTP_AUTHORIZATION' => 'Bearer 123456', + 'HTTP_PRIVATE_TOKEN' => 'abcdef', + 'HTTP_JOB_TOKEN' => 'secret123', + 'HTTP_GITLAB_WORKHORSE_PROXY_START' => 123456, + 'HTTP_COOKIE' => 'yummy_cookie=choco; tasty_cookie=strawberry', + 'QUERY_STRING' => 'token=secret&access_token=secret&job_token=secret&private_token=secret', + 'Content-Type' => 'application/json', + 'rack.input' => StringIO.new('{"name":"new_project", "some_token":"value"}') + } + end + + it 'filters sensitive headers' do + expect(result_hash[:request][:headers]).to include( + 'Authorization' => '[FILTERED]', + 'Private-Token' => '[FILTERED]', + 'Job-Token' => '[FILTERED]', + 'Gitlab-Workhorse-Proxy-Start' => '123456' + ) + end + + it 'filters query string parameters' do + expect(result_hash[:request][:query_string]).not_to include('secret') + end + + it 'removes cookies' do + expect(result_hash[:request][:cookies]).to be_empty + end + + it 'removes data' do + expect(result_hash[:request][:data]).to be_empty + end + end + end +end diff --git a/spec/lib/gitlab/error_tracking_spec.rb b/spec/lib/gitlab/error_tracking_spec.rb index 1ade3a51c55..fd859ae40fb 100644 --- a/spec/lib/gitlab/error_tracking_spec.rb +++ b/spec/lib/gitlab/error_tracking_spec.rb @@ -424,5 +424,25 @@ RSpec.describe Gitlab::ErrorTracking do end end end + + context 'when request contains sensitive information' do + before do + Sentry.get_current_scope.set_rack_env({ + 'HTTP_AUTHORIZATION' => 'Bearer 123456', + 'HTTP_PRIVATE_TOKEN' => 'abcdef', + 'HTTP_JOB_TOKEN' => 'secret123' + }) + end + + it 'filters sensitive data' do + track_exception + + expect(sentry_event.to_hash[:request][:headers]).to include( + 'Authorization' => '[FILTERED]', + 'Private-Token' => '[FILTERED]', + 'Job-Token' => '[FILTERED]' + ) + end + end end end diff --git a/spec/lib/gitlab/pages/cache_control_spec.rb b/spec/lib/gitlab/pages/cache_control_spec.rb new file mode 100644 index 00000000000..6ed823427fb --- /dev/null +++ b/spec/lib/gitlab/pages/cache_control_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Pages::CacheControl do + it 'fails with invalid type' do + expect { described_class.new(type: :unknown, id: nil) } + .to raise_error(ArgumentError, "type must be :namespace or :project") + end + + describe '.for_namespace' do + let(:subject) { described_class.for_namespace(1) } + + it { expect(subject.cache_key).to eq('pages_domain_for_namespace_1') } + + describe '#clear_cache' do + it 'clears the cache' do + expect(Rails.cache) + .to receive(:delete) + .with('pages_domain_for_namespace_1') + + subject.clear_cache + end + end + end + + describe '.for_project' do + let(:subject) { described_class.for_project(1) } + + it { expect(subject.cache_key).to eq('pages_domain_for_project_1') } + + describe '#clear_cache' do + it 'clears the cache' do + expect(Rails.cache) + .to receive(:delete) + .with('pages_domain_for_project_1') + + subject.clear_cache + end + end + end +end diff --git a/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb b/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb index 06cc2d3800c..1d4725cf405 100644 --- a/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb +++ b/spec/lib/gitlab/tracking/destinations/snowplow_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Tracking::Destinations::Snowplow do +RSpec.describe Gitlab::Tracking::Destinations::Snowplow, :do_not_stub_snowplow_by_default do let(:emitter) { SnowplowTracker::Emitter.new('localhost', buffer_size: 1) } let(:tracker) { SnowplowTracker::Tracker.new(emitter, SnowplowTracker::Subject.new, 'namespace', 'app_id') } diff --git a/spec/lib/gitlab/tracking/incident_management_spec.rb b/spec/lib/gitlab/tracking/incident_management_spec.rb index fbcb9bf3e4c..ef7816aa0db 100644 --- a/spec/lib/gitlab/tracking/incident_management_spec.rb +++ b/spec/lib/gitlab/tracking/incident_management_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Gitlab::Tracking::IncidentManagement do described_class.track_from_params(params) end - context 'known params' do + context 'known params', :do_not_stub_snowplow_by_default do known_params = described_class.tracking_keys known_params.each do |key, values| diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index dcb9890c7cd..afcc3cee913 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -305,18 +305,20 @@ RSpec.describe ApplicationSetting do end end - context 'when snowplow is enabled' do - before do - setting.snowplow_enabled = true - end + describe 'snowplow settings', :do_not_stub_snowplow_by_default do + context 'when snowplow is enabled' do + before do + setting.snowplow_enabled = true + end - it { is_expected.not_to allow_value(nil).for(:snowplow_collector_hostname) } - it { is_expected.to allow_value("snowplow.gitlab.com").for(:snowplow_collector_hostname) } - it { is_expected.not_to allow_value('/example').for(:snowplow_collector_hostname) } - end + it { is_expected.not_to allow_value(nil).for(:snowplow_collector_hostname) } + it { is_expected.to allow_value("snowplow.gitlab.com").for(:snowplow_collector_hostname) } + it { is_expected.not_to allow_value('/example').for(:snowplow_collector_hostname) } + end - context 'when snowplow is not enabled' do - it { is_expected.to allow_value(nil).for(:snowplow_collector_hostname) } + context 'when snowplow is not enabled' do + it { is_expected.to allow_value(nil).for(:snowplow_collector_hostname) } + end end context 'when mailgun_events_enabled is enabled' do diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb index dc80e30216a..0629debda15 100644 --- a/spec/models/concerns/cacheable_attributes_spec.rb +++ b/spec/models/concerns/cacheable_attributes_spec.rb @@ -205,7 +205,12 @@ RSpec.describe CacheableAttributes do end end - it 'uses RequestStore in addition to process memory cache', :request_store, :do_not_mock_admin_mode_setting do + it( + 'uses RequestStore in addition to process memory cache', + :request_store, + :do_not_mock_admin_mode_setting, + :do_not_stub_snowplow_by_default + ) do # Warm up the cache create(:application_setting).cache! diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index ee21b857fc8..f4304056907 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -1885,15 +1885,29 @@ RSpec.describe Namespace do describe '#pages_virtual_domain' do let(:project) { create(:project, namespace: namespace) } + let(:virtual_domain) { namespace.pages_virtual_domain } - it 'returns the virual domain' do + before do project.mark_pages_as_deployed project.update_pages_deployment!(create(:pages_deployment, project: project)) + end - virtual_domain = namespace.pages_virtual_domain - + it 'returns the virual domain' do expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain) expect(virtual_domain.lookup_paths).not_to be_empty + expect(virtual_domain.cache_key).to eq("pages_domain_for_namespace_#{namespace.root_ancestor.id}") + end + + context 'when :cache_pages_domain_api is disabled' do + before do + stub_feature_flags(cache_pages_domain_api: false) + end + + it 'returns the virual domain' do + expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain) + expect(virtual_domain.lookup_paths).not_to be_empty + expect(virtual_domain.cache_key).to be_nil + end end end diff --git a/spec/models/pages/virtual_domain_spec.rb b/spec/models/pages/virtual_domain_spec.rb index 29c14cbeb3e..b5a421295b2 100644 --- a/spec/models/pages/virtual_domain_spec.rb +++ b/spec/models/pages/virtual_domain_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Pages::VirtualDomain do let(:domain) { nil } let(:project) { instance_double(Project) } - subject(:virtual_domain) { described_class.new([project], domain: domain) } + subject(:virtual_domain) { described_class.new(projects: [project], domain: domain) } it 'returns nil if there is no domain provided' do expect(virtual_domain.certificate).to be_nil @@ -35,7 +35,7 @@ RSpec.describe Pages::VirtualDomain do context 'when there is pages domain provided' do let(:domain) { instance_double(PagesDomain) } - subject(:virtual_domain) { described_class.new([project_a, project_b, project_c], domain: domain) } + subject(:virtual_domain) { described_class.new(projects: [project_a, project_b, project_c], domain: domain) } it 'returns collection of projects pages lookup paths sorted by prefix in reverse' do expect(project_a).to receive(:pages_lookup_path).with(domain: domain, trim_prefix: nil).and_return(pages_lookup_path_a) @@ -47,7 +47,7 @@ RSpec.describe Pages::VirtualDomain do end context 'when there is trim_prefix provided' do - subject(:virtual_domain) { described_class.new([project_a, project_b], trim_prefix: 'group/') } + subject(:virtual_domain) { described_class.new(projects: [project_a, project_b], trim_prefix: 'group/') } it 'returns collection of projects pages lookup paths sorted by prefix in reverse' do expect(project_a).to receive(:pages_lookup_path).with(trim_prefix: 'group/', domain: nil).and_return(pages_lookup_path_a) @@ -57,4 +57,19 @@ RSpec.describe Pages::VirtualDomain do end end end + + describe '#cache_key' do + it 'returns the cache key based in the given cache_control' do + cache_control = instance_double(::Gitlab::Pages::CacheControl, cache_key: 'cache_key') + virtual_domain = described_class.new(projects: [instance_double(Project)], cache: cache_control) + + expect(virtual_domain.cache_key).to eq('cache_key') + end + + it 'returns nil when no cache_control is given' do + virtual_domain = described_class.new(projects: [instance_double(Project)]) + + expect(virtual_domain.cache_key).to be_nil + end + end end diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index 7fde8d63947..4e463b1194c 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -544,16 +544,31 @@ RSpec.describe PagesDomain do end end - it 'returns the virual domain when there are pages deployed for the project' do - project.mark_pages_as_deployed - project.update_pages_deployment!(create(:pages_deployment, project: project)) + context 'when there are pages deployed for the project' do + let(:virtual_domain) { pages_domain.pages_virtual_domain } - expect(Pages::VirtualDomain).to receive(:new).with([project], domain: pages_domain).and_call_original + before do + project.mark_pages_as_deployed + project.update_pages_deployment!(create(:pages_deployment, project: project)) + end + + it 'returns the virual domain when there are pages deployed for the project' do + expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain) + expect(virtual_domain.lookup_paths).not_to be_empty + expect(virtual_domain.cache_key).to eq("pages_domain_for_project_#{project.id}") + end - virtual_domain = pages_domain.pages_virtual_domain + context 'when :cache_pages_domain_api is disabled' do + before do + stub_feature_flags(cache_pages_domain_api: false) + end - expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain) - expect(virtual_domain.lookup_paths).not_to be_empty + it 'returns the virual domain when there are pages deployed for the project' do + expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain) + expect(virtual_domain.lookup_paths).not_to be_empty + expect(virtual_domain.cache_key).to be_nil + end + end end end diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index cfda06da8f3..d4a8e591622 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -373,7 +373,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting do end end - context "snowplow tracking settings" do + context "snowplow tracking settings", :do_not_stub_snowplow_by_default do let(:settings) do { snowplow_collector_hostname: "snowplow.example.com", diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 06d6a8d9c0f..6f7d3e40901 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -201,10 +201,12 @@ RSpec.configure do |config| config.include SidekiqMiddleware config.include StubActionCableConnection, type: :channel config.include StubSpamServices + config.include SnowplowHelpers config.include RenderedHelpers config.include RSpec::Benchmark::Matchers, type: :benchmark include StubFeatureFlags + include StubSnowplow if ENV['CI'] || ENV['RETRIES'] # This includes the first try, i.e. tests will be run 4 times before failing. @@ -367,6 +369,9 @@ RSpec.configure do |config| stub_application_setting(admin_mode: true) unless example.metadata[:do_not_mock_admin_mode_setting] allow(Gitlab::CurrentSettings).to receive(:current_application_settings?).and_return(false) + + # Ensure that Snowplow is enabled by default unless forced to the opposite + stub_snowplow unless example.metadata[:do_not_stub_snowplow_by_default] end config.around(:example, :quarantine) do |example| diff --git a/spec/support/gitlab_experiment.rb b/spec/support/gitlab_experiment.rb index 823aab0436e..4236091ca6c 100644 --- a/spec/support/gitlab_experiment.rb +++ b/spec/support/gitlab_experiment.rb @@ -2,7 +2,6 @@ # Require the provided spec helper and matchers. require 'gitlab/experiment/rspec' -require_relative 'stub_snowplow' RSpec.configure do |config| config.include StubSnowplow, :experiment diff --git a/spec/support/stub_snowplow.rb b/spec/support/helpers/stub_snowplow.rb index c6e3b40972f..85c605efea3 100644 --- a/spec/support/stub_snowplow.rb +++ b/spec/support/helpers/stub_snowplow.rb @@ -13,7 +13,7 @@ module StubSnowplow .and_return(SnowplowTracker::Emitter.new(host, buffer_size: buffer_size)) # rubocop:enable RSpec/AnyInstanceOf - stub_application_setting(snowplow_enabled: true) + stub_application_setting(snowplow_enabled: true, snowplow_collector_hostname: host) allow(SnowplowTracker::SelfDescribingJson).to receive(:new).and_call_original allow(Gitlab::Tracking).to receive(:event).and_call_original # rubocop:disable RSpec/ExpectGitlabTracking diff --git a/spec/support/shared_examples/csp.rb b/spec/support/shared_examples/csp.rb index 9143d0f4720..91242ae9f37 100644 --- a/spec/support/shared_examples/csp.rb +++ b/spec/support/shared_examples/csp.rb @@ -15,64 +15,66 @@ RSpec.shared_examples 'setting CSP' do |rule_name| end end - context 'when no CSP config' do - include_context 'csp config', nil + context 'csp config and feature toggle', :do_not_stub_snowplow_by_default do + context 'when no CSP config' do + include_context 'csp config', nil - it 'does not add CSP directives' do - is_expected.to be_blank + it 'does not add CSP directives' do + is_expected.to be_blank + end end - end - describe "when a CSP config exists for #{rule_name}" do - include_context 'csp config', rule_name.parameterize.underscore.to_sym + describe "when a CSP config exists for #{rule_name}" do + include_context 'csp config', rule_name.parameterize.underscore.to_sym - context 'when feature is enabled' do - it "appends to #{rule_name}" do - is_expected.to eql("#{rule_name} #{default_csp_values} #{allowlisted_url}") + context 'when feature is enabled' do + it "appends to #{rule_name}" do + is_expected.to eql("#{rule_name} #{default_csp_values} #{allowlisted_url}") + end end - end - context 'when feature is disabled' do - include_context 'disable feature' + context 'when feature is disabled' do + include_context 'disable feature' - it "keeps original #{rule_name}" do - is_expected.to eql("#{rule_name} #{default_csp_values}") + it "keeps original #{rule_name}" do + is_expected.to eql("#{rule_name} #{default_csp_values}") + end end end - end - describe "when a CSP config exists for default-src but not #{rule_name}" do - include_context 'csp config', :default_src + describe "when a CSP config exists for default-src but not #{rule_name}" do + include_context 'csp config', :default_src - context 'when feature is enabled' do - it "uses default-src values in #{rule_name}" do - is_expected.to eql("default-src #{default_csp_values}; #{rule_name} #{default_csp_values} #{allowlisted_url}") + context 'when feature is enabled' do + it "uses default-src values in #{rule_name}" do + is_expected.to eql("default-src #{default_csp_values}; #{rule_name} #{default_csp_values} #{allowlisted_url}") + end end - end - context 'when feature is disabled' do - include_context 'disable feature' + context 'when feature is disabled' do + include_context 'disable feature' - it "does not add #{rule_name}" do - is_expected.to eql("default-src #{default_csp_values}") + it "does not add #{rule_name}" do + is_expected.to eql("default-src #{default_csp_values}") + end end end - end - describe "when a CSP config exists for font-src but not #{rule_name}" do - include_context 'csp config', :font_src + describe "when a CSP config exists for font-src but not #{rule_name}" do + include_context 'csp config', :font_src - context 'when feature is enabled' do - it "uses default-src values in #{rule_name}" do - is_expected.to eql("font-src #{default_csp_values}; #{rule_name} #{allowlisted_url}") + context 'when feature is enabled' do + it "uses default-src values in #{rule_name}" do + is_expected.to eql("font-src #{default_csp_values}; #{rule_name} #{allowlisted_url}") + end end - end - context 'when feature is disabled' do - include_context 'disable feature' + context 'when feature is disabled' do + include_context 'disable feature' - it "does not add #{rule_name}" do - is_expected.to eql("font-src #{default_csp_values}") + it "does not add #{rule_name}" do + is_expected.to eql("font-src #{default_csp_values}") + end end end end diff --git a/spec/support/snowplow.rb b/spec/support/snowplow.rb deleted file mode 100644 index d600bf008b0..00000000000 --- a/spec/support/snowplow.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require_relative 'stub_snowplow' - -RSpec.configure do |config| - config.include SnowplowHelpers, :snowplow - config.include StubSnowplow, :snowplow - - config.before(:each, :snowplow) do - stub_snowplow - end - - config.after(:each, :snowplow) do - Gitlab::Tracking.send(:tracker).send(:tracker).flush - end -end diff --git a/spec/workers/pages/invalidate_domain_cache_worker_spec.rb b/spec/workers/pages/invalidate_domain_cache_worker_spec.rb new file mode 100644 index 00000000000..8fdc7316069 --- /dev/null +++ b/spec/workers/pages/invalidate_domain_cache_worker_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Pages::InvalidateDomainCacheWorker do + let(:event) do + Pages::PageDeployedEvent.new(data: { + project_id: 1, + namespace_id: 2, + root_namespace_id: 3 + }) + end + + subject { consume_event(subscriber: described_class, event: event) } + + it_behaves_like 'subscribes to event' + + it 'enqueues ScheduleAggregationWorker' do + expect_next_instance_of(Gitlab::Pages::CacheControl, type: :project, id: 1) do |cache_control| + expect(cache_control).to receive(:clear_cache) + end + + expect_next_instance_of(Gitlab::Pages::CacheControl, type: :namespace, id: 3) do |cache_control| + expect(cache_control).to receive(:clear_cache) + end + + subject + end +end diff --git a/tooling/config/CODEOWNERS.yml b/tooling/config/CODEOWNERS.yml index 4fa7338b32d..a8ae90437e2 100644 --- a/tooling/config/CODEOWNERS.yml +++ b/tooling/config/CODEOWNERS.yml @@ -15,8 +15,8 @@ - '/{,ee/}lib/**/*%{keyword}*{,/**/*}' deny: keywords: - - '*author.*' - - '*author_*' + - '*author{,s}.*' + - '*author{,s}_*' - '*authored*' - '*authoring*' - '*.png' diff --git a/workhorse/go.mod b/workhorse/go.mod index b90368dfa94..6832647e57f 100644 --- a/workhorse/go.mod +++ b/workhorse/go.mod @@ -24,7 +24,7 @@ require ( github.com/rafaeljusto/redigomock/v3 v3.1.1 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a github.com/sirupsen/logrus v1.8.1 - github.com/smartystreets/goconvey v1.6.4 + github.com/smartystreets/goconvey v1.7.2 github.com/stretchr/testify v1.7.0 gitlab.com/gitlab-org/gitaly/v15 v15.1.0 gitlab.com/gitlab-org/golang-archive-zip v0.1.1 @@ -93,7 +93,7 @@ require ( github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500 // indirect github.com/shirou/gopsutil/v3 v3.21.2 // indirect - github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect + github.com/smartystreets/assertions v1.2.0 // indirect github.com/tinylib/msgp v1.1.2 // indirect github.com/tklauser/go-sysconf v0.3.4 // indirect github.com/tklauser/numcpus v0.2.1 // indirect diff --git a/workhorse/go.sum b/workhorse/go.sum index 58a828db4ce..1a4f0fd500e 100644 --- a/workhorse/go.sum +++ b/workhorse/go.sum @@ -1014,10 +1014,12 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= +github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= |