Commit 37ca74ac authored by Mike Greiling's avatar Mike Greiling

Merge branch '22392-eks-create-cluster-fe' into 'master'

Complete create EKS cluster workflow

See merge request gitlab-org/gitlab!19578
parents d52a715f fac814c9
...@@ -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