Commit 18305007 authored by Clement Ho's avatar Clement Ho

Merge branch 'master' into 4289-add-epic-issue-board-sidebar

parents bfcfb31a 1f7b7c81
......@@ -597,6 +597,10 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
.then(callDefault)
.catch(fail);
break;
case 'dashboard:groups:index':
import('./pages/dashboard/groups/index')
.then(callDefault)
.catch(fail);
case 'admin:licenses:new':
import(/* webpackChunkName: "admin_licenses" */ 'ee/pages/admin/licenses/new').then(m => m.default()).catch(fail);
break;
......
......@@ -10,7 +10,7 @@ import groupItemComponent from './components/group_item.vue';
Vue.use(Translate);
document.addEventListener('DOMContentLoaded', () => {
export default () => {
const el = document.getElementById('js-groups-tree');
// Don't do anything if element doesn't exist (No groups)
......@@ -71,4 +71,4 @@ document.addEventListener('DOMContentLoaded', () => {
});
},
});
});
};
import Vue from 'vue';
import VueResource from 'vue-resource';
Vue.use(VueResource);
import '../../vue_shared/vue_resource_interceptor';
export default class GroupsService {
constructor(endpoint) {
......
import initGroupsList from '../../../../groups';
export default () => {
initGroupsList();
};
import GroupsList from '~/groups_list';
import Landing from '~/landing';
import initGroupsList from '../../../groups';
export default function () {
new GroupsList(); // eslint-disable-line no-new
initGroupsList();
const landingElement = document.querySelector('.js-explore-groups-landing');
if (!landingElement) return;
const exploreGroupsLanding = new Landing(
......
......@@ -5,6 +5,7 @@ import notificationsDropdown from '~/notifications_dropdown';
import NotificationsForm from '~/notifications_form';
import ProjectsList from '~/projects_list';
import ShortcutsNavigation from '~/shortcuts_navigation';
import initGroupsList from '../../../groups';
export default () => {
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
......@@ -16,4 +17,6 @@ export default () => {
if (newGroupChildWrapper) {
new NewGroupChild(newGroupChildWrapper);
}
initGroupsList();
};
.js-groups-list-holder
#js-groups-tree{ data: { hide_projects: 'true', endpoint: dashboard_groups_path(format: :json), path: dashboard_groups_path, form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } }
.loading-container.text-center
= icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
......@@ -3,9 +3,6 @@
- header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head'
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'groups'
- if params[:filter].blank? && @groups.empty?
= render 'shared/groups/empty_state'
- else
......
.js-groups-list-holder
#js-groups-tree{ data: { hide_projects: 'true', endpoint: explore_groups_path(format: :json), path: explore_groups_path, form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } }
.loading-container.text-center
= icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
......@@ -2,9 +2,6 @@
- page_title "Groups"
- header_title "Groups", dashboard_groups_path
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'groups'
- if current_user
= render 'dashboard/groups_head'
- else
......
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'groups'
.js-groups-list-holder
#js-groups-tree{ data: { hide_projects: 'false', group_id: group.id, endpoint: group_children_path(group, format: :json), path: group_path(group), form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } }
.loading-container.text-center
= icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('group')
- parent = @group.parent
- group_path = root_url
- group_path << parent.full_path + '/' if parent
......
---
title: 'Geo: sync .gitattributes to info/attributes in secondary nodes'
merge_request: 4159
author:
type: changed
---
title: Update Geo documentation to reuse the primary node SSH host key on secondary
node
merge_request: 4198
author:
type: fixed
---
title: Fix failed LDAP logins when sync_ssh_keys is included in config
merge_request:
author:
type: fixed
---
title: Set timezone for karma to UTC
merge_request: 16602
author: Takuya Noguchi
type: other
......@@ -18,6 +18,8 @@ webpackConfig.devtool = 'cheap-inline-source-map';
// Karma configuration
module.exports = function(config) {
process.env.TZ = 'Etc/UTC';
var progressReporter = process.env.CI ? 'mocha' : 'progress';
var karmaConfig = {
......
......@@ -49,9 +49,6 @@ var config = {
graphs: './graphs/graphs_bundle.js',
graphs_charts: './graphs/graphs_charts.js',
graphs_show: './graphs/graphs_show.js',
group: './group.js',
groups: './groups/index.js',
groups_list: './groups_list.js',
help: './help/help.js',
issuable: './issuable/issuable_bundle.js',
issues: './issues/issues_bundle.js',
......@@ -133,9 +130,9 @@ var config = {
{
test: /\_worker\.js$/,
use: [
{
{
loader: 'worker-loader',
options: {
options: {
inline: true
}
},
......
......@@ -1293,7 +1293,7 @@ to the CI pipeline:
```yaml
variables:
GIT_STRATEGY: clone
GIT_CHECKOUT: false
GIT_CHECKOUT: "false"
script:
- git checkout master
- git merge $CI_BUILD_REF_NAME
......
......@@ -63,7 +63,7 @@ We have also decided against using [axios interceptors] because they are not sui
});
afterEach(() => {
mock.reset();
mock.restore();
});
```
......
......@@ -85,7 +85,62 @@ Make sure the secondary instance is
running and accessible. You can login to the secondary node
with the same credentials as used in the primary.
### Step 2. (Optional) Enabling hashed storage (from GitLab 10.0)
### Step 2. Manually replicate primary SSH host keys
GitLab integrates with the system-installed SSH daemon, designating a user
(typically named git) through which all access requests are handled.
In a [Disaster Recovery](disaster-recovery.md) situation, GitLab system
administrators will promote a secondary Geo replica to a primary and they can
update the DNS records for the primary domain to point to the secondary to prevent
the need to update all references to the primary domain to the secondary domain,
like changing Git remotes and API URLs.
This will cause all SSH requests to the newly promoted primary node from
failing due to SSH host key mismatch. To prevent this, the primary SSH host
keys must be manually replicated to the secondary node.
1. SSH into the **secondary** node and login as the `root` user:
```
sudo -i
```
1. Make a backup of any existing SSH host keys:
```bash
find /etc/ssh -iname ssh_host_* -exec mv {} {}.backup.`date +%F` \;
```
1. SSH into the **primary** node, and execute the command below:
```bash
sudo find /etc/ssh -iname ssh_host_* -not -iname '*.pub'
```
1. For each file in that list copy the file from the primary node to
the **same** location on your **secondary** node.
1. On your **secondary** node, ensure the file permissions are correct:
```bash
chown root:root /etc/ssh/ssh_host_*
chmod 0600 /etc/ssh/ssh_host_*
```
1. Regenerate the public keys from the private keys:
```bash
find /etc/ssh -iname ssh_host_* -not -iname '*.backup*' -exec sh -c 'ssh-keygen -y -f "{}" > "{}.pub"' \;
```
1. Restart sshd:
```bash
service ssh restart
```
### Step 3. (Optional) Enabling hashed storage (from GitLab 10.0)
>**Warning**
Hashed storage is in **Alpha**. It is considered experimental and not
......@@ -102,7 +157,7 @@ renames no longer require synchronization between nodes.
![](img/hashed-storage.png)
### Step 3. (Optional) Configuring the secondary to trust the primary
### Step 4. (Optional) Configuring the secondary to trust the primary
You can safely skip this step if your primary uses a CA-issued HTTPS certificate.
......@@ -112,14 +167,14 @@ certificate from the primary and follow
[these instructions](https://docs.gitlab.com/omnibus/settings/ssl.html)
on the secondary.
### Step 4. Enable Git access over HTTP/HTTPS
### Step 5. Enable Git access over HTTP/HTTPS
GitLab Geo synchronizes repositories over HTTP/HTTPS, and therefore requires this clone
method to be enabled. Navigate to **Admin Area ➔ Settings**
(`/admin/application_settings`) on the primary node, and set
`Enabled Git access protocols` to `Both SSH and HTTP(S)` or `Only HTTP(S)`.
### Step 5. Verify proper functioning of the secondary node
### Step 6. Verify proper functioning of the secondary node
Congratulations! Your secondary geo node is now configured!
......
......@@ -77,13 +77,6 @@ be manually replicated to the secondary.
service gitlab restart
```
The secondary will start automatically replicating missing data from the
primary in a process known as backfill. Meanwhile, the primary node will start
to notify changes to the secondary, which will act on those notifications
immediately. Make sure the secondary instance is running and accessible.
### Step 2. (Optional) Enabling hashed storage
Once restarted, the secondary will automatically start replicating missing data
from the primary in a process known as backfill. Meanwhile, the primary node
will start to notify the secondary of any changes, so that the secondary can
......@@ -92,11 +85,15 @@ act on those notifications immediately.
Make sure the secondary instance is running and accessible. You can login to
the secondary node with the same credentials as used in the primary.
### Step 2. (Optional) Enabling hashed storage (from GitLab 10.0)
### Step 2. Manually replicate primary SSH host keys
Read [Manually replicate primary SSH host keys](configuration.md#step-2-manually-replicate-primary-ssh-host-keys)
### Step 3. (Optional) Enabling hashed storage (from GitLab 10.0)
Read [Enabling Hashed Storage](configuration.md#step-2-optional-enabling-hashed-storage-from-gitlab-10-0)
Read [Enabling Hashed Storage](configuration.md#step-3-optional-enabling-hashed-storage-from-gitlab-10-0)
### Step 3. (Optional) Configuring the secondary to trust the primary
### Step 4. (Optional) Configuring the secondary to trust the primary
You can safely skip this step if your primary uses a CA-issued HTTPS certificate.
......@@ -112,16 +109,16 @@ cp primary.geo.example.com.crt /usr/local/share/ca-certificates
update-ca-certificates
```
### Step 4. Enable Git access over HTTP/HTTPS
### Step 5. Enable Git access over HTTP/HTTPS
GitLab Geo synchronizes repositories over HTTP/HTTPS, and therefore requires this clone
method to be enabled. Navigate to **Admin Area ➔ Settings**
(`/admin/application_settings`) on the primary node, and set
`Enabled Git access protocols` to `Both SSH and HTTP(S)` or `Only HTTP(S)`.
### Step 5. Verify proper functioning of the secondary node
### Step 6. Verify proper functioning of the secondary node
Read [Verify proper functioning of the secondary node](configuration.md#step-5-verify-proper-functioning-of-the-secondary-node).
Read [Verify proper functioning of the secondary node](configuration.md#step-6-verify-proper-functioning-of-the-secondary-node).
## Selective replication
......
......@@ -73,7 +73,7 @@ secondary domain, like changing Git remotes and API URLs.
1. SSH in to your **secondary** and login as root:
```
```bash
sudo -i
```
......@@ -82,20 +82,20 @@ secondary domain, like changing Git remotes and API URLs.
After updating the primary domain's DNS records to point to the secondary,
edit `/etc/gitlab/gitlab.rb` on the the secondary to reflect the new URL:
```
```ruby
# Change the existing external_url configuration
external_url 'https://gitlab.example.com'
```
1. Reconfigure the secondary node for the change to take effect:
```
```bash
gitlab-ctl reconfigure
```
1. Execute the command below to update the newly promoted primary node URL:
```
```bash
gitlab-rake geo:update_primary_node_url
```
......
......@@ -32,6 +32,11 @@ sudo gitlab-ctl reconfigure
If you do not perform this step, you may find that two-factor authentication
[is broken following DR](faq.md#i-followed-the-disaster-recovery-instructions-and-now-two-factor-auth-is-broken).
To prevent SSH requests to the newly promoted primary node from failing
due to SSH host key mismatch when updating the primary domain's DNS record
you should perform the step to [Manually replicate primary SSH host keys](configuration.md#step-2-manually-replicate-primary-ssh-host-keys) in each
secondary node.
## Upgrading to GitLab 10.4
There are no Geo-specific steps to take!
......@@ -44,7 +49,7 @@ In GitLab 10.2, synchronizing secondaries over SSH was deprecated. In 10.3,
support is removed entirely. All installations will switch to the HTTP/HTTPS
cloning method instead. Before upgrading, ensure that all your Geo nodes are
configured to use this method and that it works for your installation. In
particular, ensure that [Git access over HTTP/HTTPS is enabled](configuration.md#step-4-enable-git-access-over-http-https).
particular, ensure that [Git access over HTTP/HTTPS is enabled](configuration.md#step-5-enable-git-access-over-http-https).
Synchronizing repositories over the public Internet using HTTP is insecure, so
you should ensure that you have HTTPS configured before upgrading. Note that
......@@ -52,7 +57,7 @@ file synchronization is **also** insecure in these cases!
## Upgrading to GitLab 10.2
### Secure PostgreSQL replication
### Secure PostgreSQL replication
Support for TLS-secured PostgreSQL replication has been added. If you are
currently using PostgreSQL replication across the open internet without an
......
......@@ -22,6 +22,8 @@ module Geo
fetch_geo_mirror(project.repository)
end
update_gitattributes
update_registry!(finished_at: DateTime.now, attrs: { last_repository_sync_failure: nil })
log_info('Finished repository sync',
update_delay_s: update_delay_in_seconds,
......@@ -52,6 +54,13 @@ module Geo
project.repository
end
# Update info/attributes file using the contents of .gitattributes file from the default branch
def update_gitattributes
return if project.default_branch.nil?
repository.copy_gitattributes(project.default_branch)
end
def retry_count
registry.public_send("#{type}_retry_count") || -1 # rubocop:disable GitlabSecurity/PublicSend
end
......
......@@ -46,7 +46,7 @@ module EE
def ldap_attributes(config)
attributes = super + [
'memberof',
config.sync_ssh_keys
(config.sync_ssh_keys if config.sync_ssh_keys.is_a?(String))
]
attributes.compact.uniq
end
......
......@@ -69,6 +69,50 @@ describe Gitlab::LDAP::Person do
end
end
describe '.ldap_attributes' do
def stub_sync_ssh_keys(value)
stub_ldap_config(
options: {
'uid' => nil,
'attributes' => {
'name' => 'cn',
'email' => 'mail',
'username' => %w(uid mail memberof)
},
'sync_ssh_keys' => value
}
)
end
let(:config) { Gitlab::LDAP::Config.new('ldapmain') }
let(:ldap_attributes) { described_class.ldap_attributes(config) }
let(:expected_attributes) { %w(dn cn uid mail memberof) }
it 'includes a real attribute name' do
stub_sync_ssh_keys('my-ssh-attribute')
expect(ldap_attributes).to match_array(expected_attributes + ['my-ssh-attribute'])
end
it 'excludes integers' do
stub_sync_ssh_keys(0)
expect(ldap_attributes).to match_array(expected_attributes)
end
it 'excludes false values' do
stub_sync_ssh_keys(false)
expect(ldap_attributes).to match_array(expected_attributes)
end
it 'excludes true values' do
stub_sync_ssh_keys(true)
expect(ldap_attributes).to match_array(expected_attributes)
end
end
describe '#kerberos_principal' do
let(:entry) do
ldif = "dn: cn=foo, dc=bar, dc=com\nsAMAccountName: myName\n"
......
......@@ -145,6 +145,16 @@ describe Geo::RepositorySyncService do
expect(registry.reload.repository_retry_count).to be_nil
expect(registry.repository_retry_at).to be_nil
end
context 'with non empty repositories' do
let(:project) { create(:project, :repository) }
it 'syncs gitattributes to info/attributes' do
expect(repository).to receive(:copy_gitattributes)
subject.execute
end
end
end
context 'when repository sync fail' do
......
......@@ -45,7 +45,7 @@ describe('iPython notebook renderer', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('does not show loading icon', () => {
......@@ -96,7 +96,7 @@ describe('iPython notebook renderer', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('does not show loading icon', () => {
......@@ -127,7 +127,7 @@ describe('iPython notebook renderer', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('does not show loading icon', () => {
......
......@@ -55,7 +55,7 @@ describe('Board card', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('returns false when detailIssue is empty', () => {
......
......@@ -60,7 +60,7 @@ describe('Board list component', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('renders component', () => {
......
......@@ -58,7 +58,7 @@ describe('Issue boards new issue form', () => {
afterEach(() => {
vm.$destroy();
mock.reset();
mock.restore();
});
it('calls submit if submit button is clicked', (done) => {
......
......@@ -34,7 +34,7 @@ describe('Store', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('starts with a blank state', () => {
......
......@@ -30,7 +30,7 @@ describe('List model', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('gets issues when created', (done) => {
......
......@@ -78,7 +78,7 @@ describe('EpicShowApp', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('should render epic-header', () => {
......
......@@ -59,7 +59,7 @@ describe('Issuable output', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
realtimeRequestCount = 0;
vm.poll.stop();
......
......@@ -38,7 +38,7 @@ describe('Dashboard', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('shows up a loading state', (done) => {
......
......@@ -64,7 +64,7 @@ describe('ee merge request widget options', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('should render provided data', (done) => {
......@@ -87,7 +87,7 @@ describe('ee merge request widget options', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('should render provided data', (done) => {
......@@ -110,7 +110,7 @@ describe('ee merge request widget options', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('should render error indicator', (done) => {
......@@ -158,7 +158,7 @@ describe('ee merge request widget options', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('should render provided data', (done) => {
......@@ -209,7 +209,7 @@ describe('ee merge request widget options', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('should render provided data', (done) => {
......@@ -233,7 +233,7 @@ describe('ee merge request widget options', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('should render error indicator', (done) => {
......@@ -279,7 +279,7 @@ describe('ee merge request widget options', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('should render provided data', (done) => {
......@@ -331,7 +331,7 @@ describe('ee merge request widget options', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('should render provided data', (done) => {
......@@ -355,7 +355,7 @@ describe('ee merge request widget options', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('should render error indicator', (done) => {
......@@ -401,7 +401,7 @@ describe('ee merge request widget options', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('should render provided data', (done) => {
......@@ -440,7 +440,7 @@ describe('ee merge request widget options', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('should render error indicator', (done) => {
......@@ -487,7 +487,7 @@ describe('ee merge request widget options', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('should render provided data', (done) => {
......@@ -518,7 +518,7 @@ describe('ee merge request widget options', () => {
});
afterEach(() => {
mock.reset();
mock.restore();
});
it('should render error indicator', (done) => {
......
......@@ -7,6 +7,10 @@ describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData,
.to receive(:commits_count=).and_return(nil)
end
after do
[MergeRequest, MergeRequestDiff].each(&:reset_column_information)
end
describe '#perform' do
let(:mr_with_event) { create(:merge_request) }
let!(:merged_event) { create(:event, :merged, target: mr_with_event) }
......
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