Commit 7ebcead8 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent f1a40d0d
......@@ -9,6 +9,7 @@ import GitignoreSelector from './template_selectors/gitignore_selector';
import LicenseSelector from './template_selectors/license_selector';
import toast from '~/vue_shared/plugins/global_toast';
import { __ } from '~/locale';
import initPopover from '~/blob/suggest_gitlab_ci_yml';
export default class FileTemplateMediator {
constructor({ editor, currentAction, projectId }) {
......@@ -128,6 +129,7 @@ export default class FileTemplateMediator {
selectTemplateFile(selector, query, data) {
const self = this;
const { name } = selector.config;
const suggestCommitChanges = document.querySelector('.js-suggest-gitlab-ci-yml-commit-changes');
selector.renderLoading();
......@@ -146,6 +148,10 @@ export default class FileTemplateMediator {
},
},
});
if (suggestCommitChanges) {
initPopover(suggestCommitChanges);
}
})
.catch(err => new Flash(`An error occurred while fetching the template: ${err}`));
}
......
<script>
import { GlPopover, GlSprintf, GlButton, GlIcon } from '@gitlab/ui';
import Cookies from 'js-cookie';
import { parseBoolean } from '~/lib/utils/common_utils';
import { parseBoolean, scrollToElement } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import { glEmojiTag } from '~/emoji';
const popoverStates = {
suggest_gitlab_ci_yml: {
title: s__(`suggestPipeline|1/2: Choose a template`),
content: s__(
`suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}`,
),
emoji: glEmojiTag('wave'),
},
suggest_commit_first_project_gitlab_ci_yml: {
title: s__(`suggestPipeline|2/2: Commit your changes`),
content: s__(
`suggestPipeline|Commit the changes and your pipeline will automatically run for the first time.`,
),
},
};
export default {
components: {
GlPopover,
......@@ -17,7 +32,7 @@ export default {
type: String,
required: true,
},
cssClass: {
trackLabel: {
type: String,
required: true,
},
......@@ -33,17 +48,19 @@ export default {
},
computed: {
suggestTitle() {
return s__(`suggestPipeline|1/2: Choose a template`);
return popoverStates[this.trackLabel].title || '';
},
suggestContent() {
return s__(
`suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}`,
);
return popoverStates[this.trackLabel].content || '';
},
emoji() {
return glEmojiTag('wave');
return popoverStates[this.trackLabel].emoji || '';
},
},
mounted() {
if (this.trackLabel === 'suggest_commit_first_project_gitlab_ci_yml' && !this.popoverDismissed)
scrollToElement(document.querySelector(this.target));
},
methods: {
onDismiss() {
this.popoverDismissed = true;
......@@ -61,13 +78,15 @@ export default {
placement="rightbottom"
trigger="manual"
container="viewport"
:css-classes="[cssClass]"
:css-classes="['suggest-gitlab-ci-yml', 'ml-4']"
>
<template #title>
<gl-button :aria-label="__('Close')" class="btn-blank float-right" @click="onDismiss">
<gl-icon name="close" aria-hidden="true" />
</gl-button>
{{ suggestTitle }}
<span v-html="suggestTitle"></span>
<span class="ml-auto">
<gl-button :aria-label="__('Close')" class="btn-blank" @click="onDismiss">
<gl-icon name="close" aria-hidden="true" />
</gl-button>
</span>
</template>
<gl-sprintf :message="suggestContent">
......
......@@ -8,7 +8,7 @@ export default el =>
return createElement(Popover, {
props: {
target: el.dataset.target,
cssClass: el.dataset.cssClass,
trackLabel: el.dataset.trackLabel,
dismissKey: el.dataset.dismissKey,
},
});
......
......@@ -4,11 +4,13 @@ import $ from 'jquery';
import NewCommitForm from '../new_commit_form';
import EditBlob from './edit_blob';
import BlobFileDropzone from '../blob/blob_file_dropzone';
import initPopover from '~/blob/suggest_gitlab_ci_yml';
export default () => {
const editBlobForm = $('.js-edit-blob-form');
const uploadBlobForm = $('.js-upload-blob-form');
const deleteBlobForm = $('.js-delete-blob-form');
const suggestEl = document.querySelector('.js-suggest-gitlab-ci-yml');
if (editBlobForm.length) {
const urlRoot = editBlobForm.data('relativeUrlRoot');
......@@ -56,4 +58,8 @@ export default () => {
if (deleteBlobForm.length) {
new NewCommitForm(deleteBlobForm);
}
if (suggestEl) {
initPopover(suggestEl);
}
};
......@@ -95,7 +95,7 @@ export default {
<li :id="groupDomId" :class="rowClass" class="group-row" @click.stop="onClickRowGroup">
<div
:class="{ 'project-row-contents': !isGroup }"
class="group-row-contents d-flex align-items-center py-2"
class="group-row-contents d-flex align-items-center py-2 pr-3"
>
<div class="folder-toggle-wrap append-right-4 d-flex align-items-center">
<item-caret :is-group-open="group.isOpen" />
......@@ -103,8 +103,8 @@ export default {
</div>
<gl-loading-icon
v-if="group.isChildrenLoading"
size="md"
class="d-none d-sm-inline-flex flex-shrink-0 append-right-10"
size="lg"
class="d-none d-sm-inline-flex flex-shrink-0 append-right-8"
/>
<div
:class="{ 'd-sm-flex': !group.isChildrenLoading }"
......
......@@ -68,7 +68,7 @@ export default {
data-placement="bottom"
class="edit-group btn btn-xs no-expand"
>
<icon name="settings" class="position-top-0" />
<icon name="settings" class="position-top-0 align-middle" />
</a>
</div>
</template>
......@@ -144,9 +144,7 @@
.popover-header {
padding: $gl-padding;
.ic-close {
margin-top: -1em;
}
display: flex;
align-items: center;
}
}
......@@ -50,7 +50,7 @@ module Clusters
end
def helm_api
@helm_api ||= Gitlab::Kubernetes::Helm::Api.new(kubeclient)
@helm_api ||= Gitlab::Kubernetes::Helm::API.new(kubeclient)
end
def install_command
......
.form-actions
= button_tag 'Commit changes', class: 'btn commit-btn js-commit-button btn-success qa-commit-button'
= button_tag 'Commit changes', id: 'commit-changes', class: 'btn commit-btn js-commit-button btn-success qa-commit-button'
= link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message}
......
......@@ -20,7 +20,10 @@
required: true, class: 'form-control new-file-name js-file-path-name-input', value: params[:file_name] || (should_suggest_gitlab_ci_yml? ? '.gitlab-ci.yml' : '')
= render 'template_selectors'
- if should_suggest_gitlab_ci_yml?
= render partial: 'suggest_gitlab_ci_yml', locals: { target: '#gitlab-ci-yml-selector', dismiss_key: "suggest_gitlab_ci_yml_#{@project.id}" }
.js-suggest-gitlab-ci-yml{ data: { toggle: 'popover',
target: '#gitlab-ci-yml-selector',
track_label: 'suggest_gitlab_ci_yml',
dismiss_key: "suggest_gitlab_ci_yml_#{@project.id}" } }
.file-buttons
- if is_markdown
......
.js-suggest-gitlab-ci-yml{ data: { toggle: 'popover',
target: target,
css_class: 'suggest-gitlab-ci-yml ml-4',
dismiss_key: dismiss_key } }
......@@ -13,3 +13,8 @@
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
cancel_path: project_tree_path(@project, @id)
- if should_suggest_gitlab_ci_yml?
.js-suggest-gitlab-ci-yml-commit-changes{ data: { toggle: 'popover',
target: '#commit-changes',
track_label: 'suggest_commit_first_project_gitlab_ci_yml',
dismiss_key: "suggest_commit_first_project_gitlab_ci_yml_#{@project.id}" } }
---
title: Added a padding-right to items in subgroup list
merge_request: 26791
author:
type: fixed
# frozen_string_literal: true
class AddIndexServicesOnTemplate < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
# This migration is a corrective action to add the missing
# index_services_on_template index on staging.
def up
add_concurrent_index(:services, :template) unless index_exists?(:services, :template)
end
def down
# No reverse action as this is a corrective migration.
end
end
......@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_03_06_170531) do
ActiveRecord::Schema.define(version: 2020_03_09_105539) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
......
......@@ -91,7 +91,8 @@ and are as follows:
1. For `master` branch, create a pipeline (this includes on schedules, pushes, merges, etc.).
1. For tags, create a pipeline.
1. If `$GITLAB_INTERNAL` isn't set, don't create a pipeline.
1. For stable, auto-deploy, and security branches, create a pipeline.
1. For stable, auto-deploy, and security branches, create a pipeline.
1. For any other cases (e.g. when pushing a branch with no MR for it), no pipeline is created.
## `rules`, `if:` conditions and `changes:` patterns
......
......@@ -183,6 +183,7 @@ Continued reading:
- [Best Practices](best_practices.md)
- [Testing with feature flags](feature_flags.md)
- [Flows](flows.md)
- [RSpec metadata/tags](rspec_metadata_tests.md)
## Where can I ask for help?
......
# RSpec metadata for end-to-end tests
This is a partial list of the [RSpec metadata](https://relishapp.com/rspec/rspec-core/docs/metadata/user-defined-metadata)
(a.k.a. tags) that are used in our end-to-end tests.
<!-- Please keep the tags in alphabetical order -->
| Tag | Description |
|-|-|
| `:elasticsearch` | The test requires an Elasticsearch service. It is used by the [instance-level scenario](https://gitlab.com/gitlab-org/gitlab-qa#definitions) [`Test::Integration::Elasticsearch`](https://gitlab.com/gitlab-org/gitlab/-/blob/72b62b51bdf513e2936301cb6c7c91ec27c35b4d/qa/qa/ee/scenario/test/integration/elasticsearch.rb) to include only tests that require Elasticsearch. |
| `:orchestrated` | The GitLab instance under test may be [configured by `gitlab-qa`](https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/what_tests_can_be_run.md#orchestrated-tests) to be different to the default GitLab configuration, or `gitlab-qa` may launch additional services in separate docker containers, or both. Tests tagged with `:orchestrated` are excluded when testing environments where we can't dynamically modify GitLab's configuration (for example, Staging). |
| `:quarantine` | The test has been [quarantined](https://about.gitlab.com/handbook/engineering/quality/guidelines/debugging-qa-test-failures/#quarantining-tests), will run in a separate job that only includes quarantined tests, and is allowed to fail. The test will be skipped in its regular job so that if it fails it will not hold up the pipeline. |
| `:requires_admin` | The test requires an admin account. Tests with the tag are excluded when run against Canary and Production environments. |
......@@ -4,7 +4,7 @@ type: howto
# How to unlock a locked user from the command line
After six failed login attempts a user gets in a locked state.
After ten failed login attempts a user gets in a locked state.
To unlock a locked user:
......
......@@ -744,9 +744,9 @@ workers:
By default, all Kubernetes pods are
[non-isolated](https://kubernetes.io/docs/concepts/services-networking/network-policies/#isolated-and-non-isolated-pods),
and accept traffic from any source. You can use
meaning that they will accept traffic to and from any source. You can use
[NetworkPolicy](https://kubernetes.io/docs/concepts/services-networking/network-policies/)
to restrict connections to selected pods or namespaces.
to restrict connections to and from selected pods, namespaces, and the Internet.
NOTE: **Note:**
You must use a Kubernetes network plugin that implements support for
......@@ -767,7 +767,7 @@ networkPolicy:
The default policy deployed by the auto deploy pipeline will allow
traffic within a local namespace and from the `gitlab-managed-apps`
namespace. All other inbound connection will be blocked. Outbound
traffic is not affected by the default policy.
traffic (for example, to the Internet) is not affected by the default policy.
You can also provide a custom [policy specification](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.16/#networkpolicyspec-v1-networking-k8s-io)
via the `.gitlab/auto-deploy-values.yaml` file, for example:
......@@ -788,6 +788,9 @@ networkPolicy:
app.gitlab.com/managed_by: gitlab
```
For more information on how to install Network Policies, see
[Install Cilium using GitLab CI](../../user/clusters/applications.md#install-cilium-using-gitlab-ci).
#### Web Application Firewall (ModSecurity) customization
> [Introduced](https://gitlab.com/gitlab-org/charts/auto-deploy-app/-/merge_requests/44) in GitLab 12.8.
......
......@@ -3,7 +3,7 @@
module Gitlab
module Kubernetes
module Helm
class Api
class API
def initialize(kubeclient)
@kubeclient = kubeclient
@namespace = Gitlab::Kubernetes::Namespace.new(
......
# frozen_string_literal: true
module Gitlab
module SidekiqLogging
class DeduplicationLogger
include Singleton
include LogsJobs
def log(job, deduplication_type)
payload = parse_job(job)
payload['job_status'] = 'deduplicated'
payload['message'] = "#{base_message(payload)}: deduplicated: #{deduplication_type}"
payload['deduplication_type'] = deduplication_type
Sidekiq.logger.info payload
end
end
end
end
# frozen_string_literal: true
module Gitlab
module SidekiqLogging
module LogsJobs
def base_message(payload)
"#{payload['class']} JID-#{payload['jid']}"
end
def parse_job(job)
# Error information from the previous try is in the payload for
# displaying in the Sidekiq UI, but is very confusing in logs!
job = job.except('error_backtrace', 'error_class', 'error_message')
# Add process id params
job['pid'] = ::Process.pid
job.delete('args') unless ENV['SIDEKIQ_LOG_ARGUMENTS']
job['args'] = Gitlab::Utils::LogLimitedArray.log_limited_array(job['args'].map(&:to_s)) if job['args']
job
end
end
end
end
......@@ -6,6 +6,8 @@ require 'active_record/log_subscriber'
module Gitlab
module SidekiqLogging
class StructuredLogger
include LogsJobs
def call(job, queue)
started_time = get_time
base_payload = parse_job(job)
......@@ -24,10 +26,6 @@ module Gitlab
private
def base_message(payload)
"#{payload['class']} JID-#{payload['jid']}"
end
def add_instrumentation_keys!(job, output_payload)
output_payload.merge!(job.slice(*::Gitlab::InstrumentationHelper::KEYS))
end
......@@ -76,20 +74,6 @@ module Gitlab
payload['completed_at'] = Time.now.utc.to_f
end
def parse_job(job)
# Error information from the previous try is in the payload for
# displaying in the Sidekiq UI, but is very confusing in logs!
job = job.except('error_backtrace', 'error_class', 'error_message')
# Add process id params
job['pid'] = ::Process.pid
job.delete('args') unless ENV['SIDEKIQ_LOG_ARGUMENTS']
job['args'] = Gitlab::Utils::LogLimitedArray.log_limited_array(job['args'].map(&:to_s)) if job['args']
job
end
def elapsed(t0)
t1 = get_time
{
......
# frozen_string_literal: true
require 'digest'
module Gitlab
module SidekiqMiddleware
module DuplicateJobs
def self.drop_duplicates?
Feature.enabled?(:drop_duplicate_sidekiq_jobs)
end
end
end
end
......@@ -66,6 +66,10 @@ module Gitlab
jid != existing_jid
end
def droppable?
idempotent? && duplicate? && DuplicateJobs.drop_duplicates?
end
private
attr_reader :queue_name, :strategy, :job
......@@ -98,6 +102,14 @@ module Gitlab
def idempotency_string
"#{worker_class_name}:#{arguments.join('-')}"
end
def idempotent?
worker_class = worker_class_name.to_s.safe_constantize
return false unless worker_class
return false unless worker_class.respond_to?(:idempotent?)
worker_class.idempotent?
end
end
end
end
......
......@@ -17,6 +17,11 @@ module Gitlab
job['duplicate-of'] = duplicate_job.existing_jid
end
if duplicate_job.droppable?
Gitlab::SidekiqLogging::DeduplicationLogger.instance.log(job, "dropped until executing")
return false
end
yield
end
......
......@@ -24211,6 +24211,12 @@ msgstr ""
msgid "suggestPipeline|1/2: Choose a template"
msgstr ""
msgid "suggestPipeline|2/2: Commit your changes"
msgstr ""
msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time."
msgstr ""
msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}"
msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import Popover from '~/blob/suggest_gitlab_ci_yml/components/popover.vue';
import Cookies from 'js-cookie';
import * as utils from '~/lib/utils/common_utils';
const popoverTarget = 'gitlab-ci-yml-selector';
jest.mock('~/lib/utils/common_utils', () => ({
...jest.requireActual('~/lib/utils/common_utils'),
scrollToElement: jest.fn(),
}));
const target = 'gitlab-ci-yml-selector';
const dismissKey = 'suggest_gitlab_ci_yml_99';
const defaultTrackLabel = 'suggest_gitlab_ci_yml';
describe('Suggest gitlab-ci.yml Popover', () => {
let wrapper;
function createWrapper() {
function createWrapper(trackLabel) {
wrapper = shallowMount(Popover, {
propsData: {
target: popoverTarget,
cssClass: 'js-class',
target,
trackLabel,
dismissKey,
},
});
......@@ -25,7 +32,7 @@ describe('Suggest gitlab-ci.yml Popover', () => {
describe('when no dismiss cookie is set', () => {
beforeEach(() => {
createWrapper();
createWrapper(defaultTrackLabel);
});
it('sets popoverDismissed to false', () => {
......@@ -36,11 +43,26 @@ describe('Suggest gitlab-ci.yml Popover', () => {
describe('when the dismiss cookie is set', () => {
beforeEach(() => {
Cookies.set(dismissKey, true);
createWrapper();
createWrapper(defaultTrackLabel);
});
it('sets popoverDismissed to true', () => {
expect(wrapper.vm.popoverDismissed).toEqual(true);
});
beforeEach(() => {
Cookies.remove(dismissKey);
});
});
describe('when the popover is mounted with the trackLabel of the Confirm button popover at the bottom of the page', () => {
it('calls scrollToElement so that the Confirm button and popover will be in sight', () => {
const scrollToElementSpy = jest.spyOn(utils, 'scrollToElement');
const commitTrackLabel = 'suggest_commit_first_project_gitlab_ci_yml';
createWrapper(commitTrackLabel);
expect(scrollToElementSpy).toHaveBeenCalled();
});
});
});
import Vue from 'vue';
import Vuex from 'vuex';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { mount, createLocalVue } from '@vue/test-utils';
import TreeList from '~/diffs/components/tree_list.vue';
import createStore from '~/diffs/store/modules';
describe('Diffs tree list component', () => {
let Component;
let vm;
beforeAll(() => {
Component = Vue.extend(TreeList);
});
beforeEach(() => {
Vue.use(Vuex);
let wrapper;
const getFileRows = () => wrapper.findAll('.file-row');
const localVue = createLocalVue();
localVue.use(Vuex);
const createComponent = state => {
const store = new Vuex.Store({
modules: {
diffs: createStore(),
......@@ -22,26 +17,38 @@ describe('Diffs tree list component', () => {
});
// Setup initial state
store.state.diffs.addedLines = 10;
store.state.diffs.removedLines = 20;
store.state.diffs.diffFiles.push('test');
store.state.diffs = {
addedLines: 10,
removedLines: 20,
...store.state.diffs,
...state,
};
wrapper = mount(TreeList, {
store,
localVue,
propsData: { hideFileStats: false },
});
};
beforeEach(() => {
localStorage.removeItem('mr_diff_tree_list');
vm = mountComponentWithStore(Component, { store, props: { hideFileStats: false } });
createComponent();
});
afterEach(() => {
vm.$destroy();
wrapper.destroy();
});
it('renders empty text', () => {
expect(vm.$el.textContent).toContain('No files found');
expect(wrapper.text()).toContain('No files found');
});
describe('with files', () => {
beforeEach(done => {
Object.assign(vm.$store.state.diffs.treeEntries, {
beforeEach(() => {
const treeEntries = {
'index.js': {
addedLines: 0,
changed: true,
......@@ -62,64 +69,69 @@ describe('Diffs tree list component', () => {
type: 'tree',
tree: [],
},
};
createComponent({
treeEntries,
tree: [treeEntries['index.js'], treeEntries.app],
});
vm.$store.state.diffs.tree = [
vm.$store.state.diffs.treeEntries['index.js'],
vm.$store.state.diffs.treeEntries.app,
];
vm.$nextTick(done);
return wrapper.vm.$nextTick();
});
it('renders tree', () => {
expect(vm.$el.querySelectorAll('.file-row').length).toBe(2);
expect(vm.$el.querySelectorAll('.file-row')[0].textContent).toContain('index.js');
expect(vm.$el.querySelectorAll('.file-row')[1].textContent).toContain('app');
expect(getFileRows()).toHaveLength(2);
expect(
getFileRows()
.at(0)
.text(),
).toContain('index.js');
expect(
getFileRows()
.at(1)
.text(),
).toContain('app');
});
it('hides file stats', done => {
vm.hideFileStats = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.file-row-stats')).toBe(null);
it('hides file stats', () => {
wrapper.setProps({ hideFileStats: true });
done();
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find('.file-row-stats').exists()).toBe(false);
});
});
it('calls toggleTreeOpen when clicking folder', () => {
spyOn(vm.$store, 'dispatch').and.stub();
jest.spyOn(wrapper.vm.$store, 'dispatch').mockReturnValue(undefined);
vm.$el.querySelectorAll('.file-row')[1].click();
getFileRows()
.at(1)
.trigger('click');
expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/toggleTreeOpen', 'app');
expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('diffs/toggleTreeOpen', 'app');
});
it('calls scrollToFile when clicking blob', () => {
spyOn(vm.$store, 'dispatch').and.stub();
jest.spyOn(wrapper.vm.$store, 'dispatch').mockReturnValue(undefined);
vm.$el.querySelector('.file-row').click();
wrapper.find('.file-row').trigger('click');
expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/scrollToFile', 'app/index.js');
expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('diffs/scrollToFile', 'app/index.js');
});
it('renders as file list when renderTreeList is false', done => {
vm.$store.state.diffs.renderTreeList = false;
it('renders as file list when renderTreeList is false', () => {
wrapper.vm.$store.state.diffs.renderTreeList = false;
vm.$nextTick(() => {
expect(vm.$el.querySelectorAll('.file-row').length).toBe(1);
done();
return wrapper.vm.$nextTick().then(() => {
expect(getFileRows()).toHaveLength(1);
});
});
it('renders file paths when renderTreeList is false', done => {
vm.$store.state.diffs.renderTreeList = false;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.file-row').textContent).toContain('index.js');
it('renders file paths when renderTreeList is false', () => {
wrapper.vm.$store.state.diffs.renderTreeList = false;
done();
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find('.file-row').text()).toContain('index.js');
});
});
});
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe Gitlab::Kubernetes::Helm::Api do
describe Gitlab::Kubernetes::Helm::API do
let(:client) { double('kubernetes client') }
let(:helm) { described_class.new(client) }
let(:gitlab_namespace) { Gitlab::Kubernetes::Helm::NAMESPACE }
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::SidekiqLogging::DeduplicationLogger do
describe '#log_deduplication' do
let(:job) do
{
'class' => 'TestWorker',
'args' => [1234, 'hello', { 'key' => 'value' }],
'jid' => 'da883554ee4fe414012f5f42',
'correlation_id' => 'cid',
'duplicate-of' => 'other_jid'
}
end
it 'logs a deduplication message to the sidekiq logger' do
expected_payload = {
'job_status' => 'deduplicated',
'message' => "#{job['class']} JID-#{job['jid']}: deduplicated: a fancy strategy",
'deduplication_type' => 'a fancy strategy'
}
expect(Sidekiq.logger).to receive(:info).with(a_hash_including(expected_payload)).and_call_original
described_class.instance.log(job, "a fancy strategy")
end
it "does not modify the job" do
expect { described_class.instance.log(job, "a fancy strategy") }
.not_to change { job }
end
end
end
......@@ -3,6 +3,8 @@
require 'spec_helper'
describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gitlab_redis_queues do
using RSpec::Parameterized::TableSyntax
subject(:duplicate_job) do
described_class.new(job, queue)
end
......@@ -110,6 +112,36 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gitlab_r
end
end
describe 'droppable?' do
where(:idempotent, :duplicate, :feature_enabled) do
# [true, false].repeated_permutation(3)
[[true, true, true],
[true, true, false],
[true, false, true],
[true, false, false],
[false, true, true],
[false, true, false],
[false, false, true],
[false, false, false]]
end
with_them do
before do
allow(AuthorizedProjectsWorker).to receive(:idempotent?).and_return(idempotent)
allow(duplicate_job).to receive(:duplicate?).and_return(duplicate)
stub_feature_flags(drop_duplicate_sidekiq_jobs: feature_enabled)
end
it 'is droppable when all conditions are met' do
if idempotent && duplicate && feature_enabled
expect(duplicate_job).to be_droppable
else
expect(duplicate_job).not_to be_droppable
end
end
end
end
def set_idempotency_key(key, value = '1')
Sidekiq.redis { |r| r.set(key, value) }
end
......
......@@ -10,14 +10,21 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
subject(:strategy) { described_class.new(fake_duplicate_job) }
describe '#schedule' do
before do
allow(Gitlab::SidekiqLogging::DeduplicationLogger.instance).to receive(:log)
end
it 'checks for duplicates before yielding' do
expect(fake_duplicate_job).to receive(:check!).ordered.and_return('a jid')
expect(fake_duplicate_job).to receive(:duplicate?).ordered.and_return(false)
expect(fake_duplicate_job).to receive(:droppable?).ordered.and_return(false)
expect { |b| strategy.schedule({}, &b) }.to yield_control
end
it 'adds the jid of the existing job to the job hash' do
allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
job_hash = {}
expect(fake_duplicate_job).to receive(:duplicate?).and_return(true)
......@@ -27,6 +34,33 @@ describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do
expect(job_hash).to include('duplicate-of' => 'the jid')
end
context "when the job is droppable" do
before do
allow(fake_duplicate_job).to receive(:check!).and_return('the jid')
allow(fake_duplicate_job).to receive(:duplicate?).and_return(true)
allow(fake_duplicate_job).to receive(:existing_jid).and_return('the jid')
allow(fake_duplicate_job).to receive(:droppable?).and_return(true)
end
it 'drops the job' do
schedule_result = nil
expect(fake_duplicate_job).to receive(:droppable?).and_return(true)
expect { |b| schedule_result = strategy.schedule({}, &b) }.not_to yield_control
expect(schedule_result).to be(false)
end
it 'logs that the job wass dropped' do
fake_logger = instance_double(Gitlab::SidekiqLogging::DeduplicationLogger)
expect(Gitlab::SidekiqLogging::DeduplicationLogger).to receive(:instance).and_return(fake_logger)
expect(fake_logger).to receive(:log).with(a_hash_including({ 'jid' => 'new jid' }), 'dropped until executing')
strategy.schedule({ 'jid' => 'new jid' }) {}
end
end
end
describe '#perform' do
......
......@@ -501,64 +501,20 @@ describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
it_behaves_like 'dns rebinding checks'
end
end
context 'with ip ranges in whitelist' do
let(:ipv4_range) { '127.0.0.0/28' }
let(:ipv6_range) { 'fd84:6d02:f6d8:c89e::/124' }
let(:whitelist) do
[
ipv4_range,
ipv6_range
]
end
it 'blocks ipv4 range when not in whitelist' do
stub_application_setting(outbound_local_requests_whitelist: [])
IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
expect(described_class).to be_blocked_url("http://#{ip}",
url_blocker_attributes)
end
end
it 'allows all ipv4s in the range when in whitelist' do
IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
expect(described_class).not_to be_blocked_url("http://#{ip}",
url_blocker_attributes)
end
end
it 'blocks ipv6 range when not in whitelist' do
stub_application_setting(outbound_local_requests_whitelist: [])
IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
expect(described_class).to be_blocked_url("http://[#{ip}]",
url_blocker_attributes)
end
end
it 'allows all ipv6s in the range when in whitelist' do
IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
expect(described_class).not_to be_blocked_url("http://[#{ip}]",
url_blocker_attributes)
end
end
it 'blocks IPs outside the range' do
expect(described_class).to be_blocked_url("http://[fd84:6d02:f6d8:c89e:0:0:1:f]",
url_blocker_attributes)
expect(described_class).to be_blocked_url("http://127.0.1.15",
url_blocker_attributes)
end
end
end
end
def stub_domain_resolv(domain, ip, &block)
address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false, ipv4?: false)
allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([address])
def stub_domain_resolv(domain, ip, port = 80, &block)
address = instance_double(Addrinfo,
ip_address: ip,
ipv4_private?: true,
ipv6_linklocal?: false,
ipv4_loopback?: false,
ipv6_loopback?: false,
ipv4?: false,
ip_port: port
)
allow(Addrinfo).to receive(:getaddrinfo).with(domain, port, any_args).and_return([address])
allow(address).to receive(:ipv6_v4mapped?).and_return(false)
yield
......
......@@ -68,5 +68,51 @@ describe Gitlab::UrlBlockers::UrlWhitelist do
it 'returns false when ip is blank' do
expect(described_class).not_to be_ip_whitelisted(nil)
end
context 'with ip ranges in whitelist' do
let(:ipv4_range) { '127.0.0.0/28' }
let(:ipv6_range) { 'fd84:6d02:f6d8:c89e::/124' }
let(:whitelist) do
[
ipv4_range,
ipv6_range
]
end
it 'does not whitelist ipv4 range when not in whitelist' do
stub_application_setting(outbound_local_requests_whitelist: [])
IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
expect(described_class).not_to be_ip_whitelisted(ip.to_s)
end
end
it 'whitelists all ipv4s in the range when in whitelist' do
IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
expect(described_class).to be_ip_whitelisted(ip.to_s)
end
end
it 'does not whitelist ipv6 range when not in whitelist' do
stub_application_setting(outbound_local_requests_whitelist: [])
IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
expect(described_class).not_to be_ip_whitelisted(ip.to_s)
end
end
it 'whitelists all ipv6s in the range when in whitelist' do
IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
expect(described_class).to be_ip_whitelisted(ip.to_s)
end
end
it 'does not whitelist IPs outside the range' do
expect(described_class).not_to be_ip_whitelisted("fd84:6d02:f6d8:c89e:0:0:1:f")
expect(described_class).not_to be_ip_whitelisted("127.0.1.15")
end
end
end
end
......@@ -144,7 +144,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
end
it 'removes the installation POD' do
expect_next_instance_of(Gitlab::Kubernetes::Helm::Api) do |instance|
expect_next_instance_of(Gitlab::Kubernetes::Helm::API) do |instance|
expect(instance).to receive(:delete_pod!).with(kind_of(String)).once
end
expect(service).to receive(:remove_installation_pod).and_call_original
......
......@@ -48,7 +48,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED }
before do
expect_next_instance_of(Gitlab::Kubernetes::Helm::Api) do |instance|
expect_next_instance_of(Gitlab::Kubernetes::Helm::API) do |instance|
expect(instance).to receive(:delete_pod!).with(kind_of(String)).once
end
expect(service).to receive(:pod_phase).once.and_return(phase)
......
......@@ -7,7 +7,7 @@ describe Clusters::Applications::InstallService do
let(:application) { create(:clusters_applications_helm, :scheduled) }
let!(:install_command) { application.install_command }
let(:service) { described_class.new(application) }
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) }
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::API) }
before do
allow(service).to receive(:install_command).and_return(install_command)
......
......@@ -7,7 +7,7 @@ describe Clusters::Applications::PatchService do
let(:application) { create(:clusters_applications_knative, :scheduled) }
let!(:update_command) { application.update_command }
let(:service) { described_class.new(application) }
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) }
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::API) }
before do
allow(service).to receive(:update_command).and_return(update_command)
......
......@@ -5,7 +5,7 @@ require 'spec_helper'
describe Clusters::Applications::UninstallService, '#execute' do
let(:application) { create(:clusters_applications_prometheus, :scheduled) }
let(:service) { described_class.new(application) }
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) }
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::API) }
let(:worker_class) { Clusters::Applications::WaitForUninstallAppWorker }
before do
......
......@@ -7,7 +7,7 @@ describe Clusters::Applications::UpgradeService do
let(:application) { create(:clusters_applications_helm, :scheduled) }
let!(:install_command) { application.install_command }
let(:service) { described_class.new(application) }
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) }
let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::API) }
before do
allow(service).to receive(:install_command).and_return(install_command)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment