diff options
83 files changed, 1315 insertions, 202 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c1a9d38d5aa..36108d04e9c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,7 @@ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33" stages: + - sync - prepare - quick-test - test @@ -40,3 +41,4 @@ include: - local: .gitlab/ci/setup.gitlab-ci.yml - local: .gitlab/ci/test-metadata.gitlab-ci.yml - local: .gitlab/ci/yaml.gitlab-ci.yml + - local: .gitlab/ci/releases.gitlab-ci.yml diff --git a/.gitlab/ci/releases.gitlab-ci.yml b/.gitlab/ci/releases.gitlab-ci.yml new file mode 100644 index 00000000000..1ddc4e90fcf --- /dev/null +++ b/.gitlab/ci/releases.gitlab-ci.yml @@ -0,0 +1,22 @@ +--- + +# Syncs any changes pushed to a stable branch to the corresponding CE stable +# branch. We run this prior to any tests so that random failures don't prevent a +# sync. +sync-stable-branch: + # We don't need/want any global before/after commands, so we overwrite these + # settings. + image: alpine:edge + stage: sync + # This job should only run on EE stable branches on the canonical GitLab.com + # repository. + only: + variables: + - $CI_SERVER_HOST == "gitlab.com" + refs: + - /^[\d-]+-stable-ee$/@gitlab-org/gitlab + before_script: + - apk add --no-cache --update curl bash + after_script: [] + script: + - bash scripts/sync-stable-branch.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 9411180abff..16a36724b4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 12.4.1 + +### Security (12 changes) + +- Standardize error response when route is missing. +- Do not display project labels that are not visible for user accessing group labels. +- Show cross-referenced label and milestones in issues' activities only to authorized users. +- Analyze incoming GraphQL queries and check for recursion. +- Disallow unprivileged users from commenting on private repository commits. +- Don't allow maintainers of a target project to delete the source branch of a merge request from a fork. +- Require Maintainer permission on group where project is transferred to. +- Don't leak private members in project member autocomplete suggestions. +- Return 404 on LFS request if project doesn't exist. +- Mask sentry auth token in Error Tracking dashboard. +- Fixes a Open Redirect issue in `InternalRedirect`. +- Sanitize all wiki markup formats with GitLab sanitization pipelines. + + ## 12.4.0 ### Security (14 changes) @@ -1 +1 @@ -12.3.0-pre +12.5.0-pre diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue index 19a2db2db25..aa270a374ae 100644 --- a/app/assets/javascripts/repository/components/last_commit.vue +++ b/app/assets/javascripts/repository/components/last_commit.vue @@ -125,19 +125,21 @@ export default { </div> <div class="commit-actions flex-row"> <div v-if="commit.signatureHtml" v-html="commit.signatureHtml"></div> - <gl-link - v-if="commit.latestPipeline" - v-gl-tooltip - :href="commit.latestPipeline.detailedStatus.detailsPath" - :title="statusTitle" - class="js-commit-pipeline" - > - <ci-icon - :status="commit.latestPipeline.detailedStatus" - :size="24" - :aria-label="statusTitle" - /> - </gl-link> + <div class="ci-status-link"> + <gl-link + v-if="commit.latestPipeline" + v-gl-tooltip + :href="commit.latestPipeline.detailedStatus.detailsPath" + :title="statusTitle" + class="js-commit-pipeline" + > + <ci-icon + :status="commit.latestPipeline.detailedStatus" + :size="24" + :aria-label="statusTitle" + /> + </gl-link> + </div> <div class="commit-sha-group d-flex"> <div class="label label-monospace monospace"> {{ showCommitId }} diff --git a/app/assets/stylesheets/framework/memory_graph.scss b/app/assets/stylesheets/framework/memory_graph.scss index 81cdf6b59e4..c84010c6f10 100644 --- a/app/assets/stylesheets/framework/memory_graph.scss +++ b/app/assets/stylesheets/framework/memory_graph.scss @@ -1,11 +1,7 @@ .memory-graph-container { svg { background: $white-light; - cursor: pointer; - - &:hover { - box-shadow: 0 0 4px $gray-darkest inset; - } + border: 1px solid $gray-200; } path { diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index bb47ccb72b3..abec237dd1d 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -142,6 +142,7 @@ class Clusters::ClustersController < Clusters::BaseController :environment_scope, :managed, :base_domain, + :management_project_id, platform_kubernetes_attributes: [ :api_url, :token, @@ -155,6 +156,7 @@ class Clusters::ClustersController < Clusters::BaseController :environment_scope, :managed, :base_domain, + :management_project_id, platform_kubernetes_attributes: [ :namespace ] diff --git a/app/controllers/concerns/internal_redirect.rb b/app/controllers/concerns/internal_redirect.rb index 99bbfd56516..a35bc19aa37 100644 --- a/app/controllers/concerns/internal_redirect.rb +++ b/app/controllers/concerns/internal_redirect.rb @@ -6,7 +6,7 @@ module InternalRedirect def safe_redirect_path(path) return unless path # Verify that the string starts with a `/` and a known route character. - return unless path =~ %r{^/[-\w].*$} + return unless path =~ %r{\A/[-\w].*\z} uri = URI(path) # Ignore anything path of the redirect except for the path, querystring and, diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index 733265f4099..417bb169f39 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -34,6 +34,7 @@ module LfsRequest end def lfs_check_access! + return render_lfs_not_found unless project return if download_request? && lfs_download_access? return if upload_request? && lfs_upload_access? diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index d76a0f3a3b8..e2524938e10 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -133,15 +133,7 @@ module MarkupHelper issuable_state_filter_enabled: true ) - html = - case wiki_page.format - when :markdown - markdown_unsafe(text, context) - when :asciidoc - asciidoc_unsafe(text) - else - wiki_page.formatted_content.html_safe - end + html = markup_unsafe(wiki_page.path, text, context) prepare_for_rendering(html, context) end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index c600717f5df..e3e2f06c313 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -117,6 +117,8 @@ module Clusters scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) } + scope :for_project_namespace, -> (namespace_id) { joins(:projects).where(projects: { namespace_id: namespace_id }) } + def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc) return [] if clusterable.is_a?(Instance) diff --git a/app/models/clusters/clusters_hierarchy.rb b/app/models/clusters/clusters_hierarchy.rb index a906eb2888b..c9c18d8c96a 100644 --- a/app/models/clusters/clusters_hierarchy.rb +++ b/app/models/clusters/clusters_hierarchy.rb @@ -20,7 +20,7 @@ module Clusters .with .recursive(cte.to_arel) .from(cte_alias) - .order(DEPTH_COLUMN => :asc) + .order(depth_order_clause) end private @@ -40,7 +40,7 @@ module Clusters end if clusterable.is_a?(::Project) && include_management_project - cte << management_clusters_query + cte << same_namespace_management_clusters_query end cte << base_query @@ -49,13 +49,42 @@ module Clusters cte end + # Returns project-level clusters where the project is the management project + # for the cluster. The management project has to be in the same namespace / + # group as the cluster's project. + # + # Support for management project in sub-groups is planned in + # https://gitlab.com/gitlab-org/gitlab/issues/34650 + # + # NB: group_parent_id is un-used but we still need to match the same number of + # columns as other queries in the CTE. + def same_namespace_management_clusters_query + clusterable.management_clusters + .project_type + .select([clusters_star, 'NULL AS group_parent_id', "0 AS #{DEPTH_COLUMN}"]) + .for_project_namespace(clusterable.namespace_id) + end + # Management clusters should be first in the hierarchy so we use 0 for the # depth column. # - # group_parent_id is un-used but we still need to match the same number of - # columns as other queries in the CTE. - def management_clusters_query - clusterable.management_clusters.select([clusters_star, 'NULL AS group_parent_id', "0 AS #{DEPTH_COLUMN}"]) + # Only applicable if the clusterable is a project (most especially when + # requesting project.deployment_platform). + def depth_order_clause + return { DEPTH_COLUMN => :asc } unless clusterable.is_a?(::Project) && include_management_project + + order = <<~SQL + (CASE clusters.management_project_id + WHEN :project_id THEN 0 + ELSE #{DEPTH_COLUMN} + END) ASC + SQL + + values = { + project_id: clusterable.id + } + + model.sanitize_sql_array([Arel.sql(order), values]) end def group_clusters_base_query diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb index fe8e9609820..d0b10fd8671 100644 --- a/app/models/concerns/deployment_platform.rb +++ b/app/models/concerns/deployment_platform.rb @@ -12,7 +12,7 @@ module DeploymentPlatform private def cluster_management_project_enabled? - Feature.enabled?(:cluster_management_project, default_enabled: true) + Feature.enabled?(:cluster_management_project, self) end def find_deployment_platform(environment) diff --git a/app/models/discussion.rb b/app/models/discussion.rb index 0d066d0d99f..b8525f7b135 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -16,6 +16,7 @@ class Discussion :commit_id, :for_commit?, :for_merge_request?, + :noteable_ability_name, :to_ability_name, :editable?, :visible_for?, diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 2fa0cfc9b93..a9f4cdec901 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -261,6 +261,10 @@ class Milestone < ApplicationRecord group || project end + def to_ability_name + model_name.singular + end + def group_milestone? group_id.present? end diff --git a/app/models/note.rb b/app/models/note.rb index 43f349c6fa2..ce60413b8a0 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -361,6 +361,10 @@ class Note < ApplicationRecord end def to_ability_name + model_name.singular + end + + def noteable_ability_name for_snippet? ? noteable.class.name.underscore : noteable_type.demodulize.underscore end diff --git a/app/models/project.rb b/app/models/project.rb index b5046bbb6ee..f12adc489a2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1265,6 +1265,10 @@ class Project < ApplicationRecord end end + def to_ability_name + model_name.singular + end + # rubocop: disable CodeReuse/ServiceClass def execute_hooks(data, hooks_scope = :push_hooks) run_after_commit_or_now do diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index 6b3cb0b39d8..3006753e9e7 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -121,6 +121,12 @@ class WikiPage @version ||= @page.version end + def path + return unless persisted? + + @path ||= @page.path + end + def versions(options = {}) return [] unless persisted? diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb index b2af6c874c7..dcde8cefa0d 100644 --- a/app/policies/note_policy.rb +++ b/app/policies/note_policy.rb @@ -9,7 +9,7 @@ class NotePolicy < BasePolicy condition(:editable, scope: :subject) { @subject.editable? } - condition(:can_read_noteable) { can?(:"read_#{@subject.to_ability_name}") } + condition(:can_read_noteable) { can?(:"read_#{@subject.noteable_ability_name}") } condition(:is_visible) { @subject.visible_for?(@user) } diff --git a/app/serializers/merge_request_diff_entity.rb b/app/serializers/merge_request_diff_entity.rb index 7e3053e5881..5c79b165ee9 100644 --- a/app/serializers/merge_request_diff_entity.rb +++ b/app/serializers/merge_request_diff_entity.rb @@ -21,6 +21,8 @@ class MergeRequestDiffEntity < Grape::Entity expose :latest?, as: :latest expose :short_commit_sha do |merge_request_diff| + next unless merge_request_diff.head_commit_sha + short_sha(merge_request_diff.head_commit_sha) end diff --git a/app/services/clusters/update_service.rb b/app/services/clusters/update_service.rb index 25d26e761b1..98dd6b26a47 100644 --- a/app/services/clusters/update_service.rb +++ b/app/services/clusters/update_service.rb @@ -9,7 +9,55 @@ module Clusters end def execute(cluster) - cluster.update(params) + if validate_params(cluster) + cluster.update(params) + else + false + end + end + + private + + def can_admin_pipeline_for_project?(project) + Ability.allowed?(current_user, :admin_pipeline, project) + end + + def validate_params(cluster) + if params[:management_project_id] + management_project = management_project_scope(cluster).find_by_id(params[:management_project_id]) + + unless management_project + cluster.errors.add(:management_project_id, _('Project does not exist or you don\'t have permission to perform this action')) + + return false + end + + unless can_admin_pipeline_for_project?(management_project) + # Use same message as not found to prevent enumeration + cluster.errors.add(:management_project_id, _('Project does not exist or you don\'t have permission to perform this action')) + + return false + end + end + + true + end + + def management_project_scope(cluster) + return ::Project.all if cluster.instance_type? + + group = + if cluster.group_type? + cluster.first_group + elsif cluster.project_type? + cluster.first_project&.namespace + end + + # Prevent users from selecting nested projects until + # https://gitlab.com/gitlab-org/gitlab/issues/34650 is resolved + include_subgroups = cluster.group_type? + + ::GroupProjectsFinder.new(group: group, current_user: current_user, options: { only_owned: true, include_subgroups: include_subgroups }).execute end end end diff --git a/app/services/error_tracking/list_projects_service.rb b/app/services/error_tracking/list_projects_service.rb index 8d08f0cda94..92d4ef85ecf 100644 --- a/app/services/error_tracking/list_projects_service.rb +++ b/app/services/error_tracking/list_projects_service.rb @@ -32,7 +32,7 @@ module ErrorTracking project_slug: 'proj' ) - setting.token = params[:token] + setting.token = token(setting) setting.enabled = true end end @@ -40,5 +40,12 @@ module ErrorTracking def can_read? can?(current_user, :read_sentry_issue, project) end + + def token(setting) + # Use param token if not masked, otherwise use database token + return params[:token] unless /\A\*+\z/.match?(params[:token]) + + setting.token + end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index b56b2cf14e3..1709474a6c7 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -281,7 +281,7 @@ class NotificationService end def send_new_note_notifications(note) - notify_method = "note_#{note.to_ability_name}_email".to_sym + notify_method = "note_#{note.noteable_ability_name}_email".to_sym recipients = NotificationRecipientService.build_new_note_recipients(note) recipients.each do |recipient| diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb index 64519501ff4..0ca89664304 100644 --- a/app/services/projects/operations/update_service.rb +++ b/app/services/projects/operations/update_service.rb @@ -36,15 +36,17 @@ module Projects organization_slug: settings.dig(:project, :organization_slug) ) - { + params = { error_tracking_setting_attributes: { api_url: api_url, - token: settings[:token], enabled: settings[:enabled], project_name: settings.dig(:project, :name), organization_name: settings.dig(:project, :organization_name) } } + params[:error_tracking_setting_attributes][:token] = settings[:token] unless /\A\*+\z/.match?(settings[:token]) # Don't update token if we receive masked value + + params end def grafana_integration_params diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index 9744d293c8b..c8c96297672 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -8,8 +8,9 @@ .input-group-text = s_("CompareBranches|Source") = hidden_field_tag :to, params[:to] - = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip monospace", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do - .dropdown-toggle-text.str-truncated= params[:to] || _("Select branch/tag") + = button_tag type: 'button', title: params[:to], class: "btn form-control compare-dropdown-toggle js-compare-dropdown has-tooltip", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do + .dropdown-toggle-text.str-truncated.monospace.float-left= params[:to] || _("Select branch/tag") + = sprite_icon('arrow-down', size: 16, css_class: 'float-right') = render 'shared/ref_dropdown' .compare-ellipsis.inline ... .form-group.dropdown.compare-form-group.from.js-compare-from-dropdown @@ -18,8 +19,9 @@ .input-group-text = s_("CompareBranches|Target") = hidden_field_tag :from, params[:from] - = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip monospace", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do - .dropdown-toggle-text.str-truncated= params[:from] || _("Select branch/tag") + = button_tag type: 'button', title: params[:from], class: "btn form-control compare-dropdown-toggle js-compare-dropdown has-tooltip", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do + .dropdown-toggle-text.str-truncated.monospace.float-left= params[:from] || _("Select branch/tag") + = sprite_icon('arrow-down', size: 16, css_class: 'float-right') = render 'shared/ref_dropdown' = button_tag s_("CompareBranches|Compare"), class: "btn btn-success commits-compare-btn" diff --git a/app/views/projects/settings/operations/_error_tracking.html.haml b/app/views/projects/settings/operations/_error_tracking.html.haml index 583fc08f375..589d3037eba 100644 --- a/app/views/projects/settings/operations/_error_tracking.html.haml +++ b/app/views/projects/settings/operations/_error_tracking.html.haml @@ -17,4 +17,4 @@ project: error_tracking_setting_project_json, api_host: setting.api_host, enabled: setting.enabled.to_json, - token: setting.token } } + token: setting.token.present? ? '*' * 12 : nil } } diff --git a/changelogs/unreleased/28336-dropdown-icon-missing-on-compare-page.yml b/changelogs/unreleased/28336-dropdown-icon-missing-on-compare-page.yml new file mode 100644 index 00000000000..75f40d1ac8a --- /dev/null +++ b/changelogs/unreleased/28336-dropdown-icon-missing-on-compare-page.yml @@ -0,0 +1,5 @@ +--- +title: Adding dropdown arrow icon and updated text alignment +merge_request: +author: +type: other diff --git a/changelogs/unreleased/31390-remove-pointer-cursor-from-memory-usage-chart.yml b/changelogs/unreleased/31390-remove-pointer-cursor-from-memory-usage-chart.yml new file mode 100644 index 00000000000..0bbbcb11523 --- /dev/null +++ b/changelogs/unreleased/31390-remove-pointer-cursor-from-memory-usage-chart.yml @@ -0,0 +1,5 @@ +--- +title: Remove pointer cursor from MemoryUsage chart on MR widget deployment +merge_request: 18599 +author: +type: fixed diff --git a/changelogs/unreleased/8558-bump-ado-image-for-modsec-secruleengine.yml b/changelogs/unreleased/8558-bump-ado-image-for-modsec-secruleengine.yml new file mode 100644 index 00000000000..615ae1452d0 --- /dev/null +++ b/changelogs/unreleased/8558-bump-ado-image-for-modsec-secruleengine.yml @@ -0,0 +1,5 @@ +--- +title: Bump Auto-Deploy image to v0.3.0 +merge_request: 18809 +author: +type: added diff --git a/changelogs/unreleased/cluster_management_projects_updating.yml b/changelogs/unreleased/cluster_management_projects_updating.yml new file mode 100644 index 00000000000..8f1876fc5a1 --- /dev/null +++ b/changelogs/unreleased/cluster_management_projects_updating.yml @@ -0,0 +1,5 @@ +--- +title: Adds ability to set management project for cluster via API +merge_request: 18429 +author: +type: added diff --git a/changelogs/unreleased/id-nil-short-commit-sha.yml b/changelogs/unreleased/id-nil-short-commit-sha.yml new file mode 100644 index 00000000000..3d925e10616 --- /dev/null +++ b/changelogs/unreleased/id-nil-short-commit-sha.yml @@ -0,0 +1,5 @@ +--- +title: Serialize short sha as nil if head commit is blank +merge_request: 19014 +author: +type: fixed diff --git a/changelogs/unreleased/introduce-feature-flag-api-enable-disable.yml b/changelogs/unreleased/introduce-feature-flag-api-enable-disable.yml new file mode 100644 index 00000000000..1c60b87d7b2 --- /dev/null +++ b/changelogs/unreleased/introduce-feature-flag-api-enable-disable.yml @@ -0,0 +1,5 @@ +--- +title: Support Enable/Disable operations in Feature Flag API +merge_request: 18368 +author: +type: added diff --git a/changelogs/unreleased/ph-fixAdminGeoSidebarFlyOut.yml b/changelogs/unreleased/ph-fixAdminGeoSidebarFlyOut.yml new file mode 100644 index 00000000000..daafd2539fd --- /dev/null +++ b/changelogs/unreleased/ph-fixAdminGeoSidebarFlyOut.yml @@ -0,0 +1,5 @@ +--- +title: Fixed admin geo collapsed sidebar fly out not showing +merge_request: 19012 +author: +type: fixed diff --git a/changelogs/unreleased/security-id-fix-disclosure-of-private-repo-names.yml b/changelogs/unreleased/security-id-fix-disclosure-of-private-repo-names.yml new file mode 100644 index 00000000000..dfd7a2d11f9 --- /dev/null +++ b/changelogs/unreleased/security-id-fix-disclosure-of-private-repo-names.yml @@ -0,0 +1,5 @@ +--- +title: Return 404 on LFS request if project doesn't exist +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-mask-sentry-token-ce.yml b/changelogs/unreleased/security-mask-sentry-token-ce.yml new file mode 100644 index 00000000000..e9fe780a488 --- /dev/null +++ b/changelogs/unreleased/security-mask-sentry-token-ce.yml @@ -0,0 +1,4 @@ +--- +title: Mask sentry auth token in Error Tracking dashboard +author: +type: security diff --git a/changelogs/unreleased/security-open-redirect-internalredirect.yml b/changelogs/unreleased/security-open-redirect-internalredirect.yml new file mode 100644 index 00000000000..5ac65a4b355 --- /dev/null +++ b/changelogs/unreleased/security-open-redirect-internalredirect.yml @@ -0,0 +1,5 @@ +--- +title: Fixes a Open Redirect issue in `InternalRedirect`. +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-wiki-rdoc-content.yml b/changelogs/unreleased/security-wiki-rdoc-content.yml new file mode 100644 index 00000000000..f40f1abcd94 --- /dev/null +++ b/changelogs/unreleased/security-wiki-rdoc-content.yml @@ -0,0 +1,5 @@ +--- +title: Sanitize all wiki markup formats with GitLab sanitization pipelines +merge_request: +author: +type: security diff --git a/doc/api/group_clusters.md b/doc/api/group_clusters.md index e878bb5fa4d..6eb2cef4097 100644 --- a/doc/api/group_clusters.md +++ b/doc/api/group_clusters.md @@ -53,6 +53,16 @@ Example response: "api_url":"https://104.197.68.152", "authorization_type":"rbac", "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----" + }, + "management_project": + { + "id":2, + "description":null, + "name":"project2", + "name_with_namespace":"John Doe8 / project2", + "path":"project2", + "path_with_namespace":"namespace2/project2", + "created_at":"2019-10-11T02:55:54.138Z" } }, { @@ -111,6 +121,16 @@ Example response: "authorization_type":"rbac", "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----" }, + "management_project": + { + "id":2, + "description":null, + "name":"project2", + "name_with_namespace":"John Doe8 / project2", + "path":"project2", + "path_with_namespace":"namespace2/project2", + "created_at":"2019-10-11T02:55:54.138Z" + }, "group": { "id":26, @@ -135,6 +155,7 @@ Parameters: | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) | | `name` | String | yes | The name of the cluster | | `domain` | String | no | The [base domain](../user/group/clusters/index.md#base-domain) of the cluster | +| `management_project_id` | integer | no | The ID of the [management project](../user/clusters/management_project.md) for the cluster | | `enabled` | Boolean | no | Determines if cluster is active or not, defaults to true | | `managed` | Boolean | no | Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true | | `platform_kubernetes_attributes[api_url]` | String | yes | The URL to access the Kubernetes API | @@ -178,6 +199,7 @@ Example response: "authorization_type":"rbac", "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----" }, + "management_project":null, "group": { "id":26, @@ -248,6 +270,16 @@ Example response: "authorization_type":"rbac", "ca_cert":null }, + "management_project": + { + "id":2, + "description":null, + "name":"project2", + "name_with_namespace":"John Doe8 / project2", + "path":"project2", + "path_with_namespace":"namespace2/project2", + "created_at":"2019-10-11T02:55:54.138Z" + }, "group": { "id":26, diff --git a/doc/api/project_clusters.md b/doc/api/project_clusters.md index 633ef20deb4..e733cd7ea7b 100644 --- a/doc/api/project_clusters.md +++ b/doc/api/project_clusters.md @@ -54,6 +54,16 @@ Example response: "namespace":"cluster-1-namespace", "authorization_type":"rbac", "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----" + }, + "management_project": + { + "id":2, + "description":null, + "name":"project2", + "name_with_namespace":"John Doe8 / project2", + "path":"project2", + "path_with_namespace":"namespace2/project2", + "created_at":"2019-10-11T02:55:54.138Z" } }, { @@ -113,6 +123,16 @@ Example response: "authorization_type":"rbac", "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----" }, + "management_project": + { + "id":2, + "description":null, + "name":"project2", + "name_with_namespace":"John Doe8 / project2", + "path":"project2", + "path_with_namespace":"namespace2/project2", + "created_at":"2019-10-11T02:55:54.138Z" + }, "project": { "id":26, @@ -205,6 +225,7 @@ Example response: "authorization_type":"rbac", "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----" }, + "management_project":null, "project": { "id":26, @@ -253,6 +274,7 @@ Parameters: | `cluster_id` | integer | yes | The ID of the cluster | | `name` | String | no | The name of the cluster | | `domain` | String | no | The [base domain](../user/project/clusters/index.md#base-domain) of the cluster | +| `management_project_id` | integer | no | The ID of the [management project](../user/clusters/management_project.md) for the cluster | | `platform_kubernetes_attributes[api_url]` | String | no | The URL to access the Kubernetes API | | `platform_kubernetes_attributes[token]` | String | no | The token to authenticate against Kubernetes | | `platform_kubernetes_attributes[ca_cert]` | String | no | TLS certificate (needed if API is using a self-signed TLS certificate | @@ -300,6 +322,16 @@ Example response: "authorization_type":"rbac", "ca_cert":null }, + "management_project": + { + "id":2, + "description":null, + "name":"project2", + "name_with_namespace":"John Doe8 / project2", + "path":"project2", + "path_with_namespace":"namespace2/project2", + "created_at":"2019-10-11T02:55:54.138Z" + }, "project": { "id":26, diff --git a/doc/development/documentation/site_architecture/index.md b/doc/development/documentation/site_architecture/index.md index f5a12e9c216..bf873995e54 100644 --- a/doc/development/documentation/site_architecture/index.md +++ b/doc/development/documentation/site_architecture/index.md @@ -4,14 +4,16 @@ description: "Learn how GitLab's documentation website is architectured." # Documentation site architecture -Learn how we build and architecture [`gitlab-docs`](https://gitlab.com/gitlab-org/gitlab-docs) -and deploy it to <https://docs.gitlab.com>. +The [`gitlab-docs`](https://gitlab.com/gitlab-org/gitlab-docs) project hosts +the repository which is used to generate the GitLab documentation website and +is deployed to <https://docs.gitlab.com>. It uses the [Nanoc](http://nanoc.ws) +static site generator. -## Repository +## Architecture While the source of the documentation content is stored in GitLab's respective product -repositories, the source that is used to build the documentation site _from that content_ -is located at <https://gitlab.com/gitlab-org/gitlab-docs>. +repositories, the source that is used to build the documentation +site _from that content_ is located at <https://gitlab.com/gitlab-org/gitlab-docs>. The following diagram illustrates the relationship between the repositories from where content is sourced, the `gitlab-docs` project, and the published output. @@ -43,8 +45,23 @@ from where content is sourced, the `gitlab-docs` project, and the published outp G --> L ``` -See the [README there](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/README.md) -for detailed information. +You will not find any GitLab docs content in the `gitlab-docs` repository. +All documentation files are hosted in the respective repository of each +product, and all together are pulled to generate the docs website: + +- [GitLab](https://gitlab.com/gitlab-org/gitlab/tree/master/doc) +- [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/doc) +- [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner/tree/master/docs) +- [GitLab Chart](https://gitlab.com/charts/gitlab/tree/master/doc) + +NOTE: **Note:** +In September 2019, we [moved towards a single codebase](https://gitlab.com/gitlab-org/gitlab-ee/issues/2952), +as such the docs for CE and EE are now identical. For historical reasons and +in order not to break any existing links throughout the internet, we still +maintain the CE docs (`https://docs.gitlab.com/ce/`), although it is hidden +from the website, and is now a symlink to the EE docs. When +[Pages supports redirects](https://gitlab.com/gitlab-org/gitlab-pages/issues/24), +we will be able to remove this completely. ## Assets @@ -73,28 +90,112 @@ Read through [the global navigation documentation](global_nav.md) to understand: - How the global navigation is built. - How to add new navigation items. -## Deployment +<!-- +## Helpers -The docs site is deployed to production with GitLab Pages, and previewed in -merge requests with Review Apps. +TBA +--> -The deployment aspects will be soon transferred from the [original document](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/README.md) -to this page. +## Using YAML data files -<!-- -## Repositories +The easiest way to achieve something similar to +[Jekyll's data files](https://jekyllrb.com/docs/datafiles/) in Nanoc is by +using the [`@items`](https://nanoc.ws/doc/reference/variables/#items-and-layouts) +variable. -TBA +The data file must be placed inside the `content/` directory and then it can +be referenced in an ERB template. -## Search engine +Suppose we have the `content/_data/versions.yaml` file with the content: -TBA +```yaml +versions: +- 10.6 +- 10.5 +- 10.4 +``` -## Versions +We can then loop over the `versions` array with something like: -TBA +```erb +<% @items['/_data/versions.yaml'][:versions].each do | version | %> -## Helpers +<h3><%= version %></h3> -TBA ---> +<% end &> +``` + +Note that the data file must have the `yaml` extension (not `yml`) and that +we reference the array with a symbol (`:versions`). + +## Bumping versions of CSS and Javascript + +Whenever the custom CSS and Javascript files under `content/assets/` change, +make sure to bump their version in the frontmatter. This method guarantees that +your changes will take effect by clearing the cache of previous files. + +Always use Nanoc's way of including those files, do not hardcode them in the +layouts. For example use: + +```erb +<script async type="application/javascript" src="<%= @items['/assets/javascripts/badges.*'].path %>"></script> + +<link rel="stylesheet" href="<%= @items['/assets/stylesheets/toc.*'].path %>"> +``` + +The links pointing to the files should be similar to: + +```erb +<%= @items['/path/to/assets/file.*'].path %> +``` + +Nanoc will then build and render those links correctly according with what's +defined in [`Rules`](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/Rules). + +## Linking to source files + +A helper called [`edit_on_gitlab`](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/lib/helpers/edit_on_gitlab.rb) can be used +to link to a page's source file. We can link to both the simple editor and the +web IDE. Here's how you can use it in a Nanoc layout: + +- Default editor: `<a href="<%= edit_on_gitlab(@item, editor: :simple) %>">Simple editor</a>` +- Web IDE: `<a href="<%= edit_on_gitlab(@item, editor: :webide) %>">Web IDE</a>` + +If you don't specify `editor:`, the simple one is used by default. + +## Algolia search engine + +The docs site uses [Algolia docsearch](https://community.algolia.com/docsearch/) +for its search function. This is how it works: + +1. GitLab is a member of the [docsearch program](https://community.algolia.com/docsearch/#join-docsearch-program), + which is the free tier of [Algolia](https://www.algolia.com/). +1. Algolia hosts a [doscsearch config](https://github.com/algolia/docsearch-configs/blob/master/configs/gitlab.json) + for the GitLab docs site, and we've worked together to refine it. +1. That [config](https://community.algolia.com/docsearch/config-file.html) is + parsed by their [crawler](https://community.algolia.com/docsearch/crawler-overview.html) + every 24h and [stores](https://community.algolia.com/docsearch/inside-the-engine.html) + the [docsearch index](https://community.algolia.com/docsearch/how-do-we-build-an-index.html) + on [Algolia's servers](https://community.algolia.com/docsearch/faq.html#where-is-my-data-hosted%3F). +1. On the docs side, we use a [docsearch layout](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/layouts/docsearch.html) which + is present on pretty much every page except <https://docs.gitlab.com/search/>, + which uses its [own layout](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/layouts/instantsearch.html). In those layouts, + there's a javascript snippet which initiates docsearch by using an API key + and an index name (`gitlab`) that are needed for Algolia to show the results. + +NOTE: **For GitLab employees:** +The credentials to access the Algolia dashboard are stored in 1Password. If you +want to receive weekly reports of the search usage, search the Google doc with +title "Email, Slack, and GitLab Groups and Aliases", search for `docsearch`, +and add a comment with your email to be added to the alias that gets the weekly +reports. + +## Monthly release process (versions) + +The docs website supports versions and each month we add the latest one to the list. +For more information, read about the [monthly release process](release_process.md). + +## Review Apps for documentation merge requests + +If you are contributing to GitLab docs read how to [create a Review App with each +merge request](../index.md#previewing-the-changes-live). diff --git a/doc/development/documentation/site_architecture/release_process.md b/doc/development/documentation/site_architecture/release_process.md new file mode 100644 index 00000000000..6f723531f4c --- /dev/null +++ b/doc/development/documentation/site_architecture/release_process.md @@ -0,0 +1,241 @@ +# GitLab Docs monthly release process + +The [`dockerfiles` directory](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/dockerfiles/) +contains all needed Dockerfiles to build and deploy the versioned website. It +is heavily inspired by Docker's +[Dockerfile](https://github.com/docker/docker.github.io/blob/06ed03db13895bfe867761b6fc2ad40acf6026dd/Dockerfile). + +The following Dockerfiles are used. + +| Dockerfile | Docker image | Description | +| ---------- | ------------ | ----------- | +| [`Dockerfile.bootstrap`](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/dockerfiles/Dockerfile.bootstrap) | `gitlab-docs:bootstrap` | Contains all the dependencies that are needed to build the website. If the gems are updated and `Gemfile{,.lock}` changes, the image must be rebuilt. | +| [`Dockerfile.builder.onbuild`](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/dockerfiles/Dockerfile.builder.onbuild) | `gitlab-docs:builder-onbuild` | Base image to build the docs website. It uses `ONBUILD` to perform all steps and depends on `gitlab-docs:bootstrap`. | +| [`Dockerfile.nginx.onbuild`](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/dockerfiles/Dockerfile.nginx.onbuild) | `gitlab-docs:nginx-onbuild` | Base image to use for building documentation archives. It uses `ONBUILD` to perform all required steps to copy the archive, and relies upon its parent `Dockerfile.builder.onbuild` that is invoked when building single documentation achives (see the `Dockerfile` of each branch. | +| [`Dockerfile.archives`](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/dockerfiles/Dockerfile.archives) | `gitlab-docs:archives` | Contains all the versions of the website in one archive. It copies all generated HTML files from every version in one location. | + +## How to build the images + +Although build images are built automatically via GitLab CI/CD, you can build +and tag all tooling images locally: + +1. Make sure you have [Docker installed](https://docs.docker.com/install/). +1. Make sure you're on the `dockerfiles/` directory of the `gitlab-docs` repo. +1. Build the images: + + ```sh + docker build -t registry.gitlab.com/gitlab-org/gitlab-docs:bootstrap -f Dockerfile.bootstrap ../ + docker build -t registry.gitlab.com/gitlab-org/gitlab-docs:builder-onbuild -f Dockerfile.builder.onbuild ../ + docker build -t registry.gitlab.com/gitlab-org/gitlab-docs:nginx-onbuild -f Dockerfile.nginx.onbuild ../ + ``` + +For each image, there's a manual job under the `images` stage in +[`.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/.gitlab-ci.yml) which can be invoked at will. + +## Monthly release process + +When a new GitLab version is released on the 22nd, we need to create the respective +single Docker image, and update some files so that the dropdown works correctly. + +### 1. Add the chart version + +Since the charts use a different version number than all the other GitLab +products, we need to add a +[version mapping](https://docs.gitlab.com/charts/installation/version_mappings.html): + +1. Check that there is a [stable branch created](https://gitlab.com/gitlab-org/charts/gitlab/-/branches) + for the new chart version. If you're unsure or can't find it, drop a line in + the `#g_delivery` channel. +1. Make sure you're on the root path of the `gitlab-docs` repo. +1. Open `content/_data/chart_versions.yaml` and add the new stable branch version using the + version mapping. Note that only the `major.minor` version is needed. +1. Create a new merge request and merge it. + +TIP: **Tip:** +It can be handy to create the future mappings since they are pretty much known. +In that case, when a new GitLab version is released, you don't have to repeat +this first step. + +### 2. Create an image for a single version + +The single docs version must be created before the release merge request, but +this needs to happen when the stable branches for all products have been created. + +1. Make sure you're on the root path of the `gitlab-docs` repo. +1. Run the raketask to create the single version: + + ```sh + ./bin/rake "release:single[12.0]" + ``` + + A new `Dockerfile.12.0` should have been created and committed to a new branch. + +1. Push the newly created branch, but **don't create a merge request**. + Once you push, the `image:docker-singe` job will create a new Docker image + tagged with the branch name you created in the first step. In the end, the + image will be uploaded in the [Container Registry](https://gitlab.com/gitlab-org/gitlab-docs/container_registry) + and it will be listed under the + [`registry` environment folder](https://gitlab.com/gitlab-org/gitlab-docs/environments/folders/registry). + +Optionally, you can test locally by building the image and running it: + +```sh +docker build -t docs:12.0 -f Dockerfile.12.0 . +docker run -it --rm -p 4000:4000 docs:12.0 +``` + +Visit `http://localhost:4000/12.0/` to see if everything works correctly. + +### 3. Create the release merge request + +Now it's time to create the monthly release merge request that adds the new +version and rotates the old one: + +1. Make sure you're on the root path of the `gitlab-docs` repo. +1. Create a branch `release-X-Y`: + + ```sh + git checkout -b release-12-0 + ``` + +1. **Rotate the online and offline versions:** + + At any given time, there are 4 browsable online versions: one pulled from + the upstream master branches (docs for GitLab.com) and the three latest + stable versions. + + Edit `content/_data/versions.yaml` and rotate the versions to reflect the + new changes: + + - `online`: The 3 latest stable versions. + - `offline`: All the previous versions offered as an offline archive. + +1. **Add the new offline version in the 404 page redirect script:** + + Since we're deprecating the oldest version each month, we need to redirect + those URLs in order not to create [404 entries](https://gitlab.com/gitlab-org/gitlab-docs/issues/221). + There's a temporary hack for now: + + 1. Edit `content/404.html`, making sure all offline versions under + `content/_data/versions.yaml` are in the Javascript snippet at the end of + the document. + +1. **Update the `:latest` and `:archives` Docker images:** + + The following two Dockerfiles need to be updated: + + 1. `dockerfiles/Dockerfile.archives` - Add the latest version at the top of + the list. + 1. `Dockerfile.master` - Rotate the versions (oldest gets removed and latest + is added at the top of the list). + +1. In the end, there should be four files in total that have changed. + Commit and push to create the merge request using the "Release" template: + + ```sh + git add content/ Dockerfile.master dockerfiles/Dockerfile.archives + git commit -m "Release 12.0" + git push origin release-12-0 + ``` + +### 4. Update the dropdown for all online versions + +The versions dropdown is in a way "hardcoded". When the site is built, it looks +at the contents of `content/_data/versions.yaml` and based on that, the dropdown +is populated. So, older branches will have different content, which means the +dropdown will be one or more releases behind. Remember that the new changes of +the dropdown are included in the unmerged `release-X-Y` branch. + +The content of `content/_data/versions.yaml` needs to change for all online +versions: + +1. Before creating the merge request, [disable the scheduled pipeline](https://gitlab.com/gitlab-org/gitlab-docs/pipeline_schedules/228/edit) + by unchecking the "Active" option. Since all steps must run in sequence, we need + to do this to avoid race conditions in the event some previous versions are + updated before the release merge request is merged. +1. Run the raketask that will create all the respective merge requests needed to + update the dropdowns and will be set to automatically be merged when their + pipelines succeed. The `release-X-Y` branch needs to be present locally, + otherwise the raketask will fail: + + ```sh + ./bin/rake release:dropdowns + ``` + +Once all are merged, proceed to the following and final step. + +TIP: **Tip:** +In case a pipeline fails, see [troubleshooting](#troubleshooting). + +### 5. Merge the release merge request + +The dropdown merge requests should have now been merged into their respective +version (stable branch), which will trigger another pipeline. At this point, +you need to only babysit the pipelines and make sure they don't fail: + +1. Check the [pipelines page](https://gitlab.com/gitlab-org/gitlab-docs/pipelines) + and make sure all stable branches have green pipelines. +1. After all the pipelines of the online versions succeed, merge the release merge request. +1. Finally, re-activate the [scheduled pipeline](https://gitlab.com/gitlab-org/gitlab-docs/pipeline_schedules/228/edit), + save it, and hit the play button to get it started. + +Once the scheduled pipeline succeeds, the docs site will be deployed with all +new versions online. + +## Update an old Docker image with new upstream docs content + +If there are any changes to any of the stable branches of the products that are +not included in the single Docker image, just +[rerun the pipeline](https://gitlab.com/gitlab-org/gitlab-docs/pipelines/new) +for the version in question. + +## Porting new website changes to old versions + +CAUTION: **Warning:** +Porting changes to older branches can have unintended effects as we're constantly +changing the backend of the website. Use only when you know what you're doing +and make sure to test locally. + +The website will keep changing and being improved. In order to consolidate +those changes to the stable branches, we'd need to pick certain changes +from time to time. + +If this is not possible or there are many changes, merge master into them: + +```sh +git branch 12.0 +git fetch origin master +git merge origin/master +``` + +## Troubleshooting + +Releasing a new version is a long process that involves many moving parts. + +### `test_internal_links_and_anchors` failing on dropdown merge requests + +When [updating the dropdown for the stable versions](#4-update-the-dropdown-for-all-online-versions), +there may be cases where some links might fail. The process of how the +dropdown MRs are created have a caveat, and that is that the tests run by +pulling the master branches of all products, instead of the respective stable +ones. + +In a real world scenario, the [Update 12.2 dropdown to match that of 12.4](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/604) +merge request failed because of the [`test_internal_links_and_anchors` test](https://gitlab.com/gitlab-org/gitlab-docs/-/jobs/328042431). + +This happened because there has been a rename of a product (`gitlab-monitor` to `gitlab-exporter`) +and the old name was still referenced in the 12.2 docs. If the respective stable +branches for 12.2 were used, this wouldn't have failed, but as we can see from +the [`compile_dev` job](https://gitlab.com/gitlab-org/gitlab-docs/-/jobs/328042427), +the `master` branches were pulled. + +To fix this, you need to [re-run the pipeline](https://gitlab.com/gitlab-org/gitlab-docs/pipelines/new) +for the `update-12-2-for-release-12-4` branch, by including the following environment variables: + +- `BRANCH_CE` set to `12-2-stable` +- `BRANCH_EE` set to `12-2-stable-ee` +- `BRANCH_OMNIBUS` set to `12-2-stable` +- `BRANCH_RUNNER` set to `12-2-stable` +- `BRANCH_CHARTS` set to `2-2-stable` + +This should make the MR pass. diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md index 3dd403f148e..ecfcbc731e1 100644 --- a/doc/development/testing_guide/review_apps.md +++ b/doc/development/testing_guide/review_apps.md @@ -189,10 +189,10 @@ that the `review-apps-ce/ee` cluster is unhealthy. Leading indicators may be hea The following items may help diagnose this: -- [Instance group CPU Utilization in GCP](https://console.cloud.google.com/compute/instanceGroups/details/us-central1-b/gke-review-apps-ee-preemp-n1-standard-8affc0f5-grp?project=gitlab-review-apps&tab=monitoring&graph=GCE_CPU&duration=P30D) - helpful to identify if nodes are problematic or the entire cluster is trending towards unhealthy -- [Instance Group size in GCP](https://console.cloud.google.com/compute/instanceGroups/details/us-central1-b/gke-review-apps-ee-preemp-n1-standard-8affc0f5-grp?project=gitlab-review-apps&tab=monitoring&graph=GCE_SIZE&duration=P30D) - aids in identifying load spikes on the cluster. Kubernetes will add nodes up to 220 based on total resource requests. -- `kubectl top nodes --sort-by=cpu` - can identify if node spikes are common or load on specific nodes which may get rebalanced by the Kubernetes scheduler. -- `kubectl top pods --sort-by=cpu` - +- [Review Apps Health dashboard](https://app.google.stackdriver.com/dashboards/6798952013815386466?project=gitlab-review-apps&timeDomain=1d) + - Aids in identifying load spikes on the cluster, and if nodes are problematic or the entire cluster is trending towards unhealthy. +- `kubectl top nodes | sort --key 3 --numeric` - can identify if node spikes are common or load on specific nodes which may get rebalanced by the Kubernetes scheduler. +- `kubectl top pods | sort --key 2 --numeric` - - [K9s] - K9s is a powerful command line dashboard which allows you to filter by labels. This can help identify trends with apps exceeding the [review-app resource requests](https://gitlab.com/gitlab-org/gitlab/blob/master/scripts/review_apps/base-config.yaml). Kubernetes will schedule pods to nodes based on resource requests and allow for CPU usage up to the limits. - In K9s you can sort or add filters by typing the `/` character - `-lrelease=<review-app-slug>` - filters down to all pods for a release. This aids in determining what is having issues in a single deployment diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index a1373639a87..270c6255350 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -924,6 +924,7 @@ applications. | `AUTO_DEVOPS_CHART_REPOSITORY_NAME` | From GitLab 11.11, used to set the name of the Helm repository. Defaults to `gitlab`. | | `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME` | From GitLab 11.11, used to set a username to connect to the Helm repository. Defaults to no credentials. Also set `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD`. | | `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD` | From GitLab 11.11, used to set a password to connect to the Helm repository. Defaults to no credentials. Also set `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME`. | +| `AUTO_DEVOPS_MODSECURITY_SEC_RULE_ENGINE` | From GitLab 12.5, used in combination with [Modsecurity feature flag](../../user/clusters/applications.md#web-application-firewall-modsecurity) to toggle [Modsecurity's `SecRuleEngine`](https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-(v2.x)#SecRuleEngine) behavior. Defaults to `DetectionOnly`. | | `BUILDPACK_URL` | Buildpack's full URL. Can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`. For example `https://github.com/heroku/heroku-buildpack-ruby.git#v142`. | | `CANARY_ENABLED` | From GitLab 11.0, used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments-premium). | | `CANARY_PRODUCTION_REPLICAS` | Number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md) in the production environment. Takes precedence over `CANARY_REPLICAS`. Defaults to 1. | diff --git a/doc/user/clusters/management_project.md b/doc/user/clusters/management_project.md index 37308ad7175..2c3c6850ea5 100644 --- a/doc/user/clusters/management_project.md +++ b/doc/user/clusters/management_project.md @@ -4,7 +4,7 @@ CAUTION: **Warning:** This is an _alpha_ feature, and it is subject to change at any time without prior notice. -> [Introduced](https://gitlab.com/gitlab-org/gitlab/merge_requests/17866) in GitLab 12.4 +> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/32810) in GitLab 12.5 A project can be designated as the management project for a cluster. A management project can be used to run deployment jobs with @@ -22,6 +22,14 @@ This can be useful for: Only the management project will receive `cluster-admin` privileges. All other projects will continue to receive [namespace scoped `edit` level privileges](../project/clusters/index.md#rbac-cluster-resources). +Management projects are restricted to the following: + +- For project-level clusters, the management project must in the same + namespace (or descendants) as the cluster's project. +- For group-level clusters, the management project must in the same + group (or descendants) as as the cluster's group. +- For instance-level clusters, there are no such restrictions. + ## Usage ### Selecting a cluster management project @@ -87,9 +95,9 @@ configure production cluster: name: production ``` -## Disabling this feature +## Enabling this feature -This feature is enabled by default. To disable this feature, disable the +This feature is disabled by default. To enable this feature, enable the feature flag `:cluster_management_project`. To check if the feature flag is enabled on your GitLab instance, diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 933a571c17d..de12695af37 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1791,6 +1791,7 @@ module API expose :user, using: Entities::UserBasic expose :platform_kubernetes, using: Entities::Platform::Kubernetes expose :provider_gcp, using: Entities::Provider::Gcp + expose :management_project, using: Entities::ProjectIdentity end class ClusterProject < Cluster diff --git a/lib/api/group_clusters.rb b/lib/api/group_clusters.rb index a70ac63cc6e..abfe10b7fa1 100644 --- a/lib/api/group_clusters.rb +++ b/lib/api/group_clusters.rb @@ -84,6 +84,7 @@ module API requires :cluster_id, type: Integer, desc: 'The cluster ID' optional :name, type: String, desc: 'Cluster name' optional :domain, type: String, desc: 'Cluster base domain' + optional :management_project_id, type: Integer, desc: 'The ID of the management project' optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do optional :api_url, type: String, desc: 'URL to access the Kubernetes API' optional :token, type: String, desc: 'Token to authenticate against Kubernetes' diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index 4c575381d30..dfac777e4a1 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -140,7 +140,8 @@ module API { repository: repository.gitaly_repository, address: Gitlab::GitalyClient.address(project.repository_storage), - token: Gitlab::GitalyClient.token(project.repository_storage) + token: Gitlab::GitalyClient.token(project.repository_storage), + features: Feature::Gitaly.server_feature_flags } end end diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb index 45c800d7d1e..8e35914f48a 100644 --- a/lib/api/project_clusters.rb +++ b/lib/api/project_clusters.rb @@ -88,6 +88,7 @@ module API requires :cluster_id, type: Integer, desc: 'The cluster ID' optional :name, type: String, desc: 'Cluster name' optional :domain, type: String, desc: 'Cluster base domain' + optional :management_project_id, type: Integer, desc: 'The ID of the management project' optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do optional :api_url, type: String, desc: 'URL to access the Kubernetes API' optional :token, type: String, desc: 'Token to authenticate against Kubernetes' diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml index a8ec2d4781d..6de7aace8db 100644 --- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml @@ -1,5 +1,5 @@ .auto-deploy: - image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v0.1.0" + image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v0.3.0" review: extends: .auto-deploy diff --git a/lib/gitlab/experimentation.rb b/lib/gitlab/experimentation.rb index 8b315ae5606..2ccc8a367aa 100644 --- a/lib/gitlab/experimentation.rb +++ b/lib/gitlab/experimentation.rb @@ -43,7 +43,7 @@ module Gitlab end def experiment_enabled?(experiment_key) - Experimentation.enabled_for_user?(experiment_key, experimentation_subject_index) + Experimentation.enabled_for_user?(experiment_key, experimentation_subject_index) || forced_enabled?(experiment_key) end def track_experiment_event(experiment_key, action) @@ -94,6 +94,10 @@ module Gitlab experiment_enabled?(experiment_key) ? 'experimental_group' : 'control_group' end + + def forced_enabled?(experiment_key) + params.has_key?(:force_experiment) && params[:force_experiment] == experiment_key.to_s + end end class << self diff --git a/lib/gitlab/other_markup.rb b/lib/gitlab/other_markup.rb index bc467486eee..0dd6b8a809c 100644 --- a/lib/gitlab/other_markup.rb +++ b/lib/gitlab/other_markup.rb @@ -10,7 +10,7 @@ module Gitlab def self.render(file_name, input, context) html = GitHub::Markup.render(file_name, input) .force_encoding(input.encoding) - context[:pipeline] = :markup + context[:pipeline] ||= :markup html = Banzai.render(html, context) diff --git a/lib/quality/kubernetes_client.rb b/lib/quality/kubernetes_client.rb index 190b48ba7cb..0bd16935045 100644 --- a/lib/quality/kubernetes_client.rb +++ b/lib/quality/kubernetes_client.rb @@ -13,6 +13,15 @@ module Quality end def cleanup(release_name:) + selector = case release_name + when String + %(-l release="#{release_name}") + when Array + %(-l 'release in (#{release_name.join(', ')})') + else + raise ArgumentError, 'release_name must be a string or an array' + end + command = [ %(--namespace "#{namespace}"), 'delete', @@ -20,7 +29,7 @@ module Quality '--now', '--ignore-not-found', '--include-uninitialized', - %(-l release="#{release_name}") + selector ] run_command(command) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1c8669deef3..323efe2e6eb 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -12701,6 +12701,9 @@ msgstr "" msgid "Project details" msgstr "" +msgid "Project does not exist or you don't have permission to perform this action" +msgstr "" + msgid "Project export could not be deleted." msgstr "" diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index dcf145c9882..095233b54f0 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -93,6 +93,7 @@ module QA if Runtime::Env.signup_disabled? self.fabricate_via_api! do |user| user.username = username + user.password = password end else self.fabricate! diff --git a/scripts/review_apps/automated_cleanup.rb b/scripts/review_apps/automated_cleanup.rb index 2b4f1b9fe0b..731903f823b 100755 --- a/scripts/review_apps/automated_cleanup.rb +++ b/scripts/review_apps/automated_cleanup.rb @@ -60,6 +60,8 @@ class AutomatedCleanup stop_threshold = threshold_time(days: days_for_stop) deployments_look_back_threshold = threshold_time(days: days_for_delete * 5) + releases_to_delete = [] + gitlab.deployments(project_path, per_page: DEPLOYMENTS_PER_PAGE, sort: 'desc').auto_paginate do |deployment| break if Time.parse(deployment.created_at) < deployments_look_back_threshold @@ -75,7 +77,7 @@ class AutomatedCleanup if deployed_at < delete_threshold delete_environment(environment, deployment) release = Quality::HelmClient::Release.new(environment.slug, 1, deployed_at.to_s, nil, nil, review_apps_namespace) - delete_helm_release(release) + releases_to_delete << release elsif deployed_at < stop_threshold stop_environment(environment, deployment) else @@ -84,6 +86,8 @@ class AutomatedCleanup checked_environments << environment.slug end + + delete_helm_releases(releases_to_delete) end def perform_helm_releases_cleanup!(days:) @@ -91,16 +95,20 @@ class AutomatedCleanup threshold_day = threshold_time(days: days) + releases_to_delete = [] + helm_releases.each do |release| # Prevents deleting `dns-gitlab-review-app` releases or other unrelated releases next unless release.name.start_with?('review-') if release.status == 'FAILED' || release.last_update < threshold_day - delete_helm_release(release) + releases_to_delete << release else print_release_state(subject: 'Release', release_name: release.name, release_date: release.last_update, action: 'leaving') end end + + delete_helm_releases(releases_to_delete) end private @@ -121,10 +129,17 @@ class AutomatedCleanup helm.releases(args: args) end - def delete_helm_release(release) - print_release_state(subject: 'Release', release_name: release.name, release_status: release.status, release_date: release.last_update, action: 'cleaning') - helm.delete(release_name: release.name) - kubernetes.cleanup(release_name: release.name) + def delete_helm_releases(releases) + return if releases.empty? + + releases.each do |release| + print_release_state(subject: 'Release', release_name: release.name, release_status: release.status, release_date: release.last_update, action: 'cleaning') + end + + releases_names = releases.map(&:name) + helm.delete(release_name: releases_names) + kubernetes.cleanup(release_name: releases_names) + rescue Quality::HelmClient::CommandFailedError => ex raise ex unless ignore_exception?(ex.message, IGNORED_HELM_ERRORS) diff --git a/scripts/sync-stable-branch.sh b/scripts/sync-stable-branch.sh new file mode 100644 index 00000000000..fc62453d743 --- /dev/null +++ b/scripts/sync-stable-branch.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# This script triggers a merge train job to sync an EE stable branch to its +# corresponding CE stable branch. + +set -e + +if [[ "$MERGE_TRAIN_TRIGGER_TOKEN" == '' ]] +then + echo 'The variable MERGE_TRAIN_TRIGGER_TOKEN must be set to a non-empy value' + exit 1 +fi + +if [[ "$MERGE_TRAIN_TRIGGER_URL" == '' ]] +then + echo 'The variable MERGE_TRAIN_TRIGGER_URL must be set to a non-empy value' + exit 1 +fi + +if [[ "$CI_COMMIT_REF_NAME" == '' ]] +then + echo 'The variable CI_COMMIT_REF_NAME must be set to a non-empy value' + exit 1 +fi + +curl -X POST \ + -F token="$MERGE_TRAIN_TRIGGER_TOKEN" \ + -F ref=master \ + -F "variables[MERGE_FOSS]=1" \ + -F "variables[SOURCE_BRANCH]=$CI_COMMIT_REF_NAME" \ + -F "variables[TARGET_BRANCH]=${CI_COMMIT_REF_NAME/-ee/}" \ + "$MERGE_TRAIN_TRIGGER_URL" diff --git a/scripts/trigger-build b/scripts/trigger-build index badbb562021..74c1df258c0 100755 --- a/scripts/trigger-build +++ b/scripts/trigger-build @@ -17,7 +17,7 @@ module Trigger end class Base - def invoke!(post_comment: false) + def invoke!(post_comment: false, downstream_job_name: nil) pipeline = Gitlab.run_trigger( downstream_project_path, trigger_token, @@ -28,7 +28,18 @@ module Trigger puts "Waiting for downstream pipeline status" Trigger::CommitComment.post!(pipeline, access_token) if post_comment - Trigger::Pipeline.new(downstream_project_path, pipeline.id, access_token) + downstream_job = + if downstream_job_name + Gitlab.pipeline_jobs(downstream_project_path, pipeline.id).auto_paginate.find do |potential_job| + potential_job.name == downstream_job_name + end + end + + if downstream_job + Trigger::Job.new(downstream_project_path, downstream_job.id, access_token) + else + Trigger::Pipeline.new(downstream_project_path, pipeline.id, access_token) + end end private @@ -187,6 +198,14 @@ module Trigger attr_reader :project, :id, :api_token + def self.unscoped_class_name + name.split('::').last + end + + def self.gitlab_api_method_name + unscoped_class_name.downcase + end + def initialize(project, id, api_token) @project = project @id = id @@ -199,17 +218,17 @@ module Trigger def wait! loop do - raise "Pipeline timed out after waiting for #{duration} minutes!" if timeout? + raise "#{self.class.unscoped_class_name} timed out after waiting for #{duration} minutes!" if timeout? case status when :created, :pending, :running print "." sleep INTERVAL when :success - puts "Pipeline succeeded in #{duration} minutes!" + puts "#{self.class.unscoped_class_name} succeeded in #{duration} minutes!" break else - raise "Pipeline did not succeed!" + raise "#{self.class.unscoped_class_name} did not succeed!" end STDOUT.flush @@ -225,7 +244,7 @@ module Trigger end def status - Gitlab.pipeline(project, id).status.to_sym + Gitlab.public_send(self.class.gitlab_api_method_name, project, id).status.to_sym # rubocop:disable GitlabSecurity/PublicSend rescue Gitlab::Error::Error => error puts "Ignoring the following error: #{error}" # Ignore GitLab API hiccups. If GitLab is really down, we'll hit the job @@ -233,11 +252,13 @@ module Trigger :running end end + + Job = Class.new(Pipeline) end case ARGV[0] when 'omnibus' - Trigger::Omnibus.new.invoke!(post_comment: true).wait! + Trigger::Omnibus.new.invoke!(post_comment: true, downstream_job_name: 'Trigger:qa-test').wait! when 'cng' Trigger::CNG.new.invoke!.wait! else diff --git a/spec/controllers/concerns/internal_redirect_spec.rb b/spec/controllers/concerns/internal_redirect_spec.rb index da68c8c8697..e5e50cfd55e 100644 --- a/spec/controllers/concerns/internal_redirect_spec.rb +++ b/spec/controllers/concerns/internal_redirect_spec.rb @@ -19,7 +19,8 @@ describe InternalRedirect do [ 'Hello world', '//example.com/hello/world', - 'https://example.com/hello/world' + 'https://example.com/hello/world', + "not-starting-with-a-slash\n/starting/with/slash" ] end diff --git a/spec/controllers/concerns/lfs_request_spec.rb b/spec/controllers/concerns/lfs_request_spec.rb index cb8c0b8f71c..823b9a50434 100644 --- a/spec/controllers/concerns/lfs_request_spec.rb +++ b/spec/controllers/concerns/lfs_request_spec.rb @@ -16,13 +16,17 @@ describe LfsRequest do end def project - @project ||= Project.find(params[:id]) + @project ||= Project.find_by(id: params[:id]) end def download_request? true end + def upload_request? + false + end + def ci? false end @@ -49,4 +53,41 @@ describe LfsRequest do expect(assigns(:storage_project)).to eq(project) end end + + context 'user is authenticated without access to lfs' do + before do + allow(controller).to receive(:authenticate_user) + allow(controller).to receive(:authentication_result) do + Gitlab::Auth::Result.new + end + end + + context 'with access to the project' do + it 'returns 403' do + get :show, params: { id: project.id } + + expect(response.status).to eq(403) + end + end + + context 'without access to the project' do + context 'project does not exist' do + it 'returns 404' do + get :show, params: { id: 'does not exist' } + + expect(response.status).to eq(404) + end + end + + context 'project is private' do + let(:project) { create(:project, :private) } + + it 'returns 404' do + get :show, params: { id: project.id } + + expect(response.status).to eq(404) + end + end + end + end end diff --git a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap index 08173f4f0c4..706c26403c0 100644 --- a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap +++ b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap @@ -62,19 +62,23 @@ exports[`Repository last commit component renders commit widget 1`] = ` > <!----> - <gllink-stub - class="js-commit-pipeline" - data-original-title="Commit: failed" - href="https://test.com/pipeline" - title="" + <div + class="ci-status-link" > - <ciicon-stub - aria-label="Commit: failed" - cssclasses="" - size="24" - status="[object Object]" - /> - </gllink-stub> + <gllink-stub + class="js-commit-pipeline" + data-original-title="Commit: failed" + href="https://test.com/pipeline" + title="" + > + <ciicon-stub + aria-label="Commit: failed" + cssclasses="" + size="24" + status="[object Object]" + /> + </gllink-stub> + </div> <div class="commit-sha-group d-flex" @@ -165,19 +169,23 @@ exports[`Repository last commit component renders the signature HTML as returned </button> </div> - <gllink-stub - class="js-commit-pipeline" - data-original-title="Commit: failed" - href="https://test.com/pipeline" - title="" + <div + class="ci-status-link" > - <ciicon-stub - aria-label="Commit: failed" - cssclasses="" - size="24" - status="[object Object]" - /> - </gllink-stub> + <gllink-stub + class="js-commit-pipeline" + data-original-title="Commit: failed" + href="https://test.com/pipeline" + title="" + > + <ciicon-stub + aria-label="Commit: failed" + cssclasses="" + size="24" + status="[object Object]" + /> + </gllink-stub> + </div> <div class="commit-sha-group d-flex" diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index 364f215420a..32851249b2e 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -3,18 +3,18 @@ require 'spec_helper' describe MarkupHelper do - let!(:project) { create(:project, :repository) } - - let(:user) { create(:user, username: 'gfm') } - let(:commit) { project.commit } - let(:issue) { create(:issue, project: project) } - let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - let(:snippet) { create(:project_snippet, project: project) } - - before do - # Ensure the generated reference links aren't redacted + set(:project) { create(:project, :repository) } + set(:user) do + user = create(:user, username: 'gfm') project.add_maintainer(user) + user + end + set(:issue) { create(:issue, project: project) } + set(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + set(:snippet) { create(:project_snippet, project: project) } + let(:commit) { project.commit } + before do # Helper expects a @project instance variable helper.instance_variable_set(:@project, project) @@ -44,8 +44,8 @@ describe MarkupHelper do describe "override default project" do let(:actual) { issue.to_reference } - let(:second_project) { create(:project, :public) } - let(:second_issue) { create(:issue, project: second_project) } + set(:second_project) { create(:project, :public) } + set(:second_issue) { create(:issue, project: second_project) } it 'links to the issue' do expected = urls.project_issue_path(second_project, second_issue) @@ -55,7 +55,7 @@ describe MarkupHelper do describe 'uploads' do let(:text) { "" } - let(:group) { create(:group) } + set(:group) { create(:group) } subject { helper.markdown(text) } @@ -77,7 +77,7 @@ describe MarkupHelper do end describe "with a group in the context" do - let(:project_in_group) { create(:project, group: group) } + set(:project_in_group) { create(:project, group: group) } before do helper.instance_variable_set(:@group, group) @@ -235,38 +235,48 @@ describe MarkupHelper do end describe '#render_wiki_content' do + let(:wiki) { double('WikiPage', path: "file.#{extension}") } + let(:context) do + { + pipeline: :wiki, project: project, project_wiki: wiki, + page_slug: 'nested/page', issuable_state_filter_enabled: true + } + end + before do - @wiki = double('WikiPage') - allow(@wiki).to receive(:content).and_return('wiki content') - allow(@wiki).to receive(:slug).and_return('nested/page') - helper.instance_variable_set(:@project_wiki, @wiki) + expect(wiki).to receive(:content).and_return('wiki content') + expect(wiki).to receive(:slug).and_return('nested/page') + helper.instance_variable_set(:@project_wiki, wiki) end - it "uses Wiki pipeline for markdown files" do - allow(@wiki).to receive(:format).and_return(:markdown) + context 'when file is Markdown' do + let(:extension) { 'md' } - expect(helper).to receive(:markdown_unsafe).with('wiki content', - pipeline: :wiki, project: project, project_wiki: @wiki, page_slug: "nested/page", - issuable_state_filter_enabled: true) + it 'renders using #markdown_unsafe helper method' do + expect(helper).to receive(:markdown_unsafe).with('wiki content', context) - helper.render_wiki_content(@wiki) + helper.render_wiki_content(wiki) + end end - it "uses Asciidoctor for asciidoc files" do - allow(@wiki).to receive(:format).and_return(:asciidoc) + context 'when file is Asciidoc' do + let(:extension) { 'adoc' } - expect(helper).to receive(:asciidoc_unsafe).with('wiki content') + it 'renders using Gitlab::Asciidoc' do + expect(Gitlab::Asciidoc).to receive(:render) - helper.render_wiki_content(@wiki) + helper.render_wiki_content(wiki) + end end - it "uses the Gollum renderer for all other file types" do - allow(@wiki).to receive(:format).and_return(:rdoc) - formatted_content_stub = double('formatted_content') - expect(formatted_content_stub).to receive(:html_safe) - allow(@wiki).to receive(:formatted_content).and_return(formatted_content_stub) + context 'any other format' do + let(:extension) { 'foo' } - helper.render_wiki_content(@wiki) + it 'renders all other formats using Gitlab::OtherMarkup' do + expect(Gitlab::OtherMarkup).to receive(:render) + + helper.render_wiki_content(wiki) + end end end diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb index a7d3f628741..725c23e913c 100644 --- a/spec/lib/gitlab/experimentation_spec.rb +++ b/spec/lib/gitlab/experimentation_spec.rb @@ -71,6 +71,24 @@ describe Gitlab::Experimentation do controller.experiment_enabled?(:test_experiment) end end + + describe 'URL parameter to force enable experiment' do + context 'is not present' do + it 'returns false' do + get :index, params: { force_experiment: :test_experiment2 } + + expect(controller.experiment_enabled?(:test_experiment)).to be_falsey + end + end + + context 'is present' do + it 'returns true' do + get :index, params: { force_experiment: :test_experiment } + + expect(controller.experiment_enabled?(:test_experiment)).to be_truthy + end + end + end end describe '#track_experiment_event' do diff --git a/spec/lib/quality/helm_client_spec.rb b/spec/lib/quality/helm_client_spec.rb index 7abb9688d5a..da5ba4c4d99 100644 --- a/spec/lib/quality/helm_client_spec.rb +++ b/spec/lib/quality/helm_client_spec.rb @@ -107,5 +107,25 @@ RSpec.describe Quality::HelmClient do expect(subject.delete(release_name: release_name)).to eq('') end + + context 'with multiple release names' do + let(:release_name) { ['my-release', 'my-release-2'] } + + it 'raises an error if the Helm command fails' do + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with([%(helm delete --tiller-namespace "#{namespace}" --purge #{release_name.join(' ')})]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) + + expect { subject.delete(release_name: release_name) }.to raise_error(described_class::CommandFailedError) + end + + it 'calls helm delete with multiple release names' do + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with([%(helm delete --tiller-namespace "#{namespace}" --purge #{release_name.join(' ')})]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) + + expect(subject.delete(release_name: release_name)).to eq('') + end + end end end diff --git a/spec/lib/quality/kubernetes_client_spec.rb b/spec/lib/quality/kubernetes_client_spec.rb index 4e77dcc97e6..a42f6151a5e 100644 --- a/spec/lib/quality/kubernetes_client_spec.rb +++ b/spec/lib/quality/kubernetes_client_spec.rb @@ -29,5 +29,30 @@ RSpec.describe Quality::KubernetesClient do # We're not verifying the output here, just silencing it expect { subject.cleanup(release_name: release_name) }.to output.to_stdout end + + context 'with multiple releases' do + let(:release_name) { ['my-release', 'my-release-2'] } + + it 'raises an error if the Kubernetes command fails' do + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with([%(kubectl --namespace "#{namespace}" delete ) \ + 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa ' \ + "--now --ignore-not-found --include-uninitialized -l 'release in (#{release_name.join(', ')})'"]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false))) + + expect { subject.cleanup(release_name: release_name) }.to raise_error(described_class::CommandFailedError) + end + + it 'calls kubectl with the correct arguments' do + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with([%(kubectl --namespace "#{namespace}" delete ) \ + 'ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa ' \ + "--now --ignore-not-found --include-uninitialized -l 'release in (#{release_name.join(', ')})'"]) + .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true))) + + # We're not verifying the output here, just silencing it + expect { subject.cleanup(release_name: release_name) }.to output.to_stdout + end + end end end diff --git a/spec/migrations/schedule_fix_gitlab_com_pages_access_level_spec.rb b/spec/migrations/schedule_fix_gitlab_com_pages_access_level_spec.rb index 88e5c101d32..1ddb468f6b7 100644 --- a/spec/migrations/schedule_fix_gitlab_com_pages_access_level_spec.rb +++ b/spec/migrations/schedule_fix_gitlab_com_pages_access_level_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require Rails.root.join('db', 'post_migrate', '20191017045817_schedule_fix_gitlab_com_pages_access_level.rb') diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb index 66b65d8b6d8..96d81f4cc49 100644 --- a/spec/models/ci/build_trace_chunk_spec.rb +++ b/spec/models/ci/build_trace_chunk_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do @@ -63,7 +65,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do let(:data_store) { :redis } before do - build_trace_chunk.send(:unsafe_set_data!, 'Sample data in redis') + build_trace_chunk.send(:unsafe_set_data!, +'Sample data in redis') end it { is_expected.to eq('Sample data in redis') } @@ -71,7 +73,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do context 'when data_store is database' do let(:data_store) { :database } - let(:raw_data) { 'Sample data in database' } + let(:raw_data) { +'Sample data in database' } it { is_expected.to eq('Sample data in database') } end @@ -80,7 +82,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do let(:data_store) { :fog } before do - build_trace_chunk.send(:unsafe_set_data!, 'Sample data in fog') + build_trace_chunk.send(:unsafe_set_data!, +'Sample data in fog') end it { is_expected.to eq('Sample data in fog') } @@ -90,7 +92,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do describe '#append' do subject { build_trace_chunk.append(new_data, offset) } - let(:new_data) { 'Sample new data' } + let(:new_data) { +'Sample new data' } let(:offset) { 0 } let(:merged_data) { data + new_data.to_s } @@ -143,7 +145,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end context 'when new_data is empty' do - let(:new_data) { '' } + let(:new_data) { +'' } it 'does not append' do subject @@ -172,7 +174,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do shared_examples_for 'Scheduling sidekiq worker to flush data to persist store' do context 'when new data fulfilled chunk size' do - let(:new_data) { 'a' * described_class::CHUNK_SIZE } + let(:new_data) { +'a' * described_class::CHUNK_SIZE } it 'schedules trace chunk flush worker' do expect(Ci::BuildTraceChunkFlushWorker).to receive(:perform_async).once @@ -194,7 +196,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do shared_examples_for 'Scheduling no sidekiq worker' do context 'when new data fulfilled chunk size' do - let(:new_data) { 'a' * described_class::CHUNK_SIZE } + let(:new_data) { +'a' * described_class::CHUNK_SIZE } it 'does not schedule trace chunk flush worker' do expect(Ci::BuildTraceChunkFlushWorker).not_to receive(:perform_async) @@ -219,7 +221,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do let(:data_store) { :redis } context 'when there are no data' do - let(:data) { '' } + let(:data) { +'' } it 'has no data' do expect(build_trace_chunk.data).to be_empty @@ -230,7 +232,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end context 'when there are some data' do - let(:data) { 'Sample data in redis' } + let(:data) { +'Sample data in redis' } before do build_trace_chunk.send(:unsafe_set_data!, data) @@ -249,7 +251,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do let(:data_store) { :database } context 'when there are no data' do - let(:data) { '' } + let(:data) { +'' } it 'has no data' do expect(build_trace_chunk.data).to be_empty @@ -260,7 +262,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end context 'when there are some data' do - let(:raw_data) { 'Sample data in database' } + let(:raw_data) { +'Sample data in database' } let(:data) { raw_data } it 'has data' do @@ -276,7 +278,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do let(:data_store) { :fog } context 'when there are no data' do - let(:data) { '' } + let(:data) { +'' } it 'has no data' do expect(build_trace_chunk.data).to be_empty @@ -287,7 +289,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end context 'when there are some data' do - let(:data) { 'Sample data in fog' } + let(:data) { +'Sample data in fog' } before do build_trace_chunk.send(:unsafe_set_data!, data) @@ -332,7 +334,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do context 'when data_store is redis' do let(:data_store) { :redis } - let(:data) { 'Sample data in redis' } + let(:data) { +'Sample data in redis' } before do build_trace_chunk.send(:unsafe_set_data!, data) @@ -343,7 +345,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do context 'when data_store is database' do let(:data_store) { :database } - let(:raw_data) { 'Sample data in database' } + let(:raw_data) { +'Sample data in database' } let(:data) { raw_data } it_behaves_like 'truncates' @@ -351,7 +353,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do context 'when data_store is fog' do let(:data_store) { :fog } - let(:data) { 'Sample data in fog' } + let(:data) { +'Sample data in fog' } before do build_trace_chunk.send(:unsafe_set_data!, data) @@ -368,7 +370,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do let(:data_store) { :redis } context 'when data exists' do - let(:data) { 'Sample data in redis' } + let(:data) { +'Sample data in redis' } before do build_trace_chunk.send(:unsafe_set_data!, data) @@ -386,7 +388,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do let(:data_store) { :database } context 'when data exists' do - let(:raw_data) { 'Sample data in database' } + let(:raw_data) { +'Sample data in database' } let(:data) { raw_data } it { is_expected.to eq(data.bytesize) } @@ -401,7 +403,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do let(:data_store) { :fog } context 'when data exists' do - let(:data) { 'Sample data in fog' } + let(:data) { +'Sample data in fog' } let(:key) { "tmp/builds/#{build.id}/chunks/#{chunk_index}.log" } before do @@ -443,7 +445,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end context 'when data size reached CHUNK_SIZE' do - let(:data) { 'a' * described_class::CHUNK_SIZE } + let(:data) { +'a' * described_class::CHUNK_SIZE } it 'persists the data' do expect(build_trace_chunk.redis?).to be_truthy @@ -463,7 +465,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end context 'when data size has not reached CHUNK_SIZE' do - let(:data) { 'Sample data in redis' } + let(:data) { +'Sample data in redis' } it 'does not persist the data and the orignal data is intact' do expect { subject }.to raise_error(described_class::FailedToPersistDataError) @@ -492,7 +494,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end context 'when data size reached CHUNK_SIZE' do - let(:data) { 'a' * described_class::CHUNK_SIZE } + let(:data) { +'a' * described_class::CHUNK_SIZE } it 'persists the data' do expect(build_trace_chunk.database?).to be_truthy @@ -512,7 +514,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end context 'when data size has not reached CHUNK_SIZE' do - let(:data) { 'Sample data in database' } + let(:data) { +'Sample data in database' } it 'does not persist the data and the orignal data is intact' do expect { subject }.to raise_error(described_class::FailedToPersistDataError) @@ -561,7 +563,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do end context 'when data size has not reached CHUNK_SIZE' do - let(:data) { 'Sample data in fog' } + let(:data) { +'Sample data in fog' } it 'does not raise error' do expect { subject }.not_to raise_error diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index c2c57379461..8a3a7eee25d 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -152,6 +152,16 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do end end + describe '.for_project_namespace' do + subject { described_class.for_project_namespace(namespace_id) } + + let!(:cluster) { create(:cluster, :project) } + let!(:another_cluster) { create(:cluster, :project) } + let(:namespace_id) { cluster.first_project.namespace_id } + + it { is_expected.to contain_exactly(cluster) } + end + describe 'validations' do subject { cluster.valid? } diff --git a/spec/models/clusters/clusters_hierarchy_spec.rb b/spec/models/clusters/clusters_hierarchy_spec.rb index fc35b8257e9..1957e1fc5ee 100644 --- a/spec/models/clusters/clusters_hierarchy_spec.rb +++ b/spec/models/clusters/clusters_hierarchy_spec.rb @@ -42,6 +42,28 @@ describe Clusters::ClustersHierarchy do it 'returns clusters for project' do expect(base_and_ancestors(cluster.project)).to eq([cluster]) end + + context 'cluster has management project' do + let(:management_project) { create(:project, namespace: cluster.first_project.namespace) } + + before do + cluster.update!(management_project: management_project) + end + + context 'management_project is in same namespace as cluster' do + it 'returns cluster for management_project' do + expect(base_and_ancestors(management_project)).to eq([cluster]) + end + end + + context 'management_project is in a different namespace from cluster' do + let(:management_project) { create(:project) } + + it 'returns nothing' do + expect(base_and_ancestors(management_project)).to be_empty + end + end + end end context 'cluster has management project' do @@ -50,16 +72,12 @@ describe Clusters::ClustersHierarchy do let(:group) { create(:group) } let(:project) { create(:project, group: group) } - let(:management_project) { create(:project) } + let(:management_project) { create(:project, group: group) } it 'returns clusters for management_project' do expect(base_and_ancestors(management_project)).to eq([group_cluster]) end - it 'returns nothing if include_management_project is false' do - expect(base_and_ancestors(management_project, include_management_project: false)).to be_empty - end - it 'returns clusters for project' do expect(base_and_ancestors(project)).to eq([project_cluster, group_cluster]) end @@ -70,17 +88,21 @@ describe Clusters::ClustersHierarchy do end context 'project in nested group with clusters at some levels' do - let!(:child) { create(:cluster, :group, groups: [child_group], management_project: management_project) } - let!(:ancestor) { create(:cluster, :group, groups: [ancestor_group]) } + let!(:child) { create(:cluster, :group, groups: [child_group]) } + let!(:ancestor) { create(:cluster, :group, groups: [ancestor_group], management_project: management_project) } let(:ancestor_group) { create(:group) } let(:parent_group) { create(:group, parent: ancestor_group) } let(:child_group) { create(:group, parent: parent_group) } let(:project) { create(:project, group: child_group) } - let(:management_project) { create(:project) } + let(:management_project) { create(:project, group: child_group) } + + it 'returns clusters for management_project' do + expect(base_and_ancestors(management_project)).to eq([ancestor, child]) + end it 'returns clusters for management_project' do - expect(base_and_ancestors(management_project)).to eq([child]) + expect(base_and_ancestors(management_project, include_management_project: false)).to eq([child, ancestor]) end it 'returns clusters for project' do diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb index 2cac42f56ff..9164c3a75c5 100644 --- a/spec/models/concerns/deployment_platform_spec.rb +++ b/spec/models/concerns/deployment_platform_spec.rb @@ -13,7 +13,11 @@ describe DeploymentPlatform do end context 'when project is the cluster\'s management project ' do - let!(:cluster_with_management_project) { create(:cluster, :provided_by_user, management_project: project) } + let(:another_project) { create(:project, namespace: project.namespace) } + + let!(:cluster_with_management_project) do + create(:cluster, :provided_by_user, projects: [another_project], management_project: project) + end context 'cluster_management_project feature is enabled' do it 'returns the cluster with management project' do @@ -66,7 +70,11 @@ describe DeploymentPlatform do end context 'when project is the cluster\'s management project ' do - let!(:cluster_with_management_project) { create(:cluster, :provided_by_user, management_project: project) } + let(:another_project) { create(:project, namespace: project.namespace) } + + let!(:cluster_with_management_project) do + create(:cluster, :provided_by_user, projects: [another_project], management_project: project) + end context 'cluster_management_project feature is enabled' do it 'returns the cluster with management project' do diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 2ecbe548520..120ba67f328 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -227,6 +227,14 @@ describe Milestone do end end + describe '#to_ability_name' do + it 'returns milestone' do + milestone = build(:milestone) + + expect(milestone.to_ability_name).to eq('milestone') + end + end + describe '.search' do let(:milestone) { create(:milestone, title: 'foo', description: 'bar') } diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 4c320b4b145..1c895f084b0 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -578,24 +578,30 @@ describe Note do end describe '#to_ability_name' do - it 'returns snippet for a project snippet note' do - expect(build(:note_on_project_snippet).to_ability_name).to eq('project_snippet') + it 'returns note' do + expect(build(:note).to_ability_name).to eq('note') + end + end + + describe '#noteable_ability_name' do + it 'returns project_snippet for a project snippet note' do + expect(build(:note_on_project_snippet).noteable_ability_name).to eq('project_snippet') end it 'returns personal_snippet for a personal snippet note' do - expect(build(:note_on_personal_snippet).to_ability_name).to eq('personal_snippet') + expect(build(:note_on_personal_snippet).noteable_ability_name).to eq('personal_snippet') end it 'returns merge_request for an MR note' do - expect(build(:note_on_merge_request).to_ability_name).to eq('merge_request') + expect(build(:note_on_merge_request).noteable_ability_name).to eq('merge_request') end it 'returns issue for an issue note' do - expect(build(:note_on_issue).to_ability_name).to eq('issue') + expect(build(:note_on_issue).noteable_ability_name).to eq('issue') end - it 'returns issue for a commit note' do - expect(build(:note_on_commit).to_ability_name).to eq('commit') + it 'returns commit for a commit note' do + expect(build(:note_on_commit).noteable_ability_name).to eq('commit') end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 5922a6f36f5..b4d9ce28829 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -4444,6 +4444,14 @@ describe Project do end end + describe '#to_ability_name' do + it 'returns project' do + project = build(:project_empty_repo) + + expect(project.to_ability_name).to eq('project') + end + end + describe '#execute_hooks' do let(:data) { { ref: 'refs/heads/master', data: 'data' } } it 'executes active projects hooks with the specified scope' do diff --git a/spec/models/shard_spec.rb b/spec/models/shard_spec.rb index 83104711b55..4da86858b54 100644 --- a/spec/models/shard_spec.rb +++ b/spec/models/shard_spec.rb @@ -1,4 +1,5 @@ -# frozen_string_literals: true +# frozen_string_literal: true + require 'spec_helper' describe Shard do diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index 8f3730f97ac..38415613b9e 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -358,6 +358,23 @@ describe WikiPage do end end + describe '#path' do + let(:path) { 'mypath.md' } + let(:wiki_page) { instance_double('Gitlab::Git::WikiPage', path: path).as_null_object } + + it 'returns the path when persisted' do + page = described_class.new(wiki, wiki_page, true) + + expect(page.path).to eq(path) + end + + it 'returns nil when not persisted' do + page = described_class.new(wiki, wiki_page, false) + + expect(page.path).to be_nil + end + end + describe '#directory' do context 'when the page is at the root directory' do it 'returns an empty string' do diff --git a/spec/requests/api/group_clusters_spec.rb b/spec/requests/api/group_clusters_spec.rb index 46e3dd650cc..97465647a87 100644 --- a/spec/requests/api/group_clusters_spec.rb +++ b/spec/requests/api/group_clusters_spec.rb @@ -286,12 +286,15 @@ describe API::GroupClusters do let(:update_params) do { domain: domain, - platform_kubernetes_attributes: platform_kubernetes_attributes + platform_kubernetes_attributes: platform_kubernetes_attributes, + management_project_id: management_project_id } end let(:domain) { 'new-domain.com' } let(:platform_kubernetes_attributes) { {} } + let(:management_project) { create(:project, group: group) } + let(:management_project_id) { management_project.id } let(:cluster) do create(:cluster, :group, :provided_by_gcp, @@ -308,6 +311,8 @@ describe API::GroupClusters do context 'authorized user' do before do + management_project.add_maintainer(current_user) + put api("/groups/#{group.id}/clusters/#{cluster.id}", current_user), params: update_params cluster.reload @@ -320,6 +325,7 @@ describe API::GroupClusters do it 'updates cluster attributes' do expect(cluster.domain).to eq('new-domain.com') + expect(cluster.management_project).to eq(management_project) end end @@ -332,6 +338,7 @@ describe API::GroupClusters do it 'does not update cluster attributes' do expect(cluster.domain).to eq('old-domain.com') + expect(cluster.management_project).to be_nil end it 'returns validation errors' do @@ -339,6 +346,18 @@ describe API::GroupClusters do end end + context 'current user does not have access to management_project_id' do + let(:management_project_id) { create(:project).id } + + it 'responds with 400' do + expect(response).to have_gitlab_http_status(400) + end + + it 'returns validation errors' do + expect(json_response['message']['management_project_id'].first).to match('don\'t have permission') + end + end + context 'with a GCP cluster' do context 'when user tries to change GCP specific fields' do let(:platform_kubernetes_attributes) do diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb index 63b75b810dc..02d1e4bf2f1 100644 --- a/spec/requests/api/internal/base_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -316,6 +316,7 @@ describe API::Internal::Base do expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) + expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-get-all-lfs-pointers-go' => 'true', 'gitaly-feature-inforef-uploadpack-cache' => 'true') expect(user.reload.last_activity_on).to eql(Date.today) end end @@ -335,6 +336,7 @@ describe API::Internal::Base do expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) + expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-get-all-lfs-pointers-go' => 'true', 'gitaly-feature-inforef-uploadpack-cache' => 'true') expect(user.reload.last_activity_on).to be_nil end end @@ -576,6 +578,7 @@ describe API::Internal::Base do expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) + expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-get-all-lfs-pointers-go' => 'true', 'gitaly-feature-inforef-uploadpack-cache' => 'true') end end diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index a7b919de2ef..04e59238877 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -281,11 +281,14 @@ describe API::ProjectClusters do let(:api_url) { 'https://kubernetes.example.com' } let(:namespace) { 'new-namespace' } let(:platform_kubernetes_attributes) { { namespace: namespace } } + let(:management_project) { create(:project, namespace: project.namespace) } + let(:management_project_id) { management_project.id } let(:update_params) do { domain: 'new-domain.com', - platform_kubernetes_attributes: platform_kubernetes_attributes + platform_kubernetes_attributes: platform_kubernetes_attributes, + management_project_id: management_project_id } end @@ -310,6 +313,8 @@ describe API::ProjectClusters do context 'authorized user' do before do + management_project.add_maintainer(current_user) + put api("/projects/#{project.id}/clusters/#{cluster.id}", current_user), params: update_params cluster.reload @@ -323,6 +328,7 @@ describe API::ProjectClusters do it 'updates cluster attributes' do expect(cluster.domain).to eq('new-domain.com') expect(cluster.platform_kubernetes.namespace).to eq('new-namespace') + expect(cluster.management_project).to eq(management_project) end end @@ -336,6 +342,7 @@ describe API::ProjectClusters do it 'does not update cluster attributes' do expect(cluster.domain).not_to eq('new_domain.com') expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace') + expect(cluster.management_project).not_to eq(management_project) end it 'returns validation errors' do @@ -343,6 +350,18 @@ describe API::ProjectClusters do end end + context 'current user does not have access to management_project_id' do + let(:management_project_id) { create(:project).id } + + it 'responds with 400' do + expect(response).to have_gitlab_http_status(400) + end + + it 'returns validation errors' do + expect(json_response['message']['management_project_id'].first).to match('don\'t have permission') + end + end + context 'with a GCP cluster' do context 'when user tries to change GCP specific fields' do let(:platform_kubernetes_attributes) do diff --git a/spec/serializers/merge_request_diff_entity_spec.rb b/spec/serializers/merge_request_diff_entity_spec.rb index 062f17963c0..59ec0b22158 100644 --- a/spec/serializers/merge_request_diff_entity_spec.rb +++ b/spec/serializers/merge_request_diff_entity_spec.rb @@ -7,14 +7,15 @@ describe MergeRequestDiffEntity do let(:request) { EntityRequest.new(project: project) } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } let(:merge_request_diffs) { merge_request.merge_request_diffs } + let(:merge_request_diff) { merge_request_diffs.first } let(:entity) do - described_class.new(merge_request_diffs.first, request: request, merge_request: merge_request, merge_request_diffs: merge_request_diffs) + described_class.new(merge_request_diff, request: request, merge_request: merge_request, merge_request_diffs: merge_request_diffs) end - context 'as json' do - subject { entity.as_json } + subject { entity.as_json } + context 'as json' do it 'exposes needed attributes' do expect(subject).to include( :version_index, :created_at, :commits_count, @@ -23,4 +24,16 @@ describe MergeRequestDiffEntity do ) end end + + describe '#short_commit_sha' do + it 'returns short sha' do + expect(subject[:short_commit_sha]).to eq('b83d6e39') + end + + it 'returns nil if head_commit_sha does not exist' do + allow(merge_request_diff).to receive(:head_commit_sha).and_return(nil) + + expect(subject[:short_commit_sha]).to eq(nil) + end + end end diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb index 3ee45375dca..8c2d8c9246e 100644 --- a/spec/services/clusters/update_service_spec.rb +++ b/spec/services/clusters/update_service_spec.rb @@ -90,5 +90,115 @@ describe Clusters::UpdateService do end end end + + context 'when params includes :management_project_id' do + context 'management_project is non-existent' do + let(:params) do + { management_project_id: 0 } + end + + it 'does not update management_project_id' do + is_expected.to eq(false) + + expect(cluster.errors[:management_project_id]).to include('Project does not exist or you don\'t have permission to perform this action') + + cluster.reload + expect(cluster.management_project_id).to be_nil + end + end + + shared_examples 'setting a management project' do + context 'user is authorized to adminster manangement_project' do + before do + management_project.add_maintainer(cluster.user) + end + + let(:params) do + { management_project_id: management_project.id } + end + + it 'updates management_project_id' do + is_expected.to eq(true) + + expect(cluster.management_project).to eq(management_project) + end + end + + context 'user is not authorized to adminster manangement_project' do + let(:params) do + { management_project_id: management_project.id } + end + + it 'does not update management_project_id' do + is_expected.to eq(false) + + expect(cluster.errors[:management_project_id]).to include('Project does not exist or you don\'t have permission to perform this action') + + cluster.reload + expect(cluster.management_project_id).to be_nil + end + end + end + + context 'project cluster' do + include_examples 'setting a management project' do + let(:management_project) { create(:project, namespace: cluster.first_project.namespace) } + end + + context 'manangement_project is outside of the namespace scope' do + before do + management_project.update(group: create(:group)) + end + + let(:params) do + { management_project_id: management_project.id } + end + + it 'does not update management_project_id' do + is_expected.to eq(false) + + expect(cluster.errors[:management_project_id]).to include('Project does not exist or you don\'t have permission to perform this action') + + cluster.reload + expect(cluster.management_project_id).to be_nil + end + end + end + + context 'group cluster' do + let(:cluster) { create(:cluster, :group) } + + include_examples 'setting a management project' do + let(:management_project) { create(:project, group: cluster.first_group) } + end + + context 'manangement_project is outside of the namespace scope' do + before do + management_project.update(group: create(:group)) + end + + let(:params) do + { management_project_id: management_project.id } + end + + it 'does not update management_project_id' do + is_expected.to eq(false) + + expect(cluster.errors[:management_project_id]).to include('Project does not exist or you don\'t have permission to perform this action') + + cluster.reload + expect(cluster.management_project_id).to be_nil + end + end + end + + context 'instance cluster' do + let(:cluster) { create(:cluster, :instance) } + + include_examples 'setting a management project' do + let(:management_project) { create(:project) } + end + end + end end end diff --git a/spec/services/error_tracking/list_projects_service_spec.rb b/spec/services/error_tracking/list_projects_service_spec.rb index 730fccc599e..a272a604184 100644 --- a/spec/services/error_tracking/list_projects_service_spec.rb +++ b/spec/services/error_tracking/list_projects_service_spec.rb @@ -50,6 +50,19 @@ describe ErrorTracking::ListProjectsService do end end + context 'masked param token' do + let(:params) { ActionController::Parameters.new(token: "*********", api_host: new_api_host) } + + before do + expect(error_tracking_setting).to receive(:list_sentry_projects) + .and_return({ projects: [] }) + end + + it 'uses database token' do + expect { subject.execute }.not_to change { error_tracking_setting.token } + end + end + context 'sentry client raises exception' do context 'Sentry::Client::Error' do before do diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb index b2f9fd6df79..81d59a98b9b 100644 --- a/spec/services/projects/operations/update_service_spec.rb +++ b/spec/services/projects/operations/update_service_spec.rb @@ -145,6 +145,27 @@ describe Projects::Operations::UpdateService do end end + context 'with masked param token' do + let(:params) do + { + error_tracking_setting_attributes: { + enabled: false, + token: '*' * 8 + } + } + end + + before do + create(:project_error_tracking_setting, project: project, token: 'token') + end + + it 'does not update token' do + expect(result[:status]).to eq(:success) + + expect(project.error_tracking_setting.token).to eq('token') + end + end + context 'with invalid parameters' do let(:params) { {} } diff --git a/spec/sidekiq/cron/job_gem_dependency_spec.rb b/spec/sidekiq/cron/job_gem_dependency_spec.rb index 2e7de75fd08..20347b4d306 100644 --- a/spec/sidekiq/cron/job_gem_dependency_spec.rb +++ b/spec/sidekiq/cron/job_gem_dependency_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Sidekiq::Cron::Job do diff --git a/spec/support/helpers/smime_helper.rb b/spec/support/helpers/smime_helper.rb index 656b3e196ba..3ad19cd3da0 100644 --- a/spec/support/helpers/smime_helper.rb +++ b/spec/support/helpers/smime_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SmimeHelper include OpenSSL |