diff options
39 files changed, 492 insertions, 228 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3d2f32a0390..a02d8d83ff3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -127,7 +127,7 @@ variables: # Run with decomposed databases by default DECOMPOSED_DB: "true" - DOCS_REVIEW_APPS_DOMAIN: "docs.gitlab-review-app" + DOCS_REVIEW_APPS_DOMAIN: "docs.gitlab-review.app" DOCS_GITLAB_REPO_SUFFIX: "ee" REVIEW_APPS_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/ruby-3.0:gcloud-383-kubectl-1.23-helm-3.5" diff --git a/.gitlab/ci/package-and-test/main.gitlab-ci.yml b/.gitlab/ci/package-and-test/main.gitlab-ci.yml index 9d306eed77c..1a1c67bf572 100644 --- a/.gitlab/ci/package-and-test/main.gitlab-ci.yml +++ b/.gitlab/ci/package-and-test/main.gitlab-ci.yml @@ -7,7 +7,7 @@ include: - local: .gitlab/ci/package-and-test/rules.gitlab-ci.yml - local: .gitlab/ci/package-and-test/variables.gitlab-ci.yml - project: gitlab-org/quality/pipeline-common - ref: 1.4.0 + ref: 1.3.0 file: - /ci/base.gitlab-ci.yml - /ci/allure-report.yml diff --git a/.gitlab/ci/review-apps/qa.gitlab-ci.yml b/.gitlab/ci/review-apps/qa.gitlab-ci.yml index af7c0231a74..0214f5ef3f2 100644 --- a/.gitlab/ci/review-apps/qa.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/qa.gitlab-ci.yml @@ -1,6 +1,6 @@ include: - project: gitlab-org/quality/pipeline-common - ref: 1.4.0 + ref: 1.3.0 file: - /ci/base.gitlab-ci.yml - /ci/allure-report.yml diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index bcdd23a89c1..00fe755b145 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -ef8362fdf1c0eca9c73fb0fa4dc5b45c5c7965d8 +3d5bb4cdfd5f31e5e5f0c7506905755e51d0b611 diff --git a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stage.vue b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stage.vue index f1c6c6633eb..10a48c83716 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stage.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stage.vue @@ -27,6 +27,7 @@ export default { }, dropdownPopperOpts: { placement: 'bottom', + positionFixed: true, }, components: { CiIcon, diff --git a/app/assets/javascripts/runner/components/runner_jobs_table.vue b/app/assets/javascripts/runner/components/runner_jobs_table.vue index 7817577bab0..3941781408a 100644 --- a/app/assets/javascripts/runner/components/runner_jobs_table.vue +++ b/app/assets/javascripts/runner/components/runner_jobs_table.vue @@ -2,6 +2,7 @@ import { GlTableLite } from '@gitlab/ui'; import { __, s__ } from '~/locale'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { durationTimeFormatted } from '~/lib/utils/datetime_utility'; import CiBadge from '~/vue_shared/components/ci_badge_link.vue'; import RunnerTags from '~/runner/components/runner_tags.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; @@ -47,6 +48,14 @@ export default { commitPath(job) { return job.commitPath; }, + duration(job) { + const { duration } = job; + return duration ? durationTimeFormatted(duration) : ''; + }, + queued(job) { + const { queuedDuration } = job; + return queuedDuration ? durationTimeFormatted(queuedDuration) : ''; + }, }, fields: [ tableField({ key: 'status', label: s__('Job|Status') }), @@ -54,6 +63,8 @@ export default { tableField({ key: 'project', label: __('Project') }), tableField({ key: 'commit', label: __('Commit') }), tableField({ key: 'finished_at', label: s__('Job|Finished at') }), + tableField({ key: 'duration', label: s__('Job|Duration') }), + tableField({ key: 'queued', label: s__('Job|Queued') }), tableField({ key: 'tags', label: s__('Runners|Tags') }), ], }; @@ -84,12 +95,20 @@ export default { <link-cell :href="commitPath(item)"> {{ commitShortSha(item) }}</link-cell> </template> - <template #cell(tags)="{ item = {} }"> - <runner-tags :tag-list="item.tags" /> - </template> - <template #cell(finished_at)="{ item = {} }"> <time-ago v-if="item.finishedAt" :time="item.finishedAt" /> </template> + + <template #cell(duration)="{ item = {} }"> + {{ duration(item) }} + </template> + + <template #cell(queued)="{ item = {} }"> + {{ queued(item) }} + </template> + + <template #cell(tags)="{ item = {} }"> + <runner-tags :tag-list="item.tags" /> + </template> </gl-table-lite> </template> diff --git a/app/assets/javascripts/runner/graphql/show/runner_jobs.query.graphql b/app/assets/javascripts/runner/graphql/show/runner_jobs.query.graphql index 14585e62bf2..edfc22f644b 100644 --- a/app/assets/javascripts/runner/graphql/show/runner_jobs.query.graphql +++ b/app/assets/javascripts/runner/graphql/show/runner_jobs.query.graphql @@ -25,8 +25,10 @@ query getRunnerJobs($id: CiRunnerID!, $first: Int, $last: Int, $before: String, } shortSha commitPath - tags finishedAt + duration + queuedDuration + tags } pageInfo { ...PageInfo diff --git a/app/controllers/concerns/web_hooks/hook_actions.rb b/app/controllers/concerns/web_hooks/hook_actions.rb index ea11f13c7ef..69c0f2f30d5 100644 --- a/app/controllers/concerns/web_hooks/hook_actions.rb +++ b/app/controllers/concerns/web_hooks/hook_actions.rb @@ -20,7 +20,7 @@ module WebHooks unless hook.valid? self.hooks = relation.select(&:persisted?) - flash[:alert] = hook.errors.full_messages.join.html_safe + flash[:alert] = hook.errors.full_messages.to_sentence.html_safe end redirect_to action: :index @@ -64,7 +64,9 @@ module WebHooks end def hook_param_names - %i[enable_ssl_verification token url push_events_branch_filter] + param_names = %i[enable_ssl_verification token url push_events_branch_filter] + param_names.push(:branch_filter_strategy) if Feature.enabled?(:enhanced_webhook_support_regex) + param_names end def destroy_hook(hook) diff --git a/app/models/hooks/active_hook_filter.rb b/app/models/hooks/active_hook_filter.rb index 283e2d680f4..cdcfd3f3ff5 100644 --- a/app/models/hooks/active_hook_filter.rb +++ b/app/models/hooks/active_hook_filter.rb @@ -3,14 +3,36 @@ class ActiveHookFilter def initialize(hook) @hook = hook - @push_events_filter_matcher = RefMatcher.new(@hook.push_events_branch_filter) end def matches?(hooks_scope, data) - return true if hooks_scope != :push_hooks + return true unless hooks_scope == :push_hooks + + matches_branch?(data) + end + + private + + def matches_branch?(data) return true if @hook.push_events_branch_filter.blank? branch_name = Gitlab::Git.branch_name(data[:ref]) - @push_events_filter_matcher.matches?(branch_name) + + if Feature.disabled?(:enhanced_webhook_support_regex) + return RefMatcher.new(@hook.push_events_branch_filter).matches?(branch_name) + end + + case @hook.branch_filter_strategy + when 'all_branches' + true + when 'wildcard' + RefMatcher.new(@hook.push_events_branch_filter).matches?(branch_name) + when 'regex' + begin + Gitlab::UntrustedRegexp.new(@hook.push_events_branch_filter) === branch_name + rescue RegexpError + false + end + end end end diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 71794964c99..2822e3131f8 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -36,11 +36,22 @@ class WebHook < ApplicationRecord validates :url, public_url: true, unless: ->(hook) { hook.is_a?(SystemHook) } validates :token, format: { without: /\n/ } - validates :push_events_branch_filter, branch_filter: true + after_initialize :initialize_url_variables + before_validation :set_branch_filter_nil, \ + if: -> { branch_filter_strategy_all_branches? && enhanced_webhook_support_regex? } + validates :push_events_branch_filter, \ + untrusted_regexp: true, if: -> { branch_filter_strategy_regex? && enhanced_webhook_support_regex? } + validates :push_events_branch_filter, \ + "web_hooks/wildcard_branch_filter": true, if: -> { branch_filter_strategy_wildcard? } + validates :url_variables, json_schema: { filename: 'web_hooks_url_variables' } validate :no_missing_url_variables - after_initialize :initialize_url_variables + enum branch_filter_strategy: { + wildcard: 0, + regex: 1, + all_branches: 2 + }, _prefix: true scope :executable, -> do next all unless Feature.enabled?(:web_hooks_disable_failed) @@ -224,4 +235,12 @@ class WebHook < ApplicationRecord errors.add(:url, "Invalid URL template. Missing keys: #{missing}") end + + def enhanced_webhook_support_regex? + Feature.enabled?(:enhanced_webhook_support_regex) + end + + def set_branch_filter_nil + self.push_events_branch_filter = nil + end end diff --git a/app/validators/branch_filter_validator.rb b/app/validators/branch_filter_validator.rb deleted file mode 100644 index 89d6343a9a4..00000000000 --- a/app/validators/branch_filter_validator.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -# BranchFilterValidator -# -# Custom validator for branch names. Squishes whitespace and ignores empty -# string. This only checks that a string is a valid git branch name. It does -# not check whether a branch already exists. -# -# Example: -# -# class Webhook < ActiveRecord::Base -# validates :push_events_branch_filter, branch_name: true -# end -# -class BranchFilterValidator < ActiveModel::EachValidator - def validate_each(record, attribute, value) - value.squish! unless value.nil? - - if value.present? - value_without_wildcards = value.tr('*', 'x') - - unless Gitlab::GitRefValidator.validate(value_without_wildcards) - record.errors.add(attribute, "is not a valid branch name") - end - - unless value.length <= 4000 - record.errors.add(attribute, "is longer than the allowed length of 4000 characters.") - end - end - end - - private - - def contains_wildcard?(value) - value.include?('*') - end -end diff --git a/app/validators/web_hooks/wildcard_branch_filter_validator.rb b/app/validators/web_hooks/wildcard_branch_filter_validator.rb new file mode 100644 index 00000000000..12ec78f05de --- /dev/null +++ b/app/validators/web_hooks/wildcard_branch_filter_validator.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# WildcardBranchFilterValidator +# +# Custom validator for wildcard branch filter. Squishes whitespace and ignores +# empty string. This only checks that a string is a valid wildcard git branch +# like "feature/login" and "feature/*". It doesn't check whether a branch already +# exists. +# +# Example: +# +# class Webhook < ActiveRecord::Base +# validates :push_events_branch_filter, "web_hooks/wildcard_branch_filter": true +# end +# +module WebHooks + class WildcardBranchFilterValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + value.squish! unless value.nil? + + return unless value.present? + + value_without_wildcards = value.tr('*', 'x') + + unless Gitlab::GitRefValidator.validate(value_without_wildcards) + record.errors.add(attribute, "is not a valid branch name") + end + + return if value.length <= 4000 + + record.errors.add(attribute, "is longer than the allowed length of 4000 characters.") + end + end +end diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index 0476193c2cb..e42c5f4c890 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -9,6 +9,6 @@ .col-lg-8.gl-mb-3 = gitlab_ui_form_for @hook, as: :hook, url: polymorphic_path([@project, :hooks]) do |f| = render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook } - = f.submit 'Add webhook', pajamas_button: true + = f.submit _('Add webhook'), pajamas_button: true, data: { qa_selector: "create_webhook_button" } = render 'shared/web_hooks/index', hooks: @hooks, hook_class: @hook.class diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index c95e63bdc83..d5b03647f85 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -19,12 +19,16 @@ = form.label :url, s_('Webhooks|Trigger'), class: 'label-bold' %ul.list-unstyled %li.gl-pb-5 - = form.gitlab_ui_checkbox_component :push_events, s_('Webhooks|Push events') - .gl-pl-6 - = form.text_field :push_events_branch_filter, class: 'form-control gl-form-input', - placeholder: 'Branch name or wildcard pattern to trigger on (leave blank for all)' - %p.form-text.text-muted.custom-control - = s_('Webhooks|Push to the repository.') + - if Feature.enabled?(:enhanced_webhook_support_regex) + - is_new_hook = hook.id.nil? + .js-vue-push-events{ data: { push_events: hook.push_events.to_s, strategy: hook.branch_filter_strategy, is_new_hook: is_new_hook.to_s, push_events_branch_filter: hook.push_events_branch_filter } } + - else + = form.gitlab_ui_checkbox_component :push_events, s_('Webhooks|Push events') + .gl-pl-6 + = form.text_field :push_events_branch_filter, class: 'form-control gl-form-input', + placeholder: 'Branch name or wildcard pattern to trigger on (leave blank for all)' + %p.form-text.text-muted.custom-control + = s_('Webhooks|Push to the repository.') %li.gl-pb-5 = form.gitlab_ui_checkbox_component :tag_push_events, s_('Webhooks|Tag push events'), diff --git a/config/feature_flags/development/cascade_package_forwarding_settings.yml b/config/feature_flags/development/enhanced_webhook_support_regex.yml index 2b09c25d6f9..2c0d2c82dbf 100644 --- a/config/feature_flags/development/cascade_package_forwarding_settings.yml +++ b/config/feature_flags/development/enhanced_webhook_support_regex.yml @@ -1,8 +1,8 @@ --- -name: cascade_package_forwarding_settings -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/99285 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/375761 -milestone: '15.5' +name: enhanced_webhook_support_regex +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97235 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/375728 +milestone: '15.6' type: development -group: group::package +group: group::integrations default_enabled: false diff --git a/data/deprecations/15-5-disable-file-type-var-expansion-ci-pipeline.yml b/data/deprecations/15-5-disable-file-type-var-expansion-ci-pipeline.yml new file mode 100644 index 00000000000..31b0d9c2dbc --- /dev/null +++ b/data/deprecations/15-5-disable-file-type-var-expansion-ci-pipeline.yml @@ -0,0 +1,13 @@ +- name: "File Type variable expansion in `.gitlab-ci.yml`" # (required) The name of the feature to be deprecated + announcement_milestone: "15.5" # (required) The milestone when this feature was first announced as deprecated. + announcement_date: "2022-10-22" # (required) The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post. + removal_milestone: "15.7" # (required) The milestone when this feature is planned to be removed + removal_date: # (required) The date of the milestone release when this feature is planned to be removed. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post. + breaking_change: true # (required) If this deprecation is a breaking change, set this value to true + reporter: DarrenEastman # (required) GitLab username of the person reporting the deprecation + stage: Verify # (required) String value of the stage that the feature was created in. e.g., Growth + issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/29407 # (required) Link to the deprecation issue in GitLab + body: | # (required) Do not modify this line, instead modify the lines below. + Previously, variables that referenced or applied alias file variables expanded the value of the `File` type variable. For example, the file contents. This behavior was incorrect because it did not comply with typical shell variable expansion rules. To leak secrets or sensitive information stored in `File` type variables, a user could run an $echo command with the variable as an input parameter. + + This breaking change fixes this issue but could disrupt user workflows that work around the behavior. With this change, job variable expansions that reference or apply alias file variables, expand to the file name or path of the `File` type variable, instead of its value, such as the file contents. diff --git a/doc/ci/runners/configure_runners.md b/doc/ci/runners/configure_runners.md index 19e0c1e3c81..682b5035d78 100644 --- a/doc/ci/runners/configure_runners.md +++ b/doc/ci/runners/configure_runners.md @@ -97,6 +97,13 @@ Whenever a project is forked, it copies the settings of the jobs that relate to it. This means that if you have shared runners set up for a project and someone forks that project, the shared runners serve jobs of this project. +Because of a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/364303), you might encounter the message `An error occurred while forking the project. Please try again.` if the runner settings of the project you are forking does not match the new project namespace. + +To work around this issue, you should make sure that the shared runner settings are consistent in the forked project and the new namespace. + +- If shared runners are **enabled** on the forked project, then this should also be **enabled** on the new namespace. +- If shared runners are **disabled** on the forked project, then this should also be **disabled** on the new namespace. + ### Attack vectors in runners Mentioned briefly earlier, but the following things of runners can be exploited. diff --git a/doc/legal/corporate_contributor_license_agreement.md b/doc/legal/corporate_contributor_license_agreement.md index 257b26dd7c4..b27376ba58e 100644 --- a/doc/legal/corporate_contributor_license_agreement.md +++ b/doc/legal/corporate_contributor_license_agreement.md @@ -8,23 +8,23 @@ info: To determine the technical writer assigned to the Stage/Group associated w You accept and agree to the following terms and conditions for Your present and future Contributions submitted to GitLab B.V.. Except for the license granted herein to GitLab B.V. and recipients of software distributed by GitLab B.V., You reserve all right, title, and interest in and to Your Contributions. -- **Definitions:** +"1." **Definitions:** "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab B.V.. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "Contribution" shall mean the code, documentation or other original works of authorship, including any modifications or additions to an existing work, that is submitted by You to GitLab B.V. for inclusion in, or documentation of, any of the products owned or managed by GitLab B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." -- **Grant of Copyright License:** +"2." **Grant of Copyright License:** Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. -- **Grant of Patent License:** +"3." **Grant of Patent License:** Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. You represent that You are legally entitled to grant the above license. You represent further that each of Your employees is authorized to submit Contributions on Your behalf, but excluding employees that are designated in writing by You as "Not authorized to submit Contributions on behalf of (name of Your corporation here)." Such designations of exclusion for unauthorized employees are to be submitted via email to `legal@gitlab.com`. It is Your responsibility to notify GitLab B.V. when any change is required to the list of designated employees excluded from submitting Contributions on Your behalf. Such notification should also be sent via email to `legal@gitlab.com`. -- **Contributions:** +"4." **Contributions:** You represent that each of Your Contributions is Your original creation. diff --git a/doc/legal/individual_contributor_license_agreement.md b/doc/legal/individual_contributor_license_agreement.md index 125ab1e6a8e..0601b34f5f7 100644 --- a/doc/legal/individual_contributor_license_agreement.md +++ b/doc/legal/individual_contributor_license_agreement.md @@ -8,23 +8,23 @@ info: To determine the technical writer assigned to the Stage/Group associated w You accept and agree to the following terms and conditions for Your present and future Contributions submitted to GitLab B.V.. Except for the license granted herein to GitLab B.V. and recipients of software distributed by GitLab B.V., You reserve all right, title, and interest in and to Your Contributions. -- **Definitions:** +"1." **Definitions:** "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab B.V.. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to GitLab B.V. for inclusion in, or documentation of, any of the products owned or managed by GitLab B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." -- **Grant of Copyright License:** +"2." **Grant of Copyright License:** Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. -- **Grant of Patent License:** +"3." **Grant of Patent License:** Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to GitLab B.V., or that your employer has executed a separate Corporate CLA with GitLab B.V.. -- **Contributions:** +"4." **Contributions:** You represent that each of Your Contributions is Your original creation. You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions. diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index ed9b812525e..b4247e4b984 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -47,6 +47,22 @@ sole discretion of GitLab Inc. ## Announced in 15.5 +<div class="deprecation removal-157 breaking-change"> + +### File Type variable expansion in `.gitlab-ci.yml` + +Planned removal: GitLab <span class="removal-milestone">15.7</span> () + +WARNING: +This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/). +Review the details carefully before upgrading. + +Previously, variables that referenced or applied alias file variables expanded the value of the `File` type variable. For example, the file contents. This behavior was incorrect because it did not comply with typical shell variable expansion rules. To leak secrets or sensitive information stored in `File` type variables, a user could run an $echo command with the variable as an input parameter. + +This breaking change fixes this issue but could disrupt user workflows that work around the behavior. With this change, job variable expansions that reference or apply alias file variables, expand to the file name or path of the `File` type variable, instead of its value, such as the file contents. + +</div> + <div class="deprecation removal-160 breaking-change"> ### GraphQL field `confidential` changed to `internal` on notes diff --git a/doc/user/application_security/iac_scanning/index.md b/doc/user/application_security/iac_scanning/index.md index a01ce59c860..590ba5c06a2 100644 --- a/doc/user/application_security/iac_scanning/index.md +++ b/doc/user/application_security/iac_scanning/index.md @@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w Infrastructure as Code (IaC) Scanning scans your IaC configuration files for known vulnerabilities. -Currently, IaC scanning supports configuration files for Terraform, Ansible, AWS CloudFormation, and Kubernetes. +IaC Scanning supports configuration files for Terraform, Ansible, AWS CloudFormation, and Kubernetes. ## Requirements @@ -18,13 +18,13 @@ IaC Scanning runs in the `test` stage, which is available by default. If you red We recommend a minimum of 4GB RAM to ensure consistent performance. -To run IaC scanning jobs, by default, you need GitLab Runner with the +To run IaC Scanning jobs, by default, you need GitLab Runner with the [`docker`](https://docs.gitlab.com/runner/executors/docker.html) or [`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html) executor. If you're using the shared runners on GitLab.com, this is enabled by default. WARNING: -Our IaC scanning jobs require a Linux/amd64 container type. Windows containers are not yet supported. +Our IaC Scanning jobs require a Linux/amd64 container type. Windows containers are not supported. WARNING: If you use your own runners, make sure the Docker version installed @@ -32,9 +32,9 @@ is **not** `19.03.0`. See [troubleshooting information](../sast/index.md#error-r ## Supported languages and frameworks -GitLab IaC scanning supports a variety of IaC configuration files. Our IaC security scanners also feature automatic language detection which works even for mixed-language projects. If any supported configuration files are detected in project source code we automatically run the appropriate IaC analyzers. +GitLab IaC Scanning supports a variety of IaC configuration files. Our IaC security scanners also feature automatic language detection which works even for mixed-language projects. If any supported configuration files are detected in project source code we automatically run the appropriate IaC analyzers. -| Configuration File Type | Scan tool | Introduced in GitLab Version | +| Configuration file type | Scan tool | Introduced in GitLab version | | ----------------------------------- | ------------------------ | ---------------------------- | | Ansible | [KICS](https://kics.io/) | 14.5 | | AWS CloudFormation | [KICS](https://kics.io/) | 14.5 | @@ -45,7 +45,7 @@ GitLab IaC scanning supports a variety of IaC configuration files. Our IaC secur | OpenAPI | [KICS](https://kics.io/) | 14.5 | | Terraform <sup>2</sup> | [KICS](https://kics.io/) | 14.5 | -1. IaC scanning can analyze Azure Resource Manager templates in JSON format. If you write templates in the [Bicep](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview) language, you must use [the bicep CLI](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-cli) to convert your Bicep files into JSON before GitLab IaC scanning can analyze them. +1. IaC Scanning can analyze Azure Resource Manager templates in JSON format. If you write templates in the [Bicep](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview) language, you must use [the bicep CLI](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-cli) to convert your Bicep files into JSON before GitLab IaC Scanning can analyze them. 1. Terraform modules in a custom registry are not scanned for vulnerabilities. You can follow [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/357004) for the proposed feature. ### Supported distributions @@ -107,7 +107,7 @@ include: - template: Jobs/SAST-IaC.gitlab-ci.yml ``` -The included template creates IaC scanning jobs in your CI/CD pipeline and scans +The included template creates IaC Scanning jobs in your CI/CD pipeline and scans your project's configuration files for possible vulnerabilities. The results are saved as a diff --git a/doc/user/project/issues/related_issues.md b/doc/user/project/issues/related_issues.md index 53ad7196920..1a8f19b80ba 100644 --- a/doc/user/project/issues/related_issues.md +++ b/doc/user/project/issues/related_issues.md @@ -1,6 +1,6 @@ --- stage: Plan -group: Project Management +group: Product Planning info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/lib/api/helpers/packages/dependency_proxy_helpers.rb b/lib/api/helpers/packages/dependency_proxy_helpers.rb index dc81e5e1b51..1ae863a5a25 100644 --- a/lib/api/helpers/packages/dependency_proxy_helpers.rb +++ b/lib/api/helpers/packages/dependency_proxy_helpers.rb @@ -45,7 +45,7 @@ module API raise ArgumentError, "Can't find application setting for package_type #{package_type}" unless application_setting_name - if target.present? && Feature.enabled?(:cascade_package_forwarding_settings, target) + if target.present? target.public_send(application_setting_name) # rubocop:disable GitlabSecurity/PublicSend else ::Gitlab::CurrentSettings diff --git a/lib/gitlab/ci/parsers/security/common.rb b/lib/gitlab/ci/parsers/security/common.rb index 0c117d5f214..c437bec965f 100644 --- a/lib/gitlab/ci/parsers/security/common.rb +++ b/lib/gitlab/ci/parsers/security/common.rb @@ -157,13 +157,7 @@ module Gitlab signature_value: value ) - if signature.valid? - signature - else - e = SecurityReportParserError.new("Vulnerability tracking signature is not valid: #{signature}") - Gitlab::ErrorTracking.track_exception(e) - nil - end + signature if signature.valid? end.compact end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 7b4956c8e45..7ab86b63a1d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -23312,6 +23312,9 @@ msgstr "" msgid "Job|Download" msgstr "" +msgid "Job|Duration" +msgstr "" + msgid "Job|Erase job log and artifacts" msgstr "" @@ -23351,6 +23354,9 @@ msgstr "" msgid "Job|Preparing" msgstr "" +msgid "Job|Queued" +msgstr "" + msgid "Job|Retry" msgstr "" diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb index ba7b712964c..54c958d6350 100644 --- a/spec/controllers/projects/hooks_controller_spec.rb +++ b/spec/controllers/projects/hooks_controller_spec.rb @@ -106,8 +106,9 @@ RSpec.describe Projects::HooksController do it 'sets all parameters' do hook_params = { enable_ssl_verification: true, - token: "TEST TOKEN", - url: "http://example.com", + token: 'TEST TOKEN', + url: 'http://example.com', + branch_filter_strategy: 'regex', push_events: true, tag_push_events: true, @@ -124,13 +125,39 @@ RSpec.describe Projects::HooksController do url_variables: [{ key: 'token', value: 'some secret value' }] } - post :create, params: { namespace_id: project.namespace, project_id: project, hook: hook_params } + params = { namespace_id: project.namespace, project_id: project, hook: hook_params } + + expect { post :create, params: params }.to change(ProjectHook, :count).by(1) + + project_hook = ProjectHook.order_id_desc.take + + expect(project_hook).to have_attributes( + **hook_params.merge(url_variables: { 'token' => 'some secret value' }) + ) + expect(response).to have_gitlab_http_status(:found) + expect(flash[:alert]).to be_blank + end + + it 'ignores branch_filter_strategy when flag is disabled' do + stub_feature_flags(enhanced_webhook_support_regex: false) + hook_params = { + url: 'http://example.com', + branch_filter_strategy: 'regex', + push_events: true + } + params = { namespace_id: project.namespace, project_id: project, hook: hook_params } + + expect { post :create, params: params }.to change(ProjectHook, :count).by(1) + + project_hook = ProjectHook.order_id_desc.take + + expect(project_hook).to have_attributes( + url: 'http://example.com', + branch_filter_strategy: 'wildcard' + ) expect(response).to have_gitlab_http_status(:found) expect(flash[:alert]).to be_blank - expect(ProjectHook.count).to eq(1) - expect(ProjectHook.first).to have_attributes(hook_params.except(:url_variables)) - expect(ProjectHook.first).to have_attributes(url_variables: { 'token' => 'some secret value' }) end it 'alerts the user if the new hook is invalid' do diff --git a/spec/features/projects/settings/webhooks_settings_spec.rb b/spec/features/projects/settings/webhooks_settings_spec.rb index 25752bcaf45..ad434749198 100644 --- a/spec/features/projects/settings/webhooks_settings_spec.rb +++ b/spec/features/projects/settings/webhooks_settings_spec.rb @@ -48,22 +48,47 @@ RSpec.describe 'Projects > Settings > Webhook Settings' do expect(page).to have_content('Releases events') end - it 'create webhook', :js do - visit webhooks_path + context 'when feature flag "enhanced_webhook_support_regex" is disabled' do + before do + stub_feature_flags(enhanced_webhook_support_regex: false) + end - fill_in 'URL', with: url - check 'Tag push events' - fill_in 'hook_push_events_branch_filter', with: 'master' - check 'Enable SSL verification' - check 'Job events' + it 'create webhook', :js do + visit webhooks_path - click_button 'Add webhook' + fill_in 'URL', with: url + check 'Tag push events' + fill_in 'hook_push_events_branch_filter', with: 'master' + check 'Enable SSL verification' + check 'Job events' - expect(page).to have_content(url) - expect(page).to have_content('SSL Verification: enabled') - expect(page).to have_content('Push events') - expect(page).to have_content('Tag push events') - expect(page).to have_content('Job events') + click_button 'Add webhook' + + expect(page).to have_content(url) + expect(page).to have_content('SSL Verification: enabled') + expect(page).to have_content('Tag push events') + expect(page).to have_content('Job events') + expect(page).to have_content('Push events') + end + end + + context 'when feature flag "enhanced_webhook_support_regex" is enabled' do + it 'create webhook', :js do + visit webhooks_path + + fill_in 'URL', with: url + check 'Tag push events' + check 'Enable SSL verification' + check 'Job events' + + click_button 'Add webhook' + + expect(page).to have_content(url) + expect(page).to have_content('SSL Verification: enabled') + expect(page).to have_content('Tag push events') + expect(page).to have_content('Job events') + expect(page).to have_selector('.js-vue-push-events', visible: :all) + end end it 'edit existing webhook', :js do diff --git a/spec/frontend/runner/components/runner_jobs_table_spec.js b/spec/frontend/runner/components/runner_jobs_table_spec.js index 5f4905ad2a8..499ba503dec 100644 --- a/spec/frontend/runner/components/runner_jobs_table_spec.js +++ b/spec/frontend/runner/components/runner_jobs_table_spec.js @@ -61,6 +61,8 @@ describe('RunnerJobsTable', () => { __('Project'), __('Commit'), s__('Job|Finished at'), + s__('Job|Duration'), + s__('Job|Queued'), s__('Runners|Tags'), ]); }); @@ -108,6 +110,22 @@ describe('RunnerJobsTable', () => { expect(findCell({ field: 'finished_at' }).text()).toBe('1 hour ago'); }); + it('Formats duration time', () => { + mockJobsCopy[0].duration = 60; + + createComponent({ props: { jobs: mockJobsCopy } }, mountExtended); + + expect(findCell({ field: 'duration' }).text()).toBe('00:01:00'); + }); + + it('Formats queued time', () => { + mockJobsCopy[0].queuedDuration = 30; + + createComponent({ props: { jobs: mockJobsCopy } }, mountExtended); + + expect(findCell({ field: 'queued' }).text()).toBe('00:00:30'); + }); + it('Formats tags', () => { mockJobsCopy[0].tags = ['tag-1', 'tag-2']; diff --git a/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb b/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb index 66cf06cde20..3c77f8df350 100644 --- a/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb +++ b/spec/lib/api/helpers/packages/dependency_proxy_helpers_spec.rb @@ -10,7 +10,7 @@ RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do include_context 'dependency proxy helpers context' let_it_be(:group) { create(:group) } - let_it_be(:project) { create(:project, group: group) } + let_it_be_with_reload(:project) { create(:project, group: group) } let_it_be_with_reload(:package_setting) { create(:namespace_package_setting, namespace: group) } let(:target) { project } @@ -76,19 +76,6 @@ RSpec.describe API::Helpers::Packages::DependencyProxyHelpers do end end - context 'when cascade_package_forwarding_settings is disabled' do - let(:package_type) { forwardable_package_type } - let(:forward_to_registry) { true } - - before do - stub_feature_flags(cascade_package_forwarding_settings: false) - allow_fetch_cascade_application_setting(attribute: "#{forwardable_package_type}_package_requests_forwarding", return_value: true) - package_setting.update!("#{forwardable_package_type}_package_requests_forwarding" => false) - end - - it_behaves_like 'executing redirect' - end - context 'when no target is present' do let(:package_type) { forwardable_package_type } let(:forward_to_registry) { true } diff --git a/spec/lib/gitlab/ci/parsers/security/common_spec.rb b/spec/lib/gitlab/ci/parsers/security/common_spec.rb index 7dbad354e4c..253abe38648 100644 --- a/spec/lib/gitlab/ci/parsers/security/common_spec.rb +++ b/spec/lib/gitlab/ci/parsers/security/common_spec.rb @@ -400,28 +400,9 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do end describe 'parsing tracking' do - let(:tracking_data) do - { - 'type' => 'source', - 'items' => [ - 'signatures' => [ - { 'algorithm' => 'hash', 'value' => 'hash_value' }, - { 'algorithm' => 'location', 'value' => 'location_value' }, - { 'algorithm' => 'scope_offset', 'value' => 'scope_offset_value' } - ] - ] - } - end - - context 'with valid tracking information' do - it 'creates signatures for each algorithm' do - finding = report.findings.first - expect(finding.signatures.size).to eq(3) - expect(finding.signatures.map(&:algorithm_type).to_set).to eq(Set['hash', 'location', 'scope_offset']) - end - end - context 'with invalid tracking information' do + let(:finding) { report.findings.first } + let(:number_of_findings) { report.findings.length } let(:tracking_data) do { 'type' => 'source', @@ -435,14 +416,26 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do } end - it 'ignores invalid algorithm types' do - finding = report.findings.first + it 'ignores invalid algorithm types and logs warning' do expect(finding.signatures.size).to eq(2) expect(finding.signatures.map(&:algorithm_type).to_set).to eq(Set['hash', 'location']) end end context 'with valid tracking information' do + let(:tracking_data) do + { + 'type' => 'source', + 'items' => [ + 'signatures' => [ + { 'algorithm' => 'hash', 'value' => 'hash_value' }, + { 'algorithm' => 'location', 'value' => 'location_value' }, + { 'algorithm' => 'scope_offset', 'value' => 'scope_offset_value' } + ] + ] + } + end + it 'creates signatures for each signature algorithm' do finding = report.findings.first expect(finding.signatures.size).to eq(3) diff --git a/spec/migrations/change_public_projects_cost_factor_spec.rb b/spec/migrations/change_public_projects_cost_factor_spec.rb index cec0f242b6c..039edda750b 100644 --- a/spec/migrations/change_public_projects_cost_factor_spec.rb +++ b/spec/migrations/change_public_projects_cost_factor_spec.rb @@ -53,8 +53,6 @@ RSpec.describe ChangePublicProjectsCostFactor, migration: :gitlab_ci do expect(shared_2.public_projects_minutes_cost_factor).to eq(0) expect(shared_3.public_projects_minutes_cost_factor).to eq(1) expect(group_1.public_projects_minutes_cost_factor).to eq(0) - - schema_migrate_up! end end end diff --git a/spec/models/hooks/active_hook_filter_spec.rb b/spec/models/hooks/active_hook_filter_spec.rb index 1f693ce9fde..38e4d9d3a72 100644 --- a/spec/models/hooks/active_hook_filter_spec.rb +++ b/spec/models/hooks/active_hook_filter_spec.rb @@ -6,65 +6,102 @@ RSpec.describe ActiveHookFilter do subject(:filter) { described_class.new(hook) } describe '#matches?' do - context 'for push event hooks' do - let(:hook) do - create(:project_hook, push_events: true, push_events_branch_filter: branch_filter) + using RSpec::Parameterized::TableSyntax + + context 'for various types of branch_filter' do + let_it_be_with_reload(:hook) do + create(:project_hook, push_events: true, issues_events: true) end - context 'branch filter is specified' do - let(:branch_filter) { 'master' } + where(:branch_filter_strategy, :branch_filter, :ref, :expected_matches?) do + 'all_branches' | 'master' | 'refs/heads/master' | true + 'all_branches' | '' | 'refs/heads/master' | true + 'all_branches' | nil | 'refs/heads/master' | true + 'all_branches' | '.*' | 'refs/heads/master' | true + 'wildcard' | 'master' | 'refs/heads/master' | true + 'wildcard' | 'master' | 'refs/heads/my_branch' | false + 'wildcard' | 'features/*' | 'refs/heads/features/my-branch' | true + 'wildcard' | 'features/*' | 'refs/heads/features/my-branch/something' | true + 'wildcard' | 'features/*' | 'refs/heads/master' | false + 'wildcard' | nil | 'refs/heads/master' | true + 'wildcard' | '' | 'refs/heads/master' | true + 'regex' | 'master' | 'refs/heads/master' | true + 'regex' | 'master' | 'refs/heads/my_branch' | false + 'regex' | 'features/*' | 'refs/heads/xxxx/features/my-branch' | true + 'regex' | 'features/*' | 'refs/heads/features/' | true + 'regex' | 'features/*' | 'refs/heads/features' | true + 'regex' | 'features/.*' | 'refs/heads/features/my-branch' | true + 'regex' | 'features/.*' | 'refs/heads/features/my-branch/something' | true + 'regex' | 'features/.*' | 'refs/heads/master' | false + 'regex' | '(feature|dev)' | 'refs/heads/feature' | true + 'regex' | '(feature|dev)' | 'refs/heads/dev' | true + 'regex' | '(feature|dev)' | 'refs/heads/master' | false + 'regex' | nil | 'refs/heads/master' | true + 'regex' | '' | 'refs/heads/master' | true + end - it 'returns true if branch matches' do - expect(filter.matches?(:push_hooks, { ref: 'refs/heads/master' })).to be true + with_them do + before do + hook.assign_attributes( + push_events_branch_filter: branch_filter, + branch_filter_strategy: branch_filter_strategy + ) end - it 'returns false if branch does not match' do - expect(filter.matches?(:push_hooks, { ref: 'refs/heads/my_branch' })).to be false - end + it { expect(filter.matches?(:push_hooks, { ref: ref })).to be expected_matches? } + it { expect(filter.matches?(:issues_events, { ref: ref })).to be true } + end - it 'returns false if ref is nil' do - expect(filter.matches?(:push_hooks, {})).to be false + context 'when the branch filter is a invalid regex' do + let_it_be(:hook) do + create( + :project_hook, + push_events: true, + push_events_branch_filter: 'master', + branch_filter_strategy: 'regex' + ) end - context 'branch filter contains wildcard' do - let(:branch_filter) { 'features/*' } - - it 'returns true if branch matches' do - expect(filter.matches?(:push_hooks, { ref: 'refs/heads/features/my-branch' })).to be true - expect(filter.matches?(:push_hooks, { ref: 'refs/heads/features/my-branch/something' })).to be true - end - - it 'returns false if branch does not match' do - expect(filter.matches?(:push_hooks, { ref: 'refs/heads/master' })).to be false - end + before do + allow(hook).to receive(:push_events_branch_filter).and_return("invalid-regex[") end - end - - context 'branch filter is not specified' do - let(:branch_filter) { nil } - it 'returns true' do - expect(filter.matches?(:push_hooks, { ref: 'refs/heads/master' })).to be true - end + it { expect(filter.matches?(:push_hooks, { ref: 'refs/heads/master' })).to be false } end - context 'branch filter is empty string' do - let(:branch_filter) { '' } + context 'when the branch filter is not properly set to nil' do + let_it_be(:hook) do + create( + :project_hook, + push_events: true, + branch_filter_strategy: 'all_branches' + ) + end - it 'acts like branch is not specified' do - expect(filter.matches?(:push_hooks, { ref: 'refs/heads/master' })).to be true + before do + allow(hook).to receive(:push_events_branch_filter).and_return("master") end + + it { expect(filter.matches?(:push_hooks, { ref: 'refs/heads/feature1' })).to be true } end end - context 'for non-push-events hooks' do - let(:hook) do - create(:project_hook, issues_events: true, push_events: false, push_events_branch_filter: '') + context 'when feature flag is disabled' do + before do + stub_feature_flags(enhanced_webhook_support_regex: false) end - it 'returns true as branch filters are not yet supported for these' do - expect(filter.matches?(:issues_events, { ref: 'refs/heads/master' })).to be true + let_it_be(:hook) do + create( + :project_hook, + push_events: true, + push_events_branch_filter: '(master)', + branch_filter_strategy: 'regex' + ) end + + it { expect(filter.matches?(:push_hooks, { ref: 'refs/heads/master' })).to be false } + it { expect(filter.matches?(:push_hooks, { ref: 'refs/heads/(master)' })).to be true } end end end diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index da8c10b67a6..dd5bf8b99dd 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -113,24 +113,81 @@ RSpec.describe WebHook do end describe 'push_events_branch_filter' do - it { is_expected.to allow_values("good_branch_name", "another/good-branch_name").for(:push_events_branch_filter) } - it { is_expected.to allow_values("").for(:push_events_branch_filter) } - it { is_expected.not_to allow_values("bad branch name", "bad~branchname").for(:push_events_branch_filter) } - - it 'gets rid of whitespace' do - hook.push_events_branch_filter = ' branch ' - hook.save! + before do + subject.branch_filter_strategy = strategy + end + + context 'with "all branches" strategy' do + let(:strategy) { 'all_branches' } + + it { + is_expected.to allow_values( + "good_branch_name", + "another/good-branch_name", + "good branch name", + "good~branchname", + "good_branchname(", + "good_branchname[", + "" + ).for(:push_events_branch_filter) + } + end + + context 'with "wildcard" strategy' do + let(:strategy) { 'wildcard' } + + it { + is_expected.to allow_values( + "good_branch_name", + "another/good-branch_name", + "good_branch_name(", + "" + ).for(:push_events_branch_filter) + } + + it { + is_expected.not_to allow_values( + "bad branch name", + "bad~branchname", + "bad_branch_name[" + ).for(:push_events_branch_filter) + } + + it 'gets rid of whitespace' do + hook.push_events_branch_filter = ' branch ' + hook.save! + + expect(hook.push_events_branch_filter).to eq('branch') + end - expect(hook.push_events_branch_filter).to eq('branch') + it 'stores whitespace only as empty' do + hook.push_events_branch_filter = ' ' + hook.save! + expect(hook.push_events_branch_filter).to eq('') + end end - it 'stores whitespace only as empty' do - hook.push_events_branch_filter = ' ' - hook.save! + context 'with "regex" strategy' do + let(:strategy) { 'regex' } + + it { + is_expected.to allow_values( + "good_branch_name", + "another/good-branch_name", + "good branch name", + "good~branch~name", + "" + ).for(:push_events_branch_filter) + } - expect(hook.push_events_branch_filter).to eq('') + it { is_expected.not_to allow_values("bad_branch_name(", "bad_branch_name[").for(:push_events_branch_filter) } end end + + it "only consider these branch filter strategies are valid" do + expected_valid_types = %w[all_branches regex wildcard] + expect(described_class.branch_filter_strategies.keys).to contain_exactly(*expected_valid_types) + end end describe 'encrypted attributes' do diff --git a/spec/support/database/multiple_databases.rb b/spec/support/database/multiple_databases.rb index 96bdab5171d..b863767b5df 100644 --- a/spec/support/database/multiple_databases.rb +++ b/spec/support/database/multiple_databases.rb @@ -2,6 +2,15 @@ module Database module MultipleDatabases + def run_and_cleanup(example) + # Each example may call `migrate!`, so we must ensure we are migrated down every time + schema_migrate_down! + + example.run + + delete_from_all_tables!(except: deletion_except_tables) + end + def skip_if_multiple_databases_not_setup skip 'Skipping because multiple databases not set up' unless Gitlab::Database.has_config?(:ci) end @@ -22,6 +31,21 @@ module Database model.establish_connection(new_db_config) end + def ensure_schema_and_empty_tables + # Ensure all schemas for both databases are migrated back + Gitlab::Database.database_base_models.each do |_, base_model| + with_reestablished_active_record_base do + reconfigure_db_connection( + model: ActiveRecord::Base, + config_model: base_model + ) + + schema_migrate_up! + delete_from_all_tables!(except: deletion_except_tables) + end + end + end + # The usage of this method switches temporarily used `connection_handler` # allowing full manipulation of ActiveRecord::Base connections without # having side effects like: @@ -109,7 +133,13 @@ RSpec.configure do |config| end end + config.append_after(:context, :migration) do + recreate_databases_and_seed_if_needed || ensure_schema_and_empty_tables + end + config.around(:each, :migration) do |example| + self.class.use_transactional_tests = false + migration_schema = example.metadata[:migration] migration_schema = :gitlab_main if migration_schema == true base_model = Gitlab::Database.schemas_to_base_models.fetch(migration_schema).first @@ -122,11 +152,13 @@ RSpec.configure do |config| config_model: base_model ) - example.run + run_and_cleanup(example) end else - example.run + run_and_cleanup(example) end + + self.class.use_transactional_tests = true end end diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb index f8ddf3e66a5..7bd1f0c5dfa 100644 --- a/spec/support/database_cleaner.rb +++ b/spec/support/database_cleaner.rb @@ -13,19 +13,6 @@ RSpec.configure do |config| DatabaseCleaner.clean_with(:deletion) end - config.append_after(:context, :migration) do - delete_from_all_tables!(except: ['work_item_types']) - - # Postgres maximum number of columns in a table is 1600 (https://github.com/postgres/postgres/blob/de41869b64d57160f58852eab20a27f248188135/src/include/access/htup_details.h#L23-L47). - # We drop and recreate the database if any table has more than 1200 columns, just to be safe. - if any_connection_class_with_more_than_allowed_columns? - recreate_all_databases! - - # Seed required data as recreating DBs will delete it - TestEnv.seed_db - end - end - config.around(:each, :delete) do |example| self.class.use_transactional_tests = false @@ -35,14 +22,4 @@ RSpec.configure do |config| self.class.use_transactional_tests = true end - - config.around(:each, :migration) do |example| - self.class.use_transactional_tests = false - - example.run - - delete_from_all_tables!(except: ['work_item_types']) - - self.class.use_transactional_tests = true - end end diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb index 2a1302f971a..24cdbe04fc2 100644 --- a/spec/support/db_cleaner.rb +++ b/spec/support/db_cleaner.rb @@ -78,6 +78,19 @@ module DbCleaner puts "Databases re-creation done in #{Gitlab::Metrics::System.monotonic_time - start}" end + def recreate_databases_and_seed_if_needed + # Postgres maximum number of columns in a table is 1600 (https://github.com/postgres/postgres/blob/de41869b64d57160f58852eab20a27f248188135/src/include/access/htup_details.h#L23-L47). + # We drop and recreate the database if any table has more than 1200 columns, just to be safe. + return false unless any_connection_class_with_more_than_allowed_columns? + + recreate_all_databases! + + # Seed required data as recreating DBs will delete it + TestEnv.seed_db + + true + end + def force_disconnect_all_connections! cmd = <<~SQL SELECT pg_terminate_backend(pg_stat_activity.pid) diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb index 60097e301c4..e1d28a807e3 100644 --- a/spec/support/helpers/migrations_helpers.rb +++ b/spec/support/helpers/migrations_helpers.rb @@ -87,6 +87,8 @@ module MigrationsHelpers [ApplicationSetting, SystemHook].each do |model| model.define_attribute_methods end + + Gitlab.ee { License.define_attribute_methods } end def reset_column_information(klass) diff --git a/spec/support/migration.rb b/spec/support/migration.rb index 3c359af886d..24e2fc2ff31 100644 --- a/spec/support/migration.rb +++ b/spec/support/migration.rb @@ -19,13 +19,9 @@ RSpec.configure do |config| # Each example may call `migrate!`, so we must ensure we are migrated down every time config.before(:each, :migration) do use_fake_application_settings - - schema_migrate_down! end config.after(:context, :migration) do - schema_migrate_up! - Gitlab::CurrentSettings.clear_in_memory_application_settings! end end diff --git a/spec/validators/branch_filter_validator_spec.rb b/spec/validators/web_hooks/wildcard_branch_filter_validator_spec.rb index 2d869fa674d..7f29e1df007 100644 --- a/spec/validators/branch_filter_validator_spec.rb +++ b/spec/validators/web_hooks/wildcard_branch_filter_validator_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BranchFilterValidator do +RSpec.describe WebHooks::WildcardBranchFilterValidator do let(:validator) { described_class.new(attributes: [:push_events_branch_filter]) } let(:hook) { build(:project_hook) } |