UNPKG

@spotinst/spinnaker-deck

Version:

Spinnaker-Deck service, forked with support to Spotinst

586 lines (515 loc) 21.7 kB
'use strict'; import UIROUTER_ANGULARJS from '@uirouter/angularjs'; import { module } from 'angular'; import ANGULAR_UI_BOOTSTRAP from 'angular-ui-bootstrap'; import _ from 'lodash'; import { CloudProviderRegistry, ConfirmationModalService, FirewallLabels, InstanceReader, RecentHistoryService, SETTINGS, } from '@spinnaker/core'; import { AmazonInstanceWriter } from '../amazon.instance.write.service'; import { applyHealthCheckInfoToTargetGroups, getAllTargetGroups } from './utils'; import { AMAZON_VPC_VPCTAG_DIRECTIVE } from '../../vpc/vpcTag.directive'; export const AMAZON_INSTANCE_DETAILS_INSTANCE_DETAILS_CONTROLLER = 'spinnaker.amazon.instance.details.controller'; export const name = AMAZON_INSTANCE_DETAILS_INSTANCE_DETAILS_CONTROLLER; // for backwards compatibility module(AMAZON_INSTANCE_DETAILS_INSTANCE_DETAILS_CONTROLLER, [ UIROUTER_ANGULARJS, ANGULAR_UI_BOOTSTRAP, AMAZON_VPC_VPCTAG_DIRECTIVE, ]).controller('awsInstanceDetailsCtrl', [ '$scope', '$state', 'instance', 'app', 'moniker', 'environment', '$q', 'overrides', function ($scope, $state, instance, app, moniker, environment, $q, overrides) { // needed for standalone instances $scope.detailsTemplateUrl = CloudProviderRegistry.getValue('aws', 'instance.detailsTemplateUrl'); $scope.state = { loading: true, standalone: app.isStandalone, instancePort: _.get(app, 'attributes.instancePort') || SETTINGS.defaultInstancePort || 80, }; $scope.application = app; $scope.moniker = moniker; $scope.environment = environment; $scope.securityGroupsLabel = FirewallLabels.get('Firewalls'); function extractHealthMetrics(instance, latest) { // do not backfill on standalone instances if (app.isStandalone) { instance.health = latest.health; } instance.health = instance.health || []; const displayableMetrics = instance.health.filter(function (metric) { return metric.type !== 'Amazon' || metric.state !== 'Unknown'; }); if (!app.isStandalone) { // augment with target group healthcheck data const targetGroups = getAllTargetGroups( app.loadBalancers.data.filter(function (loadBalancer) { return loadBalancer.cloudProvider === 'aws'; }), ); applyHealthCheckInfoToTargetGroups(displayableMetrics, targetGroups, instance.account); } // backfill details where applicable if (latest.health) { displayableMetrics.forEach(function (metric) { const detailsMatch = latest.health.filter(function (latestHealth) { return latestHealth.type === metric.type; }); if (detailsMatch.length) { _.defaults(metric, detailsMatch[0]); } }); } $scope.healthMetrics = displayableMetrics; } function retrieveInstance() { const extraData = {}; let instanceSummary, loadBalancers, targetGroups, account, region, vpcId, serverGroupDisabled; if (!app.serverGroups) { // standalone instance instanceSummary = { id: instance.instanceId }; // terminate call expects `id` to be populated loadBalancers = []; targetGroups = []; account = instance.account; region = instance.region; } else { app.serverGroups.data.some(function (serverGroup) { return serverGroup.instances.some(function (possibleInstance) { if (possibleInstance.id === instance.instanceId) { instanceSummary = possibleInstance; loadBalancers = serverGroup.loadBalancers; targetGroups = serverGroup.targetGroups; account = serverGroup.account; region = serverGroup.region; vpcId = serverGroup.vpcId; serverGroupDisabled = serverGroup.isDisabled; extraData.serverGroup = serverGroup.name; extraData.vpcId = serverGroup.vpcId; return true; } }); }); if (!instanceSummary) { // perhaps it is in a server group that is part of another app app.loadBalancers.data.some(function (loadBalancer) { return ( loadBalancer.instances.some(function (possibleInstance) { if (possibleInstance.id === instance.instanceId) { instanceSummary = possibleInstance; loadBalancers = [loadBalancer.name]; account = loadBalancer.account; region = loadBalancer.region; vpcId = loadBalancer.vpcId; return true; } }) || loadBalancer.targetGroups.some(function (targetGroup) { return targetGroup.instances.some(function (possibleInstance) { if (possibleInstance.id === instance.instanceId) { instanceSummary = possibleInstance; targetGroups = [targetGroup.name]; account = loadBalancer.account; region = loadBalancer.region; vpcId = loadBalancer.vpcId; return true; } }); }) ); }); if (!instanceSummary) { // perhaps it is in a disabled server group via a load balancer app.loadBalancers.data.some(function (loadBalancer) { return ( loadBalancer.serverGroups.some(function (serverGroup) { if (!serverGroup.isDisabled) { return false; } return serverGroup.instances.some(function (possibleInstance) { if (possibleInstance.id === instance.instanceId) { instanceSummary = possibleInstance; loadBalancers = [loadBalancer.name]; account = loadBalancer.account; region = loadBalancer.region; vpcId = loadBalancer.vpcId; return true; } }); }) || loadBalancer.targetGroups.some(function (targetGroup) { targetGroup.serverGroups.some(function (serverGroup) { if (!serverGroup.isDisabled) { return false; } return serverGroup.instances.some(function (possibleInstance) { if (possibleInstance.id === instance.instanceId) { instanceSummary = possibleInstance; loadBalancers = [loadBalancer.name]; account = loadBalancer.account; region = loadBalancer.region; vpcId = loadBalancer.vpcId; return true; } }); }); }) ); }); } } } if (instanceSummary && account && region) { instanceSummary.account = account; extraData.account = account; extraData.region = region; RecentHistoryService.addExtraDataToLatest('instances', extraData); return InstanceReader.getInstanceDetails(account, region, instance.instanceId).then((details) => { if ($scope.$$destroyed) { return; } $scope.state.loading = false; extractHealthMetrics(instanceSummary, details); $scope.instance = _.defaults(details, instanceSummary); $scope.instance.account = account; $scope.instance.region = region; $scope.instance.vpcId = vpcId; $scope.instance.serverGroupDisabled = serverGroupDisabled; $scope.instance.loadBalancers = loadBalancers; $scope.instance.targetGroups = targetGroups; if ($scope.instance.networkInterfaces) { $scope.instance.ipv6Addresses = _.flatMap($scope.instance.networkInterfaces, (i) => i.ipv6Addresses.map((a) => ({ ip: a.ipv6Address, url: `http://${a.ipv6Address}:${$scope.state.instancePort}`, })), ); const permanentNetworkInterfaces = $scope.instance.networkInterfaces.filter( (f) => f.attachment.deleteOnTermination === false, ); if (permanentNetworkInterfaces.length) { $scope.instance.permanentIps = permanentNetworkInterfaces.map((f) => f.privateIpAddress); } } $scope.baseIpAddress = details.publicDnsName || details.privateIpAddress; if (overrides.instanceDetailsLoaded) { overrides.instanceDetailsLoaded(); } }, autoClose); } if (!instanceSummary) { $scope.instanceIdNotFound = instance.instanceId; $scope.state.loading = false; } return $q.when(null); } function autoClose() { if ($scope.$$destroyed) { return; } if (app.isStandalone) { $scope.state.loading = false; $scope.instanceIdNotFound = instance.instanceId; $scope.state.notFoundStandalone = true; RecentHistoryService.removeLastItem('instances'); } else { $state.go('^', { allowModalToStayOpen: true }, { location: 'replace' }); } } this.canDeregisterFromLoadBalancer = function () { const healthMetrics = $scope.instance.health || []; return healthMetrics.some(function (health) { return health.type === 'LoadBalancer'; }); }; this.canRegisterWithLoadBalancer = function () { const instance = $scope.instance; const healthMetrics = instance.health || []; if (!instance.loadBalancers || !instance.loadBalancers.length) { return false; } const outOfService = healthMetrics.some(function (health) { return health.type === 'LoadBalancer' && health.state === 'OutOfService'; }); const hasLoadBalancerHealth = healthMetrics.some(function (health) { return health.type === 'LoadBalancer'; }); return outOfService || !hasLoadBalancerHealth; }; this.canDeregisterFromTargetGroup = function () { const healthMetrics = $scope.instance.health || []; return healthMetrics.some(function (health) { return health.type === 'TargetGroup' && health.state !== 'OutOfService'; }); }; this.canRegisterWithTargetGroup = function () { const instance = $scope.instance; const healthMetrics = instance.health || []; if (!instance.targetGroups || !instance.targetGroups.length) { return false; } const outOfService = healthMetrics.some(function (health) { return health.type === 'TargetGroup' && health.state === 'OutOfService'; }); const hasTargetGroupHealth = healthMetrics.some(function (health) { return health.type === 'TargetGroup'; }); return outOfService || !hasTargetGroupHealth; }; this.canRegisterWithDiscovery = function () { const instance = $scope.instance; const healthMetrics = instance.health || []; const discoveryHealth = healthMetrics.filter(function (health) { return health.type === 'Discovery'; }); return discoveryHealth.length ? discoveryHealth[0].state === 'OutOfService' : false; }; this.terminateInstance = function terminateInstance() { const instance = $scope.instance; const taskMonitor = { application: app, title: 'Terminating ' + instance.instanceId, onTaskComplete: function () { if ($state.includes('**.instanceDetails', { instanceId: instance.instanceId })) { $state.go('^'); } }, }; const submitMethod = function () { return AmazonInstanceWriter.terminateInstance(instance, app); }; ConfirmationModalService.confirm({ header: 'Really terminate ' + instance.instanceId + '?', buttonText: 'Terminate ' + instance.instanceId, account: instance.account, taskMonitorConfig: taskMonitor, submitMethod: submitMethod, }); }; this.terminateInstanceAndShrinkServerGroup = function terminateInstanceAndShrinkServerGroup() { const instance = $scope.instance; const taskMonitor = { application: app, title: 'Terminating ' + instance.instanceId + ' and shrinking server group', onTaskComplete: function () { if ($state.includes('**.instanceDetails', { instanceId: instance.instanceId })) { $state.go('^'); } }, }; const submitMethod = function () { return AmazonInstanceWriter.terminateInstanceAndShrinkServerGroup(instance, app); }; ConfirmationModalService.confirm({ header: 'Really terminate ' + instance.instanceId + ' and shrink ' + instance.serverGroup + '?', buttonText: 'Terminate ' + instance.instanceId + ' and shrink ' + instance.serverGroup, account: instance.account, taskMonitorConfig: taskMonitor, submitMethod: submitMethod, }); }; this.rebootInstance = function rebootInstance() { const instance = $scope.instance; const taskMonitor = { application: app, title: 'Rebooting ' + instance.instanceId, }; const submitMethod = (params = {}) => { if (app.attributes && app.attributes.platformHealthOnlyShowOverride && app.attributes.platformHealthOnly) { params.interestingHealthProviderNames = ['Amazon']; } return AmazonInstanceWriter.rebootInstance(instance, app, params); }; ConfirmationModalService.confirm({ header: 'Really reboot ' + instance.instanceId + '?', buttonText: 'Reboot ' + instance.instanceId, account: instance.account, platformHealthOnlyShowOverride: app.attributes.platformHealthOnlyShowOverride, platformHealthType: 'Amazon', taskMonitorConfig: taskMonitor, submitMethod: submitMethod, }); }; this.registerInstanceWithLoadBalancer = function registerInstanceWithLoadBalancer() { const instance = $scope.instance; const loadBalancerNames = instance.loadBalancers.join(' and '); const taskMonitor = { application: app, title: 'Registering ' + instance.instanceId + ' with ' + loadBalancerNames, }; const submitMethod = function () { return AmazonInstanceWriter.registerInstanceWithLoadBalancer(instance, app); }; ConfirmationModalService.confirm({ header: 'Really register ' + instance.instanceId + ' with ' + loadBalancerNames + '?', buttonText: 'Register ' + instance.instanceId, account: instance.account, taskMonitorConfig: taskMonitor, submitMethod: submitMethod, }); }; this.deregisterInstanceFromLoadBalancer = function deregisterInstanceFromLoadBalancer() { const instance = $scope.instance; const loadBalancerNames = instance.loadBalancers.join(' and '); const taskMonitor = { application: app, title: 'Deregistering ' + instance.instanceId + ' from ' + loadBalancerNames, }; const submitMethod = function () { return AmazonInstanceWriter.deregisterInstanceFromLoadBalancer(instance, app); }; ConfirmationModalService.confirm({ header: 'Really deregister ' + instance.instanceId + ' from ' + loadBalancerNames + '?', buttonText: 'Deregister ' + instance.instanceId, account: instance.account, taskMonitorConfig: taskMonitor, submitMethod: submitMethod, }); }; this.registerInstanceWithTargetGroup = function registerInstanceWithTargetGroup() { const instance = $scope.instance; const targetGroupNames = instance.targetGroups.join(' and '); const taskMonitor = { application: app, title: 'Registering ' + instance.instanceId + ' with ' + targetGroupNames, }; const submitMethod = function () { return AmazonInstanceWriter.registerInstanceWithTargetGroup(instance, app); }; ConfirmationModalService.confirm({ header: 'Really register ' + instance.instanceId + ' with ' + targetGroupNames + '?', buttonText: 'Register ' + instance.instanceId, account: instance.account, taskMonitorConfig: taskMonitor, submitMethod: submitMethod, }); }; this.deregisterInstanceFromTargetGroup = function deregisterInstanceFromTargetGroup() { const instance = $scope.instance; const targetGroupNames = instance.targetGroups.join(' and '); const taskMonitor = { application: app, title: 'Deregistering ' + instance.instanceId + ' from ' + targetGroupNames, }; const submitMethod = function () { return AmazonInstanceWriter.deregisterInstanceFromTargetGroup(instance, app); }; ConfirmationModalService.confirm({ header: 'Really deregister ' + instance.instanceId + ' from ' + targetGroupNames + '?', buttonText: 'Deregister ' + instance.instanceId, account: instance.account, taskMonitorConfig: taskMonitor, submitMethod: submitMethod, }); }; this.enableInstanceInDiscovery = function enableInstanceInDiscovery() { const instance = $scope.instance; const taskMonitor = { application: app, title: 'Enabling ' + instance.instanceId + ' in discovery', }; const submitMethod = function () { return AmazonInstanceWriter.enableInstanceInDiscovery(instance, app); }; ConfirmationModalService.confirm({ header: 'Really enable ' + instance.instanceId + ' in discovery?', buttonText: 'Enable ' + instance.instanceId, account: instance.account, taskMonitorConfig: taskMonitor, submitMethod: submitMethod, }); }; this.disableInstanceInDiscovery = function disableInstanceInDiscovery() { const instance = $scope.instance; const taskMonitor = { application: app, title: 'Disabling ' + instance.instanceId + ' in discovery', }; const submitMethod = function () { return AmazonInstanceWriter.disableInstanceInDiscovery(instance, app); }; ConfirmationModalService.confirm({ header: 'Really disable ' + instance.instanceId + ' in discovery?', buttonText: 'Disable ' + instance.instanceId, account: instance.account, taskMonitorConfig: taskMonitor, submitMethod: submitMethod, }); }; this.hasHealthState = function hasHealthState(healthProviderType, state) { const instance = $scope.instance; const healthMetrics = instance.health || []; return healthMetrics.some(function (health) { return health.type === healthProviderType && health.state === state; }); }; const constructInstanceActions = (instance) => { const constantActions = [ { label: 'Reboot', triggerAction: this.rebootInstance }, { label: 'Terminate', triggerAction: this.terminateInstance }, { label: 'Terminate and Shrink Server Group', triggerAction: this.terminateInstanceAndShrinkServerGroup }, ]; const conditionalActions = []; if (this.canRegisterWithDiscovery() && !instance.serverGroupDisabled) { conditionalActions.push({ label: 'Enable In Discovery', triggerAction: this.enableInstanceInDiscovery, }); } if (this.hasHealthState('Discovery', 'Up') || this.hasHealthState('Discovery', 'Down')) { conditionalActions.push({ label: 'Disable in Discovery', triggerAction: this.disableInstanceInDiscovery, }); } if (this.canRegisterWithLoadBalancer()) { conditionalActions.push({ label: 'Register with Load Balancer', triggerAction: this.registerInstanceWithLoadBalancer, }); } if (this.canDeregisterFromLoadBalancer()) { conditionalActions.push({ label: 'Deregister from Load Balancer', triggerAction: this.deregisterInstanceFromLoadBalancer, }); } if (this.canRegisterWithTargetGroup()) { conditionalActions.push({ label: 'Register with Target Group', triggerAction: this.registerInstanceWithTargetGroup, }); } if (this.canDeregisterFromTargetGroup()) { conditionalActions.push({ label: 'Deregister from Target Group', triggerAction: this.deregisterInstanceFromTargetGroup, }); } return conditionalActions.concat(constantActions); }; const initialize = app.isStandalone ? retrieveInstance() : $q.all([app.serverGroups.ready(), app.loadBalancers.ready()]).then(retrieveInstance); initialize.then(() => { $scope.instanceActions = constructInstanceActions($scope.instance); // Two things to look out for here: // 1. If the retrieveInstance call completes *after* the user has navigated away from the view, there // is no point in subscribing to the refresh // 2. If this is a standalone instance, there is no application that will refresh if (!$scope.$$destroyed && !app.isStandalone) { app.serverGroups.onRefresh($scope, retrieveInstance); } }); $scope.account = instance.account; }, ]);