Commit 2d96e61c authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent e7238677
......@@ -109,7 +109,7 @@ export default {
<template>
<p v-html="text"></p>
<p v-html="confirmationTextLabel"></p>
<form ref="form" :action="deleteUserUrl" method="post">
<form ref="form" :action="deleteUserUrl" method="post" @submit.prevent>
<input ref="method" type="hidden" name="_method" value="delete" />
<input :value="csrfToken" type="hidden" name="authenticity_token" />
<gl-form-input
......
......@@ -64,7 +64,7 @@
%p.form-text.text-muted
= s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end }
- if Feature.enabled?(:create_cloud_run_clusters, clusterable)
- if Feature.enabled?(:create_cloud_run_clusters, clusterable, default_enabled: true)
.form-group
= provider_gcp_field.check_box :cloud_run, { label: s_('ClusterIntegration|Enable Cloud Run on GKE (beta)'),
label_class: 'label-bold' }
......
---
title: Fix delete user dialog bypass caused by hitting enter
merge_request: 17343
author:
type: fixed
---
title: Change commit_id type on commit_user_mentions table
merge_request: 21651
author:
type: fixed
---
title: Re-enable the cloud run feature
merge_request: https://gitlab.com/gitlab-org/gitlab/merge_requests/21762
author:
type: fixed
# frozen_string_literal: true
class ChangeCommitUserMentionsCommitIdColumnType < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
OLD_INDEX = 'commit_user_mentions_on_commit_id_and_note_id_index'
OLD_TMP_INDEX = 'temp_commit_id_and_note_id_index'
NEW_TMP_INDEX = 'temp_commit_id_for_type_change_and_note_id_index'
NEW_INDEX = 'commit_id_and_note_id_index'
def up
# the initial index name is too long and fails during migration. Renaming the index first.
add_concurrent_index :commit_user_mentions, [:commit_id, :note_id], name: OLD_TMP_INDEX
remove_concurrent_index_by_name :commit_user_mentions, OLD_INDEX
change_column_type_concurrently :commit_user_mentions, :commit_id, :string
# change_column_type_concurrently creates a new index for new column `commit_id_for_type` based on existing
# `temp_commit_id_and_note_id_index` naming it `temp_commit_id_for_type_change_and_note_id_index`, yet keeping
# `temp_commit_id_and_note_id_index` for `commit_id`, that will be cleaned
# by `cleanup_concurrent_column_type_change :commit_user_mentions, :commit_id` in a later migration.
#
# So we'll rename `temp_commit_id_for_type_change_and_note_id_index` to initialy intended name: `commit_id_and_note_id_index`.
add_concurrent_index :commit_user_mentions, [:commit_id_for_type_change, :note_id], name: NEW_INDEX
remove_concurrent_index_by_name :commit_user_mentions, NEW_TMP_INDEX
end
def down
cleanup_concurrent_column_type_change :commit_user_mentions, :commit_id
end
end
# frozen_string_literal: true
class ChangeCommitUserMentionsCommitIdColumnTypeCleanup < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
NEW_INDEX = 'commit_id_for_type_change_and_note_id_index'
OLD_INDEX = 'commit_user_mentions_on_commit_id_and_note_id_index'
def up
cleanup_concurrent_column_type_change :commit_user_mentions, :commit_id
end
def down
change_column_type_concurrently :commit_user_mentions, :commit_id, :binary
# change_column_type_concurrently creates a new index based on existing commit_id_and_note_id_index` naming it
# `commit_id_for_type_change_and_note_id_index` so we'll rename it back to its original name.
add_concurrent_index :commit_user_mentions, [:commit_id_for_type_change, :note_id], name: OLD_INDEX
remove_concurrent_index_by_name :commit_user_mentions, NEW_INDEX
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_17_160632) do
ActiveRecord::Schema.define(version: 2019_12_16_183532) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
......@@ -1217,11 +1217,11 @@ ActiveRecord::Schema.define(version: 2019_12_17_160632) do
create_table "commit_user_mentions", force: :cascade do |t|
t.integer "note_id", null: false
t.binary "commit_id", null: false
t.integer "mentioned_users_ids", array: true
t.integer "mentioned_projects_ids", array: true
t.integer "mentioned_groups_ids", array: true
t.index ["commit_id", "note_id"], name: "commit_user_mentions_on_commit_id_and_note_id_index"
t.string "commit_id", null: false
t.index ["commit_id", "note_id"], name: "commit_id_and_note_id_index"
t.index ["note_id"], name: "index_commit_user_mentions_on_note_id", unique: true
end
......
......@@ -66,7 +66,7 @@ We need to manage the following secrets and make them match across hosts:
#### Praefect
On the Praefect node we disable all other services, including Gitaly. We list each
Gitaly node that will be connected to Praefect under `praefect['storage_nodes']`.
Gitaly node that will be connected to Praefect as members of the `praefect` hash in `praefect['virtual_storages']`.
In the example below, the Gitaly nodes are named `gitaly-N`. Note that one
node is designated as primary by setting the primary to `true`.
......@@ -84,15 +84,6 @@ unicorn['enable'] = false
sidekiq['enable'] = false
gitlab_workhorse['enable'] = false
gitaly['enable'] = false
```
##### Set up Praefect and its Gitaly nodes
In the example below, the Gitaly nodes are named `gitaly-X`. Note that one node is designated as
primary, by setting the primary to `true`:
```ruby
# /etc/gitlab/gitlab.rb on praefect server
# Prevent database connections during 'gitlab-ctl reconfigure'
gitlab_rails['rake_cache_clear'] = false
......@@ -104,15 +95,14 @@ praefect['enable'] = true
# firewalls to restrict access to this address/port.
praefect['listen_addr'] = '0.0.0.0:2305'
# virtual_storage_name must match the same storage name given to praefect in git_data_dirs
praefect['virtual_storage_name'] = 'praefect'
# Replace PRAEFECT_EXTERNAL_TOKEN with a real secret
praefect['auth_token'] = 'PRAEFECT_EXTERNAL_TOKEN'
# Replace each instance of PRAEFECT_INTERNAL_TOKEN below with a real
# secret, distinct from PRAEFECT_EXTERNAL_TOKEN.
praefect['storage_nodes'] = {
# Name of storage hash must match storage name in git_data_dirs on GitLab server.
praefect['virtual_storages'] = {
'praefect' => {
'gitaly-1' => {
'address' => 'tcp://gitaly-1.internal:8075',
'token' => 'PRAEFECT_INTERNAL_TOKEN',
......@@ -126,6 +116,7 @@ praefect['storage_nodes'] = {
'address' => 'tcp://gitaly-3.internal:8075',
'token' => 'PRAEFECT_INTERNAL_TOKEN'
}
}
}
```
......@@ -140,7 +131,7 @@ auth tokens from Praefect instead of GitLab.
Below is an example configuration for `gitaly-1`, the only difference for the
other Gitaly nodes is the storage name under `git_data_dirs`.
Note that `gitaly['auth_token']` matches the `token` value listed under `praefect['storage_nodes']`
Note that `gitaly['auth_token']` matches the `token` value listed under `praefect['virtual_storages']`
on the Praefect node.
```ruby
......@@ -155,6 +146,7 @@ grafana['enable'] = false
unicorn['enable'] = false
sidekiq['enable'] = false
gitlab_workhorse['enable'] = false
prometheus_monitoring['enable'] = false
# Prevent database connections during 'gitlab-ctl reconfigure'
gitlab_rails['rake_cache_clear'] = false
......@@ -197,7 +189,7 @@ is present, there should be two storages available to GitLab:
# Replace PRAEFECT_EXTERNAL_TOKEN below with real secret.
git_data_dirs({
"default" => {
"gitaly_address" => "tcp://gitaly.internal"
"path" => "/var/opt/gitlab/git-data"
},
"praefect" => {
"gitaly_address" => "tcp://praefect.internal:2305",
......@@ -212,7 +204,9 @@ gitlab_shell['secret_token'] = 'GITLAB_SHELL_SECRET_TOKEN'
Note that the storage name used is the same as the `praefect['virtual_storage_name']` set
on the Praefect node.
Restart GitLab using `gitlab-ctl restart` on the GitLab node.
Save your changes and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
Run `gitlab-rake gitlab:gitaly:check` to confirm that GitLab can reach Praefect.
### Testing Praefect
......@@ -220,6 +214,18 @@ To test Praefect, first set it as the default storage node for new projects
using **Admin Area > Settings > Repository > Repository storage**. Next,
create a new project and check the "Initialize repository with a README" box.
If you receive a 503 error, check `/var/log/gitlab/gitlab-rails/production.log`.
A `GRPC::Unavailable (14:failed to connect to all addresses)` error indicates
that GitLab was unable to connect to Praefect.
If you receive an error, check `/var/log/gitlab/gitlab-rails/production.log`.
Here are common errors and potential causes:
- 500 response code
- **ActionView::Template::Error (7:permission denied)**
- `praefect['auth_token']` and `gitlab_rails['gitaly_token']` do not match on the GitLab server.
- **Unable to save project. Error: 7:permission denied**
- Secret token in `praefect['storage_nodes']` on GitLab server does not match the
value in `gitaly['auth_token']` on one or more Gitaly servers.
- 503 response code
- **GRPC::Unavailable (14:failed to connect to all addresses)**
- GitLab was unable to reach Praefect.
- **GRPC::Unavailable (14:all SubCons are in TransientFailure...)**
- Praefect cannot reach one or more of its child Gitaly nodes.
......@@ -57,7 +57,7 @@ Parameters:
Creates a two-way relation between two issues. User must be allowed to update both issues in order to succeed.
```
POST /projects/:id/issues/:issue_iid/links
POST /projects/:id/issues/:issue_iid/links/:target_project_id/:target_issue_iid
```
| Attribute | Type | Required | Description |
......
......@@ -3,26 +3,46 @@ import { GlButton, GlFormInput } from '@gitlab/ui';
import DeleteUserModal from '~/pages/admin/users/components/delete_user_modal.vue';
import ModalStub from './stubs/modal_stub';
const TEST_DELETE_USER_URL = 'delete-url';
const TEST_BLOCK_USER_URL = 'block-url';
const TEST_CSRF = 'csrf';
describe('User Operation confirmation modal', () => {
let wrapper;
let formSubmitSpy;
const findButton = variant =>
wrapper
.findAll(GlButton)
.filter(w => w.attributes('variant') === variant)
.at(0);
const findForm = () => wrapper.find('form');
const findUsernameInput = () => wrapper.find(GlFormInput);
const findPrimaryButton = () => findButton('danger');
const findSecondaryButton = () => findButton('warning');
const findAuthenticityToken = () => new FormData(findForm().element).get('authenticity_token');
const getUsername = () => findUsernameInput().attributes('value');
const getMethodParam = () => new FormData(findForm().element).get('_method');
const getFormAction = () => findForm().attributes('action');
const setUsername = username => {
findUsernameInput().vm.$emit('input', username);
};
const username = 'username';
const badUsername = 'bad_username';
const createComponent = (props = {}) => {
wrapper = shallowMount(DeleteUserModal, {
propsData: {
username,
title: 'title',
content: 'content',
action: 'action',
secondaryAction: 'secondaryAction',
deleteUserUrl: 'delete-url',
blockUserUrl: 'block-url',
username: 'username',
csrfToken: 'csrf',
deleteUserUrl: TEST_DELETE_USER_URL,
blockUserUrl: TEST_BLOCK_USER_URL,
csrfToken: TEST_CSRF,
...props,
},
stubs: {
......@@ -32,6 +52,10 @@ describe('User Operation confirmation modal', () => {
});
};
beforeEach(() => {
formSubmitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit').mockImplementation();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
......@@ -42,44 +66,84 @@ describe('User Operation confirmation modal', () => {
expect(wrapper.element).toMatchSnapshot();
});
it.each`
variant | prop | action
${'danger'} | ${'deleteUserUrl'} | ${'delete'}
${'warning'} | ${'blockUserUrl'} | ${'block'}
`('closing modal with $variant button triggers $action', ({ variant, prop }) => {
describe('on created', () => {
beforeEach(() => {
createComponent();
});
it('has disabled buttons', () => {
expect(findPrimaryButton().attributes('disabled')).toBeTruthy();
expect(findSecondaryButton().attributes('disabled')).toBeTruthy();
});
});
describe('with incorrect username', () => {
beforeEach(() => {
createComponent();
const form = wrapper.find('form');
jest.spyOn(form.element, 'submit').mockReturnValue();
const modalButton = findButton(variant);
modalButton.vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(form.element.submit).toHaveBeenCalled();
expect(form.element.action).toContain(wrapper.props(prop));
expect(new FormData(form.element).get('authenticity_token')).toEqual(
wrapper.props('csrfToken'),
);
setUsername(badUsername);
return wrapper.vm.$nextTick();
});
it('shows incorrect username', () => {
expect(getUsername()).toEqual(badUsername);
});
it('has disabled buttons', () => {
expect(findPrimaryButton().attributes('disabled')).toBeTruthy();
expect(findSecondaryButton().attributes('disabled')).toBeTruthy();
});
});
it('disables buttons by default', () => {
describe('with correct username', () => {
beforeEach(() => {
createComponent();
const blockButton = findButton('warning');
const deleteButton = findButton('danger');
expect(blockButton.attributes().disabled).toBeTruthy();
expect(deleteButton.attributes().disabled).toBeTruthy();
setUsername(username);
return wrapper.vm.$nextTick();
});
it('shows correct username', () => {
expect(getUsername()).toEqual(username);
});
it('has enabled buttons', () => {
expect(findPrimaryButton().attributes('disabled')).toBeFalsy();
expect(findSecondaryButton().attributes('disabled')).toBeFalsy();
});
describe('when primary action is submitted', () => {
beforeEach(() => {
findPrimaryButton().vm.$emit('click');
return wrapper.vm.$nextTick();
});
it('clears the input', () => {
expect(getUsername()).toEqual('');
});
it('has correct form attributes and calls submit', () => {
expect(getFormAction()).toBe(TEST_DELETE_USER_URL);
expect(getMethodParam()).toBe('delete');
expect(findAuthenticityToken()).toBe(TEST_CSRF);
expect(formSubmitSpy).toHaveBeenCalled();
});
});
it('enables button when username is typed', () => {
createComponent({
username: 'some-username',
describe('when secondary action is submitted', () => {
beforeEach(() => {
findSecondaryButton().vm.$emit('click');
return wrapper.vm.$nextTick();
});
wrapper.find(GlFormInput).vm.$emit('input', 'some-username');
const blockButton = findButton('warning');
const deleteButton = findButton('danger');
return wrapper.vm.$nextTick().then(() => {
expect(blockButton.attributes().disabled).toBeFalsy();
expect(deleteButton.attributes().disabled).toBeFalsy();
it('has correct form attributes and calls submit', () => {
expect(getFormAction()).toBe(TEST_BLOCK_USER_URL);
expect(getMethodParam()).toBe('put');
expect(findAuthenticityToken()).toBe(TEST_CSRF);
expect(formSubmitSpy).toHaveBeenCalled();
});
});
});
});
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