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

Add latest changes from gitlab-org/gitlab@12-5-stable-ee

parent 4d477238
No related branches found
No related tags found
No related merge requests found
Showing
with 748 additions and 210 deletions
import Vue from 'vue';
import ContributorsGraphs from './components/contributors.vue';
import store from './stores';
export default () => {
const el = document.querySelector('.js-contributors-graph');
if (!el) return null;
return new Vue({
el,
store,
render(createElement) {
return createElement(ContributorsGraphs, {
props: {
endpoint: el.dataset.projectGraphPath,
branch: el.dataset.projectBranch,
},
});
},
});
};
import axios from '~/lib/utils/axios_utils';
export default {
fetchChartData(endpoint) {
return axios.get(endpoint);
},
};
import flash from '~/flash';
import { __ } from '~/locale';
import service from '../services/contributors_service';
import * as types from './mutation_types';
export const fetchChartData = ({ commit }, endpoint) => {
commit(types.SET_LOADING_STATE, true);
return service
.fetchChartData(endpoint)
.then(res => res.data)
.then(data => {
commit(types.SET_CHART_DATA, data);
commit(types.SET_LOADING_STATE, false);
})
.catch(() => flash(__('An error occurred while loading chart data')));
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
export const showChart = state => Boolean(!state.loading && state.chartData);
export const parsedData = state => {
const byAuthor = {};
const total = {};
state.chartData.forEach(({ date, author_name, author_email }) => {
total[date] = total[date] ? total[date] + 1 : 1;
const authorData = byAuthor[author_name];
if (!authorData) {
byAuthor[author_name] = {
email: author_email.toLowerCase(),
commits: 1,
dates: {
[date]: 1,
},
};
} else {
authorData.commits += 1;
authorData.dates[date] = authorData.dates[date] ? authorData.dates[date] + 1 : 1;
}
});
return {
total,
byAuthor,
};
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import mutations from './mutations';
import * as getters from './getters';
import * as actions from './actions';
Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
actions,
mutations,
getters,
state: state(),
});
export default createStore();
export const SET_CHART_DATA = 'SET_CHART_DATA';
export const SET_LOADING_STATE = 'SET_LOADING_STATE';
export const SET_ACTIVE_BRANCH = 'SET_ACTIVE_BRANCH';
import * as types from './mutation_types';
export default {
[types.SET_LOADING_STATE](state, value) {
state.loading = value;
},
[types.SET_CHART_DATA](state, chartData) {
Object.assign(state, {
chartData,
});
},
[types.SET_ACTIVE_BRANCH](state, branch) {
Object.assign(state, {
branch,
});
},
};
export default () => ({
loading: false,
chartData: null,
branch: 'master',
});
import { getMonthNames } from '~/lib/utils/datetime_utility';
/**
* Converts provided string to date and returns formatted value as a year for date in January and month name for the rest
* @param {String}
* @returns {String} - formatted value
*
* xAxisLabelFormatter('01-12-2019') will return '2019'
* xAxisLabelFormatter('02-12-2019') will return 'Feb'
* xAxisLabelFormatter('07-12-2019') will return 'Jul'
*/
export const xAxisLabelFormatter = val => {
const date = new Date(val);
const month = date.getUTCMonth();
const year = date.getUTCFullYear();
return month === 0 ? `${year}` : getMonthNames(true)[month];
};
/**
* Formats provided date to YYYY-MM-DD format
* @param {Date}
* @returns {String} - formatted value
*/
export const dateFormatter = date => {
const year = date.getUTCFullYear();
const month = date.getUTCMonth();
const day = date.getUTCDate();
return `${year}-${`0${month + 1}`.slice(-2)}-${`0${day}`.slice(-2)}`;
};
Loading
Loading
@@ -2,14 +2,19 @@
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
import { GlIcon } from '@gitlab/ui';
 
const findItem = (items, valueProp, value) => items.find(item => item[valueProp] === value);
const toArray = value => [].concat(value);
const itemsProp = (items, prop) => items.map(item => item[prop]);
const defaultSearchFn = (searchQuery, labelProp) => item =>
item[labelProp].toLowerCase().indexOf(searchQuery) > -1;
 
export default {
components: {
DropdownButton,
DropdownSearchInput,
DropdownHiddenInput,
GlIcon,
},
props: {
fieldName: {
Loading
Loading
@@ -28,7 +33,7 @@ export default {
default: '',
},
value: {
type: [Object, String],
type: [Object, Array, String],
required: false,
default: () => null,
},
Loading
Loading
@@ -72,6 +77,11 @@ export default {
required: false,
default: false,
},
multiple: {
type: Boolean,
required: false,
default: false,
},
errorMessage: {
type: String,
required: false,
Loading
Loading
@@ -90,12 +100,11 @@ export default {
searchFn: {
type: Function,
required: false,
default: searchQuery => item => item.name.toLowerCase().indexOf(searchQuery) > -1,
default: defaultSearchFn,
},
},
data() {
return {
selectedItem: findItem(this.items, this.value),
searchQuery: '',
};
},
Loading
Loading
@@ -109,36 +118,52 @@ export default {
return this.disabledText;
}
 
if (!this.selectedItem) {
if (!this.selectedItems.length) {
return this.placeholder;
}
 
return this.selectedItemLabel;
return this.selectedItemsLabels;
},
results() {
if (!this.items) {
return [];
}
return this.items.filter(this.searchFn(this.searchQuery));
return this.getItemsOrEmptyList().filter(this.searchFn(this.searchQuery, this.labelProperty));
},
selectedItemLabel() {
return this.selectedItem && this.selectedItem[this.labelProperty];
selectedItems() {
const valueProp = this.valueProperty;
const valueList = toArray(this.value);
const items = this.getItemsOrEmptyList();
return items.filter(item => valueList.some(value => item[valueProp] === value));
},
selectedItemValue() {
return (this.selectedItem && this.selectedItem[this.valueProperty]) || '';
selectedItemsLabels() {
return itemsProp(this.selectedItems, this.labelProperty).join(', ');
},
},
watch: {
value(value) {
this.selectedItem = findItem(this.items, this.valueProperty, value);
selectedItemsValues() {
return itemsProp(this.selectedItems, this.valueProperty).join(', ');
},
},
methods: {
select(item) {
this.selectedItem = item;
getItemsOrEmptyList() {
return this.items || [];
},
selectSingle(item) {
this.$emit('input', item[this.valueProperty]);
},
selectMultiple(item) {
const value = toArray(this.value);
const itemValue = item[this.valueProperty];
const itemValueIndex = value.indexOf(itemValue);
if (itemValueIndex > -1) {
value.splice(itemValueIndex, 1);
} else {
value.push(itemValue);
}
this.$emit('input', value);
},
isSelected(item) {
return this.selectedItems.includes(item);
},
},
};
</script>
Loading
Loading
@@ -146,7 +171,7 @@ export default {
<template>
<div>
<div class="js-gcp-machine-type-dropdown dropdown">
<dropdown-hidden-input :name="fieldName" :value="selectedItemValue" />
<dropdown-hidden-input :name="fieldName" :value="selectedItemsValues" />
<dropdown-button
:class="{ 'border-danger': hasErrors }"
:is-disabled="disabled"
Loading
Loading
@@ -158,15 +183,28 @@ export default {
<div class="dropdown-content">
<ul>
<li v-if="!results.length">
<span class="js-empty-text menu-item">
{{ emptyText }}
</span>
<span class="js-empty-text menu-item">{{ emptyText }}</span>
</li>
<li v-for="item in results" :key="item.id">
<button class="js-dropdown-item" type="button" @click.prevent="select(item)">
<slot name="item" :item="item">
{{ item.name }}
</slot>
<button
v-if="multiple"
class="js-dropdown-item d-flex align-items-center"
type="button"
@click.stop.prevent="selectMultiple(item)"
>
<gl-icon
:class="[{ invisible: !isSelected(item) }, 'mr-1']"
name="mobile-issue-close"
/>
<slot name="item" :item="item">{{ item.name }}</slot>
</button>
<button
v-else
class="js-dropdown-item"
type="button"
@click.prevent="selectSingle(item)"
>
<slot name="item" :item="item">{{ item.name }}</slot>
</button>
</li>
</ul>
Loading
Loading
@@ -182,8 +220,7 @@ export default {
'text-muted': !hasErrors,
},
]"
>{{ errorMessage }}</span
>
{{ errorMessage }}
</span>
</div>
</template>
<script>
import { mapState } from 'vuex';
import ServiceCredentialsForm from './service_credentials_form.vue';
import EksClusterConfigurationForm from './eks_cluster_configuration_form.vue';
 
Loading
Loading
@@ -16,14 +17,37 @@ export default {
type: String,
required: true,
},
accountAndExternalIdsHelpPath: {
type: String,
required: true,
},
createRoleArnHelpPath: {
type: String,
required: true,
},
externalLinkIcon: {
type: String,
required: true,
},
},
computed: {
...mapState(['hasCredentials']),
},
};
</script>
<template>
<div class="js-create-eks-cluster">
<eks-cluster-configuration-form
v-if="hasCredentials"
:gitlab-managed-cluster-help-path="gitlabManagedClusterHelpPath"
:kubernetes-integration-help-path="kubernetesIntegrationHelpPath"
:external-link-icon="externalLinkIcon"
/>
<service-credentials-form
v-else
:create-role-arn-help-path="createRoleArnHelpPath"
:account-and-external-ids-help-path="accountAndExternalIdsHelpPath"
:external-link-icon="externalLinkIcon"
/>
</div>
</template>
Loading
Loading
@@ -4,8 +4,8 @@ import { sprintf, s__ } from '~/locale';
import _ from 'underscore';
import { GlFormInput, GlFormCheckbox } from '@gitlab/ui';
import ClusterFormDropdown from './cluster_form_dropdown.vue';
import RegionDropdown from './region_dropdown.vue';
import { KUBERNETES_VERSIONS } from '../constants';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
 
const { mapState: mapRolesState, mapActions: mapRolesActions } = createNamespacedHelpers('roles');
const { mapState: mapRegionsState, mapActions: mapRegionsActions } = createNamespacedHelpers(
Loading
Loading
@@ -22,13 +22,17 @@ const {
mapState: mapSecurityGroupsState,
mapActions: mapSecurityGroupsActions,
} = createNamespacedHelpers('securityGroups');
const {
mapState: mapInstanceTypesState,
mapActions: mapInstanceTypesActions,
} = createNamespacedHelpers('instanceTypes');
 
export default {
components: {
ClusterFormDropdown,
RegionDropdown,
GlFormInput,
GlFormCheckbox,
LoadingButton,
},
props: {
gitlabManagedClusterHelpPath: {
Loading
Loading
@@ -39,6 +43,10 @@ export default {
type: String,
required: true,
},
externalLinkIcon: {
type: String,
required: true,
},
},
computed: {
...mapState([
Loading
Loading
@@ -51,7 +59,10 @@ export default {
'selectedSubnet',
'selectedRole',
'selectedSecurityGroup',
'selectedInstanceType',
'nodeCount',
'gitlabManagedCluster',
'isCreatingCluster',
]),
...mapRolesState({
roles: 'items',
Loading
Loading
@@ -83,6 +94,11 @@ export default {
isLoadingSecurityGroups: 'isLoadingItems',
loadingSecurityGroupsError: 'loadingItemsError',
}),
...mapInstanceTypesState({
instanceTypes: 'items',
isLoadingInstanceTypes: 'isLoadingItems',
loadingInstanceTypesError: 'loadingItemsError',
}),
kubernetesVersions() {
return KUBERNETES_VERSIONS;
},
Loading
Loading
@@ -98,6 +114,27 @@ export default {
securityGroupDropdownDisabled() {
return !this.selectedVpc;
},
createClusterButtonDisabled() {
return (
!this.clusterName ||
!this.environmentScope ||
!this.kubernetesVersion ||
!this.selectedRegion ||
!this.selectedKeyPair ||
!this.selectedVpc ||
!this.selectedSubnet ||
!this.selectedRole ||
!this.selectedSecurityGroup ||
!this.selectedInstanceType ||
!this.nodeCount ||
this.isCreatingCluster
);
},
createClusterButtonLabel() {
return this.isCreatingCluster
? s__('ClusterIntegration|Creating Kubernetes cluster')
: s__('ClusterIntegration|Create Kubernetes cluster');
},
kubernetesIntegrationHelpText() {
const escapedUrl = _.escape(this.kubernetesIntegrationHelpPath);
 
Loading
Loading
@@ -115,11 +152,26 @@ export default {
roleDropdownHelpText() {
return sprintf(
s__(
'ClusterIntegration|Select the IAM Role to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role name, first create one on %{startLink}Amazon Web Services%{endLink}.',
'ClusterIntegration|Select the IAM Role to allow Amazon EKS and the Kubernetes control plane to manage AWS resources on your behalf. To use a new role name, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}.',
),
{
startLink:
'<a href="https://docs.aws.amazon.com/eks/latest/userguide/getting-started-console.html#role-create" target="_blank" rel="noopener noreferrer">',
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>',
},
false,
);
},
regionsDropdownHelpText() {
return sprintf(
s__(
'ClusterIntegration|Learn more about %{startLink}Regions %{externalLinkIcon}%{endLink}.',
),
{
startLink:
'<a href="https://console.aws.amazon.com/iam/home?#roles" target="_blank" rel="noopener noreferrer">',
'<a href="https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/" target="_blank" rel="noopener noreferrer">',
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>',
},
false,
Loading
Loading
@@ -128,11 +180,12 @@ export default {
keyPairDropdownHelpText() {
return sprintf(
s__(
'ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{startLink}Amazon Web Services%{endLink}.',
'ClusterIntegration|Select the key pair name that will be used to create EC2 nodes. To use a new key pair name, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}.',
),
{
startLink:
'<a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#having-ec2-create-your-key-pair" target="_blank" rel="noopener noreferrer">',
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>',
},
false,
Loading
Loading
@@ -141,11 +194,12 @@ export default {
vpcDropdownHelpText() {
return sprintf(
s__(
'ClusterIntegration|Select a VPC to use for your EKS Cluster resources. To use a new VPC, first create one on %{startLink}Amazon Web Services%{endLink}.',
'ClusterIntegration|Select a VPC to use for your EKS Cluster resources. To use a new VPC, first create one on %{startLink}Amazon Web Services %{externalLinkIcon} %{endLink}.',
),
{
startLink:
'<a href="https://console.aws.amazon.com/vpc/home?#vpc" target="_blank" rel="noopener noreferrer">',
'<a href="https://docs.aws.amazon.com/eks/latest/userguide/getting-started-console.html#vpc-create" target="_blank" rel="noopener noreferrer">',
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>',
},
false,
Loading
Loading
@@ -154,11 +208,12 @@ export default {
subnetDropdownHelpText() {
return sprintf(
s__(
'ClusterIntegration|Choose the %{startLink}subnets%{endLink} in your VPC where your worker nodes will run.',
'ClusterIntegration|Choose the %{startLink}subnets %{externalLinkIcon} %{endLink} in your VPC where your worker nodes will run.',
),
{
startLink:
'<a href="https://console.aws.amazon.com/vpc/home?#subnets" target="_blank" rel="noopener noreferrer">',
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>',
},
false,
Loading
Loading
@@ -167,11 +222,26 @@ export default {
securityGroupDropdownHelpText() {
return sprintf(
s__(
'ClusterIntegration|Choose the %{startLink}security groups%{endLink} to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets.',
'ClusterIntegration|Choose the %{startLink}security group %{externalLinkIcon} %{endLink} to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets.',
),
{
startLink:
'<a href="https://console.aws.amazon.com/vpc/home?#securityGroups" target="_blank" rel="noopener noreferrer">',
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>',
},
false,
);
},
instanceTypesDropdownHelpText() {
return sprintf(
s__(
'ClusterIntegration|Choose the worker node %{startLink}instance type %{externalLinkIcon} %{endLink}.',
),
{
startLink:
'<a href="https://aws.amazon.com/ec2/instance-types" target="_blank" rel="noopener noreferrer">',
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>',
},
false,
Loading
Loading
@@ -195,9 +265,12 @@ export default {
mounted() {
this.fetchRegions();
this.fetchRoles();
this.fetchInstanceTypes();
},
methods: {
...mapActions([
'createCluster',
'signOut',
'setClusterName',
'setEnvironmentScope',
'setKubernetesVersion',
Loading
Loading
@@ -207,6 +280,8 @@ export default {
'setRole',
'setKeyPair',
'setSecurityGroup',
'setInstanceType',
'setNodeCount',
'setGitlabManagedCluster',
]),
...mapRegionsActions({ fetchRegions: 'fetchItems' }),
Loading
Loading
@@ -215,15 +290,22 @@ export default {
...mapRolesActions({ fetchRoles: 'fetchItems' }),
...mapKeyPairsActions({ fetchKeyPairs: 'fetchItems' }),
...mapSecurityGroupsActions({ fetchSecurityGroups: 'fetchItems' }),
...mapInstanceTypesActions({ fetchInstanceTypes: 'fetchItems' }),
setRegionAndFetchVpcsAndKeyPairs(region) {
this.setRegion({ region });
this.setVpc({ vpc: null });
this.setKeyPair({ keyPair: null });
this.setSubnet({ subnet: null });
this.setSecurityGroup({ securityGroup: null });
this.fetchVpcs({ region });
this.fetchKeyPairs({ region });
},
setVpcAndFetchSubnets(vpc) {
this.setVpc({ vpc });
this.fetchSubnets({ vpc });
this.fetchSecurityGroups({ vpc });
this.setSubnet({ subnet: null });
this.setSecurityGroup({ securityGroup: null });
this.fetchSubnets({ vpc, region: this.selectedRegion });
this.fetchSecurityGroups({ vpc, region: this.selectedRegion });
},
},
};
Loading
Loading
@@ -233,7 +315,12 @@ export default {
<h2>
{{ s__('ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster') }}
</h2>
<p v-html="kubernetesIntegrationHelpText"></p>
<div class="mb-3" v-html="kubernetesIntegrationHelpText"></div>
<div class="mb-3">
<button class="btn btn-link js-sign-out" @click.prevent="signOut()">
{{ s__('ClusterIntegration|Select a different AWS role') }}
</button>
</div>
<div class="form-group">
<label class="label-bold" for="eks-cluster-name">{{
s__('ClusterIntegration|Kubernetes cluster name')
Loading
Loading
@@ -273,7 +360,7 @@ export default {
<cluster-form-dropdown
field-id="eks-role"
field-name="eks-role"
:input="selectedRole"
:value="selectedRole"
:items="roles"
:loading="isLoadingRoles"
:loading-text="s__('ClusterIntegration|Loading IAM Roles')"
Loading
Loading
@@ -288,13 +375,21 @@ export default {
</div>
<div class="form-group">
<label class="label-bold" for="eks-role">{{ s__('ClusterIntegration|Region') }}</label>
<region-dropdown
<cluster-form-dropdown
field-id="eks-region"
field-name="eks-region"
:value="selectedRegion"
:regions="regions"
:error="loadingRegionsError"
:items="regions"
:loading="isLoadingRegions"
:loading-text="s__('ClusterIntegration|Loading Regions')"
:placeholder="s__('ClusterIntergation|Select a region')"
:search-field-placeholder="s__('ClusterIntegration|Search regions')"
:empty-text="s__('ClusterIntegration|No region found')"
:has-errors="Boolean(loadingRegionsError)"
:error-message="s__('ClusterIntegration|Could not load regions from your AWS account')"
@input="setRegionAndFetchVpcsAndKeyPairs($event)"
/>
<p class="form-text text-muted" v-html="regionsDropdownHelpText"></p>
</div>
<div class="form-group">
<label class="label-bold" for="eks-key-pair">{{
Loading
Loading
@@ -303,7 +398,7 @@ export default {
<cluster-form-dropdown
field-id="eks-key-pair"
field-name="eks-key-pair"
:input="selectedKeyPair"
:value="selectedKeyPair"
:items="keyPairs"
:disabled="keyPairDropdownDisabled"
:disabled-text="s__('ClusterIntegration|Select a region to choose a Key Pair')"
Loading
Loading
@@ -323,7 +418,7 @@ export default {
<cluster-form-dropdown
field-id="eks-vpc"
field-name="eks-vpc"
:input="selectedVpc"
:value="selectedVpc"
:items="vpcs"
:loading="isLoadingVpcs"
:disabled="vpcDropdownDisabled"
Loading
Loading
@@ -339,11 +434,12 @@ export default {
<p class="form-text text-muted" v-html="vpcDropdownHelpText"></p>
</div>
<div class="form-group">
<label class="label-bold" for="eks-role">{{ s__('ClusterIntegration|Subnet') }}</label>
<label class="label-bold" for="eks-role">{{ s__('ClusterIntegration|Subnets') }}</label>
<cluster-form-dropdown
field-id="eks-subnet"
field-name="eks-subnet"
:input="selectedSubnet"
multiple
:value="selectedSubnet"
:items="subnets"
:loading="isLoadingSubnets"
:disabled="subnetDropdownDisabled"
Loading
Loading
@@ -360,12 +456,12 @@ export default {
</div>
<div class="form-group">
<label class="label-bold" for="eks-security-group">{{
s__('ClusterIntegration|Security groups')
s__('ClusterIntegration|Security group')
}}</label>
<cluster-form-dropdown
field-id="eks-security-group"
field-name="eks-security-group"
:input="selectedSecurityGroup"
:value="selectedSecurityGroup"
:items="securityGroups"
:loading="isLoadingSecurityGroups"
:disabled="securityGroupDropdownDisabled"
Loading
Loading
@@ -382,6 +478,39 @@ export default {
/>
<p class="form-text text-muted" v-html="securityGroupDropdownHelpText"></p>
</div>
<div class="form-group">
<label class="label-bold" for="eks-instance-type">{{
s__('ClusterIntegration|Instance type')
}}</label>
<cluster-form-dropdown
field-id="eks-instance-type"
field-name="eks-instance-type"
:value="selectedInstanceType"
:items="instanceTypes"
:loading="isLoadingInstanceTypes"
:loading-text="s__('ClusterIntegration|Loading instance types')"
:placeholder="s__('ClusterIntergation|Select an instance type')"
:search-field-placeholder="s__('ClusterIntegration|Search instance types')"
:empty-text="s__('ClusterIntegration|No instance type found')"
:has-errors="Boolean(loadingInstanceTypesError)"
:error-message="s__('ClusterIntegration|Could not load instance types')"
@input="setInstanceType({ instanceType: $event })"
/>
<p class="form-text text-muted" v-html="instanceTypesDropdownHelpText"></p>
</div>
<div class="form-group">
<label class="label-bold" for="eks-node-count">{{
s__('ClusterIntegration|Number of nodes')
}}</label>
<gl-form-input
id="eks-node-count"
type="number"
min="1"
step="1"
:value="nodeCount"
@input="setNodeCount({ nodeCount: $event })"
/>
</div>
<div class="form-group">
<gl-form-checkbox
:checked="gitlabManagedCluster"
Loading
Loading
@@ -390,5 +519,14 @@ export default {
>
<p class="form-text text-muted" v-html="gitlabManagedHelpText"></p>
</div>
<div class="form-group">
<loading-button
class="js-create-cluster btn-success"
:disabled="createClusterButtonDisabled"
:loading="isCreatingCluster"
:label="createClusterButtonLabel"
@click="createCluster()"
/>
</div>
</form>
</template>
<script>
import { sprintf, s__ } from '~/locale';
import ClusterFormDropdown from './cluster_form_dropdown.vue';
export default {
components: {
ClusterFormDropdown,
},
props: {
regions: {
type: Array,
required: false,
default: () => [],
},
loading: {
type: Boolean,
required: false,
default: false,
},
error: {
type: Object,
required: false,
default: null,
},
},
computed: {
hasErrors() {
return Boolean(this.error);
},
helpText() {
return sprintf(
s__('ClusterIntegration|Learn more about %{startLink}Regions%{endLink}.'),
{
startLink:
'<a href="https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/" target="_blank" rel="noopener noreferrer">',
endLink: '</a>',
},
false,
);
},
},
};
</script>
<template>
<div>
<cluster-form-dropdown
field-id="eks-region"
field-name="eks-region"
:items="regions"
:loading="loading"
:loading-text="s__('ClusterIntegration|Loading Regions')"
:placeholder="s__('ClusterIntergation|Select a region')"
:search-field-placeholder="s__('ClusterIntegration|Search regions')"
:empty-text="s__('ClusterIntegration|No region found')"
:has-errors="hasErrors"
:error-message="s__('ClusterIntegration|Could not load regions from your AWS account')"
v-bind="$attrs"
v-on="$listeners"
/>
<p class="form-text text-muted" v-html="helpText"></p>
</div>
</template>
<script>
import { GlFormInput } from '@gitlab/ui';
import { sprintf, s__, __ } from '~/locale';
import _ from 'underscore';
import { mapState, mapActions } from 'vuex';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
export default {
components: {
GlFormInput,
LoadingButton,
ClipboardButton,
},
props: {
accountAndExternalIdsHelpPath: {
type: String,
required: true,
},
createRoleArnHelpPath: {
type: String,
required: true,
},
externalLinkIcon: {
type: String,
required: true,
},
},
data() {
return {
roleArn: '',
};
},
computed: {
...mapState(['accountId', 'externalId', 'isCreatingRole', 'createRoleError']),
submitButtonDisabled() {
return this.isCreatingRole || !this.roleArn;
},
submitButtonLabel() {
return this.isCreatingRole
? __('Authenticating')
: s__('ClusterIntegration|Authenticate with AWS');
},
accountAndExternalIdsHelpText() {
const escapedUrl = _.escape(this.accountAndExternalIdsHelpPath);
return sprintf(
s__(
'ClusterIntegration|Create a provision role on %{startAwsLink}Amazon Web Services %{externalLinkIcon}%{endLink} using the account and external ID above. %{startMoreInfoLink}More information%{endLink}',
),
{
startAwsLink:
'<a href="https://console.aws.amazon.com/iam/home?#roles" target="_blank" rel="noopener noreferrer">',
startMoreInfoLink: `<a href="${escapedUrl}" target="_blank" rel="noopener noreferrer">`,
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>',
},
false,
);
},
provisionRoleArnHelpText() {
const escapedUrl = _.escape(this.createRoleArnHelpPath);
return sprintf(
s__(
'ClusterIntegration|The Amazon Resource Name (ARN) associated with your role. If you do not have a provision role, first create one on %{startAwsLink}Amazon Web Services %{externalLinkIcon}%{endLink} using the above account and external IDs. %{startMoreInfoLink}More information%{endLink}',
),
{
startAwsLink:
'<a href="https://console.aws.amazon.com/iam/home?#roles" target="_blank" rel="noopener noreferrer">',
startMoreInfoLink: `<a href="${escapedUrl}" target="_blank" rel="noopener noreferrer">`,
externalLinkIcon: this.externalLinkIcon,
endLink: '</a>',
},
false,
);
},
},
methods: {
...mapActions(['createRole']),
},
};
</script>
<template>
<form name="service-credentials-form"></form>
<form name="service-credentials-form" @submit.prevent="createRole({ roleArn, externalId })">
<h2>{{ s__('ClusterIntegration|Authenticate with Amazon Web Services') }}</h2>
<p>
{{
s__(
'ClusterIntegration|You must grant access to your organization’s AWS resources in order to create a new EKS cluster. To grant access, create a provision role using the account and external ID below and provide us the ARN.',
)
}}
</p>
<div v-if="createRoleError" class="js-invalid-credentials bs-callout bs-callout-danger">
{{ createRoleError }}
</div>
<div class="form-row">
<div class="form-group col-md-6">
<label for="gitlab-account-id">{{ __('Account ID') }}</label>
<div class="input-group">
<gl-form-input id="gitlab-account-id" type="text" readonly :value="accountId" />
<div class="input-group-append">
<clipboard-button
:text="accountId"
:title="__('Copy Account ID to clipboard')"
class="input-group-text js-copy-account-id-button"
/>
</div>
</div>
</div>
<div class="form-group col-md-6">
<label for="eks-external-id">{{ __('External ID') }}</label>
<div class="input-group">
<gl-form-input id="eks-external-id" type="text" readonly :value="externalId" />
<div class="input-group-append">
<clipboard-button
:text="externalId"
:title="__('Copy External ID to clipboard')"
class="input-group-text js-copy-external-id-button"
/>
</div>
</div>
</div>
<div class="col-12 mb-3 mt-n3">
<p class="form-text text-muted" v-html="accountAndExternalIdsHelpText"></p>
</div>
</div>
<div class="form-group">
<label for="eks-provision-role-arn">{{ s__('ClusterIntegration|Provision Role ARN') }}</label>
<gl-form-input id="eks-provision-role-arn" v-model="roleArn" />
<p class="form-text text-muted" v-html="provisionRoleArnHelpText"></p>
</div>
<loading-button
class="js-submit-service-credentials btn-success"
type="submit"
:disabled="submitButtonDisabled"
:loading="isCreatingRole"
:label="submitButtonLabel"
/>
</form>
</template>
// eslint-disable-next-line import/prefer-default-export
export const KUBERNETES_VERSIONS = [
{ name: '1.14', value: '1.14' },
{ name: '1.13', value: '1.13' },
{ name: '1.12', value: '1.12' },
{ name: '1.11', value: '1.11' },
];
export const KUBERNETES_VERSIONS = [{ name: '1.14', value: '1.14' }];
import Vue from 'vue';
import Vuex from 'vuex';
import { parseBoolean } from '~/lib/utils/common_utils';
import CreateEksCluster from './components/create_eks_cluster.vue';
import createStore from './store';
 
Vue.use(Vuex);
 
export default el => {
const { gitlabManagedClusterHelpPath, kubernetesIntegrationHelpPath } = el.dataset;
const {
gitlabManagedClusterHelpPath,
kubernetesIntegrationHelpPath,
accountAndExternalIdsHelpPath,
createRoleArnHelpPath,
getRolesPath,
getRegionsPath,
getKeyPairsPath,
getVpcsPath,
getSubnetsPath,
getSecurityGroupsPath,
getInstanceTypesPath,
externalId,
accountId,
hasCredentials,
createRolePath,
createClusterPath,
signOutPath,
externalLinkIcon,
} = el.dataset;
 
return new Vue({
el,
store: createStore(),
store: createStore({
initialState: {
hasCredentials: parseBoolean(hasCredentials),
externalId,
accountId,
createRolePath,
createClusterPath,
signOutPath,
},
apiPaths: {
getRolesPath,
getRegionsPath,
getKeyPairsPath,
getVpcsPath,
getSubnetsPath,
getSecurityGroupsPath,
getInstanceTypesPath,
},
}),
components: {
CreateEksCluster,
},
Loading
Loading
@@ -19,6 +57,9 @@ export default el => {
props: {
gitlabManagedClusterHelpPath,
kubernetesIntegrationHelpPath,
accountAndExternalIdsHelpPath,
createRoleArnHelpPath,
externalLinkIcon,
},
});
},
Loading
Loading
import EC2 from 'aws-sdk/clients/ec2';
import IAM from 'aws-sdk/clients/iam';
export const fetchRoles = () => {
const iam = new IAM();
return iam
.listRoles()
.promise()
.then(({ Roles: roles }) => roles.map(({ RoleName: name }) => ({ name })));
};
export const fetchKeyPairs = () => {
const ec2 = new EC2();
return ec2
.describeKeyPairs()
.promise()
.then(({ KeyPairs: keyPairs }) => keyPairs.map(({ RegionName: name }) => ({ name })));
};
export const fetchRegions = () => {
const ec2 = new EC2();
return ec2
.describeRegions()
.promise()
.then(({ Regions: regions }) =>
regions.map(({ RegionName: name }) => ({
name,
value: name,
import axios from '~/lib/utils/axios_utils';
export default apiPaths => ({
fetchRoles() {
return axios
.get(apiPaths.getRolesPath)
.then(({ data: { roles } }) =>
roles.map(({ role_name: name, arn: value }) => ({ name, value })),
);
},
fetchKeyPairs({ region }) {
return axios
.get(apiPaths.getKeyPairsPath, { params: { region } })
.then(({ data: { key_pairs: keyPairs } }) =>
keyPairs.map(({ key_name }) => ({ name: key_name, value: key_name })),
);
},
fetchRegions() {
return axios.get(apiPaths.getRegionsPath).then(({ data: { regions } }) =>
regions.map(({ region_name }) => ({
name: region_name,
value: region_name,
})),
);
};
export const fetchVpcs = () => {
const ec2 = new EC2();
return ec2
.describeVpcs()
.promise()
.then(({ Vpcs: vpcs }) =>
vpcs.map(({ VpcId: id }) => ({
value: id,
name: id,
},
fetchVpcs({ region }) {
return axios.get(apiPaths.getVpcsPath, { params: { region } }).then(({ data: { vpcs } }) =>
vpcs.map(({ vpc_id }) => ({
value: vpc_id,
name: vpc_id,
})),
);
};
export const fetchSubnets = ({ vpc }) => {
const ec2 = new EC2();
return ec2
.describeSubnets({
Filters: [
{
Name: 'vpc-id',
Values: [vpc],
},
],
})
.promise()
.then(({ Subnets: subnets }) => subnets.map(({ SubnetId: id }) => ({ id, name: id })));
};
export const fetchSecurityGroups = ({ vpc }) => {
const ec2 = new EC2();
return ec2
.describeSecurityGroups({
Filters: [
{
Name: 'vpc-id',
Values: [vpc],
},
],
})
.promise()
.then(({ SecurityGroups: securityGroups }) =>
securityGroups.map(({ GroupName: name, GroupId: value }) => ({ name, value })),
);
};
export default () => {};
},
fetchSubnets({ vpc, region }) {
return axios
.get(apiPaths.getSubnetsPath, { params: { vpc_id: vpc, region } })
.then(({ data: { subnets } }) =>
subnets.map(({ subnet_id }) => ({ name: subnet_id, value: subnet_id })),
);
},
fetchSecurityGroups({ vpc, region }) {
return axios
.get(apiPaths.getSecurityGroupsPath, { params: { vpc_id: vpc, region } })
.then(({ data: { security_groups: securityGroups } }) =>
securityGroups.map(({ group_name: name, group_id: value }) => ({ name, value })),
);
},
fetchInstanceTypes() {
return axios
.get(apiPaths.getInstanceTypesPath)
.then(({ data: { instance_types: instanceTypes } }) =>
instanceTypes.map(({ instance_type_name }) => ({
name: instance_type_name,
value: instance_type_name,
})),
);
},
});
import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
const getErrorMessage = data => {
const errorKey = Object.keys(data)[0];
return data[errorKey][0];
};
 
export const setClusterName = ({ commit }, payload) => {
commit(types.SET_CLUSTER_NAME, payload);
Loading
Loading
@@ -12,6 +20,68 @@ export const setKubernetesVersion = ({ commit }, payload) => {
commit(types.SET_KUBERNETES_VERSION, payload);
};
 
export const createRole = ({ dispatch, state: { createRolePath } }, payload) => {
dispatch('requestCreateRole');
return axios
.post(createRolePath, {
role_arn: payload.roleArn,
role_external_id: payload.externalId,
})
.then(() => dispatch('createRoleSuccess'))
.catch(error => dispatch('createRoleError', { error }));
};
export const requestCreateRole = ({ commit }) => {
commit(types.REQUEST_CREATE_ROLE);
};
export const createRoleSuccess = ({ commit }) => {
commit(types.CREATE_ROLE_SUCCESS);
};
export const createRoleError = ({ commit }, payload) => {
commit(types.CREATE_ROLE_ERROR, payload);
};
export const createCluster = ({ dispatch, state }) => {
dispatch('requestCreateCluster');
return axios
.post(state.createClusterPath, {
name: state.clusterName,
environment_scope: state.environmentScope,
managed: state.gitlabManagedCluster,
provider_aws_attributes: {
region: state.selectedRegion,
vpc_id: state.selectedVpc,
subnet_ids: state.selectedSubnet,
role_arn: state.selectedRole,
key_name: state.selectedKeyPair,
security_group_id: state.selectedSecurityGroup,
instance_type: state.selectedInstanceType,
num_nodes: state.nodeCount,
},
})
.then(({ headers: { location } }) => dispatch('createClusterSuccess', location))
.catch(({ response: { data } }) => {
dispatch('createClusterError', data);
});
};
export const requestCreateCluster = ({ commit }) => {
commit(types.REQUEST_CREATE_CLUSTER);
};
export const createClusterSuccess = (_, location) => {
window.location.assign(location);
};
export const createClusterError = ({ commit }, error) => {
commit(types.CREATE_CLUSTER_ERROR, error);
createFlash(getErrorMessage(error));
};
export const setRegion = ({ commit }, payload) => {
commit(types.SET_REGION, payload);
};
Loading
Loading
@@ -40,4 +110,16 @@ export const setGitlabManagedCluster = ({ commit }, payload) => {
commit(types.SET_GITLAB_MANAGED_CLUSTER, payload);
};
 
export default () => {};
export const setInstanceType = ({ commit }, payload) => {
commit(types.SET_INSTANCE_TYPE, payload);
};
export const setNodeCount = ({ commit }, payload) => {
commit(types.SET_NODE_COUNT, payload);
};
export const signOut = ({ commit, state: { signOutPath } }) =>
axios
.delete(signOutPath)
.then(() => commit(types.SIGN_OUT))
.catch(({ response: { data } }) => createFlash(getErrorMessage(data)));
Loading
Loading
@@ -6,14 +6,16 @@ import state from './state';
 
import clusterDropdownStore from './cluster_dropdown';
 
import * as awsServices from '../services/aws_services_facade';
import awsServicesFactory from '../services/aws_services_facade';
 
const createStore = () =>
new Vuex.Store({
const createStore = ({ initialState, apiPaths }) => {
const awsServices = awsServicesFactory(apiPaths);
return new Vuex.Store({
actions,
getters,
mutations,
state: state(),
state: Object.assign(state(), initialState),
modules: {
roles: {
namespaced: true,
Loading
Loading
@@ -39,7 +41,12 @@ const createStore = () =>
namespaced: true,
...clusterDropdownStore(awsServices.fetchSecurityGroups),
},
instanceTypes: {
namespaced: true,
...clusterDropdownStore(awsServices.fetchInstanceTypes),
},
},
});
};
 
export default createStore;
Loading
Loading
@@ -7,4 +7,13 @@ export const SET_KEY_PAIR = 'SET_KEY_PAIR';
export const SET_SUBNET = 'SET_SUBNET';
export const SET_ROLE = 'SET_ROLE';
export const SET_SECURITY_GROUP = 'SET_SECURITY_GROUP';
export const SET_INSTANCE_TYPE = 'SET_INSTANCE_TYPE';
export const SET_NODE_COUNT = 'SET_NODE_COUNT';
export const SET_GITLAB_MANAGED_CLUSTER = 'SET_GITLAB_MANAGED_CLUSTER';
export const REQUEST_CREATE_ROLE = 'REQUEST_CREATE_ROLE';
export const CREATE_ROLE_SUCCESS = 'CREATE_ROLE_SUCCESS';
export const CREATE_ROLE_ERROR = 'CREATE_ROLE_ERROR';
export const SIGN_OUT = 'SIGN_OUT';
export const REQUEST_CREATE_CLUSTER = 'REQUEST_CREATE_CLUSTER';
export const CREATE_CLUSTER_SUCCESS = 'CREATE_CLUSTER_SUCCESS';
export const CREATE_CLUSTER_ERROR = 'CREATE_CLUSTER_ERROR';
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