diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-17 00:08:00 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-17 00:08:00 +0300 |
commit | 69d6d3ca2013e97cfd2d89449669ea7bf475f4e9 (patch) | |
tree | 2cc4227ebfc52b7603691f06b0b8e09e030e8428 | |
parent | 01fdcf49b1553c22ae116fe96cedd7b91d02225c (diff) |
Add latest changes from gitlab-org/gitlab@master
45 files changed, 911 insertions, 150 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml index 5fc4af97e2d..7ede62ec979 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,6 +1,7 @@ extends: - '@gitlab' - plugin:promise/recommended + - plugin:no-jquery/slim globals: __webpack_public_path__: true gl: false @@ -44,10 +45,9 @@ rules: vue/no-use-v-if-with-v-for: off vue/no-v-html: off vue/use-v-on-exact: off - no-jquery/no-ajax: error - no-jquery/no-ajax-events: error - no-jquery/no-load: error - no-jquery/no-load-shorthand: error + no-jquery/no-animate: off + no-jquery/no-animate-toggle: off + no-jquery/no-fade: off no-jquery/no-serialize: error promise/always-return: off promise/no-callback-in-promise: off diff --git a/CHANGELOG-EE.md b/CHANGELOG-EE.md index ef59ca31a36..1e564d8ebad 100644 --- a/CHANGELOG-EE.md +++ b/CHANGELOG-EE.md @@ -1,5 +1,9 @@ Please view this file on the master branch, on stable branches it's out of date. +## 12.5.5 + +- No changes. + ## 12.5.4 ### Security (1 change) diff --git a/CHANGELOG.md b/CHANGELOG.md index aadcacbd836..6b3f2c1476f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,15 @@ entry. ## 12.5.5 -- No changes. +### Security (1 change) + +- Upgrade Akismet gem to v3.0.0. !21786 + +### Fixed (2 changes) + +- Fix error in updating runner session. !20902 +- Fix Asana integration. !21501 + ## 12.5.4 diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 465c9a362ba..674415c9d01 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -222,6 +222,7 @@ document.addEventListener('DOMContentLoaded', () => { } }); + // eslint-disable-next-line no-jquery/no-ajax-events $(document).ajaxError((e, xhrObj) => { const ref = xhrObj.status; diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index fcd5b391b38..1a8f1c659a4 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1,7 +1,7 @@ -/* eslint-disable no-restricted-properties, no-var, camelcase, -no-unused-expressions, one-var, default-case, +/* eslint-disable no-restricted-properties, camelcase, +no-unused-expressions, default-case, consistent-return, no-alert, no-param-reassign, no-else-return, -vars-on-top, no-shadow, no-useless-escape, +no-shadow, no-useless-escape, class-methods-use-this */ /* global ResolveService */ @@ -224,18 +224,18 @@ export default class Notes { } keydownNoteText(e) { - var $textarea, - discussionNoteForm, - editNote, - myLastNote, - myLastNoteEditBtn, - newText, - originalText; + let discussionNoteForm; + let editNote; + let myLastNote; + let myLastNoteEditBtn; + let newText; + let originalText; + if (isMetaKey(e)) { return; } - $textarea = $(e.target); + const $textarea = $(e.target); // Edit previous note when UP arrow is hit switch (e.which) { case 38: @@ -325,11 +325,10 @@ export default class Notes { * if there aren't new notes coming from the server */ setPollingInterval(shouldReset) { - var nthInterval; if (shouldReset == null) { shouldReset = true; } - nthInterval = this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1); + const nthInterval = this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1); if (shouldReset) { this.pollingInterval = this.basePollingInterval; } else if (this.pollingInterval < nthInterval) { @@ -339,7 +338,7 @@ export default class Notes { } handleQuickActions(noteEntity) { - var votesBlock; + let votesBlock; if (noteEntity.commands_changes) { if ('merge' in noteEntity.commands_changes) { Notes.checkMergeRequestStatus(); @@ -462,14 +461,16 @@ export default class Notes { * Render note in discussion area. To render inline notes use renderDiscussionNote. */ renderDiscussionNote(noteEntity, $form) { - var discussionContainer, form, row, lineType, diffAvatarContainer; + let discussionContainer; + let row; if (!Notes.isNewNote(noteEntity, this.note_ids)) { return; } this.note_ids.push(noteEntity.id); - form = $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`); + const form = + $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`); row = form.length || !noteEntity.discussion_line_code ? form.closest('tr') @@ -479,8 +480,8 @@ export default class Notes { row = form; } - lineType = this.isParallelView() ? form.find('#line_type').val() : 'old'; - diffAvatarContainer = row + const lineType = this.isParallelView() ? form.find('#line_type').val() : 'old'; + const diffAvatarContainer = row .prevAll('.line_holder') .first() .find(`.js-avatar-container.${lineType}_line`); @@ -491,15 +492,17 @@ export default class Notes { } if (discussionContainer.length === 0) { if (noteEntity.diff_discussion_html) { - var $discussion = $(noteEntity.diff_discussion_html).renderGFM(); + const $discussion = $(noteEntity.diff_discussion_html).renderGFM(); if (!this.isParallelView() || row.hasClass('js-temp-notes-holder') || noteEntity.on_image) { // insert the note and the reply button after the temp row row.after($discussion); } else { // Merge new discussion HTML in - var $notes = $discussion.find(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`); - var contentContainerClass = $notes + const $notes = $discussion.find( + `.notes[data-discussion-id="${noteEntity.discussion_id}"]`, + ); + const contentContainerClass = $notes .closest('.notes-content') .attr('class') .split(' ') @@ -537,7 +540,7 @@ export default class Notes { } renderDiscussionAvatar(diffAvatarContainer, noteEntity) { - var avatarHolder = diffAvatarContainer.find('.diff-comment-avatar-holders'); + let avatarHolder = diffAvatarContainer.find('.diff-comment-avatar-holders'); if (!avatarHolder.length) { avatarHolder = document.createElement('diff-note-avatars'); @@ -557,8 +560,7 @@ export default class Notes { * Resets buttons. */ resetMainTargetForm(e) { - var form; - form = $('.js-main-target-form'); + const form = $('.js-main-target-form'); // remove validation errors form.find('.js-errors').remove(); // reset text and preview @@ -572,7 +574,7 @@ export default class Notes { .data('autosave') .reset(); - var event = document.createEvent('Event'); + const event = document.createEvent('Event'); event.initEvent('autosize:update', true, false); form.find('.js-autosize')[0].dispatchEvent(event); @@ -580,8 +582,7 @@ export default class Notes { } reenableTargetFormSubmitButton() { - var form; - form = $('.js-main-target-form'); + const form = $('.js-main-target-form'); return form.find('.js-note-text').trigger('input'); } @@ -591,9 +592,8 @@ export default class Notes { * Sets some hidden fields in the form. */ setupMainTargetNoteForm(enableGFM) { - var form; // find the form - form = $('.js-new-note-form'); + const form = $('.js-new-note-form'); // Set a global clone of the form for later cloning this.formClone = form.clone(); // show the form @@ -626,10 +626,9 @@ export default class Notes { * show the form */ setupNoteForm(form, enableGFM = defaultAutocompleteConfig) { - var textarea, key; this.glForm = new GLForm(form, enableGFM); - textarea = form.find('.js-note-text'); - key = [ + const textarea = form.find('.js-note-text'); + const key = [ s__('NoteForm|Note'), form.find('#note_noteable_type').val(), form.find('#note_noteable_id').val(), @@ -686,8 +685,8 @@ export default class Notes { */ addDiscussionNote($form, note, isNewDiffComment) { if ($form.attr('data-resolve-all') != null) { - var discussionId = $form.data('discussionId'); - var mergeRequestId = $form.data('noteableIid'); + const discussionId = $form.data('discussionId'); + const mergeRequestId = $form.data('noteableIid'); if (ResolveService != null) { ResolveService.toggleResolveForDiscussion(mergeRequestId, discussionId); @@ -707,13 +706,12 @@ export default class Notes { * Updates the current note field. */ updateNote(noteEntity, $targetNote) { - var $noteEntityEl, $note_li; // Convert returned HTML to a jQuery object so we can modify it further - $noteEntityEl = $(noteEntity.html); + const $noteEntityEl = $(noteEntity.html); this.revertNoteEditForm($targetNote); $noteEntityEl.renderGFM(); // Find the note's `li` element by ID and replace it with the updated HTML - $note_li = $(`.note-row-${noteEntity.id}`); + const $note_li = $(`.note-row-${noteEntity.id}`); $note_li.replaceWith($noteEntityEl); this.setupNewNote($noteEntityEl); @@ -724,17 +722,17 @@ export default class Notes { } checkContentToAllowEditing($el) { - var initialContent = $el + const initialContent = $el .find('.original-note-content') .text() .trim(); - var currentContent = $el.find('.js-note-text').val(); - var isAllowed = true; + const currentContent = $el.find('.js-note-text').val(); + let isAllowed = true; if (currentContent === initialContent) { this.removeNoteEditForm($el); } else { - var isWidgetVisible = isInViewport($el.get(0)); + const isWidgetVisible = isInViewport($el.get(0)); if (!isWidgetVisible) { scrollToElement($el); @@ -756,13 +754,13 @@ export default class Notes { showEditForm(e) { e.preventDefault(); - var $target = $(e.target); - var $editForm = $(this.getEditFormSelector($target)); - var $note = $target.closest('.note'); - var $currentlyEditing = $('.note.is-editing:visible'); + const $target = $(e.target); + const $editForm = $(this.getEditFormSelector($target)); + const $note = $target.closest('.note'); + const $currentlyEditing = $('.note.is-editing:visible'); if ($currentlyEditing.length) { - var isEditAllowed = this.checkContentToAllowEditing($currentlyEditing); + const isEditAllowed = this.checkContentToAllowEditing($currentlyEditing); if (!isEditAllowed) { return; @@ -802,8 +800,8 @@ export default class Notes { revertNoteEditForm($target) { $target = $target || $('.note.is-editing:visible'); - var selector = this.getEditFormSelector($target); - var $editForm = $(selector); + const selector = this.getEditFormSelector($target); + const $editForm = $(selector); $editForm.insertBefore('.diffs'); $editForm.find('.js-comment-save-button').enable(); @@ -811,7 +809,7 @@ export default class Notes { } getEditFormSelector($el) { - var selector = '.note-edit-form:not(.mr-note-edit-form)'; + let selector = '.note-edit-form:not(.mr-note-edit-form)'; if ($el.parents('#diffs').length) { selector = '.note-edit-form.mr-note-edit-form'; @@ -821,7 +819,7 @@ export default class Notes { } removeNoteEditForm($note) { - var form = $note.find('.diffs .current-note-edit-form'); + const form = $note.find('.diffs .current-note-edit-form'); $note.removeClass('is-editing'); form.removeClass('current-note-edit-form'); @@ -837,9 +835,8 @@ export default class Notes { * Removes the whole discussion if the last note is being removed. */ removeNote(e) { - var noteElId, $note; - $note = $(e.currentTarget).closest('.note'); - noteElId = $note.attr('id'); + const $note = $(e.currentTarget).closest('.note'); + const noteElId = $note.attr('id'); $(`.note[id="${noteElId}"]`).each((i, el) => { // A same note appears in the "Discussion" and in the "Changes" tab, we have // to remove all. Using $('.note[id='noteId']') ensure we get all the notes, @@ -915,9 +912,8 @@ export default class Notes { } replyToDiscussionNote(target) { - var form, replyLink; - form = this.cleanForm(this.formClone.clone()); - replyLink = $(target).closest('.js-discussion-reply-button'); + const form = this.cleanForm(this.formClone.clone()); + const replyLink = $(target).closest('.js-discussion-reply-button'); // insert the form after the button replyLink .closest('.discussion-reply-holder') @@ -942,7 +938,7 @@ export default class Notes { diffFileData = dataHolder.closest('.image'); } - var discussionID = dataHolder.data('discussionId'); + const discussionID = dataHolder.data('discussionId'); if (discussionID) { form.attr('data-discussion-id', discussionID); @@ -985,7 +981,7 @@ export default class Notes { form.removeClass('js-main-target-form').addClass('discussion-form js-discussion-note-form'); if (typeof gl.diffNotesCompileComponents !== 'undefined') { - var $commentBtn = form.find('comment-and-resolve-btn'); + const $commentBtn = form.find('comment-and-resolve-btn'); $commentBtn.attr(':discussion-id', `'${discussionID}'`); gl.diffNotesCompileComponents(); @@ -1042,16 +1038,20 @@ export default class Notes { } toggleDiffNote({ target, lineType, forceShow, showReplyInput = false }) { - var $link, addForm, hasNotes, newForm, noteForm, replyButton, row, rowCssToAdd; - $link = $(target); - row = $link.closest('tr'); + let addForm; + let newForm; + let noteForm; + let replyButton; + let rowCssToAdd; + const $link = $(target); + const row = $link.closest('tr'); const nextRow = row.next(); let targetRow = row; if (nextRow.is('.notes_holder')) { targetRow = nextRow; } - hasNotes = nextRow.is('.notes_holder'); + const hasNotes = nextRow.is('.notes_holder'); addForm = false; let lineTypeSelector = ''; rowCssToAdd = @@ -1111,9 +1111,8 @@ export default class Notes { * Removes the form and if necessary it's temporary row. */ removeDiscussionNoteForm(form) { - var glForm, row; - row = form.closest('tr'); - glForm = form.data('glForm'); + const row = form.closest('tr'); + const glForm = form.data('glForm'); glForm.destroy(); form .find('.js-note-text') @@ -1158,10 +1157,9 @@ export default class Notes { * Updates the file name for the selected attachment. */ updateFormAttachment() { - var filename, form; - form = $(this).closest('form'); + const form = $(this).closest('form'); // get only the basename - filename = $(this) + const filename = $(this) .val() .replace(/^.*[\\\/]/, ''); return form.find('.js-attachment-filename').text(filename); @@ -1175,11 +1173,12 @@ export default class Notes { } updateTargetButtons(e) { - var closebtn, closetext, form, reopenbtn, reopentext, textarea; - textarea = $(e.target); - form = textarea.parents('form'); - reopenbtn = form.find('.js-note-target-reopen'); - closebtn = form.find('.js-note-target-close'); + let closetext; + let reopentext; + const textarea = $(e.target); + const form = textarea.parents('form'); + const reopenbtn = form.find('.js-note-target-reopen'); + const closebtn = form.find('.js-note-target-close'); if (textarea.val().trim().length > 0) { reopentext = reopenbtn.attr('data-alternative-text'); @@ -1215,16 +1214,16 @@ export default class Notes { } putEditFormInPlace($el) { - var $editForm = $(this.getEditFormSelector($el)); - var $note = $el.closest('.note'); + const $editForm = $(this.getEditFormSelector($el)); + const $note = $el.closest('.note'); $editForm.insertAfter($note.find('.note-text')); - var $originalContentEl = $note.find('.original-note-content'); - var originalContent = $originalContentEl.text().trim(); - var postUrl = $originalContentEl.data('postUrl'); - var targetId = $originalContentEl.data('targetId'); - var targetType = $originalContentEl.data('targetType'); + const $originalContentEl = $note.find('.original-note-content'); + const originalContent = $originalContentEl.text().trim(); + const postUrl = $originalContentEl.data('postUrl'); + const targetId = $originalContentEl.data('targetId'); + const targetType = $originalContentEl.data('targetType'); this.glForm = new GLForm($editForm.find('form'), this.enableGFM); diff --git a/app/controllers/projects/environments/sample_metrics_controller.rb b/app/controllers/projects/environments/sample_metrics_controller.rb new file mode 100644 index 00000000000..79a7eab150b --- /dev/null +++ b/app/controllers/projects/environments/sample_metrics_controller.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class Projects::Environments::SampleMetricsController < Projects::ApplicationController + def query + result = Metrics::SampleMetricsService.new(params[:identifier]).query + + if result + render json: { "status": "success", "data": { "resultType": "matrix", "result": result } } + else + render_404 + end + end +end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index acc852d8b9a..620a63fdc46 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -162,7 +162,7 @@ module DiffHelper end def render_overflow_warning?(diffs_collection) - diff_files = diffs_collection.diff_files + diff_files = diffs_collection.raw_diff_files if diff_files.any?(&:too_large?) Gitlab::Metrics.add_event(:diffs_overflow_single_file_limits) diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb index 2b9285e33d0..4ac33d4e3be 100644 --- a/app/models/clusters/applications/prometheus.rb +++ b/app/models/clusters/applications/prometheus.rb @@ -55,10 +55,10 @@ module Clusters ) end - def upgrade_command(values) - ::Gitlab::Kubernetes::Helm::InstallCommand.new( + def patch_command(values) + ::Gitlab::Kubernetes::Helm::PatchCommand.new( name: name, - version: VERSION, + version: version, rbac: cluster.platform_kubernetes_rbac?, chart: chart, files: files_with_replaced_values(values) diff --git a/app/serializers/diffs_entity.rb b/app/serializers/diffs_entity.rb index 19875a1287c..88e09ae8c0b 100644 --- a/app/serializers/diffs_entity.rb +++ b/app/serializers/diffs_entity.rb @@ -42,13 +42,13 @@ class DiffsEntity < Grape::Entity # rubocop: disable CodeReuse/ActiveRecord expose :added_lines do |diffs| - diffs.diff_files.sum(&:added_lines) + diffs.raw_diff_files.sum(&:added_lines) end # rubocop: enable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord expose :removed_lines do |diffs| - diffs.diff_files.sum(&:removed_lines) + diffs.raw_diff_files.sum(&:removed_lines) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/serializers/diffs_metadata_entity.rb b/app/serializers/diffs_metadata_entity.rb index c82c686e8ef..b7024721ea9 100644 --- a/app/serializers/diffs_metadata_entity.rb +++ b/app/serializers/diffs_metadata_entity.rb @@ -2,5 +2,5 @@ class DiffsMetadataEntity < DiffsEntity unexpose :diff_files - expose :diff_files, using: DiffFileMetadataEntity + expose :raw_diff_files, as: :diff_files, using: DiffFileMetadataEntity end diff --git a/app/services/clusters/applications/base_helm_service.rb b/app/services/clusters/applications/base_helm_service.rb index f38051bcad2..57bc8bc0d9b 100644 --- a/app/services/clusters/applications/base_helm_service.rb +++ b/app/services/clusters/applications/base_helm_service.rb @@ -61,8 +61,8 @@ module Clusters @update_command ||= app.update_command end - def upgrade_command(new_values = "") - app.upgrade_command(new_values) + def patch_command(new_values = "") + app.patch_command(new_values) end end end diff --git a/app/services/metrics/sample_metrics_service.rb b/app/services/metrics/sample_metrics_service.rb new file mode 100644 index 00000000000..719bc6614e4 --- /dev/null +++ b/app/services/metrics/sample_metrics_service.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Metrics + class SampleMetricsService + DIRECTORY = "sample_metrics" + + attr_reader :identifier + + def initialize(identifier) + @identifier = identifier + end + + def query + return unless identifier && File.exist?(file_location) + + YAML.load_file(File.expand_path(file_location, __dir__)) + end + + private + + def file_location + sanitized_string = identifier.gsub(/[^0-9A-Za-z_]/, '') + File.join(Rails.root, DIRECTORY, "#{sanitized_string}.yml") + end + end +end diff --git a/changelogs/unreleased/bug-35083-check-permission-for-downstream-pipeline.yml b/changelogs/unreleased/bug-35083-check-permission-for-downstream-pipeline.yml new file mode 100644 index 00000000000..e1252f0020c --- /dev/null +++ b/changelogs/unreleased/bug-35083-check-permission-for-downstream-pipeline.yml @@ -0,0 +1,5 @@ +--- +title: Add protected branch permission check to run downstream pipelines +merge_request: 20964 +author: +type: fixed diff --git a/changelogs/unreleased/generate-test-prometheus-data.yml b/changelogs/unreleased/generate-test-prometheus-data.yml new file mode 100644 index 00000000000..861b3d45863 --- /dev/null +++ b/changelogs/unreleased/generate-test-prometheus-data.yml @@ -0,0 +1,6 @@ +--- +title: Genereate a set of sample prometheus metrics and route to the sample metrics + when enabled +merge_request: 19987 +author: +type: added diff --git a/changelogs/unreleased/rs-default-mr-target.yml b/changelogs/unreleased/rs-default-mr-target.yml index 0a600207a65..5d9ab3d3bf9 100644 --- a/changelogs/unreleased/rs-default-mr-target.yml +++ b/changelogs/unreleased/rs-default-mr-target.yml @@ -1,5 +1,5 @@ --- -title: 'When a forked project is less visible than its source, merge requests now target the less visible project by default.' +title: 'When a forked project is less visible than its source, merge requests opened in the fork now target the less visible project by default.' merge_request: 21517 author: type: changed diff --git a/changelogs/unreleased/sh-add-index-for-deployments.yml b/changelogs/unreleased/sh-add-index-for-deployments.yml new file mode 100644 index 00000000000..cf8ae31dac4 --- /dev/null +++ b/changelogs/unreleased/sh-add-index-for-deployments.yml @@ -0,0 +1,5 @@ +--- +title: Add indexes on deployments to improve environments search +merge_request: 21789 +author: +type: performance diff --git a/changelogs/unreleased/sh-fix-asana-integration.yml b/changelogs/unreleased/sh-fix-asana-integration.yml deleted file mode 100644 index 34c2f9f48e1..00000000000 --- a/changelogs/unreleased/sh-fix-asana-integration.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix Asana integration -merge_request: 21501 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-issue-34366.yml b/changelogs/unreleased/sh-fix-issue-34366.yml deleted file mode 100644 index efc19f8392e..00000000000 --- a/changelogs/unreleased/sh-fix-issue-34366.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix error in updating runner session -merge_request: 20902 -author: -type: fixed diff --git a/changelogs/unreleased/sh-upgrade-akismet-gem.yml b/changelogs/unreleased/sh-upgrade-akismet-gem.yml deleted file mode 100644 index c6a545e2bba..00000000000 --- a/changelogs/unreleased/sh-upgrade-akismet-gem.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade Akismet gem to v3.0.0 -merge_request: 21786 -author: -type: security diff --git a/changelogs/unreleased/sy-add-embeds-limit.yml b/changelogs/unreleased/sy-add-embeds-limit.yml new file mode 100644 index 00000000000..3e89b64ef9d --- /dev/null +++ b/changelogs/unreleased/sy-add-embeds-limit.yml @@ -0,0 +1,5 @@ +--- +title: Limit max metrics embeds in GFM to 100 +merge_request: 21356 +author: +type: performance diff --git a/config/routes/project.rb b/config/routes/project.rb index c29d673f315..ea406d17bef 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -232,6 +232,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', format: false get '/prometheus/api/v1/*proxy_path', to: 'environments/prometheus_api#proxy', as: :prometheus_api + + get '/sample_metrics', to: 'environments/sample_metrics#query' if ENV['USE_SAMPLE_METRICS'] end collection do diff --git a/db/migrate/20191214175727_add_indexes_to_deployments_on_project_id_and_ref.rb b/db/migrate/20191214175727_add_indexes_to_deployments_on_project_id_and_ref.rb new file mode 100644 index 00000000000..5dacc3c0c66 --- /dev/null +++ b/db/migrate/20191214175727_add_indexes_to_deployments_on_project_id_and_ref.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class AddIndexesToDeploymentsOnProjectIdAndRef < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + INDEX_NAME = 'partial_index_deployments_for_project_id_and_tag'.freeze + + disable_ddl_transaction! + + def up + add_concurrent_index :deployments, [:project_id, :ref] + add_concurrent_index :deployments, [:project_id], where: 'tag IS TRUE', name: INDEX_NAME + end + + def down + remove_concurrent_index :deployments, [:project_id, :ref] + remove_concurrent_index :deployments, [:project_id], where: 'tag IS TRUE', name: INDEX_NAME + end +end diff --git a/db/schema.rb b/db/schema.rb index ede50e7ed06..870eb22e1f1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_12_08_071112) do +ActiveRecord::Schema.define(version: 2019_12_14_175727) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -1357,9 +1357,11 @@ ActiveRecord::Schema.define(version: 2019_12_08_071112) do t.index ["id"], name: "partial_index_deployments_for_legacy_successful_deployments", where: "((finished_at IS NULL) AND (status = 2))" t.index ["project_id", "id"], name: "index_deployments_on_project_id_and_id", order: { id: :desc } t.index ["project_id", "iid"], name: "index_deployments_on_project_id_and_iid", unique: true + t.index ["project_id", "ref"], name: "index_deployments_on_project_id_and_ref" t.index ["project_id", "status", "created_at"], name: "index_deployments_on_project_id_and_status_and_created_at" t.index ["project_id", "status"], name: "index_deployments_on_project_id_and_status" t.index ["project_id", "updated_at", "id"], name: "index_deployments_on_project_id_and_updated_at_and_id", order: { updated_at: :desc, id: :desc } + t.index ["project_id"], name: "partial_index_deployments_for_project_id_and_tag", where: "(tag IS TRUE)" end create_table "description_versions", force: :cascade do |t| diff --git a/doc/raketasks/generate_sample_prometheus_data.md b/doc/raketasks/generate_sample_prometheus_data.md new file mode 100644 index 00000000000..2489a2c2ad3 --- /dev/null +++ b/doc/raketasks/generate_sample_prometheus_data.md @@ -0,0 +1,16 @@ +# Generate Sample Prometheus Data + +This command will run Prometheus queries for each of the metrics of a specific environment +for a default time interval of 7 days ago to now. The results of each of query are stored +under a `sample_metrics` directory as a yaml file named by the metric's `identifier`. +When the environmental variable `USE_SAMPLE_METRICS` is set, the Prometheus API query is +re-routed to `Projects::Environments::SampleMetricsController` which loads the appropriate +data set if it is present within the `sample_metrics` directory. + +- This command requires an id from an Environment with an available Prometheus installation. + +**Example:** + +``` +bundle exec rake gitlab:generate_sample_prometheus_data[21] +``` diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index de8aea6e468..3b7309ea7e4 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -476,7 +476,7 @@ Prometheus server. > [Introduced][ce-29691] in GitLab 12.2. -It is possible to display metrics charts within [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm). +It is possible to display metrics charts within [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm). The maximum number of embeds allowed in a GitLab Flavored Markdown field is 100. NOTE: **Note:** Requires [Kubernetes](prometheus_library/kubernetes.md) metrics. diff --git a/lib/banzai/filter/inline_metrics_redactor_filter.rb b/lib/banzai/filter/inline_metrics_redactor_filter.rb index e84ba83e03e..c70897fccbf 100644 --- a/lib/banzai/filter/inline_metrics_redactor_filter.rb +++ b/lib/banzai/filter/inline_metrics_redactor_filter.rb @@ -8,6 +8,7 @@ module Banzai include Gitlab::Utils::StrongMemoize METRICS_CSS_CLASS = '.js-render-metrics' + EMBED_LIMIT = 100 URL = Gitlab::Metrics::Dashboard::Url Embed = Struct.new(:project_path, :permission) @@ -35,9 +36,16 @@ module Banzai # Returns all nodes which the FE will identify as # a metrics embed placeholder element # + # Removes any nodes beyond the first 100 + # # @return [Nokogiri::XML::NodeSet] def nodes - @nodes ||= doc.css(METRICS_CSS_CLASS) + strong_memoize(:nodes) do + nodes = doc.css(METRICS_CSS_CLASS) + nodes.drop(EMBED_LIMIT).each(&:remove) + + nodes + end end # Maps a node to key properties of an embed. diff --git a/lib/gitlab/diff/file_collection/base.rb b/lib/gitlab/diff/file_collection/base.rb index b1f171c0e7d..38b636e4e5a 100644 --- a/lib/gitlab/diff/file_collection/base.rb +++ b/lib/gitlab/diff/file_collection/base.rb @@ -8,7 +8,7 @@ module Gitlab attr_reader :project, :diff_options, :diff_refs, :fallback_diff_refs, :diffable - delegate :count, :size, :real_size, to: :diff_files + delegate :count, :size, :real_size, to: :raw_diff_files def self.default_options ::Commit.max_diff_options.merge(ignore_whitespace_change: false, expanded: false, include_stats: true) diff --git a/lib/gitlab/kubernetes/helm/client_command.rb b/lib/gitlab/kubernetes/helm/client_command.rb index df3f35d1075..b953ce24c4a 100644 --- a/lib/gitlab/kubernetes/helm/client_command.rb +++ b/lib/gitlab/kubernetes/helm/client_command.rb @@ -43,6 +43,10 @@ module Gitlab optional_tls_flags end + def repository_update_command + 'helm repo update' + end + def optional_tls_flags return [] unless files.key?(:'ca.pem') diff --git a/lib/gitlab/kubernetes/helm/install_command.rb b/lib/gitlab/kubernetes/helm/install_command.rb index 82f6f5d0024..8e24cb4c24f 100644 --- a/lib/gitlab/kubernetes/helm/install_command.rb +++ b/lib/gitlab/kubernetes/helm/install_command.rb @@ -39,10 +39,6 @@ module Gitlab private - def repository_update_command - 'helm repo update' - end - # Uses `helm upgrade --install` which means we can use this for both # installation and uprade of applications def install_command diff --git a/lib/gitlab/kubernetes/helm/patch_command.rb b/lib/gitlab/kubernetes/helm/patch_command.rb new file mode 100644 index 00000000000..ed7a5c2b2d6 --- /dev/null +++ b/lib/gitlab/kubernetes/helm/patch_command.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +# PatchCommand is for updating values in installed charts without overwriting +# existing values. +module Gitlab + module Kubernetes + module Helm + class PatchCommand + include BaseCommand + include ClientCommand + + attr_reader :name, :files, :chart, :repository + attr_accessor :version + + def initialize(name:, chart:, files:, rbac:, version:, repository: nil) + # version is mandatory to prevent chart mismatches + # we do not want our values interpreted in the context of the wrong version + raise ArgumentError, 'version is required' if version.blank? + + @name = name + @chart = chart + @version = version + @rbac = rbac + @files = files + @repository = repository + end + + def generate_script + super + [ + init_command, + wait_for_tiller_command, + repository_command, + repository_update_command, + upgrade_command + ].compact.join("\n") + end + + def rbac? + @rbac + end + + private + + def upgrade_command + command = ['helm', 'upgrade', name, chart] + + reuse_values_flag + + tls_flags_if_remote_tiller + + version_flag + + namespace_flag + + value_flag + + command.shelljoin + end + + def reuse_values_flag + ['--reuse-values'] + end + + def value_flag + ['-f', "/data/helm/#{name}/config/values.yaml"] + end + + def namespace_flag + ['--namespace', Gitlab::Kubernetes::Helm::NAMESPACE] + end + + def version_flag + ['--version', version] + end + end + end + end +end diff --git a/lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb b/lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb index c00ef208848..4f5e9a98799 100644 --- a/lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb +++ b/lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb @@ -16,12 +16,20 @@ module Gitlab private def endpoint_for_metric(metric) - Gitlab::Routing.url_helpers.prometheus_api_project_environment_path( - project, - params[:environment], - proxy_path: query_type(metric), - query: query_for_metric(metric) - ) + if ENV['USE_SAMPLE_METRICS'] + Gitlab::Routing.url_helpers.sample_metrics_project_environment_path( + project, + params[:environment], + identifier: metric[:id] + ) + else + Gitlab::Routing.url_helpers.prometheus_api_project_environment_path( + project, + params[:environment], + proxy_path: query_type(metric), + query: query_for_metric(metric) + ) + end end def query_type(metric) diff --git a/lib/tasks/gitlab/generate_sample_prometheus_data.rake b/lib/tasks/gitlab/generate_sample_prometheus_data.rake new file mode 100644 index 00000000000..a988494ca61 --- /dev/null +++ b/lib/tasks/gitlab/generate_sample_prometheus_data.rake @@ -0,0 +1,20 @@ +namespace :gitlab do + desc "GitLab | Generate Sample Prometheus Data" + task :generate_sample_prometheus_data, [:environment_id] => :gitlab_environment do |_, args| + environment = Environment.find(args[:environment_id]) + metrics = PrometheusMetric.where(project_id: [environment.project.id, nil]) + query_variables = Gitlab::Prometheus::QueryVariables.call(environment) + + sample_metrics_directory_name = Metrics::SampleMetricsService::DIRECTORY + FileUtils.mkdir_p(sample_metrics_directory_name) + + metrics.each do |metric| + query = metric.query % query_variables + result = environment.prometheus_adapter.prometheus_client.query_range(query, start: 7.days.ago) + + next unless metric.identifier + + File.write("#{sample_metrics_directory_name}/#{metric.identifier}.yml", result.to_yaml) + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d6e15fba348..86f838f0eb9 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -10318,6 +10318,9 @@ msgstr "" msgid "Learn GitLab" msgstr "" +msgid "Learn More" +msgstr "" + msgid "Learn how to %{link_start}contribute to the built-in templates%{link_end}" msgstr "" @@ -18388,7 +18391,19 @@ msgstr "" msgid "ThreatMonitoring|A Web Application Firewall (WAF) provides monitoring and rules to protect production applications. GitLab adds the modsecurity WAF plug-in when you install the Ingress app in your Kubernetes cluster." msgstr "" -msgid "ThreatMonitoring|The graph below is an overview of traffic coming to your application. View the documentation for instructions on how to access the WAF logs to see what type of malicious traffic is trying to access your app." +msgid "ThreatMonitoring|At this time, threat monitoring only supports WAF data." +msgstr "" + +msgid "ThreatMonitoring|Environment" +msgstr "" + +msgid "ThreatMonitoring|Something went wrong, unable to fetch WAF statistics" +msgstr "" + +msgid "ThreatMonitoring|Something went wrong, unable to fetch environments" +msgstr "" + +msgid "ThreatMonitoring|The graph below is an overview of traffic coming to your application as tracked by the Web Application Firewall (WAF). View the docs for instructions on how to access the WAF logs to see what type of malicious traffic is trying to access your app. The docs link is also accessible by clicking the \"?\" icon next to the title below." msgstr "" msgid "ThreatMonitoring|Threat Monitoring" @@ -18397,6 +18412,9 @@ msgstr "" msgid "ThreatMonitoring|Threat Monitoring help page link" msgstr "" +msgid "ThreatMonitoring|View WAF documentation" +msgstr "" + msgid "ThreatMonitoring|Web Application Firewall not enabled" msgstr "" diff --git a/package.json b/package.json index da69960107d..e60ae6d5a80 100644 --- a/package.json +++ b/package.json @@ -159,7 +159,7 @@ "eslint-import-resolver-webpack": "^0.10.1", "eslint-plugin-jasmine": "^2.10.1", "eslint-plugin-jest": "^22.3.0", - "eslint-plugin-no-jquery": "^2.1.0", + "eslint-plugin-no-jquery": "^2.3.0", "gettext-extractor": "^3.4.3", "gettext-extractor-vue": "^4.0.2", "graphql-tag": "^2.10.0", diff --git a/spec/controllers/projects/environments/sample_metrics_controller_spec.rb b/spec/controllers/projects/environments/sample_metrics_controller_spec.rb new file mode 100644 index 00000000000..4faa3ecb567 --- /dev/null +++ b/spec/controllers/projects/environments/sample_metrics_controller_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::Environments::SampleMetricsController do + include StubENV + + let_it_be(:project) { create(:project) } + let_it_be(:environment) { create(:environment, project: project) } + let_it_be(:user) { create(:user) } + + before(:context) do + RSpec::Mocks.with_temporary_scope do + stub_env('USE_SAMPLE_METRICS', 'true') + Rails.application.reload_routes! + end + end + + after(:context) do + Rails.application.reload_routes! + end + + before do + project.add_reporter(user) + sign_in(user) + end + + describe 'GET #query' do + context 'when the file is not found' do + before do + get :query, params: environment_params + end + + it 'returns a 404' do + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when the sample data is found' do + before do + allow_next_instance_of(Metrics::SampleMetricsService) do |service| + allow(service).to receive(:query).and_return([]) + end + get :query, params: environment_params + end + + it 'returns JSON with a message and a 200 status code' do + expect(json_response.keys).to contain_exactly('status', 'data') + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + private + + def environment_params(params = {}) + { + id: environment.id.to_s, + namespace_id: project.namespace.full_path, + project_id: project.name, + identifier: 'sample_metric_query_result' + }.merge(params) + end +end diff --git a/spec/fixtures/gitlab/sample_metrics/sample_metric_query_result.yml b/spec/fixtures/gitlab/sample_metrics/sample_metric_query_result.yml new file mode 100644 index 00000000000..ba074912b3b --- /dev/null +++ b/spec/fixtures/gitlab/sample_metrics/sample_metric_query_result.yml @@ -0,0 +1,151 @@ +--- +- metric: {} + values: + - - 1573560714.209 + - '0.02361297607421875' + - - 1573560774.209 + - '0.02361297607421875' + - - 1573560834.209 + - '0.02362823486328125' + - - 1573560894.209 + - '0.02361297607421875' + - - 1573560954.209 + - '0.02385711669921875' + - - 1573561014.209 + - '0.02361297607421875' + - - 1573561074.209 + - '0.02361297607421875' + - - 1573561134.209 + - '0.02362060546875' + - - 1573561194.209 + - '0.02362060546875' + - - 1573561254.209 + - '0.02362060546875' + - - 1573561314.209 + - '0.02362060546875' + - - 1573561374.209 + - '0.023624420166015625' + - - 1573561434.209 + - '0.023651123046875' + - - 1573561494.209 + - '0.02362060546875' + - - 1573561554.209 + - '0.0236358642578125' + - - 1573561614.209 + - '0.02362060546875' + - - 1573561674.209 + - '0.02362060546875' + - - 1573561734.209 + - '0.02362060546875' + - - 1573561794.209 + - '0.02362060546875' + - - 1573561854.209 + - '0.02362060546875' + - - 1573561914.209 + - '0.023651123046875' + - - 1573561974.209 + - '0.02362060546875' + - - 1573562034.209 + - '0.02362060546875' + - - 1573562094.209 + - '0.02362060546875' + - - 1573562154.209 + - '0.02362060546875' + - - 1573562214.209 + - '0.023624420166015625' + - - 1573562274.209 + - '0.02362060546875' + - - 1573562334.209 + - '0.023868560791015625' + - - 1573562394.209 + - '0.02374267578125' + - - 1573562454.209 + - '0.02362060546875' + - - 1573562514.209 + - '0.02362060546875' + - - 1573562574.209 + - '0.02362060546875' + - - 1573562634.209 + - '0.02362060546875' + - - 1573562694.209 + - '0.023639678955078125' + - - 1573562754.209 + - '0.0236358642578125' + - - 1573562814.209 + - '0.02362060546875' + - - 1573562874.209 + - '0.0236358642578125' + - - 1573562934.209 + - '0.023651123046875' + - - 1573562994.209 + - '0.02362060546875' + - - 1573563054.209 + - '0.023624420166015625' + - - 1573563114.209 + - '0.02362060546875' + - - 1573563174.209 + - '0.02362060546875' + - - 1573563234.209 + - '0.02362060546875' + - - 1573563294.209 + - '0.02362060546875' + - - 1573563354.209 + - '0.02362060546875' + - - 1573563414.209 + - '0.023651123046875' + - - 1573563474.209 + - '0.023651123046875' + - - 1573563534.209 + - '0.023651123046875' + - - 1573563594.209 + - '0.023773193359375' + - - 1573563654.209 + - '0.023681640625' + - - 1573563714.209 + - '0.023895263671875' + - - 1573563774.209 + - '0.023651123046875' + - - 1573563834.209 + - '0.023651123046875' + - - 1573563894.209 + - '0.023651123046875' + - - 1573563954.209 + - '0.0236663818359375' + - - 1573564014.209 + - '0.023651123046875' + - - 1573564074.209 + - '0.023681640625' + - - 1573564134.209 + - '0.0236663818359375' + - - 1573564194.209 + - '0.0236663818359375' + - - 1573564254.209 + - '0.023651123046875' + - - 1573564314.209 + - '0.023651123046875' + - - 1573564374.209 + - '0.023651123046875' + - - 1573564434.209 + - '0.023773193359375' + - - 1573564494.209 + - '0.023651123046875' + - - 1573564554.209 + - '0.023681640625' + - - 1573564614.209 + - '0.023773193359375' + - - 1573564674.209 + - '0.023651123046875' + - - 1573564734.209 + - '0.023651123046875' + - - 1573564794.209 + - '0.023651123046875' + - - 1573564854.209 + - '0.023651123046875' + - - 1573564914.209 + - '0.023651123046875' + - - 1573564974.209 + - '0.023651123046875' + - - 1573565034.209 + - '0.023651123046875' + - - 1573565094.209 + - '0.023895263671875'
\ No newline at end of file diff --git a/spec/frontend/create_cluster/init_create_cluster_spec.js b/spec/frontend/create_cluster/init_create_cluster_spec.js index e7b9a7adde4..1fdcb57492d 100644 --- a/spec/frontend/create_cluster/init_create_cluster_spec.js +++ b/spec/frontend/create_cluster/init_create_cluster_spec.js @@ -3,6 +3,11 @@ import initGkeDropdowns from '~/create_cluster/gke_cluster'; import initGkeNamespace from '~/create_cluster/gke_cluster_namespace'; import PersistentUserCallout from '~/persistent_user_callout'; +// This import is loaded dynamically in `init_create_cluster`. +// Let's eager import it here so that the first spec doesn't timeout. +// https://gitlab.com/gitlab-org/gitlab/issues/118499 +import '~/create_cluster/eks_cluster'; + jest.mock('~/create_cluster/gke_cluster', () => jest.fn()); jest.mock('~/create_cluster/gke_cluster_namespace', () => jest.fn()); jest.mock('~/persistent_user_callout', () => ({ @@ -20,10 +25,9 @@ describe('initCreateCluster', () => { }; gon = { features: {} }; }); + afterEach(() => { - initGkeDropdowns.mockReset(); - initGkeNamespace.mockReset(); - PersistentUserCallout.factory.mockReset(); + jest.clearAllMocks(); }); describe.each` diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index def3078c652..7f988c60817 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -259,7 +259,7 @@ describe DiffHelper do end context '#render_overflow_warning?' do - let(:diffs_collection) { instance_double(Gitlab::Diff::FileCollection::MergeRequestDiff, diff_files: diff_files) } + let(:diffs_collection) { instance_double(Gitlab::Diff::FileCollection::MergeRequestDiff, raw_diff_files: diff_files) } let(:diff_files) { Gitlab::Git::DiffCollection.new(files) } let(:safe_file) { { too_large: false, diff: '' } } let(:large_file) { { too_large: true, diff: '' } } diff --git a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb index 745b9133529..e2615ea5069 100644 --- a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb +++ b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb @@ -55,11 +55,29 @@ describe Banzai::Filter::InlineMetricsRedactorFilter do it_behaves_like 'a supported metrics dashboard url' end - context 'for an internal non-dashboard url' do - let(:url) { urls.project_url(project) } + context 'the user has requisite permissions' do + let(:user) { create(:user) } + let(:doc) { filter(input, current_user: user) } - it 'leaves the placeholder' do - expect(doc.to_s).to be_empty + before do + project.add_maintainer(user) + end + + context 'for an internal non-dashboard url' do + let(:url) { urls.project_url(project) } + + it 'leaves the placeholder' do + expect(doc.to_s).to be_empty + end + end + + context 'with over 100 embeds' do + let(:embed) { %(<div class="js-render-metrics" data-dashboard-url="#{url}"></div>) } + let(:input) { embed * 150 } + + it 'redacts ill-advised embeds' do + expect(doc.to_s.length).to eq(embed.length * 100) + end end end end diff --git a/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb new file mode 100644 index 00000000000..064efebdb96 --- /dev/null +++ b/spec/lib/gitlab/kubernetes/helm/patch_command_spec.rb @@ -0,0 +1,218 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Kubernetes::Helm::PatchCommand do + let(:files) { { 'ca.pem': 'some file content' } } + let(:repository) { 'https://repository.example.com' } + let(:rbac) { false } + let(:version) { '1.2.3' } + + subject(:patch_command) do + described_class.new( + name: 'app-name', + chart: 'chart-name', + rbac: rbac, + files: files, + version: version, + repository: repository + ) + end + + context 'when local tiller feature is disabled' do + before do + stub_feature_flags(managed_apps_local_tiller: false) + end + + let(:tls_flags) do + <<~EOS.squish + --tls + --tls-ca-cert /data/helm/app-name/config/ca.pem + --tls-cert /data/helm/app-name/config/cert.pem + --tls-key /data/helm/app-name/config/key.pem + EOS + end + + it_behaves_like 'helm commands' do + let(:commands) do + <<~EOS + helm init --upgrade + for i in $(seq 1 30); do helm version #{tls_flags} && s=0 && break || s=$?; sleep 1s; echo \"Retrying ($i)...\"; done; (exit $s) + helm repo add app-name https://repository.example.com + helm repo update + #{helm_upgrade_comand} + EOS + end + + let(:helm_upgrade_comand) do + <<~EOS.squish + helm upgrade app-name chart-name + --reuse-values + #{tls_flags} + --version 1.2.3 + --namespace gitlab-managed-apps + -f /data/helm/app-name/config/values.yaml + EOS + end + end + end + + it_behaves_like 'helm commands' do + let(:commands) do + <<~EOS + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + helm init --client-only + helm repo add app-name https://repository.example.com + helm repo update + #{helm_upgrade_comand} + EOS + end + + let(:helm_upgrade_comand) do + <<~EOS.squish + helm upgrade app-name chart-name + --reuse-values + --version 1.2.3 + --namespace gitlab-managed-apps + -f /data/helm/app-name/config/values.yaml + EOS + end + end + + context 'when rbac is true' do + let(:rbac) { true } + + it_behaves_like 'helm commands' do + let(:commands) do + <<~EOS + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + helm init --client-only + helm repo add app-name https://repository.example.com + helm repo update + #{helm_upgrade_command} + EOS + end + + let(:helm_upgrade_command) do + <<~EOS.squish + helm upgrade app-name chart-name + --reuse-values + --version 1.2.3 + --namespace gitlab-managed-apps + -f /data/helm/app-name/config/values.yaml + EOS + end + end + end + + context 'when there is no ca.pem file' do + let(:files) { { 'file.txt': 'some content' } } + + it_behaves_like 'helm commands' do + let(:commands) do + <<~EOS + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + helm init --client-only + helm repo add app-name https://repository.example.com + helm repo update + #{helm_upgrade_command} + EOS + end + + let(:helm_upgrade_command) do + <<~EOS.squish + helm upgrade app-name chart-name + --reuse-values + --version 1.2.3 + --namespace gitlab-managed-apps + -f /data/helm/app-name/config/values.yaml + EOS + end + end + end + + describe '#pod_name' do + subject { patch_command.pod_name } + + it { is_expected.to eq 'install-app-name' } + end + + context 'when there is no version' do + let(:version) { nil } + + it { expect { patch_command }.to raise_error(ArgumentError, 'version is required') } + end + + describe '#rbac?' do + subject { patch_command.rbac? } + + context 'rbac is enabled' do + let(:rbac) { true } + + it { is_expected.to be_truthy } + end + + context 'rbac is not enabled' do + let(:rbac) { false } + + it { is_expected.to be_falsey } + end + end + + describe '#pod_resource' do + subject { patch_command.pod_resource } + + context 'rbac is enabled' do + let(:rbac) { true } + + it 'generates a pod that uses the tiller serviceAccountName' do + expect(subject.spec.serviceAccountName).to eq('tiller') + end + end + + context 'rbac is not enabled' do + let(:rbac) { false } + + it 'generates a pod that uses the default serviceAccountName' do + expect(subject.spec.serviceAcccountName).to be_nil + end + end + end + + describe '#config_map_resource' do + let(:metadata) do + { + name: "values-content-configuration-app-name", + namespace: 'gitlab-managed-apps', + labels: { name: "values-content-configuration-app-name" } + } + end + + let(:resource) { ::Kubeclient::Resource.new(metadata: metadata, data: files) } + + subject { patch_command.config_map_resource } + + it 'returns a KubeClient resource with config map content for the application' do + is_expected.to eq(resource) + end + end + + describe '#service_account_resource' do + subject { patch_command.service_account_resource } + + it 'returns nothing' do + is_expected.to be_nil + end + end + + describe '#cluster_role_binding_resource' do + subject { patch_command.cluster_role_binding_resource } + + it 'returns nothing' do + is_expected.to be_nil + end + end +end diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index ce4d817b1d7..d588ce3bc38 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -206,21 +206,19 @@ describe Clusters::Applications::Prometheus do end end - describe '#upgrade_command' do + describe '#patch_command' do + subject(:patch_command) { prometheus.patch_command(values) } + let(:prometheus) { build(:clusters_applications_prometheus) } let(:values) { prometheus.values } - it 'returns an instance of Gitlab::Kubernetes::Helm::InstallCommand' do - expect(prometheus.upgrade_command(values)).to be_an_instance_of(::Gitlab::Kubernetes::Helm::InstallCommand) - end + it { is_expected.to be_an_instance_of(::Gitlab::Kubernetes::Helm::PatchCommand) } it 'is initialized with 3 arguments' do - command = prometheus.upgrade_command(values) - - expect(command.name).to eq('prometheus') - expect(command.chart).to eq('stable/prometheus') - expect(command.version).to eq('6.7.3') - expect(command.files).to eq(prometheus.files) + expect(patch_command.name).to eq('prometheus') + expect(patch_command.chart).to eq('stable/prometheus') + expect(patch_command.version).to eq('6.7.3') + expect(patch_command.files).to eq(prometheus.files) end end diff --git a/spec/serializers/diffs_metadata_entity_spec.rb b/spec/serializers/diffs_metadata_entity_spec.rb index aaca393ec27..0fa643d37b3 100644 --- a/spec/serializers/diffs_metadata_entity_spec.rb +++ b/spec/serializers/diffs_metadata_entity_spec.rb @@ -36,8 +36,16 @@ describe DiffsMetadataEntity do describe 'diff_files' do it 'returns diff files metadata' do - payload = - DiffFileMetadataEntity.represent(merge_request_diff.diffs.diff_files).as_json + raw_diff_files = merge_request_diff.diffs.raw_diff_files + + expect_next_instance_of(Gitlab::Diff::FileCollection::MergeRequestDiff) do |instance| + # Use lightweight version instead. Several methods delegate to it, so putting a 5 + # calls limit. + expect(instance).to receive(:raw_diff_files).at_most(5).times.and_call_original + expect(instance).not_to receive(:diff_files) + end + + payload = DiffFileMetadataEntity.represent(raw_diff_files).as_json expect(subject[:diff_files]).to eq(payload) end diff --git a/spec/services/metrics/sample_metrics_service_spec.rb b/spec/services/metrics/sample_metrics_service_spec.rb new file mode 100644 index 00000000000..8574674ebc4 --- /dev/null +++ b/spec/services/metrics/sample_metrics_service_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Metrics::SampleMetricsService do + describe 'query' do + subject { described_class.new(identifier).query } + + context 'when the file is not found' do + let(:identifier) { nil } + + it { is_expected.to be_nil } + end + + context 'when the file is found' do + let(:identifier) { 'sample_metric_query_result' } + let(:source) { File.join(Rails.root, 'spec/fixtures/gitlab/sample_metrics', "#{identifier}.yml") } + let(:destination) { File.join(Rails.root, Metrics::SampleMetricsService::DIRECTORY, "#{identifier}.yml") } + + around do |example| + FileUtils.mkdir_p(Metrics::SampleMetricsService::DIRECTORY) + FileUtils.cp(source, destination) + + example.run + ensure + FileUtils.rm(destination) + end + + subject { described_class.new(identifier).query } + + it 'loads data from the sample file correctly' do + expect(subject).to eq(YAML.load_file(source)) + end + end + + context 'when the identifier is for a path outside of sample_metrics' do + let(:identifier) { '../config/secrets' } + + it { is_expected.to be_nil } + end + end +end diff --git a/spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb b/spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb new file mode 100644 index 00000000000..72e61f5c524 --- /dev/null +++ b/spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'rake_helper' + +describe 'gitlab:generate_sample_prometheus_data rake task' do + let(:cluster) { create(:cluster, :provided_by_user, :project) } + let(:environment) { create(:environment, project: cluster.project) } + let(:sample_query_file) { File.join(Rails.root, Metrics::SampleMetricsService::DIRECTORY, 'test_query_result.yml') } + let!(:metric) { create(:prometheus_metric, project: cluster.project, identifier: 'test_query_result') } + + around do |example| + example.run + ensure + FileUtils.rm(sample_query_file) + end + + it 'creates the file correctly' do + Rake.application.rake_require 'tasks/gitlab/generate_sample_prometheus_data' + allow(Environment).to receive(:find).and_return(environment) + allow(environment).to receive_message_chain(:prometheus_adapter, :prometheus_client, :query_range) { sample_query_result } + run_rake_task('gitlab:generate_sample_prometheus_data', [environment.id]) + + expect(File.exist?(sample_query_file)).to be true + + query_file_content = YAML.load_file(sample_query_file) + + expect(query_file_content).to eq(sample_query_result) + end +end + +def sample_query_result + file = File.join(Rails.root, 'spec/fixtures/gitlab/sample_metrics', 'sample_metric_query_result.yml') + YAML.load_file(File.expand_path(file, __dir__)) +end diff --git a/yarn.lock b/yarn.lock index d20fef4bb6a..fbc6733fea4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4270,10 +4270,10 @@ eslint-plugin-jest@^22.3.0: resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.3.0.tgz#a10f10dedfc92def774ec9bb5bfbd2fb8e1c96d2" integrity sha512-P1mYVRNlOEoO5T9yTqOfucjOYf1ktmJ26NjwjH8sxpCFQa6IhBGr5TpKl3hcAAT29hOsRJVuMWmTsHoUVo9FoA== -eslint-plugin-no-jquery@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.1.0.tgz#d03b74224c5cfbc7fc0bdd12ce4eb400d09e0c0b" - integrity sha512-5sr5tOJRfuRviyAvFTe/mr80TXWxTteD/JHRuJtDN8q/bxAh16eSKoKLAevLC7wZCRN2iwnEfhQPQV4rp/gYtg== +eslint-plugin-no-jquery@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.3.0.tgz#fccdad84afa61baa4c0527dd6249cdcbfa0f74a8" + integrity sha512-XQQZM5yKO72Y8QAojNhH8oYLnLZU34FovNHVoJlPLBuBPJk0kkiPNOS/K6wRFbVgn47iZHsT6E+7mSLwbcQEsg== eslint-plugin-promise@^4.1.1: version "4.1.1" |