@spotinst/spinnaker-deck
Version:
Spinnaker-Deck service, forked with support to Spotinst
775 lines (682 loc) • 29.9 kB
JavaScript
;
import * as angular from 'angular';
import _ from 'lodash';
import {
AccountService,
CACHE_INITIALIZER_SERVICE,
LOAD_BALANCER_READ_SERVICE,
NetworkReader,
SECURITY_GROUP_READER,
SubnetReader,
} from '@spinnaker/core';
import { GCEProviderSettings } from 'google/gce.settings';
import { GCE_HEALTH_CHECK_READER } from 'google/healthCheck/healthCheck.read.service';
import { getHealthCheckOptions } from 'google/healthCheck/healthCheckUtils';
import { GceImageReader } from 'google/image';
import { GCE_HTTP_LOAD_BALANCER_UTILS } from 'google/loadBalancer/httpLoadBalancerUtils.service';
import { LOAD_BALANCER_SET_TRANSFORMER } from 'google/loadBalancer/loadBalancer.setTransformer';
import { GOOGLE_INSTANCE_CUSTOM_CUSTOMINSTANCEBUILDER_GCE_SERVICE } from './../../instance/custom/customInstanceBuilder.gce.service';
import { GOOGLE_INSTANCE_GCEINSTANCETYPE_SERVICE } from '../../instance/gceInstanceType.service';
import { GceAcceleratorService } from './wizard/advancedSettings/gceAccelerator.service';
import { GOOGLE_SERVERGROUP_CONFIGURE_WIZARD_SECURITYGROUPS_TAGMANAGER_SERVICE } from './wizard/securityGroups/tagManager.service';
export const GOOGLE_SERVERGROUP_CONFIGURE_SERVERGROUPCONFIGURATION_SERVICE =
'spinnaker.serverGroup.configure.gce.configuration.service';
export const name = GOOGLE_SERVERGROUP_CONFIGURE_SERVERGROUPCONFIGURATION_SERVICE; // for backwards compatibility
angular
.module(GOOGLE_SERVERGROUP_CONFIGURE_SERVERGROUPCONFIGURATION_SERVICE, [
LOAD_BALANCER_SET_TRANSFORMER,
SECURITY_GROUP_READER,
CACHE_INITIALIZER_SERVICE,
LOAD_BALANCER_READ_SERVICE,
GOOGLE_INSTANCE_GCEINSTANCETYPE_SERVICE,
GOOGLE_INSTANCE_CUSTOM_CUSTOMINSTANCEBUILDER_GCE_SERVICE,
GCE_HTTP_LOAD_BALANCER_UTILS,
GCE_HEALTH_CHECK_READER,
GOOGLE_SERVERGROUP_CONFIGURE_WIZARD_SECURITYGROUPS_TAGMANAGER_SERVICE,
])
.factory('gceServerGroupConfigurationService', [
'securityGroupReader',
'gceInstanceTypeService',
'cacheInitializer',
'$q',
'loadBalancerReader',
'gceCustomInstanceBuilderService',
'gceHttpLoadBalancerUtils',
'gceHealthCheckReader',
'gceTagManager',
'gceLoadBalancerSetTransformer',
function (
securityGroupReader,
gceInstanceTypeService,
cacheInitializer,
$q,
loadBalancerReader,
gceCustomInstanceBuilderService,
gceHttpLoadBalancerUtils,
gceHealthCheckReader,
gceTagManager,
gceLoadBalancerSetTransformer,
) {
const persistentDiskTypes = ['pd-standard', 'pd-ssd'];
const authScopes = [
'cloud-platform',
'userinfo.email',
'compute.readonly',
'compute',
'cloud.useraccounts.readonly',
'cloud.useraccounts',
'devstorage.read_only',
'devstorage.write_only',
'devstorage.full_control',
'taskqueue',
'bigquery',
'sqlservice.admin',
'datastore',
'logging.write',
'logging.read',
'logging.admin',
'monitoring.write',
'monitoring.read',
'monitoring',
'bigtable.data.readonly',
'bigtable.data',
'bigtable.admin',
'bigtable.admin.table',
];
function configureCommand(application, command) {
return $q
.all([
AccountService.getCredentialsKeyedByAccount('gce'),
securityGroupReader.getAllSecurityGroups(),
NetworkReader.listNetworksByProvider('gce'),
SubnetReader.listSubnetsByProvider('gce'),
loadBalancerReader.listLoadBalancers('gce'),
loadAllImages(command.credentials),
gceInstanceTypeService.getAllTypesByRegion(),
$q.when(angular.copy(persistentDiskTypes)),
$q.when(angular.copy(authScopes)),
gceHealthCheckReader.listHealthChecks(),
AccountService.listAccounts('gce'),
])
.then(function ([
credentialsKeyedByAccount,
securityGroups,
networks,
subnets,
loadBalancers,
allImages,
instanceTypes,
persistentDiskTypes,
authScopes,
healthChecks,
accounts,
]) {
const backingData = {
credentialsKeyedByAccount,
securityGroups,
networks,
subnets,
loadBalancers,
allImages,
instanceTypes,
persistentDiskTypes,
authScopes,
healthChecks,
accounts,
};
let securityGroupReloader = $q.when(null);
let networkReloader = $q.when(null);
let healthCheckReloader = $q.when(null);
backingData.filtered = {};
backingData.distributionPolicyTargetShapes = getDistributionPolicyTargetShapes();
command.backingData = backingData;
configureImages(command);
if (command.securityGroups && command.securityGroups.length) {
// Verify all firewalls are accounted for; otherwise, try refreshing firewalls cache.
const securityGroupIds = _.map(getSecurityGroups(command), 'id');
if (_.intersection(command.securityGroups, securityGroupIds).length < command.securityGroups.length) {
securityGroupReloader = refreshSecurityGroups(command, true);
}
}
if (command.network) {
// Verify network is accounted for; otherwise, try refreshing networks cache.
const networkNames = getNetworkNames(command);
if (!networkNames.includes(command.network)) {
networkReloader = refreshNetworks(command);
}
}
if (command.autoHealingPolicy) {
command.enableAutoHealing = true;
}
if (_.has(command, 'autoHealingPolicy.healthCheck')) {
// Verify health check is accounted for; otherwise, try refreshing health checks cache.
const healthChecks = getHealthChecks(command);
if (!_.chain(healthChecks).map('selfLink').includes(command.autoHealingPolicy.healthCheckUrl).value()) {
healthCheckReloader = refreshHealthChecks(command, true);
}
}
return $q.all([securityGroupReloader, networkReloader, healthCheckReloader]).then(() => {
gceTagManager.register(command);
attachEventHandlers(command);
});
});
}
function getDistributionPolicyTargetShapes() {
return ['ANY', 'EVEN'];
}
function configureDistributionPolicyTargetShape(command) {
const accountDetails = command.backingData.credentialsKeyedByAccount[command.credentials];
if (!command.distributionPolicy.targetShape) {
command.distributionPolicy.targetShape = 'EVEN';
}
}
function loadAllImages(account) {
return GceImageReader.findImages({
account: account,
provider: 'gce',
q: '*',
});
}
function configureInstanceTypes(command) {
const result = { dirty: {} };
if (command.region) {
const results = [result.dirty];
results.push(configureCustomInstanceTypes(command).dirty);
results.push(configureStandardInstanceTypes(command).dirty);
angular.extend(...results);
} else {
command.backingData.filtered.instanceTypes = [];
}
return result;
}
function configureCpuPlatforms(command) {
const result = { dirty: {} };
const filteredData = command.backingData.filtered;
const locationToCpuPlatformsMap =
command.backingData.credentialsKeyedByAccount[command.credentials].locationToCpuPlatformsMap;
filteredData.cpuPlatforms = ['(Automatic)'];
const location = command.regional ? command.region : command.zone;
if (_.has(locationToCpuPlatformsMap, location)) {
filteredData.cpuPlatforms = _.concat(filteredData.cpuPlatforms, locationToCpuPlatformsMap[location]);
}
if (!_.includes(filteredData.cpuPlatforms, command.minCpuPlatform)) {
delete command.minCpuPlatform;
result.dirty.minCpuPlatform = true;
}
return result;
}
function configureStandardInstanceTypes(command) {
const c = command;
const result = { dirty: {} };
const locations = c.regional ? [c.region] : [c.zone];
const { credentialsKeyedByAccount } = c.backingData;
const { locationToInstanceTypesMap } = credentialsKeyedByAccount[c.credentials];
if (locations.every((l) => !l)) {
return result;
}
let filtered = gceInstanceTypeService.getAvailableTypesForLocations(locationToInstanceTypesMap, locations);
filtered = sortInstanceTypes(filtered);
const instanceType = c.instanceType;
if (_.every([instanceType, !_.includes(instanceType, 'custom-'), !_.includes(filtered, instanceType)])) {
result.dirty.instanceType = c.instanceType;
c.instanceType = null;
}
c.backingData.filtered.instanceTypes = filtered;
return result;
}
function configureCustomInstanceTypes(command) {
const c = command;
const result = { dirty: {} };
let vCpuCount = _.get(c, 'viewState.customInstance.vCpuCount');
const instanceFamily = _.get(c, 'viewState.customInstance.instanceFamily');
const memory = _.get(c, 'viewState.customInstance.memory');
const { zone, regional, region } = c;
const { locationToInstanceTypesMap } = c.backingData.credentialsKeyedByAccount[c.credentials];
const location = regional ? region : zone;
if (!location) {
return result;
}
if (zone || regional) {
_.set(
c,
'backingData.customInstanceTypes.vCpuList',
gceCustomInstanceBuilderService.generateValidVCpuListForLocation(location, locationToInstanceTypesMap),
);
}
// initializes vCpuCount so that memory selector will be populated.
if (
!vCpuCount ||
!gceCustomInstanceBuilderService.vCpuCountForLocationIsValid(
instanceFamily,
vCpuCount,
location,
locationToInstanceTypesMap,
)
) {
vCpuCount = _.get(c, 'backingData.customInstanceTypes.vCpuList[0]');
_.set(c, 'viewState.customInstance.vCpuCount', vCpuCount);
}
_.set(
c,
'backingData.customInstanceTypes.memoryList',
gceCustomInstanceBuilderService.generateValidMemoryListForVCpuCount(instanceFamily, vCpuCount),
);
if (
_.every([
memory,
vCpuCount,
!gceCustomInstanceBuilderService.memoryIsValid(instanceFamily, memory, vCpuCount),
])
) {
_.set(c, 'viewState.customInstance.memory', undefined);
result.dirty.instanceType = c.instanceType;
c.instanceType = null;
}
_.set(
c,
'backingData.customInstanceTypes.instanceFamilyList',
gceCustomInstanceBuilderService.generateValidInstanceFamilyList(),
);
return result;
}
function configureAccelerators(command) {
const result = { dirty: {} };
const acceleratorTypes = GceAcceleratorService.getAvailableAccelerators(command);
_.set(command, ['viewState', 'acceleratorTypes'], acceleratorTypes);
result.dirty.acceleratorTypes = true;
const chosenAccelerators = _.get(command, 'acceleratorConfigs', []);
if (chosenAccelerators.length > 0) {
command.acceleratorConfigs = chosenAccelerators.filter(
(chosen) => !!acceleratorTypes.find((a) => a.name === chosen.acceleratorType),
);
if (command.acceleratorConfigs.length === 0) {
delete command.acceleratorConfigs;
}
}
return result;
}
// n1-standard-8 should come before n1-standard-16, so we must sort by the individual segments of the names.
function sortInstanceTypes(instanceTypes) {
const tokenizedInstanceTypes = _.map(instanceTypes, (instanceType) => {
const tokens = instanceType.split('-');
return {
class: tokens[0],
group: tokens[1],
index: Number(tokens[2]) || 0,
};
});
const sortedTokenizedInstanceTypes = _.sortBy(tokenizedInstanceTypes, ['class', 'group', 'index']);
return _.map(sortedTokenizedInstanceTypes, (sortedTokenizedInstanceType) => {
return (
sortedTokenizedInstanceType.class +
'-' +
sortedTokenizedInstanceType.group +
(sortedTokenizedInstanceType.index ? '-' + sortedTokenizedInstanceType.index : '')
);
});
}
function configureImages(command) {
command.image = command.viewState.imageId;
const result = { dirty: {} };
if (command.credentials !== command.viewState.lastImageAccount) {
command.viewState.lastImageAccount = command.credentials;
if (!_.chain(command.backingData.allImages).find({ imageName: command.image }).value()) {
command.image = null;
result.dirty.imageName = true;
}
}
return result;
}
function configureZones(command) {
const result = { dirty: {} };
const filteredData = command.backingData.filtered;
if (command.region === null) {
return result;
}
const regions = command.backingData.credentialsKeyedByAccount[command.credentials].regions;
if (_.isArray(regions)) {
filteredData.zones = _.find(regions, { name: command.region }).zones;
filteredData.truncatedZones = _.takeRight(filteredData.zones.sort(), 3);
} else {
// TODO(duftler): Remove this once we finish deprecating the old style regions/zones in clouddriver GCE credentials.
filteredData.zones = regions[command.region];
}
if (!_.chain(filteredData.zones).includes(command.zone).value()) {
delete command.zone;
if (!command.regional) {
result.dirty.zone = true;
}
}
return result;
}
function getHealthChecks(command) {
const matchingHealthChecks = _.filter(command.backingData.healthChecks, { account: command.credentials });
return getHealthCheckOptions(matchingHealthChecks);
}
function configureHealthChecks(command) {
const result = { dirty: {} };
const filteredData = command.backingData.filtered;
if (command.credentials === null) {
return result;
}
filteredData.healthChecks = getHealthChecks(command);
if (
_.has(command, 'autoHealingPolicy.healthCheck') &&
!_.chain(filteredData.healthChecks).map('selfLink').includes(command.autoHealingPolicy.healthCheckUrl).value()
) {
delete command.autoHealingPolicy.healthCheck;
result.dirty.autoHealingPolicy = true;
} else {
result.dirty.autoHealingPolicy = null;
}
return result;
}
function getLoadBalancers(command) {
return _.chain(command.backingData.loadBalancers)
.map('accounts')
.flattenDeep()
.filter({ name: command.credentials })
.map('regions')
.flattenDeep()
.map('loadBalancers')
.flattenDeep()
.filter(_.curry(isRelevantLoadBalancer)(command))
.uniq()
.value();
}
function isRelevantLoadBalancer(command, loadBalancer) {
return loadBalancer.region === command.region || loadBalancer.region === 'global';
}
function configureLoadBalancerOptions(command) {
const results = { dirty: {} };
const current = command.loadBalancers;
const newLoadBalancerObjects = gceLoadBalancerSetTransformer.normalizeLoadBalancerSet(
getLoadBalancers(command),
);
command.backingData.filtered.loadBalancerIndex = _.keyBy(newLoadBalancerObjects, 'name');
command.backingData.filtered.loadBalancers = _.map(newLoadBalancerObjects, 'name');
if (current && command.loadBalancers) {
command.loadBalancers = gceHttpLoadBalancerUtils.normalizeLoadBalancerNamesForAccount(
command.loadBalancers,
command.credentials,
newLoadBalancerObjects,
);
const matched = _.intersection(command.backingData.filtered.loadBalancers, command.loadBalancers);
const removed = _.xor(matched, command.loadBalancers);
command.loadBalancers = matched;
configureBackendServiceOptions(command);
if (removed.length) {
results.dirty.loadBalancers = removed;
}
}
return results;
}
function configureBackendServiceOptions(command) {
/*
a server group has a list of backend services, but there's no mapping from l7 -> backend service
for the server group. this will not populate the wizard perfectly,
but it is the best we can do with the given data.
*/
const backendsFromMetadata = command.backendServiceMetadata;
const lbIndex = command.backingData.filtered.loadBalancerIndex;
const backendServices = command.loadBalancers.reduce((backendServices, lbName) => {
if (gceHttpLoadBalancerUtils.isHttpLoadBalancer(lbIndex[lbName])) {
backendServices[lbName] = _.intersection(lbIndex[lbName].backendServices, backendsFromMetadata);
}
return backendServices;
}, {});
if (Object.keys(backendServices).length > 0) {
command.backendServices = backendServices;
}
}
function refreshLoadBalancers(command, skipCommandReconfiguration) {
return loadBalancerReader.listLoadBalancers('gce').then(function (loadBalancers) {
command.backingData.loadBalancers = loadBalancers;
if (!skipCommandReconfiguration) {
configureLoadBalancerOptions(command);
}
});
}
function refreshHealthChecks(command, skipCommandReconfiguration) {
return cacheInitializer
.refreshCache('healthChecks')
.then(function () {
return gceHealthCheckReader.listHealthChecks();
})
.then(function (healthChecks) {
command.backingData.healthChecks = healthChecks;
if (!skipCommandReconfiguration) {
configureHealthChecks(command);
}
});
}
function configureSubnets(command) {
const result = { dirty: {} };
const filteredData = command.backingData.filtered;
if (command.region === null) {
return result;
}
filteredData.subnets = _.chain(command.backingData.subnets)
.filter({ account: command.credentials, network: command.network, region: command.region })
.map('id')
.value();
if (!_.chain(filteredData.subnets).includes(command.subnet).value()) {
command.subnet = '';
result.dirty.subnet = true;
}
return result;
}
function getSecurityGroups(command) {
let newSecurityGroups = command.backingData.securityGroups[command.credentials] || { gce: {} };
newSecurityGroups = _.filter(newSecurityGroups.gce.global, function (securityGroup) {
return securityGroup.network === command.network;
});
return _.chain(newSecurityGroups).sortBy('name').value();
}
function getXpnHostProjectIfAny(network) {
if (network && network.includes('/')) {
return network.split('/')[0] + '/';
} else {
return '';
}
}
function configureSecurityGroupOptions(command) {
const results = { dirty: {} };
const currentOptions = command.backingData.filtered.securityGroups;
const newSecurityGroups = getSecurityGroups(command);
if (currentOptions && command.securityGroups) {
// not initializing - we are actually changing groups
const currentGroupNames = command.securityGroups.map(function (groupId) {
const match = _.chain(currentOptions).find({ id: groupId }).value();
return match ? match.id : groupId;
});
const matchedGroups = command.securityGroups
.map(function (groupId) {
const securityGroup = _.chain(currentOptions).find({ id: groupId }).value();
return securityGroup ? securityGroup.id : null;
})
.map(function (groupName) {
return _.chain(newSecurityGroups).find({ id: groupName }).value();
})
.filter(function (group) {
return group;
});
command.securityGroups = _.map(matchedGroups, 'id');
const removed = _.xor(currentGroupNames, command.securityGroups);
if (removed.length) {
results.dirty.securityGroups = removed;
}
}
// Only include explicit firewall options in the pulldown list.
command.backingData.filtered.securityGroups = _.filter(newSecurityGroups, function (securityGroup) {
return !_.isEmpty(securityGroup.targetTags);
});
// Identify implicit firewalls so they can be optionally listed in a read-only state.
command.implicitSecurityGroups = _.filter(newSecurityGroups, function (securityGroup) {
return _.isEmpty(securityGroup.targetTags);
});
// Only include explicitly-selected firewalls in the body of the command.
const xpnHostProject = getXpnHostProjectIfAny(command.network);
const decoratedSecurityGroups = _.map(command.securityGroups, (sg) =>
!sg.startsWith(xpnHostProject) ? xpnHostProject + sg : sg,
);
command.securityGroups = _.difference(decoratedSecurityGroups, _.map(command.implicitSecurityGroups, 'id'));
return results;
}
function refreshSecurityGroups(command, skipCommandReconfiguration) {
return cacheInitializer.refreshCache('securityGroups').then(function () {
return securityGroupReader.getAllSecurityGroups().then(function (securityGroups) {
command.backingData.securityGroups = securityGroups;
if (!skipCommandReconfiguration) {
configureSecurityGroupOptions(command);
}
});
});
}
function getNetworkNames(command) {
return _.map(_.filter(command.backingData.networks, { account: command.credentials }), 'id');
}
function refreshNetworks(command) {
NetworkReader.listNetworksByProvider('gce').then(function (gceNetworks) {
command.backingData.networks = gceNetworks;
});
}
function refreshInstanceTypes(command) {
return cacheInitializer.refreshCache('instanceTypes').then(function () {
return gceInstanceTypeService.getAllTypesByRegion().then(function (instanceTypes) {
command.backingData.instanceTypes = instanceTypes;
configureInstanceTypes(command);
});
});
}
function attachEventHandlers(cmd) {
cmd.regionalChanged = function regionalChanged(command) {
const result = { dirty: {} };
const filteredData = command.backingData.filtered;
const defaults = GCEProviderSettings.defaults;
if (command.regional) {
command.zone = null;
configureDistributionPolicyTargetShape(command);
} else if (!command.zone) {
if (command.region === defaults.region) {
command.zone = defaults.zone;
} else {
command.zone = filteredData.zones[0];
}
angular.extend(result.dirty, configureZones(command).dirty);
}
command.viewState.dirty = command.viewState.dirty || {};
angular.extend(command.viewState.dirty, result.dirty);
return result;
};
cmd.regionChanged = function regionChanged(command) {
const result = { dirty: {} };
const filteredData = command.backingData.filtered;
angular.extend(result.dirty, configureSubnets(command).dirty);
if (command.region) {
angular.extend(result.dirty, configureInstanceTypes(command).dirty);
angular.extend(result.dirty, configureZones(command).dirty);
angular.extend(result.dirty, configureCpuPlatforms(command).dirty);
// TODO: Internal Load Balancers also need to be filtered by network.
angular.extend(result.dirty, configureLoadBalancerOptions(command).dirty);
angular.extend(result.dirty, configureImages(command).dirty);
} else {
filteredData.zones = null;
}
command.viewState.dirty = command.viewState.dirty || {};
angular.extend(command.viewState.dirty, result.dirty);
return result;
};
cmd.credentialsChanged = function credentialsChanged(command) {
const result = { dirty: {} };
const backingData = command.backingData;
if (command.credentials) {
const regions = backingData.credentialsKeyedByAccount[command.credentials].regions;
if (_.isArray(regions)) {
backingData.filtered.regions = _.map(regions, 'name');
} else {
// TODO(duftler): Remove this once we finish deprecating the old style regions/zones in clouddriver GCE credentials.
backingData.filtered.regions = _.keys(regions);
}
if (!backingData.filtered.regions.includes(command.region)) {
command.region = null;
result.dirty.region = true;
} else {
angular.extend(result.dirty, command.regionChanged(command).dirty);
}
backingData.filtered.networks = getNetworkNames(command);
if (!backingData.filtered.networks.includes(command.network)) {
command.network = null;
result.dirty.network = true;
} else {
angular.extend(result.dirty, command.networkChanged(command).dirty);
}
angular.extend(result.dirty, configureHealthChecks(command).dirty);
angular.extend(result.dirty, configureInstanceTypes(command).dirty);
configureDistributionPolicyTargetShape(command);
} else {
command.region = null;
}
command.viewState.dirty = command.viewState.dirty || {};
angular.extend(command.viewState.dirty, result.dirty);
return result;
};
cmd.networkChanged = function networkChanged(command) {
const result = { dirty: {} };
command.viewState.autoCreateSubnets = _.chain(command.backingData.networks)
.filter({ account: command.credentials, id: command.network })
.map('autoCreateSubnets')
.head()
.value();
command.viewState.subnets = _.chain(command.backingData.networks)
.filter({ account: command.credentials, id: command.network })
.map('subnets')
.head()
.value();
angular.extend(result.dirty, configureSubnets(command).dirty);
angular.extend(result.dirty, configureSecurityGroupOptions(command).dirty);
// TODO: Internal Load Balancers also need to be filtered by network.
command.viewState.dirty = command.viewState.dirty || {};
angular.extend(command.viewState.dirty, result.dirty);
return result;
};
cmd.zoneChanged = function zoneChanged(command) {
const result = { dirty: {} };
if (command.zone === undefined && !command.regional) {
result.dirty.zone = true;
}
command.viewState.dirty = command.viewState.dirty || {};
angular.extend(command.viewState.dirty, result.dirty);
angular.extend(command.viewState.dirty, configureInstanceTypes(command).dirty);
angular.extend(command.viewState.dirty, configureCpuPlatforms(command).dirty);
angular.extend(command.viewState.dirty, configureAccelerators(command).dirty);
return result;
};
cmd.selectZonesChanged = function selectZonesChanged(command) {
const result = { dirty: {} };
command.viewState.dirty = command.viewState.dirty || {};
angular.extend(command.viewState.dirty, result.dirty);
angular.extend(command.viewState.dirty, configureAccelerators(command).dirty);
return result;
};
cmd.customInstanceChanged = function customInstanceChanged(command) {
const result = { dirty: {} };
command.viewState.dirty = command.viewState.dirty || {};
angular.extend(result, command.viewState.dirty, configureCustomInstanceTypes(command).dirty);
return result;
};
}
return {
configureCommand,
configureInstanceTypes,
configureImages,
configureZones,
configureSubnets,
configureLoadBalancerOptions,
refreshLoadBalancers,
refreshSecurityGroups,
refreshInstanceTypes,
refreshHealthChecks,
};
},
]);