Commit fac814c9 authored by Enrique Alcántara's avatar Enrique Alcántara Committed by Mike Greiling

Complete create EKS cluster workflow

Implement the following parts to complete the create EKS
cluster workflow in the FE:

- Implement instance type and number of nodes fields.
- Use gitlab AWS proxy API to pre-fill form dropdowns
- Allow to select multiple subnets.
- Implement create cluster action.
- Implement logout action.
parent d52a715f
...@@ -2,14 +2,19 @@ ...@@ -2,14 +2,19 @@
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue'; import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue'; import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.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 { export default {
components: { components: {
DropdownButton, DropdownButton,
DropdownSearchInput, DropdownSearchInput,
DropdownHiddenInput, DropdownHiddenInput,
GlIcon,
}, },
props: { props: {
fieldName: { fieldName: {
...@@ -28,7 +33,7 @@ export default { ...@@ -28,7 +33,7 @@ export default {
default: '', default: '',
}, },
value: { value: {
type: [Object, String], type: [Object, Array, String],
required: false, required: false,
default: () => null, default: () => null,
}, },
...@@ -72,6 +77,11 @@ export default { ...@@ -72,6 +77,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
multiple: {
type: Boolean,
required: false,
default: false,
},
errorMessage: { errorMessage: {
type: String, type: String,
required: false, required: false,
...@@ -90,12 +100,11 @@ export default { ...@@ -90,12 +100,11 @@ export default {
searchFn: { searchFn: {
type: Function, type: Function,
required: false, required: false,
default: searchQuery => item => item.name.toLowerCase().indexOf(searchQuery) > -1, default: defaultSearchFn,
}, },
}, },
data() { data() {
return { return {
selectedItem: findItem(this.items, this.value),
searchQuery: '', searchQuery: '',
}; };
}, },
...@@ -109,36 +118,52 @@ export default { ...@@ -109,36 +118,52 @@ export default {
return this.disabledText; return this.disabledText;
} }
if (!this.selectedItem) { if (!this.selectedItems.length) {
return this.placeholder; return this.placeholder;
} }
return this.selectedItemLabel; return this.selectedItemsLabels;
}, },
results() { results() {
if (!this.items) { return this.getItemsOrEmptyList().filter(this.searchFn(this.searchQuery, this.labelProperty));
return [];
}
return this.items.filter(this.searchFn(this.searchQuery));
}, },
selectedItemLabel() { selectedItems() {
return this.selectedItem && this.selectedItem[this.labelProperty]; const valueProp = this.valueProperty;
const valueList = toArray(this.value);
const items = this.getItemsOrEmptyList();
return items.filter(item => valueList.some(value => item[valueProp] === value));
}, },
selectedItemValue() { selectedItemsLabels() {
return (this.selectedItem && this.selectedItem[this.valueProperty]) || ''; return itemsProp(this.selectedItems, this.labelProperty).join(', ');
}, },
}, selectedItemsValues() {
watch: { return itemsProp(this.selectedItems, this.valueProperty).join(', ');
value(value) {
this.selectedItem = findItem(this.items, this.valueProperty, value);
}, },
}, },
methods: { methods: {
select(item) { getItemsOrEmptyList() {
this.selectedItem = item; return this.items || [];
},
selectSingle(item) {
this.$emit('input', item[this.valueProperty]); 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> </script>
...@@ -146,7 +171,7 @@ export default { ...@@ -146,7 +171,7 @@ export default {
<template> <template>
<div> <div>
<div class="js-gcp-machine-type-dropdown dropdown"> <div class="js-gcp-machine-type-dropdown dropdown">
<dropdown-hidden-input :name="fieldName" :value="selectedItemValue" /> <dropdown-hidden-input :name="fieldName" :value="selectedItemsValues" />
<dropdown-button <dropdown-button
:class="{ 'border-danger': hasErrors }" :class="{ 'border-danger': hasErrors }"
:is-disabled="disabled" :is-disabled="disabled"
...@@ -158,15 +183,28 @@ export default { ...@@ -158,15 +183,28 @@ export default {
<div class="dropdown-content"> <div class="dropdown-content">
<ul> <ul>
<li v-if="!results.length"> <li v-if="!results.length">
<span class="js-empty-text menu-item"> <span class="js-empty-text menu-item">{{ emptyText }}</span>
{{ emptyText }}
</span>
</li> </li>
<li v-for="item in results" :key="item.id"> <li v-for="item in results" :key="item.id">
<button class="js-dropdown-item" type="button" @click.prevent="select(item)"> <button
<slot name="item" :item="item"> v-if="multiple"
{{ item.name }} class="js-dropdown-item d-flex align-items-center"
</slot> 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> </button>
</li> </li>
</ul> </ul>
...@@ -182,8 +220,7 @@ export default { ...@@ -182,8 +220,7 @@ export default {
'text-muted': !hasErrors, 'text-muted': !hasErrors,
}, },
]" ]"
>{{ errorMessage }}</span
> >
{{ errorMessage }}
</span>
</div> </div>
</template> </template>
...@@ -41,6 +41,7 @@ export default { ...@@ -41,6 +41,7 @@ export default {
v-if="hasCredentials" v-if="hasCredentials"
:gitlab-managed-cluster-help-path="gitlabManagedClusterHelpPath" :gitlab-managed-cluster-help-path="gitlabManagedClusterHelpPath"
:kubernetes-integration-help-path="kubernetesIntegrationHelpPath" :kubernetes-integration-help-path="kubernetesIntegrationHelpPath"
:external-link-icon="externalLinkIcon"
/> />
<service-credentials-form <service-credentials-form
v-else v-else
......
<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>
...@@ -131,7 +131,7 @@ export default { ...@@ -131,7 +131,7 @@ export default {
<p class="form-text text-muted" v-html="provisionRoleArnHelpText"></p> <p class="form-text text-muted" v-html="provisionRoleArnHelpText"></p>
</div> </div>
<loading-button <loading-button
class="js-submit-service-credentials" class="js-submit-service-credentials btn-success"
type="submit" type="submit"
:disabled="submitButtonDisabled" :disabled="submitButtonDisabled"
:loading="isCreatingRole" :loading="isCreatingRole"
......
// eslint-disable-next-line import/prefer-default-export // eslint-disable-next-line import/prefer-default-export
export const KUBERNETES_VERSIONS = [ export const KUBERNETES_VERSIONS = [{ name: '1.14', value: '1.14' }];
{ name: '1.14', value: '1.14' },
{ name: '1.13', value: '1.13' },
{ name: '1.12', value: '1.12' },
{ name: '1.11', value: '1.11' },
];
...@@ -12,10 +12,19 @@ export default el => { ...@@ -12,10 +12,19 @@ export default el => {
kubernetesIntegrationHelpPath, kubernetesIntegrationHelpPath,
accountAndExternalIdsHelpPath, accountAndExternalIdsHelpPath,
createRoleArnHelpPath, createRoleArnHelpPath,
getRolesPath,
getRegionsPath,
getKeyPairsPath,
getVpcsPath,
getSubnetsPath,
getSecurityGroupsPath,
getInstanceTypesPath,
externalId, externalId,
accountId, accountId,
hasCredentials, hasCredentials,
createRolePath, createRolePath,
createClusterPath,
signOutPath,
externalLinkIcon, externalLinkIcon,
} = el.dataset; } = el.dataset;
...@@ -27,6 +36,17 @@ export default el => { ...@@ -27,6 +36,17 @@ export default el => {
externalId, externalId,
accountId, accountId,
createRolePath, createRolePath,
createClusterPath,
signOutPath,
},
apiPaths: {
getRolesPath,
getRegionsPath,
getKeyPairsPath,
getVpcsPath,
getSubnetsPath,
getSecurityGroupsPath,
getInstanceTypesPath,
}, },
}), }),
components: { components: {
......
import EC2 from 'aws-sdk/clients/ec2'; import axios from '~/lib/utils/axios_utils';
import IAM from 'aws-sdk/clients/iam';
export default apiPaths => ({
export const fetchRoles = () => { fetchRoles() {
const iam = new IAM(); return axios
.get(apiPaths.getRolesPath)
return iam .then(({ data: { roles } }) =>
.listRoles() roles.map(({ role_name: name, arn: value }) => ({ name, value })),
.promise() );
.then(({ Roles: roles }) => roles.map(({ RoleName: name }) => ({ name }))); },
}; fetchKeyPairs({ region }) {
return axios
export const fetchKeyPairs = () => { .get(apiPaths.getKeyPairsPath, { params: { region } })
const ec2 = new EC2(); .then(({ data: { key_pairs: keyPairs } }) =>
keyPairs.map(({ key_name }) => ({ name: key_name, value: key_name })),
return ec2 );
.describeKeyPairs() },
.promise() fetchRegions() {
.then(({ KeyPairs: keyPairs }) => keyPairs.map(({ RegionName: name }) => ({ name }))); return axios.get(apiPaths.getRegionsPath).then(({ data: { regions } }) =>
}; regions.map(({ region_name }) => ({
name: region_name,
export const fetchRegions = () => { value: region_name,
const ec2 = new EC2();
return ec2
.describeRegions()
.promise()
.then(({ Regions: regions }) =>
regions.map(({ RegionName: name }) => ({
name,
value: name,
})), })),
); );
}; },
fetchVpcs({ region }) {
export const fetchVpcs = () => { return axios.get(apiPaths.getVpcsPath, { params: { region } }).then(({ data: { vpcs } }) =>
const ec2 = new EC2(); vpcs.map(({ vpc_id }) => ({
value: vpc_id,
return ec2 name: vpc_id,
.describeVpcs()
.promise()
.then(({ Vpcs: vpcs }) =>
vpcs.map(({ VpcId: id }) => ({
value: id,
name: id,
})), })),
); );
}; },
fetchSubnets({ vpc, region }) {
export const fetchSubnets = ({ vpc }) => { return axios
const ec2 = new EC2(); .get(apiPaths.getSubnetsPath, { params: { vpc_id: vpc, region } })
.then(({ data: { subnets } }) =>
return ec2 subnets.map(({ subnet_id }) => ({ name: subnet_id, value: subnet_id })),
.describeSubnets({ );
Filters: [ },
{ fetchSecurityGroups({ vpc, region }) {
Name: 'vpc-id', return axios
Values: [vpc], .get(apiPaths.getSecurityGroupsPath, { params: { vpc_id: vpc, region } })
}, .then(({ data: { security_groups: securityGroups } }) =>
], securityGroups.map(({ group_name: name, group_id: value }) => ({ name, value })),
}) );
.promise() },
.then(({ Subnets: subnets }) => subnets.map(({ SubnetId: id }) => ({ id, name: id }))); fetchInstanceTypes() {
}; return axios
.get(apiPaths.getInstanceTypesPath)
export const fetchSecurityGroups = ({ vpc }) => { .then(({ data: { instance_types: instanceTypes } }) =>
const ec2 = new EC2(); instanceTypes.map(({ instance_type_name }) => ({
name: instance_type_name,
return ec2 value: instance_type_name,
.describeSecurityGroups({ })),
Filters: [ );
{ },
Name: 'vpc-id', });
Values: [vpc],
},
],
})
.promise()
.then(({ SecurityGroups: securityGroups }) =>
securityGroups.map(({ GroupName: name, GroupId: value }) => ({ name, value })),
);
};
export default () => {};
import * as types from './mutation_types'; import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils'; 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) => { export const setClusterName = ({ commit }, payload) => {
commit(types.SET_CLUSTER_NAME, payload); commit(types.SET_CLUSTER_NAME, payload);
...@@ -37,6 +44,44 @@ export const createRoleError = ({ commit }, payload) => { ...@@ -37,6 +44,44 @@ export const createRoleError = ({ commit }, payload) => {
commit(types.CREATE_ROLE_ERROR, 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) => { export const setRegion = ({ commit }, payload) => {
commit(types.SET_REGION, payload); commit(types.SET_REGION, payload);
}; };
...@@ -64,3 +109,17 @@ export const setSecurityGroup = ({ commit }, payload) => { ...@@ -64,3 +109,17 @@ export const setSecurityGroup = ({ commit }, payload) => {
export const setGitlabManagedCluster = ({ commit }, payload) => { export const setGitlabManagedCluster = ({ commit }, payload) => {
commit(types.SET_GITLAB_MANAGED_CLUSTER, payload); commit(types.SET_GITLAB_MANAGED_CLUSTER, payload);
}; };
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)));
...@@ -6,10 +6,12 @@ import state from './state'; ...@@ -6,10 +6,12 @@ import state from './state';
import clusterDropdownStore from './cluster_dropdown'; import clusterDropdownStore from './cluster_dropdown';
import * as awsServices from '../services/aws_services_facade'; import awsServicesFactory from '../services/aws_services_facade';
const createStore = ({ initialState }) => const createStore = ({ initialState, apiPaths }) => {
new Vuex.Store({ const awsServices = awsServicesFactory(apiPaths);
return new Vuex.Store({
actions, actions,
getters, getters,
mutations, mutations,
...@@ -39,7 +41,12 @@ const createStore = ({ initialState }) => ...@@ -39,7 +41,12 @@ const createStore = ({ initialState }) =>
namespaced: true, namespaced: true,
...clusterDropdownStore(awsServices.fetchSecurityGroups), ...clusterDropdownStore(awsServices.fetchSecurityGroups),
}, },
instanceTypes: {
namespaced: true,
...clusterDropdownStore(awsServices.fetchInstanceTypes),
},
}, },
}); });
};
export default createStore; export default createStore;
...@@ -7,7 +7,13 @@ export const SET_KEY_PAIR = 'SET_KEY_PAIR'; ...@@ -7,7 +7,13 @@ export const SET_KEY_PAIR = 'SET_KEY_PAIR';
export const SET_SUBNET = 'SET_SUBNET'; export const SET_SUBNET = 'SET_SUBNET';
export const SET_ROLE = 'SET_ROLE'; export const SET_ROLE = 'SET_ROLE';
export const SET_SECURITY_GROUP = 'SET_SECURITY_GROUP'; 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 SET_GITLAB_MANAGED_CLUSTER = 'SET_GITLAB_MANAGED_CLUSTER';
export const REQUEST_CREATE_ROLE = 'REQUEST_CREATE_ROLE'; export const REQUEST_CREATE_ROLE = 'REQUEST_CREATE_ROLE';
export const CREATE_ROLE_SUCCESS = 'CREATE_ROLE_SUCCESS'; export const CREATE_ROLE_SUCCESS = 'CREATE_ROLE_SUCCESS';
export const CREATE_ROLE_ERROR = 'CREATE_ROLE_ERROR'; 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';
...@@ -28,6 +28,12 @@ export default { ...@@ -28,6 +28,12 @@ export default {
[types.SET_SECURITY_GROUP](state, { securityGroup }) { [types.SET_SECURITY_GROUP](state, { securityGroup }) {
state.selectedSecurityGroup = securityGroup; state.selectedSecurityGroup = securityGroup;
}, },
[types.SET_INSTANCE_TYPE](state, { instanceType }) {
state.selectedInstanceType = instanceType;
},
[types.SET_NODE_COUNT](state, { nodeCount }) {
state.nodeCount = nodeCount;
},
[types.SET_GITLAB_MANAGED_CLUSTER](state, { gitlabManagedCluster }) { [types.SET_GITLAB_MANAGED_CLUSTER](state, { gitlabManagedCluster }) {
state.gitlabManagedCluster = gitlabManagedCluster; state.gitlabManagedCluster = gitlabManagedCluster;
}, },
...@@ -46,4 +52,15 @@ export default { ...@@ -46,4 +52,15 @@ export default {
state.createRoleError = error; state.createRoleError = error;
state.hasCredentials = false; state.hasCredentials = false;
}, },
[types.REQUEST_CREATE_CLUSTER](state) {
state.isCreatingCluster = true;
state.createClusterError = null;
},
[types.CREATE_CLUSTER_ERROR](state, { error }) {
state.isCreatingCluster = false;
state.createClusterError = error;
},
[types.SIGN_OUT](state) {
state.hasCredentials = false;
},
}; };
import { KUBERNETES_VERSIONS } from '../constants'; import { KUBERNETES_VERSIONS } from '../constants';
const [{ value: kubernetesVersion }] = KUBERNETES_VERSIONS;
export default () => ({ export default () => ({
createRolePath: null, createRolePath: null,
...@@ -12,13 +14,18 @@ export default () => ({ ...@@ -12,13 +14,18 @@ export default () => ({
clusterName: '', clusterName: '',
environmentScope: '*', environmentScope: '*',
kubernetesVersion: [KUBERNETES_VERSIONS].value, kubernetesVersion,
selectedRegion: '', selectedRegion: '',
selectedRole: '', selectedRole: '',
selectedKeyPair: '', selectedKeyPair: '',
selectedVpc: '', selectedVpc: '',
selectedSubnet: '', selectedSubnet: '',
selectedSecurityGroup: '', selectedSecurityGroup: '',
selectedInstanceType: 'm5.large',
nodeCount: '3',
isCreatingCluster: false,
createClusterError: false,
gitlabManagedCluster: true, gitlabManagedCluster: true,
}); });
---
title: Create AWS EKS cluster
merge_request: 19578
author:
type: added
...@@ -3547,10 +3547,13 @@ msgstr "" ...@@ -3547,10 +3547,13 @@ msgstr ""
msgid "ClusterIntegration|Choose a prefix to be used for your namespaces. Defaults to your project path." msgid "ClusterIntegration|Choose a prefix to be used for your namespaces. Defaults to your project path."
msgstr "" msgstr ""
msgid "ClusterIntegration|Choose the %{startLink}security groups%{endLink} to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets." msgid "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."
msgstr "" msgstr ""
msgid "ClusterIntegration|Choose the %{startLink}subnets%{endLink} in your VPC where your worker nodes will run." msgid "ClusterIntegration|Choose the %{startLink}subnets %{externalLinkIcon} %{endLink} in your VPC where your worker nodes will run."
msgstr ""
msgid "ClusterIntegration|Choose the worker node %{startLink}instance type %{externalLinkIcon} %{endLink}."
msgstr "" msgstr ""
msgid "ClusterIntegration|Choose which applications to install on your Kubernetes cluster. Helm Tiller is required to install any of the following applications." msgid "ClusterIntegration|Choose which applications to install on your Kubernetes cluster. Helm Tiller is required to install any of the following applications."
...@@ -3607,6 +3610,9 @@ msgstr "" ...@@ -3607,6 +3610,9 @@ msgstr ""
msgid "ClusterIntegration|Could not load VPCs for the selected region" msgid "ClusterIntegration|Could not load VPCs for the selected region"
msgstr "" msgstr ""
msgid "ClusterIntegration|Could not load instance types"
msgstr ""
msgid "ClusterIntegration|Could not load regions from your AWS account" msgid "ClusterIntegration|Could not load regions from your AWS account"
msgstr "" msgstr ""
...@@ -3634,6 +3640,9 @@ msgstr "" ...@@ -3634,6 +3640,9 @@ msgstr ""
msgid "ClusterIntegration|Create new Cluster on GKE" msgid "ClusterIntegration|Create new Cluster on GKE"
msgstr "" msgstr ""
msgid "ClusterIntegration|Creating Kubernetes cluster"
msgstr ""
msgid "ClusterIntegration|Did you know?" msgid "ClusterIntegration|Did you know?"
msgstr "" msgstr ""
...@@ -3742,6 +3751,9 @@ msgstr "" ...@@ -3742,6 +3751,9 @@ msgstr ""
msgid "ClusterIntegration|Instance cluster" msgid "ClusterIntegration|Instance cluster"
msgstr "" msgstr ""
msgid "ClusterIntegration|Instance type"
msgstr ""
msgid "ClusterIntegration|Integrate Kubernetes cluster automation" msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr "" msgstr ""
...@@ -3817,7 +3829,7 @@ msgstr "" ...@@ -3817,7 +3829,7 @@ msgstr ""
msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}." msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}."
msgstr "" msgstr ""
msgid "ClusterIntegration|Learn more about %{startLink}Regions%{endLink}." msgid "ClusterIntegration|Learn more about %{startLink}Regions %{externalLinkIcon}%{endLink}."
msgstr "" msgstr ""
msgid "ClusterIntegration|Learn more about Kubernetes" msgid "ClusterIntegration|Learn more about Kubernetes"
...@@ -3844,6 +3856,9 @@ msgstr "" ...@@ -3844,6 +3856,9 @@ msgstr ""
msgid "ClusterIntegration|Loading VPCs" msgid "ClusterIntegration|Loading VPCs"
msgstr "" msgstr ""
msgid "ClusterIntegration|Loading instance types"
msgstr ""
msgid "ClusterIntegration|Loading security groups" msgid "ClusterIntegration|Loading security groups"
msgstr "" msgstr ""
...@@ -3868,6 +3883,9 @@ msgstr "" ...@@ -3868,6 +3883,9 @@ msgstr ""
msgid "ClusterIntegration|No VPCs found" msgid "ClusterIntegration|No VPCs found"
msgstr "" msgstr ""
msgid "ClusterIntegration|No instance type found"
msgstr ""
msgid "ClusterIntegration|No machine types matched your search" msgid "ClusterIntegration|No machine types matched your search"
msgstr "" msgstr ""
...@@ -3964,6 +3982,9 @@ msgstr "" ...@@ -3964,6 +3982,9 @@ msgstr ""
msgid "ClusterIntegration|Search VPCs" msgid "ClusterIntegration|Search VPCs"
msgstr "" msgstr ""
msgid "ClusterIntegration|Search instance types"
msgstr ""
msgid "ClusterIntegration|Search machine types" msgid "ClusterIntegration|Search machine types"
msgstr "" msgstr ""
...@@ -3982,7 +4003,7 @@ msgstr "" ...@@ -3982,7 +4003,7 @@ msgstr ""
msgid "ClusterIntegration|Search zones" msgid "ClusterIntegration|Search zones"
msgstr "" msgstr ""
msgid "ClusterIntegration|Security groups" msgid "ClusterIntegration|Security group"
msgstr "" msgstr ""
msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster"
...@@ -3994,7 +4015,10 @@ msgstr "" ...@@ -3994,7 +4015,10 @@ msgstr ""
msgid "ClusterIntegration|Select a VPC to choose a subnet" msgid "ClusterIntegration|Select a VPC to choose a subnet"
msgstr "" msgstr ""
msgid "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}." msgid "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}."
msgstr ""
msgid "ClusterIntegration|Select a different AWS role"
msgstr "" msgstr ""
msgid "ClusterIntegration|Select a region to choose a Key Pair" msgid "ClusterIntegration|Select a region to choose a Key Pair"
...@@ -4015,10 +4039,10 @@ msgstr "" ...@@ -4015,10 +4039,10 @@ msgstr ""
msgid "ClusterIntegration|Select project to choose zone" msgid "ClusterIntegration|Select project to choose zone"
msgstr "" msgstr ""
msgid "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}." msgid "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}."
msgstr "" msgstr ""
msgid "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}." msgid "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}."
msgstr "" msgstr ""
msgid "ClusterIntegration|Select zone" msgid "ClusterIntegration|Select zone"
...@@ -4054,7 +4078,7 @@ msgstr "" ...@@ -4054,7 +4078,7 @@ msgstr ""
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain." msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
msgstr "" msgstr ""
msgid "ClusterIntegration|Subnet" msgid "ClusterIntegration|Subnets"
msgstr "" msgstr ""
msgid "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}" msgid "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}"
...@@ -4174,6 +4198,9 @@ msgstr "" ...@@ -4174,6 +4198,9 @@ msgstr ""
msgid "ClusterIntergation|Select a subnet" msgid "ClusterIntergation|Select a subnet"
msgstr "" msgstr ""
msgid "ClusterIntergation|Select an instance type"
msgstr ""
msgid "ClusterIntergation|Select key pair" msgid "ClusterIntergation|Select key pair"
msgstr "" msgstr ""
......
...@@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils';
import ClusterFormDropdown from '~/create_cluster/eks_cluster/components/cluster_form_dropdown.vue'; import ClusterFormDropdown from '~/create_cluster/eks_cluster/components/cluster_form_dropdown.vue';
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue'; import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue'; import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue'; import { GlIcon } from '@gitlab/ui';
describe('ClusterFormDropdown', () => { describe('ClusterFormDropdown', () => {
let vm; let vm;
...@@ -41,24 +41,50 @@ describe('ClusterFormDropdown', () => { ...@@ -41,24 +41,50 @@ describe('ClusterFormDropdown', () => {
.trigger('click'); .trigger('click');
}); });
it('displays selected item label', () => { it('emits input event with selected item', () => {
expect(vm.find(DropdownButton).props('toggleText')).toEqual(secondItem.name); expect(vm.emitted('input')[0]).toEqual([secondItem.value]);
});
});
describe('when multiple items are selected', () => {
const value = [1];
beforeEach(() => {
vm.setProps({ items, multiple: true, value });
vm.findAll('.js-dropdown-item')
.at(0)
.trigger('click');
vm.findAll('.js-dropdown-item')
.at(1)
.trigger('click');
});
it('emits input event with an array of selected items', () => {
expect(vm.emitted('input')[1]).toEqual([[firstItem.value, secondItem.value]]);
});
});
describe('when multiple items can be selected', () => {
beforeEach(() => {
vm.setProps({ items, multiple: true, value: firstItem.value });
}); });
it('sets selected value to dropdown hidden input', () => { it('displays a checked GlIcon next to the item', () => {
expect(vm.find(DropdownHiddenInput).props('value')).toEqual(secondItem.value); expect(vm.find(GlIcon).is('.invisible')).toBe(false);
expect(vm.find(GlIcon).props('name')).toBe('mobile-issue-close');
}); });
}); });
describe('when an item is selected and has a custom label property', () => { describe('when an item is selected and has a custom label property', () => {
it('displays selected item custom label', () => { it('displays selected item custom label', () => {
const labelProperty = 'customLabel'; const labelProperty = 'customLabel';
const selectedItem = { [labelProperty]: 'Name' }; const label = 'Name';
const currentValue = 1;
const customLabelItems = [{ [labelProperty]: label, value: currentValue }];
vm.setProps({ labelProperty }); vm.setProps({ labelProperty, items: customLabelItems, value: currentValue });
vm.setData({ selectedItem });
expect(vm.find(DropdownButton).props('toggleText')).toEqual(selectedItem[labelProperty]); expect(vm.find(DropdownButton).props('toggleText')).toEqual(label);
}); });
}); });
......
...@@ -4,7 +4,6 @@ import Vue from 'vue'; ...@@ -4,7 +4,6 @@ import Vue from 'vue';
import { GlFormCheckbox } from '@gitlab/ui'; import { GlFormCheckbox } from '@gitlab/ui';
import EksClusterConfigurationForm from '~/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue'; import EksClusterConfigurationForm from '~/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue';
import RegionDropdown from '~/create_cluster/eks_cluster/components/region_dropdown.vue';
import eksClusterFormState from '~/create_cluster/eks_cluster/store/state'; import eksClusterFormState from '~/create_cluster/eks_cluster/store/state';
import clusterDropdownStoreState from '~/create_cluster/eks_cluster/store/cluster_dropdown/state'; import clusterDropdownStoreState from '~/create_cluster/eks_cluster/store/cluster_dropdown/state';
...@@ -21,17 +20,21 @@ describe('EksClusterConfigurationForm', () => { ...@@ -21,17 +20,21 @@ describe('EksClusterConfigurationForm', () => {
let subnetsState; let subnetsState;
let keyPairsState; let keyPairsState;
let securityGroupsState; let securityGroupsState;
let instanceTypesState;
let vpcsActions; let vpcsActions;
let rolesActions; let rolesActions;
let regionsActions; let regionsActions;
let subnetsActions; let subnetsActions;
let keyPairsActions; let keyPairsActions;
let securityGroupsActions; let securityGroupsActions;
let instanceTypesActions;
let vm; let vm;
beforeEach(() => { beforeEach(() => {
state = eksClusterFormState(); state = eksClusterFormState();
actions = { actions = {
signOut: jest.fn(),
createCluster: jest.fn(),
setClusterName: jest.fn(), setClusterName: jest.fn(),
setEnvironmentScope: jest.fn(), setEnvironmentScope: jest.fn(),
setKubernetesVersion: jest.fn(), setKubernetesVersion: jest.fn(),
...@@ -41,6 +44,8 @@ describe('EksClusterConfigurationForm', () => { ...@@ -41,6 +44,8 @@ describe('EksClusterConfigurationForm', () => {
setRole: jest.fn(), setRole: jest.fn(),
setKeyPair: jest.fn(), setKeyPair: jest.fn(),
setSecurityGroup: jest.fn(), setSecurityGroup: jest.fn(),
setInstanceType: jest.fn(),
setNodeCount: jest.fn(),
setGitlabManagedCluster: jest.fn(), setGitlabManagedCluster: jest.fn(),
}; };
regionsActions = { regionsActions = {
...@@ -61,6 +66,9 @@ describe('EksClusterConfigurationForm', () => { ...@@ -61,6 +66,9 @@ describe('EksClusterConfigurationForm', () => {
securityGroupsActions = { securityGroupsActions = {
fetchItems: jest.fn(), fetchItems: jest.fn(),
}; };
instanceTypesActions = {
fetchItems: jest.fn(),
};
rolesState = { rolesState = {
...clusterDropdownStoreState(), ...clusterDropdownStoreState(),
}; };
...@@ -79,6 +87,9 @@ describe('EksClusterConfigurationForm', () => { ...@@ -79,6 +87,9 @@ describe('EksClusterConfigurationForm', () => {
securityGroupsState = { securityGroupsState = {
...clusterDropdownStoreState(), ...clusterDropdownStoreState(),
}; };
instanceTypesState = {
...clusterDropdownStoreState(),
};
store = new Vuex.Store({ store = new Vuex.Store({
state, state,
actions, actions,
...@@ -113,6 +124,11 @@ describe('EksClusterConfigurationForm', () => { ...@@ -113,6 +124,11 @@ describe('EksClusterConfigurationForm', () => {
state: securityGroupsState, state: securityGroupsState,
actions: securityGroupsActions, actions: securityGroupsActions,
}, },
instanceTypes: {
namespaced: true,
state: instanceTypesState,
actions: instanceTypesActions,
},
}, },
}); });
}); });
...@@ -124,6 +140,7 @@ describe('EksClusterConfigurationForm', () => { ...@@ -124,6 +140,7 @@ describe('EksClusterConfigurationForm', () => {
propsData: { propsData: {
gitlabManagedClusterHelpPath: '', gitlabManagedClusterHelpPath: '',
kubernetesIntegrationHelpPath: '', kubernetesIntegrationHelpPath: '',
externalLinkIcon: '',
}, },
}); });
}); });
...@@ -132,15 +149,34 @@ describe('EksClusterConfigurationForm', () => { ...@@ -132,15 +149,34 @@ describe('EksClusterConfigurationForm', () => {
vm.destroy(); vm.destroy();
}); });
const setAllConfigurationFields = () => {
store.replaceState({
...state,
clusterName: 'cluster name',
environmentScope: '*',
selectedRegion: 'region',
selectedRole: 'role',
selectedKeyPair: 'key pair',
selectedVpc: 'vpc',
selectedSubnet: 'subnet',
selectedSecurityGroup: 'group',
selectedInstanceType: 'small-1',
});
};
const findSignOutButton = () => vm.find('.js-sign-out');
const findCreateClusterButton = () => vm.find('.js-create-cluster');
const findClusterNameInput = () => vm.find('[id=eks-cluster-name]'); const findClusterNameInput = () => vm.find('[id=eks-cluster-name]');
const findEnvironmentScopeInput = () => vm.find('[id=eks-environment-scope]'); const findEnvironmentScopeInput = () => vm.find('[id=eks-environment-scope]');
const findKubernetesVersionDropdown = () => vm.find('[field-id="eks-kubernetes-version"]'); const findKubernetesVersionDropdown = () => vm.find('[field-id="eks-kubernetes-version"]');
const findRegionDropdown = () => vm.find(RegionDropdown); const findRegionDropdown = () => vm.find('[field-id="eks-region"]');
const findKeyPairDropdown = () => vm.find('[field-id="eks-key-pair"]'); const findKeyPairDropdown = () => vm.find('[field-id="eks-key-pair"]');
const findVpcDropdown = () => vm.find('[field-id="eks-vpc"]'); const findVpcDropdown = () => vm.find('[field-id="eks-vpc"]');
const findSubnetDropdown = () => vm.find('[field-id="eks-subnet"]'); const findSubnetDropdown = () => vm.find('[field-id="eks-subnet"]');
const findRoleDropdown = () => vm.find('[field-id="eks-role"]'); const findRoleDropdown = () => vm.find('[field-id="eks-role"]');
const findSecurityGroupDropdown = () => vm.find('[field-id="eks-security-group"]'); const findSecurityGroupDropdown = () => vm.find('[field-id="eks-security-group"]');
const findInstanceTypeDropdown = () => vm.find('[field-id="eks-instance-type"');
const findNodeCountInput = () => vm.find('[id="eks-node-count"]');
const findGitlabManagedClusterCheckbox = () => vm.find(GlFormCheckbox); const findGitlabManagedClusterCheckbox = () => vm.find(GlFormCheckbox);
describe('when mounted', () => { describe('when mounted', () => {
...@@ -151,6 +187,15 @@ describe('EksClusterConfigurationForm', () => { ...@@ -151,6 +187,15 @@ describe('EksClusterConfigurationForm', () => {
it('fetches available roles', () => { it('fetches available roles', () => {
expect(rolesActions.fetchItems).toHaveBeenCalled(); expect(rolesActions.fetchItems).toHaveBeenCalled();
}); });
it('fetches available instance types', () => {
expect(instanceTypesActions.fetchItems).toHaveBeenCalled();
});
});
it('dispatches signOut action when sign out button is clicked', () => {
findSignOutButton().trigger('click');
expect(actions.signOut).toHaveBeenCalled();
}); });
it('sets isLoadingRoles to RoleDropdown loading property', () => { it('sets isLoadingRoles to RoleDropdown loading property', () => {
...@@ -180,11 +225,13 @@ describe('EksClusterConfigurationForm', () => { ...@@ -180,11 +225,13 @@ describe('EksClusterConfigurationForm', () => {
}); });
it('sets regions to RegionDropdown regions property', () => { it('sets regions to RegionDropdown regions property', () => {
expect(findRegionDropdown().props('regions')).toBe(regionsState.items); expect(findRegionDropdown().props('items')).toBe(regionsState.items);
}); });
it('sets loadingRegionsError to RegionDropdown error property', () => { it('sets loadingRegionsError to RegionDropdown error property', () => {
expect(findRegionDropdown().props('error')).toBe(regionsState.loadingItemsError); regionsState.loadingItemsError = new Error();
expect(findRegionDropdown().props('hasErrors')).toEqual(true);
}); });
it('disables KeyPairDropdown when no region is selected', () => { it('disables KeyPairDropdown when no region is selected', () => {
...@@ -329,6 +376,34 @@ describe('EksClusterConfigurationForm', () => { ...@@ -329,6 +376,34 @@ describe('EksClusterConfigurationForm', () => {
undefined, undefined,
); );
}); });
it('cleans selected vpc', () => {
expect(actions.setVpc).toHaveBeenCalledWith(expect.anything(), { vpc: null }, undefined);
});
it('cleans selected key pair', () => {
expect(actions.setKeyPair).toHaveBeenCalledWith(
expect.anything(),
{ keyPair: null },
undefined,
);
});
it('cleans selected subnet', () => {
expect(actions.setSubnet).toHaveBeenCalledWith(
expect.anything(),
{ subnet: null },
undefined,
);
});
it('cleans selected security group', () => {
expect(actions.setSecurityGroup).toHaveBeenCalledWith(
expect.anything(),
{ securityGroup: null },
undefined,
);
});
}); });
it('dispatches setClusterName when cluster name input changes', () => { it('dispatches setClusterName when cluster name input changes', () => {
...@@ -381,8 +456,10 @@ describe('EksClusterConfigurationForm', () => { ...@@ -381,8 +456,10 @@ describe('EksClusterConfigurationForm', () => {
describe('when vpc is selected', () => { describe('when vpc is selected', () => {
const vpc = { name: 'vpc-1' }; const vpc = { name: 'vpc-1' };
const region = 'east-1';
beforeEach(() => { beforeEach(() => {
state.selectedRegion = region;
findVpcDropdown().vm.$emit('input', vpc); findVpcDropdown().vm.$emit('input', vpc);
}); });
...@@ -390,14 +467,34 @@ describe('EksClusterConfigurationForm', () => { ...@@ -390,14 +467,34 @@ describe('EksClusterConfigurationForm', () => {
expect(actions.setVpc).toHaveBeenCalledWith(expect.anything(), { vpc }, undefined); expect(actions.setVpc).toHaveBeenCalledWith(expect.anything(), { vpc }, undefined);
}); });
it('cleans selected subnet', () => {
expect(actions.setSubnet).toHaveBeenCalledWith(
expect.anything(),
{ subnet: null },
undefined,
);
});
it('cleans selected security group', () => {
expect(actions.setSecurityGroup).toHaveBeenCalledWith(
expect.anything(),
{ securityGroup: null },
undefined,
);
});
it('dispatches fetchSubnets action', () => { it('dispatches fetchSubnets action', () => {
expect(subnetsActions.fetchItems).toHaveBeenCalledWith(expect.anything(), { vpc }, undefined); expect(subnetsActions.fetchItems).toHaveBeenCalledWith(
expect.anything(),
{ vpc, region },
undefined,
);
}); });
it('dispatches fetchSecurityGroups action', () => { it('dispatches fetchSecurityGroups action', () => {
expect(securityGroupsActions.fetchItems).toHaveBeenCalledWith( expect(securityGroupsActions.fetchItems).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
{ vpc }, { vpc, region },
undefined, undefined,
); );
}); });
...@@ -454,4 +551,76 @@ describe('EksClusterConfigurationForm', () => { ...@@ -454,4 +551,76 @@ describe('EksClusterConfigurationForm', () => {
); );
}); });
}); });
describe('when instance type is selected', () => {
const instanceType = 'small-1';
beforeEach(() => {
findInstanceTypeDropdown().vm.$emit('input', instanceType);
});
it('dispatches setInstanceType action', () => {
expect(actions.setInstanceType).toHaveBeenCalledWith(
expect.anything(),
{ instanceType },
undefined,
);
});
});
it('dispatches setNodeCount when node count input changes', () => {
const nodeCount = 5;
findNodeCountInput().vm.$emit('input', nodeCount);
expect(actions.setNodeCount).toHaveBeenCalledWith(expect.anything(), { nodeCount }, undefined);
});
describe('when all cluster configuration fields are set', () => {
beforeEach(() => {
setAllConfigurationFields();
});
it('enables create cluster button', () => {
expect(findCreateClusterButton().props('disabled')).toBe(false);
});
});
describe('when at least one cluster configuration field is not set', () => {
beforeEach(() => {
setAllConfigurationFields();
store.replaceState({
...state,
clusterName: '',
});
});
it('disables create cluster button', () => {
expect(findCreateClusterButton().props('disabled')).toBe(true);
});
});
describe('when isCreatingCluster', () => {
beforeEach(() => {
setAllConfigurationFields();
store.replaceState({
...state,
isCreatingCluster: true,
});
});
it('sets create cluster button as loading', () => {
expect(findCreateClusterButton().props('loading')).toBe(true);
});
});
describe('clicking create cluster button', () => {
beforeEach(() => {
findCreateClusterButton().vm.$emit('click');
});
it('dispatches createCluster action', () => {
expect(actions.createCluster).toHaveBeenCalled();
});
});
}); });
import { shallowMount } from '@vue/test-utils';
import ClusterFormDropdown from '~/create_cluster/eks_cluster/components/cluster_form_dropdown.vue';
import RegionDropdown from '~/create_cluster/eks_cluster/components/region_dropdown.vue';
describe('RegionDropdown', () => {
let vm;
const getClusterFormDropdown = () => vm.find(ClusterFormDropdown);
beforeEach(() => {
vm = shallowMount(RegionDropdown);
});
afterEach(() => vm.destroy());
it('renders a cluster-form-dropdown', () => {
expect(getClusterFormDropdown().exists()).toBe(true);
});
it('sets regions to cluster-form-dropdown items property', () => {
const regions = [{ name: 'basic' }];
vm.setProps({ regions });
expect(getClusterFormDropdown().props('items')).toEqual(regions);
});
it('sets a loading text', () => {
expect(getClusterFormDropdown().props('loadingText')).toEqual('Loading Regions');
});
it('sets a placeholder', () => {
expect(getClusterFormDropdown().props('placeholder')).toEqual('Select a region');
});
it('sets an empty results text', () => {
expect(getClusterFormDropdown().props('emptyText')).toEqual('No region found');
});
it('sets a search field placeholder', () => {
expect(getClusterFormDropdown().props('searchFieldPlaceholder')).toEqual('Search regions');
});
it('sets hasErrors property', () => {
vm.setProps({ error: {} });
expect(getClusterFormDropdown().props('hasErrors')).toEqual(true);
});
it('sets an error message', () => {
expect(getClusterFormDropdown().props('errorMessage')).toEqual(
'Could not load regions from your AWS account',
);
});
});
import awsServicesFacadeFactory from '~/create_cluster/eks_cluster/services/aws_services_facade';
import axios from '~/lib/utils/axios_utils';
import AxiosMockAdapter from 'axios-mock-adapter';
describe('awsServicesFacade', () => {
let apiPaths;
let axiosMock;
let awsServices;
let region;
let vpc;
beforeEach(() => {
apiPaths = {
getKeyPairsPath: '/clusters/aws/api/key_pairs',
getRegionsPath: '/clusters/aws/api/regions',
getRolesPath: '/clusters/aws/api/roles',
getSecurityGroupsPath: '/clusters/aws/api/security_groups',
getSubnetsPath: '/clusters/aws/api/subnets',
getVpcsPath: '/clusters/aws/api/vpcs',
getInstanceTypesPath: '/clusters/aws/api/instance_types',
};
region = 'west-1';
vpc = 'vpc-2';
awsServices = awsServicesFacadeFactory(apiPaths);
axiosMock = new AxiosMockAdapter(axios);
});
describe('when fetchRegions succeeds', () => {
let regions;
let regionsOutput;
beforeEach(() => {
regions = [{ region_name: 'east-1' }, { region_name: 'west-2' }];
regionsOutput = regions.map(({ region_name: name }) => ({ name, value: name }));
axiosMock.onGet(apiPaths.getRegionsPath).reply(200, { regions });
});
it('return list of roles where each item has a name and value', () => {
expect(awsServices.fetchRegions()).resolves.toEqual(regionsOutput);
});
});
describe('when fetchRoles succeeds', () => {
let roles;
let rolesOutput;
beforeEach(() => {
roles = [
{ role_name: 'admin', arn: 'aws::admin' },
{ role_name: 'read-only', arn: 'aws::read-only' },
];
rolesOutput = roles.map(({ role_name: name, arn: value }) => ({ name, value }));
axiosMock.onGet(apiPaths.getRolesPath).reply(200, { roles });
});
it('return list of regions where each item has a name and value', () => {
expect(awsServices.fetchRoles()).resolves.toEqual(rolesOutput);
});
});
describe('when fetchKeyPairs succeeds', () => {
let keyPairs;
let keyPairsOutput;
beforeEach(() => {
keyPairs = [{ key_pair: 'key-pair' }, { key_pair: 'key-pair-2' }];
keyPairsOutput = keyPairs.map(({ key_name: name }) => ({ name, value: name }));
axiosMock
.onGet(apiPaths.getKeyPairsPath, { params: { region } })
.reply(200, { key_pairs: keyPairs });
});
it('return list of key pairs where each item has a name and value', () => {
expect(awsServices.fetchKeyPairs({ region })).resolves.toEqual(keyPairsOutput);
});
});
describe('when fetchVpcs succeeds', () => {
let vpcs;
let vpcsOutput;
beforeEach(() => {
vpcs = [{ vpc_id: 'vpc-1' }, { vpc_id: 'vpc-2' }];
vpcsOutput = vpcs.map(({ vpc_id: name }) => ({ name, value: name }));
axiosMock.onGet(apiPaths.getVpcsPath, { params: { region } }).reply(200, { vpcs });
});
it('return list of vpcs where each item has a name and value', () => {
expect(awsServices.fetchVpcs({ region })).resolves.toEqual(vpcsOutput);
});
});
describe('when fetchSubnets succeeds', () => {
let subnets;
let subnetsOutput;
beforeEach(() => {
subnets = [{ subnet_id: 'vpc-1' }, { subnet_id: 'vpc-2' }];
subnetsOutput = subnets.map(({ subnet_id }) => ({ name: subnet_id, value: subnet_id }));
axiosMock
.onGet(apiPaths.getSubnetsPath, { params: { region, vpc_id: vpc } })
.reply(200, { subnets });
});
it('return list of subnets where each item has a name and value', () => {
expect(awsServices.fetchSubnets({ region, vpc })).resolves.toEqual(subnetsOutput);
});
});
describe('when fetchSecurityGroups succeeds', () => {
let securityGroups;
let securityGroupsOutput;
beforeEach(() => {
securityGroups = [
{ group_name: 'admin group', group_id: 'group-1' },
{ group_name: 'basic group', group_id: 'group-2' },
];
securityGroupsOutput = securityGroups.map(({ group_id: value, group_name: name }) => ({
name,
value,
}));
axiosMock
.onGet(apiPaths.getSecurityGroupsPath, { params: { region, vpc_id: vpc } })
.reply(200, { security_groups: securityGroups });
});
it('return list of security groups where each item has a name and value', () => {
expect(awsServices.fetchSecurityGroups({ region, vpc })).resolves.toEqual(
securityGroupsOutput,
);
});
});
describe('when fetchInstanceTypes succeeds', () => {
let instanceTypes;
let instanceTypesOutput;
beforeEach(() => {
instanceTypes = [{ instance_type_name: 't2.small' }, { instance_type_name: 't2.medium' }];
instanceTypesOutput = instanceTypes.map(({ instance_type_name }) => ({
name: instance_type_name,
value: instance_type_name,
}));
axiosMock.onGet(apiPaths.getInstanceTypesPath).reply(200, { instance_types: instanceTypes });
});
it('return list of instance types where each item has a name and value', () => {
expect(awsServices.fetchInstanceTypes()).resolves.toEqual(instanceTypesOutput);
});
});
});
...@@ -13,12 +13,20 @@ import { ...@@ -13,12 +13,20 @@ import {
SET_ROLE, SET_ROLE,
SET_SECURITY_GROUP, SET_SECURITY_GROUP,
SET_GITLAB_MANAGED_CLUSTER, SET_GITLAB_MANAGED_CLUSTER,
SET_INSTANCE_TYPE,
SET_NODE_COUNT,
REQUEST_CREATE_ROLE, REQUEST_CREATE_ROLE,
CREATE_ROLE_SUCCESS, CREATE_ROLE_SUCCESS,
CREATE_ROLE_ERROR, CREATE_ROLE_ERROR,
REQUEST_CREATE_CLUSTER,
CREATE_CLUSTER_ERROR,
SIGN_OUT,
} from '~/create_cluster/eks_cluster/store/mutation_types'; } from '~/create_cluster/eks_cluster/store/mutation_types';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import createFlash from '~/flash';
jest.mock('~/flash');
describe('EKS Cluster Store Actions', () => { describe('EKS Cluster Store Actions', () => {
let clusterName; let clusterName;
...@@ -30,25 +38,34 @@ describe('EKS Cluster Store Actions', () => { ...@@ -30,25 +38,34 @@ describe('EKS Cluster Store Actions', () => {
let role; let role;
let keyPair; let keyPair;
let securityGroup; let securityGroup;
let instanceType;
let nodeCount;
let gitlabManagedCluster; let gitlabManagedCluster;
let mock; let mock;
let state; let state;
let newClusterUrl;
beforeEach(() => { beforeEach(() => {
clusterName = 'my cluster'; clusterName = 'my cluster';
environmentScope = 'production'; environmentScope = 'production';
kubernetesVersion = '11.1'; kubernetesVersion = '11.1';
region = { name: 'regions-1' }; region = 'regions-1';
vpc = { name: 'vpc-1' }; vpc = 'vpc-1';
subnet = { name: 'subnet-1' }; subnet = 'subnet-1';
role = { name: 'role-1' }; role = 'role-1';
keyPair = { name: 'key-pair-1' }; keyPair = 'key-pair-1';
securityGroup = { name: 'default group' }; securityGroup = 'default group';
instanceType = 'small-1';
nodeCount = '5';
gitlabManagedCluster = true; gitlabManagedCluster = true;
newClusterUrl = '/clusters/1';
state = { state = {
...createState(), ...createState(),
createRolePath: '/clusters/roles/', createRolePath: '/clusters/roles/',
signOutPath: '/aws/signout',
createClusterPath: '/clusters/',
}; };
}); });
...@@ -71,6 +88,8 @@ describe('EKS Cluster Store Actions', () => { ...@@ -71,6 +88,8 @@ describe('EKS Cluster Store Actions', () => {
${'setVpc'} | ${SET_VPC} | ${{ vpc }} | ${'vpc'} ${'setVpc'} | ${SET_VPC} | ${{ vpc }} | ${'vpc'}
${'setSubnet'} | ${SET_SUBNET} | ${{ subnet }} | ${'subnet'} ${'setSubnet'} | ${SET_SUBNET} | ${{ subnet }} | ${'subnet'}
${'setSecurityGroup'} | ${SET_SECURITY_GROUP} | ${{ securityGroup }} | ${'securityGroup'} ${'setSecurityGroup'} | ${SET_SECURITY_GROUP} | ${{ securityGroup }} | ${'securityGroup'}
${'setInstanceType'} | ${SET_INSTANCE_TYPE} | ${{ instanceType }} | ${'instance type'}
${'setNodeCount'} | ${SET_NODE_COUNT} | ${{ nodeCount }} | ${'node count'}
${'setGitlabManagedCluster'} | ${SET_GITLAB_MANAGED_CLUSTER} | ${gitlabManagedCluster} | ${'gitlab managed cluster'} ${'setGitlabManagedCluster'} | ${SET_GITLAB_MANAGED_CLUSTER} | ${gitlabManagedCluster} | ${'gitlab managed cluster'}
`(`$action commits $mutation with $payloadDescription payload`, data => { `(`$action commits $mutation with $payloadDescription payload`, data => {
const { action, mutation, payload } = data; const { action, mutation, payload } = data;
...@@ -149,4 +168,127 @@ describe('EKS Cluster Store Actions', () => { ...@@ -149,4 +168,127 @@ describe('EKS Cluster Store Actions', () => {
testAction(actions.createRoleError, payload, state, [{ type: CREATE_ROLE_ERROR, payload }]); testAction(actions.createRoleError, payload, state, [{ type: CREATE_ROLE_ERROR, payload }]);
}); });
}); });
describe('createCluster', () => {
let requestPayload;
beforeEach(() => {
requestPayload = {
name: clusterName,
environment_scope: environmentScope,
managed: gitlabManagedCluster,
provider_aws_attributes: {
region,
vpc_id: vpc,
subnet_ids: subnet,
role_arn: role,
key_name: keyPair,
security_group_id: securityGroup,
instance_type: instanceType,
num_nodes: nodeCount,
},
};
state = Object.assign(createState(), {
clusterName,
environmentScope,
kubernetesVersion,
selectedRegion: region,
selectedVpc: vpc,
selectedSubnet: subnet,
selectedRole: role,
selectedKeyPair: keyPair,
selectedSecurityGroup: securityGroup,
selectedInstanceType: instanceType,
nodeCount,
gitlabManagedCluster,
});
});
describe('when request succeeds', () => {
beforeEach(() => {
mock.onPost(state.createClusterPath, requestPayload).reply(201, null, {
location: '/clusters/1',
});
});
it('dispatches createClusterSuccess action', () =>
testAction(
actions.createCluster,
null,
state,
[],
[
{ type: 'requestCreateCluster' },
{ type: 'createClusterSuccess', payload: newClusterUrl },
],
));
});
describe('when request fails', () => {
let response;
beforeEach(() => {
response = 'Request failed with status code 400';
mock.onPost(state.createClusterPath, requestPayload).reply(400, response);
});
it('dispatches createRoleError action', () =>
testAction(
actions.createCluster,
null,
state,
[],
[{ type: 'requestCreateCluster' }, { type: 'createClusterError', payload: response }],
));
});
});
describe('requestCreateCluster', () => {
it('commits requestCreateCluster mutation', () => {
testAction(actions.requestCreateCluster, null, state, [{ type: REQUEST_CREATE_CLUSTER }]);
});
});
describe('createClusterSuccess', () => {
beforeEach(() => {
jest.spyOn(window.location, 'assign').mockImplementation(() => {});
});
afterEach(() => {
window.location.assign.mockRestore();
});
it('redirects to the new cluster URL', () => {
actions.createClusterSuccess(null, newClusterUrl);
expect(window.location.assign).toHaveBeenCalledWith(newClusterUrl);
});
});
describe('createClusterError', () => {
let payload;
beforeEach(() => {
payload = { name: ['Create cluster failed'] };
});
it('commits createClusterError mutation', () => {
testAction(actions.createClusterError, payload, state, [
{ type: CREATE_CLUSTER_ERROR, payload },
]);
});
it('creates a flash that displays the create cluster error', () => {
expect(createFlash).toHaveBeenCalledWith(payload.name[0]);
});
});
describe('signOut', () => {
beforeEach(() => {
mock.onDelete(state.signOutPath).reply(200, null);
});
it('commits signOut mutation', () => {
testAction(actions.signOut, null, state, [{ type: SIGN_OUT }]);
});
});
}); });
...@@ -8,10 +8,15 @@ import { ...@@ -8,10 +8,15 @@ import {
SET_SUBNET, SET_SUBNET,
SET_ROLE, SET_ROLE,
SET_SECURITY_GROUP, SET_SECURITY_GROUP,
SET_INSTANCE_TYPE,
SET_NODE_COUNT,
SET_GITLAB_MANAGED_CLUSTER, SET_GITLAB_MANAGED_CLUSTER,
REQUEST_CREATE_ROLE, REQUEST_CREATE_ROLE,
CREATE_ROLE_SUCCESS, CREATE_ROLE_SUCCESS,
CREATE_ROLE_ERROR, CREATE_ROLE_ERROR,
REQUEST_CREATE_CLUSTER,
CREATE_CLUSTER_ERROR,
SIGN_OUT,
} from '~/create_cluster/eks_cluster/store/mutation_types'; } from '~/create_cluster/eks_cluster/store/mutation_types';
import createState from '~/create_cluster/eks_cluster/store/state'; import createState from '~/create_cluster/eks_cluster/store/state';
import mutations from '~/create_cluster/eks_cluster/store/mutations'; import mutations from '~/create_cluster/eks_cluster/store/mutations';
...@@ -27,6 +32,8 @@ describe('Create EKS cluster store mutations', () => { ...@@ -27,6 +32,8 @@ describe('Create EKS cluster store mutations', () => {
let role; let role;
let keyPair; let keyPair;
let securityGroup; let securityGroup;
let instanceType;
let nodeCount;
let gitlabManagedCluster; let gitlabManagedCluster;
beforeEach(() => { beforeEach(() => {
...@@ -39,6 +46,8 @@ describe('Create EKS cluster store mutations', () => { ...@@ -39,6 +46,8 @@ describe('Create EKS cluster store mutations', () => {
role = { name: 'role-1' }; role = { name: 'role-1' };
keyPair = { name: 'key pair' }; keyPair = { name: 'key pair' };
securityGroup = { name: 'default group' }; securityGroup = { name: 'default group' };
instanceType = 'small-1';
nodeCount = '5';
gitlabManagedCluster = false; gitlabManagedCluster = false;
state = createState(); state = createState();
...@@ -53,8 +62,10 @@ describe('Create EKS cluster store mutations', () => { ...@@ -53,8 +62,10 @@ describe('Create EKS cluster store mutations', () => {
${SET_REGION} | ${'selectedRegion'} | ${{ region }} | ${region} | ${'selected region payload'} ${SET_REGION} | ${'selectedRegion'} | ${{ region }} | ${region} | ${'selected region payload'}
${SET_KEY_PAIR} | ${'selectedKeyPair'} | ${{ keyPair }} | ${keyPair} | ${'selected key pair payload'} ${SET_KEY_PAIR} | ${'selectedKeyPair'} | ${{ keyPair }} | ${keyPair} | ${'selected key pair payload'}
${SET_VPC} | ${'selectedVpc'} | ${{ vpc }} | ${vpc} | ${'selected vpc payload'} ${SET_VPC} | ${'selectedVpc'} | ${{ vpc }} | ${vpc} | ${'selected vpc payload'}
${SET_SUBNET} | ${'selectedSubnet'} | ${{ subnet }} | ${subnet} | ${'selected sybnet payload'} ${SET_SUBNET} | ${'selectedSubnet'} | ${{ subnet }} | ${subnet} | ${'selected subnet payload'}
${SET_SECURITY_GROUP} | ${'selectedSecurityGroup'} | ${{ securityGroup }} | ${securityGroup} | ${'selected security group payload'} ${SET_SECURITY_GROUP} | ${'selectedSecurityGroup'} | ${{ securityGroup }} | ${securityGroup} | ${'selected security group payload'}
${SET_INSTANCE_TYPE} | ${'selectedInstanceType'} | ${{ instanceType }} | ${instanceType} | ${'selected instance type payload'}
${SET_NODE_COUNT} | ${'nodeCount'} | ${{ nodeCount }} | ${nodeCount} | ${'node count payload'}
${SET_GITLAB_MANAGED_CLUSTER} | ${'gitlabManagedCluster'} | ${{ gitlabManagedCluster }} | ${gitlabManagedCluster} | ${'gitlab managed cluster'} ${SET_GITLAB_MANAGED_CLUSTER} | ${'gitlabManagedCluster'} | ${{ gitlabManagedCluster }} | ${gitlabManagedCluster} | ${'gitlab managed cluster'}
`(`$mutation sets $mutatedProperty to $expectedValueDescription`, data => { `(`$mutation sets $mutatedProperty to $expectedValueDescription`, data => {
const { mutation, mutatedProperty, payload, expectedValue } = data; const { mutation, mutatedProperty, payload, expectedValue } = data;
...@@ -118,4 +129,45 @@ describe('Create EKS cluster store mutations', () => { ...@@ -118,4 +129,45 @@ describe('Create EKS cluster store mutations', () => {
expect(state.hasCredentials).toBe(false); expect(state.hasCredentials).toBe(false);
}); });
}); });
describe(`mutation ${REQUEST_CREATE_CLUSTER}`, () => {
beforeEach(() => {
mutations[REQUEST_CREATE_CLUSTER](state);
});
it('sets isCreatingCluster to true', () => {
expect(state.isCreatingCluster).toBe(true);
});
it('sets createClusterError to null', () => {
expect(state.createClusterError).toBe(null);
});
});
describe(`mutation ${CREATE_CLUSTER_ERROR}`, () => {
const error = new Error();
beforeEach(() => {
mutations[CREATE_CLUSTER_ERROR](state, { error });
});
it('sets isCreatingRole to false', () => {
expect(state.isCreatingCluster).toBe(false);
});
it('sets createRoleError to the error object', () => {
expect(state.createClusterError).toBe(error);
});
});
describe(`mutation ${SIGN_OUT}`, () => {
beforeEach(() => {
state.hasCredentials = true;
mutations[SIGN_OUT](state);
});
it('sets hasCredentials to false', () => {
expect(state.hasCredentials).toBe(false);
});
});
}); });
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