Skip to content
Snippets Groups Projects
Commit bd8a202d authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 59a34981
No related branches found
No related tags found
No related merge requests found
Showing
with 125 additions and 241 deletions
Loading
Loading
@@ -6,7 +6,7 @@ const newClusterViews = [':clusters:new', ':clusters:create_gcp', ':clusters:cre
 
const isProjectLevelCluster = page => page.startsWith('project:clusters');
 
export default (document, gon) => {
export default document => {
const { page } = document.body.dataset;
const isNewClusterView = newClusterViews.some(view => page.endsWith(view));
 
Loading
Loading
@@ -19,17 +19,15 @@ export default (document, gon) => {
 
initGkeDropdowns();
 
if (gon.features.createEksClusters) {
import(/* webpackChunkName: 'eks_cluster' */ '~/create_cluster/eks_cluster')
.then(({ default: initCreateEKSCluster }) => {
const el = document.querySelector('.js-create-eks-cluster-form-container');
import(/* webpackChunkName: 'eks_cluster' */ '~/create_cluster/eks_cluster')
.then(({ default: initCreateEKSCluster }) => {
const el = document.querySelector('.js-create-eks-cluster-form-container');
 
if (el) {
initCreateEKSCluster(el);
}
})
.catch(() => {});
}
if (el) {
initCreateEKSCluster(el);
}
})
.catch(() => {});
 
if (isProjectLevelCluster(page)) {
initGkeNamespace();
Loading
Loading
Loading
Loading
@@ -12,9 +12,6 @@ class Clusters::ClustersController < Clusters::BaseController
before_action :authorize_update_cluster!, only: [:update]
before_action :authorize_admin_cluster!, only: [:destroy, :clear_cache]
before_action :update_applications_status, only: [:cluster_status]
before_action only: [:new, :create_gcp] do
push_frontend_feature_flag(:create_eks_clusters)
end
before_action only: [:show] do
push_frontend_feature_flag(:enable_cluster_application_elastic_stack)
push_frontend_feature_flag(:enable_cluster_application_crossplane)
Loading
Loading
@@ -42,8 +39,6 @@ class Clusters::ClustersController < Clusters::BaseController
end
 
def new
return unless Feature.enabled?(:create_eks_clusters)
if params[:provider] == 'aws'
@aws_role = current_user.aws_role || Aws::Role.new
@aws_role.ensure_role_external_id!
Loading
Loading
@@ -113,6 +108,7 @@ class Clusters::ClustersController < Clusters::BaseController
generate_gcp_authorize_url
validate_gcp_token
user_cluster
params[:provider] = 'gcp'
 
render :new, locals: { active_tab: 'create' }
end
Loading
Loading
@@ -277,8 +273,7 @@ class Clusters::ClustersController < Clusters::BaseController
end
 
def generate_gcp_authorize_url
params = Feature.enabled?(:create_eks_clusters) ? { provider: :gke } : {}
state = generate_session_key_redirect(clusterable.new_path(params).to_s)
state = generate_session_key_redirect(clusterable.new_path(provider: :gcp).to_s)
 
@authorize_url = GoogleApi::CloudPlatform::Client.new(
nil, callback_google_api_auth_url,
Loading
Loading
Loading
Loading
@@ -26,13 +26,13 @@ module Projects
 
def delete_tags(tags_to_delete, tags_by_digest)
deleted_digests = group_by_digest(tags_to_delete).select do |digest, tags|
delete_tag_digest(digest, tags, tags_by_digest[digest])
delete_tag_digest(tags, tags_by_digest[digest])
end
 
deleted_digests.values.flatten
end
 
def delete_tag_digest(digest, tags, other_tags)
def delete_tag_digest(tags, other_tags)
# Issue: https://gitlab.com/gitlab-org/gitlab-foss/issues/21405
# we have to remove all tags due
# to Docker Distribution bug unable
Loading
Loading
Loading
Loading
@@ -24,32 +24,36 @@ module Projects
dummy_manifest = container_repository.client.generate_empty_manifest(container_repository.path)
return error('could not generate manifest') if dummy_manifest.nil?
 
# update the manifests of the tags with the new dummy image
deleted_tags = []
tag_digests = []
deleted_tags = replace_tag_manifests(container_repository, dummy_manifest, tag_names)
# Deletes the dummy image
# All created tag digests are the same since they all have the same dummy image.
# a single delete is sufficient to remove all tags with it
if deleted_tags.any? && container_repository.delete_tag_by_digest(deleted_tags.values.first)
success(deleted: deleted_tags.keys)
else
error('could not delete tags')
end
end
# update the manifests of the tags with the new dummy image
def replace_tag_manifests(container_repository, dummy_manifest, tag_names)
deleted_tags = {}
 
tag_names.each do |name|
digest = container_repository.client.put_tag(container_repository.path, name, dummy_manifest)
next unless digest
 
deleted_tags << name
tag_digests << digest
deleted_tags[name] = digest
end
 
# make sure the digests are the same (it should always be)
tag_digests.uniq!
digests = deleted_tags.values.uniq
 
# rubocop: disable CodeReuse/ActiveRecord
Gitlab::Sentry.track_exception(ArgumentError.new('multiple tag digests')) if tag_digests.many?
Gitlab::Sentry.track_exception(ArgumentError.new('multiple tag digests')) if digests.many?
 
# Deletes the dummy image
# All created tag digests are the same since they all have the same dummy image.
# a single delete is sufficient to remove all tags with it
if tag_digests.any? && container_repository.delete_tag_by_digest(tag_digests.first)
success(deleted: deleted_tags)
else
error('could not delete tags')
end
deleted_tags
end
end
end
Loading
Loading
Loading
Loading
@@ -8,5 +8,5 @@
= render_if_exists 'admin/application_settings/slack'
= render 'admin/application_settings/third_party_offers'
= render 'admin/application_settings/snowplow'
= render 'admin/application_settings/eks' if Feature.enabled?(:create_eks_clusters)
= render 'admin/application_settings/eks'
 
= render 'clusters/clusters/gcp/header'
- if @valid_gcp_token
= render 'clusters/clusters/gcp/form'
- elsif @authorize_url
= render 'clusters/clusters/gcp/signin_with_google_button'
- else
= render 'clusters/clusters/gcp/gcp_not_configured'
.signin-with-google
- create_account_link = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: 'https://accounts.google.com/SignUpWithoutGmail?service=cloudconsole&continue=https%3A%2F%2Fconsole.cloud.google.com%2Ffreetrial%3Futm_campaign%3D2018_cpanel%26utm_source%3Dgitlab%26utm_medium%3Dreferral' }
= link_to(image_tag('auth_buttons/signin_with_google.png', width: '191px', alt: _('Sign in with Google')), @authorize_url)
= s_('or %{link_start}create a new Google account%{link_end}').html_safe % { link_start: create_account_link, link_end: '</a>'.html_safe }
- breadcrumb_title _('Kubernetes')
- page_title _('Kubernetes Cluster')
- create_eks_enabled = Feature.enabled?(:create_eks_clusters)
- active_tab = local_assigns.fetch(:active_tab, 'create')
= javascript_include_tag 'https://apis.google.com/js/api.js'
 
Loading
Loading
@@ -14,21 +13,14 @@
%li.nav-item{ role: 'presentation' }
%a.nav-link{ href: '#create-cluster-pane', id: 'create-cluster-tab', class: active_when(active_tab == 'create'), data: { toggle: 'tab' }, role: 'tab' }
%span
- if create_eks_enabled
= create_new_cluster_label(provider: params[:provider])
- else
= create_new_cluster_label(provider: 'gcp')
= create_new_cluster_label(provider: params[:provider])
%li.nav-item{ role: 'presentation' }
%a.nav-link{ href: '#add-cluster-pane', id: 'add-cluster-tab', class: active_when(active_tab == 'add'), data: { toggle: 'tab' }, role: 'tab' }
%span Add existing cluster
 
.tab-content.gitlab-tab-content
- if create_eks_enabled
.tab-pane{ id: 'create-cluster-pane', class: active_when(active_tab == 'create'), role: 'tabpanel' }
= render new_cluster_partial(provider: params[:provider])
- else
.tab-pane{ id: 'create-cluster-pane', class: active_when(active_tab == 'create'), role: 'tabpanel' }
= render new_cluster_partial(provider: 'gcp')
.tab-pane{ id: 'create-cluster-pane', class: active_when(active_tab == 'create'), role: 'tabpanel' }
= render new_cluster_partial(provider: params[:provider])
 
.tab-pane{ id: 'add-cluster-pane', class: active_when(active_tab == 'add'), role: 'tabpanel' }
= render 'clusters/clusters/user/header'
Loading
Loading
---
title: Enable creating Amazon EKS clusters from GitLab
merge_request: 20333
author:
type: added
Loading
Loading
@@ -211,33 +211,9 @@ GitLab supports:
Before creating your first cluster on Amazon EKS with GitLab's integration,
make sure the following requirements are met:
 
- Self-managed GitLab instances have the `create_eks_clusters` feature flag enabled.
- An [Amazon Web Services](https://aws.amazon.com/) account is set up and you are able to log in.
- You have permissions to manage IAM resources.
 
##### Enable the `create_eks_clusters` feature flag **(CORE ONLY)**
Self-managed instances must have the feature flag `create_eks_clusters` enabled to create
EKS clusters. To enable EKS cluster creation, ask a GitLab administrator with Rails console access
to run the following command:
```ruby
Feature.enable(:create_eks_clusters)
```
To have it enabled for a specific project only, ask a GitLab administrator to run the following
command using a Rails console:
```ruby
Feature.enable(:create_eks_clusters, Project.find_by_full_path('my_group/my_project'))
```
To have this feature disabled, ask a GitLab administrator to run the following command:
```ruby
Feature.disable(:create_eks_clusters)
```
##### Additional requirements for self-managed instances
 
If you are using a self-managed GitLab instance, GitLab must first
Loading
Loading
Loading
Loading
@@ -15948,9 +15948,6 @@ msgstr ""
msgid "Sign in via 2FA code"
msgstr ""
 
msgid "Sign in with Google"
msgstr ""
msgid "Sign in with Single Sign-On"
msgstr ""
 
Loading
Loading
@@ -21263,9 +21260,6 @@ msgstr ""
msgid "opened %{timeAgoString} by %{user}"
msgstr ""
 
msgid "or %{link_start}create a new Google account%{link_end}"
msgstr ""
msgid "out of %d total test"
msgid_plural "out of %d total tests"
msgstr[0] ""
Loading
Loading
Loading
Loading
@@ -84,29 +84,11 @@ describe Admin::ClustersController do
GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(key)
end
 
before do
stub_feature_flags(create_eks_clusters: false)
allow(SecureRandom).to receive(:hex).and_return(key)
end
it 'has authorize_url' do
get_new
expect(assigns(:authorize_url)).to include(key)
expect(session[session_key_for_redirect_uri]).to eq(new_admin_cluster_path)
end
context 'when create_eks_clusters feature flag is enabled' do
before do
stub_feature_flags(create_eks_clusters: true)
end
context 'when selected provider is gke and no valid gcp token exists' do
it 'redirects to gcp authorize_url' do
get_new
context 'when selected provider is gke and no valid gcp token exists' do
it 'redirects to gcp authorize_url' do
get_new
 
expect(response).to redirect_to(assigns(:authorize_url))
end
expect(response).to redirect_to(assigns(:authorize_url))
end
end
end
Loading
Loading
Loading
Loading
@@ -97,29 +97,15 @@ describe Groups::ClustersController do
end
 
before do
stub_feature_flags(create_eks_clusters: false)
allow(SecureRandom).to receive(:hex).and_return(key)
end
 
it 'has authorize_url' do
it 'redirects to gcp authorize_url' do
go
 
expect(assigns(:authorize_url)).to include(key)
expect(session[session_key_for_redirect_uri]).to eq(new_group_cluster_path(group))
end
context 'when create_eks_clusters feature flag is enabled' do
before do
stub_feature_flags(create_eks_clusters: true)
end
context 'when selected provider is gke and no valid gcp token exists' do
it 'redirects to gcp authorize_url' do
go
expect(response).to redirect_to(assigns(:authorize_url))
end
end
expect(session[session_key_for_redirect_uri]).to eq(new_group_cluster_path(group, provider: :gcp))
expect(response).to redirect_to(assigns(:authorize_url))
end
end
 
Loading
Loading
Loading
Loading
@@ -95,29 +95,15 @@ describe Projects::ClustersController do
end
 
before do
stub_feature_flags(create_eks_clusters: false)
allow(SecureRandom).to receive(:hex).and_return(key)
end
 
it 'has authorize_url' do
it 'redirects to gcp authorize_url' do
go
 
expect(assigns(:authorize_url)).to include(key)
expect(session[session_key_for_redirect_uri]).to eq(new_project_cluster_path(project))
end
context 'when create_eks_clusters feature flag is enabled' do
before do
stub_feature_flags(create_eks_clusters: true)
end
context 'when selected provider is gke and no valid gcp token exists' do
it 'redirects to gcp authorize_url' do
go
expect(response).to redirect_to(assigns(:authorize_url))
end
end
expect(session[session_key_for_redirect_uri]).to eq(new_project_cluster_path(project, provider: :gcp))
expect(response).to redirect_to(assigns(:authorize_url))
end
end
 
Loading
Loading
Loading
Loading
@@ -18,8 +18,6 @@ describe 'Gcp Cluster', :js do
let(:project_id) { 'test-project-1234' }
 
before do
stub_feature_flags(create_eks_clusters: false)
allow_any_instance_of(Projects::ClustersController)
.to receive(:token_in_session).and_return('token')
allow_any_instance_of(Projects::ClustersController)
Loading
Loading
@@ -31,7 +29,8 @@ describe 'Gcp Cluster', :js do
visit project_clusters_path(project)
 
click_link 'Add Kubernetes cluster'
click_link 'Create new Cluster on GKE'
click_link 'Create new Cluster'
click_link 'Google GKE'
end
 
context 'when user filled form with valid parameters' do
Loading
Loading
@@ -147,21 +146,6 @@ describe 'Gcp Cluster', :js do
end
end
 
context 'when user has not signed with Google' do
before do
stub_feature_flags(create_eks_clusters: false)
visit project_clusters_path(project)
click_link 'Add Kubernetes cluster'
click_link 'Create new Cluster on GKE'
end
it 'user sees a login page' do
expect(page).to have_css('.signin-with-google')
expect(page).to have_link('Google account')
end
end
context 'when a user cannot edit the environment scope' do
before do
visit project_clusters_path(project)
Loading
Loading
@@ -177,7 +161,6 @@ describe 'Gcp Cluster', :js do
 
context 'when user has not dismissed GCP signup offer' do
before do
stub_feature_flags(create_eks_clusters: false)
visit project_clusters_path(project)
end
 
Loading
Loading
@@ -190,18 +173,10 @@ describe 'Gcp Cluster', :js do
 
expect(page).to have_css('.gcp-signup-offer')
end
it 'user sees offer on cluster GCP login page' do
click_link 'Add Kubernetes cluster'
click_link 'Create new Cluster on GKE'
expect(page).to have_css('.gcp-signup-offer')
end
end
 
context 'when user has dismissed GCP signup offer' do
before do
stub_feature_flags(create_eks_clusters: false)
visit project_clusters_path(project)
end
 
Loading
Loading
Loading
Loading
@@ -49,41 +49,20 @@ describe 'Clusters', :js do
end
end
 
context 'when user has not signed in Google' do
context 'user visits create cluster page' do
before do
stub_feature_flags(create_eks_clusters: false)
visit project_clusters_path(project)
 
click_link 'Add Kubernetes cluster'
click_link 'Create new Cluster on GKE'
click_link 'Create new Cluster'
end
 
it 'user sees a login page' do
expect(page).to have_css('.signin-with-google')
expect(page).to have_link('Google account')
it 'user sees a link to create a GKE cluster' do
expect(page).to have_link('Google GKE')
end
end
context 'when create_eks_clusters feature flag is enabled' do
before do
stub_feature_flags(create_eks_clusters: true)
end
context 'when user access create cluster page' do
before do
visit project_clusters_path(project)
 
click_link 'Add Kubernetes cluster'
click_link 'Create new Cluster'
end
it 'user sees a link to create a GKE cluster' do
expect(page).to have_link('Google GKE')
end
it 'user sees a link to create an EKS cluster' do
expect(page).to have_link('Amazon EKS')
end
it 'user sees a link to create an EKS cluster' do
expect(page).to have_link('Amazon EKS')
end
end
end
Loading
Loading
@@ -4,15 +4,14 @@ import diffModule from '~/diffs/store/modules';
import SettingsDropdown from '~/diffs/components/settings_dropdown.vue';
import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants';
 
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Diff settiings dropdown component', () => {
let vm;
let actions;
 
function createComponent(extendStore = () => {}) {
const localVue = createLocalVue();
localVue.use(Vuex);
const store = new Vuex.Store({
modules: {
diffs: {
Loading
Loading
@@ -26,9 +25,10 @@ describe('Diff settiings dropdown component', () => {
 
extendStore(store);
 
vm = mount(SettingsDropdown, {
vm = mount(localVue.extend(SettingsDropdown), {
localVue,
store,
sync: false,
});
}
 
Loading
Loading
import Vue from 'vue';
import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue';
import { shallowMount } from '@vue/test-utils';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { trimText } from 'spec/helpers/text_helper';
import { mockProject } from '../mock_data'; // can also use 'mockGroup', but not useful to test here
 
const createComponent = () => {
const Component = Vue.extend(frequentItemsListItemComponent);
return shallowMount(Component, {
propsData: {
itemId: mockProject.id,
itemName: mockProject.name,
namespace: mockProject.namespace,
webUrl: mockProject.webUrl,
avatarUrl: mockProject.avatarUrl,
},
});
};
const localVue = createLocalVue();
 
describe('FrequentItemsListItemComponent', () => {
let wrapper;
let vm;
beforeEach(() => {
wrapper = createComponent();
 
({ vm } = wrapper);
});
const createComponent = (props = {}) => {
wrapper = shallowMount(localVue.extend(frequentItemsListItemComponent), {
propsData: {
itemId: mockProject.id,
itemName: mockProject.name,
namespace: mockProject.namespace,
webUrl: mockProject.webUrl,
avatarUrl: mockProject.avatarUrl,
...props,
},
sync: false,
localVue,
});
};
 
afterEach(() => {
vm.$destroy();
wrapper.destroy();
wrapper = null;
});
 
describe('computed', () => {
describe('hasAvatar', () => {
it('should return `true` or `false` if whether avatar is present or not', () => {
wrapper.setProps({ avatarUrl: 'path/to/avatar.png' });
createComponent({ avatarUrl: 'path/to/avatar.png' });
 
expect(vm.hasAvatar).toBe(true);
expect(wrapper.vm.hasAvatar).toBe(true);
});
 
wrapper.setProps({ avatarUrl: null });
it('should return `false` if avatar is not present', () => {
createComponent({ avatarUrl: null });
 
expect(vm.hasAvatar).toBe(false);
expect(wrapper.vm.hasAvatar).toBe(false);
});
});
 
describe('highlightedItemName', () => {
it('should enclose part of project name in <b> & </b> which matches with `matcher` prop', () => {
wrapper.setProps({ matcher: 'lab' });
createComponent({ matcher: 'lab' });
 
expect(wrapper.find('.js-frequent-items-item-title').html()).toContain(
'<b>L</b><b>a</b><b>b</b>',
Loading
Loading
@@ -55,7 +53,7 @@ describe('FrequentItemsListItemComponent', () => {
});
 
it('should return project name as it is if `matcher` is not available', () => {
wrapper.setProps({ matcher: null });
createComponent({ matcher: null });
 
expect(trimText(wrapper.find('.js-frequent-items-item-title').text())).toBe(
mockProject.name,
Loading
Loading
@@ -65,13 +63,13 @@ describe('FrequentItemsListItemComponent', () => {
 
describe('truncatedNamespace', () => {
it('should truncate project name from namespace string', () => {
wrapper.setProps({ namespace: 'platform / nokia-3310' });
createComponent({ namespace: 'platform / nokia-3310' });
 
expect(trimText(wrapper.find('.js-frequent-items-item-namespace').text())).toBe('platform');
});
 
it('should truncate namespace string from the middle if it includes more than two groups in path', () => {
wrapper.setProps({
createComponent({
namespace: 'platform / hardware / broadcom / Wifi Group / Mobile Chipset / nokia-3310',
});
 
Loading
Loading
@@ -84,6 +82,8 @@ describe('FrequentItemsListItemComponent', () => {
 
describe('template', () => {
it('should render component element', () => {
createComponent();
expect(wrapper.classes()).toContain('frequent-items-list-item-container');
expect(wrapper.findAll('a').length).toBe(1);
expect(wrapper.findAll('.frequent-items-item-avatar-container').length).toBe(1);
Loading
Loading
import Vue from 'vue';
import searchComponent from '~/frequent_items/components/frequent_items_search_input.vue';
import eventHub from '~/frequent_items/event_hub';
import { shallowMount } from '@vue/test-utils';
import { shallowMount, createLocalVue } from '@vue/test-utils';
 
const createComponent = (namespace = 'projects') => {
const Component = Vue.extend(searchComponent);
const localVue = createLocalVue();
 
return shallowMount(Component, { propsData: { namespace } });
};
const createComponent = (namespace = 'projects') =>
shallowMount(localVue.extend(searchComponent), {
propsData: { namespace },
localVue,
sync: false,
});
 
describe('FrequentItemsSearchInputComponent', () => {
let wrapper;
Loading
Loading
@@ -40,7 +42,7 @@ describe('FrequentItemsSearchInputComponent', () => {
spyOn(eventHub, '$on');
const vmX = createComponent().vm;
 
Vue.nextTick(() => {
localVue.nextTick(() => {
expect(eventHub.$on).toHaveBeenCalledWith(
`${vmX.namespace}-dropdownOpen`,
jasmine.any(Function),
Loading
Loading
@@ -58,7 +60,7 @@ describe('FrequentItemsSearchInputComponent', () => {
vmX.$mount();
vmX.$destroy();
 
Vue.nextTick(() => {
localVue.nextTick(() => {
expect(eventHub.$off).toHaveBeenCalledWith(
`${vmX.namespace}-dropdownOpen`,
jasmine.any(Function),
Loading
Loading
import { shallowMount } from '@vue/test-utils';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import Form from '~/jobs/components/manual_variables_form.vue';
 
const localVue = createLocalVue();
describe('Manual Variables Form', () => {
let wrapper;
const requiredProps = {
action: {
path: '/play',
Loading
Loading
@@ -14,8 +17,10 @@ describe('Manual Variables Form', () => {
};
 
const factory = (props = {}) => {
wrapper = shallowMount(Form, {
wrapper = shallowMount(localVue.extend(Form), {
propsData: props,
localVue,
sync: false,
});
};
 
Loading
Loading
@@ -23,8 +28,15 @@ describe('Manual Variables Form', () => {
factory(requiredProps);
});
 
afterEach(() => {
wrapper.destroy();
afterEach(done => {
// The component has a `nextTick` callback after some events so we need
// to wait for those to finish before destroying.
setImmediate(() => {
wrapper.destroy();
wrapper = null;
done();
});
});
 
it('renders empty form with correct placeholders', () => {
Loading
Loading
@@ -71,7 +83,7 @@ describe('Manual Variables Form', () => {
});
 
describe('when deleting a variable', () => {
it('removes the variable row', () => {
beforeEach(done => {
wrapper.vm.variables = [
{
key: 'new key',
Loading
Loading
@@ -80,6 +92,10 @@ describe('Manual Variables Form', () => {
},
];
 
wrapper.vm.$nextTick(done);
});
it('removes the variable row', () => {
wrapper.find(GlButton).vm.$emit('click');
 
expect(wrapper.vm.variables.length).toBe(0);
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment