提交 69d6d3ca 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 01fdcf49
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
......
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)
......
......@@ -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
......
......@@ -222,6 +222,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
// eslint-disable-next-line no-jquery/no-ajax-events
$(document).ajaxError((e, xhrObj) => {
const ref = xhrObj.status;
......
/* 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);
......
# 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
......@@ -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)
......
......@@ -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)
......
......@@ -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
......
......@@ -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
......@@ -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
......
# 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
---
title: Add protected branch permission check to run downstream pipelines
merge_request: 20964
author:
type: fixed
---
title: Genereate a set of sample prometheus metrics and route to the sample metrics
when enabled
merge_request: 19987
author:
type: added
---
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
---
title: Add indexes on deployments to improve environments search
merge_request: 21789
author:
type: performance
---
title: Fix Asana integration
merge_request: 21501
author:
type: fixed
---
title: Fix error in updating runner session
merge_request: 20902
author:
type: fixed
---
title: Upgrade Akismet gem to v3.0.0
merge_request: 21786
author:
type: security
---
title: Limit max metrics embeds in GFM to 100
merge_request: 21356
author:
type: performance
......@@ -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
......
# 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
......@@ -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|
......
# 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]
```
......@@ -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.
......
......@@ -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.
......
......@@ -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)
......
......@@ -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')
......
......@@ -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
......
# 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
......@@ -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)
......
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
......@@ -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 ""
......
# 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
---
- 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
......@@ -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`
......
......@@ -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: '' } }
......
......@@ -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
......
# 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
......@@ -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
......
......@@ -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
......
# 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
# 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
......@@ -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"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册