UNPKG

@spotinst/spinnaker-deck

Version:

Spinnaker-Deck service, forked with support to Spotinst

742 lines (663 loc) 29.2 kB
import { IQService, module } from 'angular'; import { chain, clone, extend, find, flatten, has, intersection, keys, some, xor } from 'lodash'; import { IAmazonLoadBalancer } from '@spinnaker/amazon'; import { AccountService, CACHE_INITIALIZER_SERVICE, IAccountDetails, IArtifact, IDeploymentStrategy, IPipeline, IRegion, IServerGroupCommand, IServerGroupCommandBackingData, IServerGroupCommandBackingDataFiltered, IServerGroupCommandDirty, IServerGroupCommandResult, IServerGroupCommandViewState, IStage, ISubnet, LOAD_BALANCER_READ_SERVICE, LoadBalancerReader, NameUtils, SECURITY_GROUP_READER, SecurityGroupReader, SERVER_GROUP_COMMAND_REGISTRY_PROVIDER, ServerGroupCommandRegistry, SubnetReader, } from '@spinnaker/core'; import { DockerImageReader, IDockerImage } from '@spinnaker/docker'; import { IEcsCapacityProviderDetails } from '../../ecsCluster/IEcsCapacityProviderDetails'; import { IEcsClusterDescriptor } from '../../ecsCluster/IEcsCluster'; import { EcsClusterReader } from '../../ecsCluster/ecsCluster.read.service'; import { IRoleDescriptor } from '../../iamRoles/IRole'; import { IamRoleReader } from '../../iamRoles/iamRole.read.service'; import { IMetricAlarmDescriptor } from '../../metricAlarm/MetricAlarm'; import { MetricAlarmReader } from '../../metricAlarm/metricAlarm.read.service'; import { IPlacementStrategy } from '../../placementStrategy/IPlacementStrategy'; import { PlacementStrategyService } from '../../placementStrategy/placementStrategy.service'; import { ISecretDescriptor } from '../../secrets/ISecret'; import { SecretReader } from '../../secrets/secret.read.service'; import { IServiceDiscoveryRegistryDescriptor } from '../../serviceDiscovery/IServiceDiscovery'; import { ServiceDiscoveryReader } from '../../serviceDiscovery/serviceDiscovery.read.service'; export interface IEcsServerGroupCommandDirty extends IServerGroupCommandDirty { targetGroups?: string[]; defaulCapacityProviders?: string[]; customCapacityProviders?: string[]; } export interface IEcsServerGroupCommandResult extends IServerGroupCommandResult { dirty: IEcsServerGroupCommandDirty; } export interface IEcsDockerImage extends IDockerImage { imageId: string; message: string; fromTrigger: boolean; fromContext: boolean; stageId: string; imageLabelOrSha: string; } export interface IEcsServerGroupCommandViewState extends IServerGroupCommandViewState { contextImages: IEcsDockerImage[]; pipeline: IPipeline; currentStage: IStage; dirty: IEcsServerGroupCommandDirty; } export interface IEcsServerGroupCommandBackingDataFiltered extends IServerGroupCommandBackingDataFiltered { targetGroups: string[]; iamRoles: string[]; ecsClusters: string[]; availableCapacityProviders: string[]; defaultCapacityProviderStrategy: IEcsCapacityProviderStrategyItem[]; metricAlarms: IMetricAlarmDescriptor[]; subnetTypes: ISubnet[]; securityGroupNames: string[]; secrets: string[]; serviceDiscoveryRegistries: IServiceDiscoveryRegistryDescriptor[]; images: IEcsDockerImage[]; } export interface IEcsServerGroupCommandBackingData extends IServerGroupCommandBackingData { filtered: IEcsServerGroupCommandBackingDataFiltered; targetGroups: string[]; ecsClusters: IEcsClusterDescriptor[]; capacityProviderDetails: IEcsCapacityProviderDetails[]; iamRoles: IRoleDescriptor[]; metricAlarms: IMetricAlarmDescriptor[]; launchTypes: string[]; networkModes: string[]; secrets: ISecretDescriptor[]; serviceDiscoveryRegistries: IServiceDiscoveryRegistryDescriptor[]; images: IEcsDockerImage[]; } export interface IEcsTaskDefinitionArtifact { artifact?: IArtifact; artifactId?: string; } export interface IEcsContainerMapping { containerName: string; imageDescription: IEcsDockerImage; } export interface IEcsTargetGroupMapping { containerName: string; containerPort: number; targetGroup: string; } export interface IEcsServiceDiscoveryRegistryAssociation { registry: IServiceDiscoveryRegistryDescriptor; containerPort: number; containerName: string; } export interface IEcsCapacityProviderStrategyItem { capacityProvider: string; base: number; weight: number; } export interface IEcsServerGroupCommand extends IServerGroupCommand { associatePublicIpAddress: boolean; backingData: IEcsServerGroupCommandBackingData; computeUnits: number; reservedMemory: number; targetHealthyDeployPercentage: number; targetGroup: string; containerPort: number; placementStrategyName: string; placementStrategySequence: IPlacementStrategy[]; imageDescription: IEcsDockerImage; networkMode: string; subnetType: string; subnetTypes: string[]; securityGroups: string[]; viewState: IEcsServerGroupCommandViewState; taskDefinitionArtifact: IEcsTaskDefinitionArtifact; taskDefinitionArtifactAccount: string; containerMappings: IEcsContainerMapping[]; loadBalancedContainer: string; targetGroupMappings: IEcsTargetGroupMapping[]; serviceDiscoveryAssociations: IEcsServiceDiscoveryRegistryAssociation[]; useTaskDefinitionArtifact: boolean; capacityProviderStrategy: IEcsCapacityProviderStrategyItem[]; useDefaultCapacityProviders: boolean; ecsClusterName: string; subnetTypeChanged: (command: IEcsServerGroupCommand) => IServerGroupCommandResult; placementStrategyNameChanged: (command: IEcsServerGroupCommand) => IServerGroupCommandResult; regionIsDeprecated: (command: IEcsServerGroupCommand) => boolean; clusterChanged: (command: IServerGroupCommand) => void; } export class EcsServerGroupConfigurationService { // private enabledMetrics = ['GroupMinSize', 'GroupMaxSize', 'GroupDesiredCapacity', 'GroupInServiceInstances', 'GroupPendingInstances', 'GroupStandbyInstances', 'GroupTerminatingInstances', 'GroupTotalInstances']; // private healthCheckTypes = ['EC2', 'ELB']; // private terminationPolicies = ['OldestInstance', 'NewestInstance', 'OldestLaunchConfiguration', 'ClosestToNextInstanceHour', 'Default']; private launchTypes = ['EC2', 'FARGATE']; private networkModes = ['bridge', 'host', 'awsvpc', 'none', 'default']; public static $inject = [ '$q', 'loadBalancerReader', 'serverGroupCommandRegistry', 'iamRoleReader', 'ecsClusterReader', 'metricAlarmReader', 'placementStrategyService', 'securityGroupReader', 'secretReader', ]; constructor( private $q: IQService, private loadBalancerReader: LoadBalancerReader, private serverGroupCommandRegistry: ServerGroupCommandRegistry, private iamRoleReader: IamRoleReader, private ecsClusterReader: EcsClusterReader, private metricAlarmReader: MetricAlarmReader, private placementStrategyService: PlacementStrategyService, private securityGroupReader: SecurityGroupReader, private secretReader: SecretReader, ) {} public configureUpdateCommand(command: IEcsServerGroupCommand): void { command.backingData = { // terminationPolicies: clone(this.terminationPolicies) launchTypes: clone(this.launchTypes), networkModes: clone(this.networkModes), } as IEcsServerGroupCommandBackingData; } // TODO (Bruno Carrier): Why do we need to inject an Application into this constructor so that the app works? This is strange, and needs investigating public configureCommand(cmd: IEcsServerGroupCommand, imageQuery = ''): PromiseLike<void> { this.applyOverrides('beforeConfiguration', cmd); cmd.toggleSuspendedProcess = (command: IEcsServerGroupCommand, process: string): void => { command.suspendedProcesses = command.suspendedProcesses || []; const processIndex = command.suspendedProcesses.indexOf(process); if (processIndex === -1) { command.suspendedProcesses.push(process); } else { command.suspendedProcesses.splice(processIndex, 1); } }; cmd.processIsSuspended = (command: IEcsServerGroupCommand, process: string): boolean => command.suspendedProcesses.includes(process); cmd.onStrategyChange = (command: IEcsServerGroupCommand, strategy: IDeploymentStrategy): void => { // Any strategy other than None or Custom should force traffic to be enabled if (strategy.key !== '' && strategy.key !== 'custom') { command.suspendedProcesses = (command.suspendedProcesses || []).filter((p) => p !== 'AddToLoadBalancer'); } }; cmd.regionIsDeprecated = (command: IEcsServerGroupCommand): boolean => { return ( has(command, 'backingData.filtered.regions') && command.backingData.filtered.regions.some((region) => region.name === command.region && region.deprecated) ); }; const imageQueries = cmd.imageDescription ? [this.grabImageAndTag(cmd.imageDescription.imageId)] : []; if (imageQuery) { imageQueries.push(imageQuery); } let imagesPromise; if (imageQueries.length) { imagesPromise = this.$q .all( imageQueries.map((q) => DockerImageReader.findImages({ provider: 'dockerRegistry', count: 50, q: q, }), ), ) .then((promises) => flatten(promises)); } else { imagesPromise = this.$q.when([]); } return this.$q .all({ credentialsKeyedByAccount: AccountService.getCredentialsKeyedByAccount('ecs'), loadBalancers: this.loadBalancerReader.listLoadBalancers('ecs'), subnets: SubnetReader.listSubnetsByProvider('ecs'), iamRoles: this.iamRoleReader.listRoles('ecs'), ecsClusters: this.ecsClusterReader.listClusters(), capacityProviderDetails: this.ecsClusterReader.describeClusters(cmd.credentials, cmd.region), metricAlarms: this.metricAlarmReader.listMetricAlarms(), securityGroups: this.securityGroupReader.getAllSecurityGroups(), launchTypes: this.$q.when(clone(this.launchTypes)), networkModes: this.$q.when(clone(this.networkModes)), secrets: this.secretReader.listSecrets(), serviceDiscoveryRegistries: ServiceDiscoveryReader.listServiceDiscoveryRegistries(), images: imagesPromise, }) .then((backingData: Partial<IEcsServerGroupCommandBackingData>) => { backingData.accounts = keys(backingData.credentialsKeyedByAccount); backingData.filtered = {} as IEcsServerGroupCommandBackingDataFiltered; if (cmd.viewState.contextImages) { backingData.images = backingData.images.concat(cmd.viewState.contextImages); } cmd.backingData = backingData as IEcsServerGroupCommandBackingData; this.configureVpcId(cmd); this.configureAvailableIamRoles(cmd); this.configureAvailableSubnetTypes(cmd); this.configureAvailableSecurityGroups(cmd); this.configureAvailableEcsClusters(cmd); this.setCapacityProviderDetails(cmd); this.configureAvailableSecrets(cmd); this.configureAvailableServiceDiscoveryRegistries(cmd); this.configureAvailableImages(cmd); this.configureAvailableRegions(cmd); this.configureLoadBalancerOptions(cmd); this.applyOverrides('afterConfiguration', cmd); this.attachEventHandlers(cmd); }); } public setCapacityProviderDetails(command: IEcsServerGroupCommand): void { this.$q .all({ capacityProviderDetails: this.ecsClusterReader.describeClusters(command.credentials, command.region), }) .then((result: Partial<IEcsServerGroupCommandBackingData>) => { command.backingData.capacityProviderDetails = chain(result.capacityProviderDetails) .map((cluster) => this.mapCapacityProviderDetails(cluster)) .value(); }); if (command.ecsClusterName != null && command.ecsClusterName.length > 0) { this.configureAvailableCapacityProviders(command); } else { command.backingData.filtered.availableCapacityProviders = []; command.backingData.filtered.defaultCapacityProviderStrategy = []; } this.checkDirtyCapacityProviders(command); } public configureAvailableCapacityProviders(command: IEcsServerGroupCommand): void { command.backingData.filtered.availableCapacityProviders = chain(command.backingData.capacityProviderDetails) .filter({ clusterName: command.ecsClusterName, }) .map((availableCPs) => availableCPs.capacityProviders) .flattenDeep<string>() .value(); command.backingData.filtered.defaultCapacityProviderStrategy = chain(command.backingData.capacityProviderDetails) .filter({ clusterName: command.ecsClusterName, }) .map((availableCPs) => availableCPs.defaultCapacityProviderStrategy) .flattenDeep<IEcsCapacityProviderStrategyItem>() .value(); } public mapCapacityProviderDetails(describeClusters: IEcsCapacityProviderDetails): IEcsCapacityProviderDetails { return { capacityProviders: describeClusters.capacityProviders, clusterName: describeClusters.clusterName, defaultCapacityProviderStrategy: describeClusters.defaultCapacityProviderStrategy, }; } public applyOverrides(phase: string, command: IEcsServerGroupCommand): void { this.serverGroupCommandRegistry.getCommandOverrides('ecs').forEach((override: any) => { if (override[phase]) { override[phase](command); } }); } public grabImageAndTag(imageId: string): string { return imageId.split('/').pop(); } public buildImageId(image: IEcsDockerImage): string { if (image.fromContext) { return `${image.imageLabelOrSha}`; } else if (image.fromTrigger && !image.tag) { return `${image.registry}/${image.repository} (Tag resolved at runtime)`; } else { return `${image.registry}/${image.repository}:${image.tag}`; } } public mapImage(image: IEcsDockerImage): IEcsDockerImage { if (image.message !== undefined) { return image; } return { repository: image.repository, tag: image.tag, imageId: this.buildImageId(image), registry: image.registry, fromContext: image.fromContext, fromTrigger: image.fromTrigger, account: image.account, imageLabelOrSha: image.imageLabelOrSha, stageId: image.stageId, message: image.message, }; } public configureAvailableImages(command: IEcsServerGroupCommand): void { // No filtering required, but need to decorate with the displayable image ID command.backingData.filtered.images = command.backingData.images.map((image) => this.mapImage(image)); } public configureAvailabilityZones(command: IEcsServerGroupCommand): void { command.backingData.filtered.availabilityZones = find<IRegion>( command.backingData.credentialsKeyedByAccount[command.credentials].regions, { name: command.region }, ).availabilityZones; command.availabilityZones = command.backingData.filtered.availabilityZones; } public configureAvailableSecurityGroups(command: IEcsServerGroupCommand): void { if (command.subnetType == null && (command.subnetTypes == null || command.subnetTypes.length == 0)) { command.backingData.filtered.securityGroups = []; return; } const vpcId = chain(command.backingData.subnets) .filter({ account: command.credentials, region: command.region, purpose: command.subnetTypes ? command.subnetTypes[0] : command.subnetType, }) .compact() .uniqBy('purpose') .map('vpcId') .value()[0]; if ( command.backingData.securityGroups[command.credentials] && command.backingData.securityGroups[command.credentials]['ecs'] && command.backingData.securityGroups[command.credentials]['ecs'][command.region] ) { const allSecurityGroups = command.backingData.securityGroups[command.credentials]['ecs'][command.region]; command.backingData.filtered.securityGroupNames = chain(allSecurityGroups) .filter({ vpcId: vpcId }) .map('name') .value() as string[]; } } public configureAvailableSubnetTypes(command: IEcsServerGroupCommand): void { command.backingData.filtered.subnetTypes = chain(command.backingData.subnets) .filter({ account: command.credentials, region: command.region, }) .compact() .uniqBy('purpose') .value() .filter(function (e: ISubnet) { return e.purpose && e.purpose.length > 0; }); } public configureAvailableEcsClusters(command: IEcsServerGroupCommand): void { command.backingData.filtered.ecsClusters = chain(command.backingData.ecsClusters) .filter({ account: command.credentials, region: command.region, }) .map('name') .value(); } public configureAvailableSecrets(command: IEcsServerGroupCommand): void { command.backingData.filtered.secrets = chain(command.backingData.secrets) .filter({ account: command.credentials, region: command.region, }) .map('name') .value(); } public buildServiceRegistryDisplayName(registry: IServiceDiscoveryRegistryDescriptor): string { return `${registry.name} (${registry.id})`; } public mapServiceRegistry(registry: IServiceDiscoveryRegistryDescriptor): IServiceDiscoveryRegistryDescriptor { return { account: registry.account, region: registry.region, name: registry.name, id: registry.id, arn: registry.arn, displayName: this.buildServiceRegistryDisplayName(registry), }; } public configureAvailableServiceDiscoveryRegistries(command: IEcsServerGroupCommand): void { command.backingData.filtered.serviceDiscoveryRegistries = chain(command.backingData.serviceDiscoveryRegistries) .filter({ account: command.credentials, region: command.region, }) .map((registry) => this.mapServiceRegistry(registry)) .value(); } public configureAvailableRegions(command: IEcsServerGroupCommand): void { const regionsForAccount: IAccountDetails = command.backingData.credentialsKeyedByAccount[command.credentials] || ({ regions: [] } as IAccountDetails); command.backingData.filtered.regions = regionsForAccount.regions; } public configureAvailableIamRoles(command: IEcsServerGroupCommand): void { command.backingData.filtered.iamRoles = chain(command.backingData.iamRoles) .filter({ accountName: command.credentials }) .map('name') .value(); if (command.backingData.filtered.iamRoles.length > 0) { command.backingData.filtered.iamRoles.splice(0, 0, 'None (No IAM role)'); } } public configureSubnetPurposes(command: IEcsServerGroupCommand): IServerGroupCommandResult { const result: IEcsServerGroupCommandResult = { dirty: {} }; const filteredData = command.backingData.filtered; if (command.region === null) { return result; } filteredData.subnetPurposes = chain(command.backingData.subnets) .filter({ account: command.credentials, region: command.region }) .reject({ target: 'elb' }) .reject({ purpose: null }) .uniqBy('purpose') .value(); if (command.subnetTypes) { result.dirty.subnetType = !command.subnetTypes.every((subnetType) => chain(filteredData.subnetPurposes).some({ purpose: subnetType }).value(), ); } else if (!chain(filteredData.subnetPurposes).some({ purpose: command.subnetType }).value()) { result.dirty.subnetType = true; } if (result.dirty.subnetType) { command.subnetTypes = null; command.subnetType = null; } return result; } private getLoadBalancerMap(command: IEcsServerGroupCommand): IAmazonLoadBalancer[] { return chain(command.backingData.loadBalancers) .map('accounts') .flattenDeep() .filter({ name: command.credentials }) .map('regions') .flattenDeep() .filter({ name: command.region }) .map<IAmazonLoadBalancer>('loadBalancers') .flattenDeep<IAmazonLoadBalancer>() .value(); } public getLoadBalancerNames(command: IEcsServerGroupCommand): string[] { const loadBalancers = this.getLoadBalancerMap(command).filter( (lb) => (!lb.loadBalancerType || lb.loadBalancerType === 'classic') && lb.vpcId === command.vpcId, ); return loadBalancers.map((lb) => lb.name).sort(); } public getVpcLoadBalancerNames(command: IEcsServerGroupCommand): string[] { const loadBalancersForVpc = this.getLoadBalancerMap(command).filter( (lb) => (!lb.loadBalancerType || lb.loadBalancerType === 'classic') && lb.vpcId, ); return loadBalancersForVpc.map((lb) => lb.name).sort(); } public getTargetGroupNames(command: IEcsServerGroupCommand): string[] { const loadBalancersV2 = this.getLoadBalancerMap(command).filter((lb) => lb.loadBalancerType !== 'classic') as any[]; const allTargetGroups = flatten(loadBalancersV2.map<string[]>((lb) => lb.targetGroups)); return allTargetGroups.sort(); } public configureLoadBalancerOptions(command: IEcsServerGroupCommand): IServerGroupCommandResult { const result: IEcsServerGroupCommandResult = { dirty: {} }; const currentLoadBalancers = (command.loadBalancers || []).concat(command.vpcLoadBalancers || []); const newLoadBalancers = this.getLoadBalancerNames(command); const vpcLoadBalancers = this.getVpcLoadBalancerNames(command); const allTargetGroups = this.getTargetGroupNames(command); const currentTargetGroups = command.targetGroupMappings.map((tg) => tg.targetGroup); if (currentLoadBalancers && command.loadBalancers) { const valid = command.vpcId ? newLoadBalancers : newLoadBalancers.concat(vpcLoadBalancers); const matched = intersection(valid, currentLoadBalancers); const removedLoadBalancers = xor(matched, currentLoadBalancers); command.loadBalancers = intersection(newLoadBalancers, matched); if (!command.vpcId) { command.vpcLoadBalancers = intersection(vpcLoadBalancers, matched); } else { delete command.vpcLoadBalancers; } if (removedLoadBalancers.length) { result.dirty.loadBalancers = removedLoadBalancers; } } if (currentTargetGroups) { const matched = intersection(allTargetGroups, currentTargetGroups); const removedTargetGroups = xor(matched, currentTargetGroups); if (removedTargetGroups && removedTargetGroups.length > 0) { command.viewState.dirty.targetGroups = removedTargetGroups; } else if (command.viewState.dirty && command.viewState.dirty.targetGroups) { command.viewState.dirty.targetGroups = []; } } command.backingData.filtered.loadBalancers = newLoadBalancers; command.backingData.filtered.vpcLoadBalancers = vpcLoadBalancers; command.backingData.filtered.targetGroups = allTargetGroups; return result; } public refreshLoadBalancers(command: IEcsServerGroupCommand, skipCommandReconfiguration?: boolean) { return this.loadBalancerReader.listLoadBalancers('ecs').then((loadBalancers) => { command.backingData.loadBalancers = loadBalancers; if (!skipCommandReconfiguration) { this.configureLoadBalancerOptions(command); } }); } public configureVpcId(command: IEcsServerGroupCommand): IEcsServerGroupCommandResult { const result: IEcsServerGroupCommandResult = { dirty: {} }; if (command.subnetType == null && (command.subnetTypes == null || command.subnetTypes.length == 0)) { command.vpcId = null; result.dirty.vpcId = true; } else if (command.subnetTypes != null && command.subnetTypes.length > 0) { command.subnetTypes.forEach(function (subnetType) { const subnet = find<ISubnet>(command.backingData.subnets, { purpose: subnetType, account: command.credentials, region: command.region, }); command.vpcId = subnet ? subnet.vpcId : null; }); } else { const subnet = find<ISubnet>(command.backingData.subnets, { purpose: command.subnetType, account: command.credentials, region: command.region, }); command.vpcId = subnet ? subnet.vpcId : null; } return result; } public checkDirtyCapacityProviders(command: IEcsServerGroupCommand): void { if (command.capacityProviderStrategy) { const availableCustomCapacityProviders = command.backingData.filtered.availableCapacityProviders; const currentCapacityProviders = command.capacityProviderStrategy.map((cp) => cp.capacityProvider); const matchedCustomCapacityProviders = intersection(availableCustomCapacityProviders, currentCapacityProviders); const removedCustomCapacityProviders = xor(matchedCustomCapacityProviders, currentCapacityProviders); if (removedCustomCapacityProviders && removedCustomCapacityProviders.length > 0) { command.viewState.dirty.customCapacityProviders = removedCustomCapacityProviders; } else if (command.viewState.dirty && command.viewState.dirty.customCapacityProviders) { command.viewState.dirty.customCapacityProviders = []; } if (command.useDefaultCapacityProviders) { const availableDefaultCapacityProvider = command.backingData.filtered.defaultCapacityProviderStrategy.map( (cp) => cp.capacityProvider, ); const matchedDefaultCapacityProviders = intersection( availableDefaultCapacityProvider, currentCapacityProviders, ); const removedDefaultCapacityProviders = xor(matchedDefaultCapacityProviders, currentCapacityProviders); if (removedDefaultCapacityProviders && removedDefaultCapacityProviders.length > 0) { command.viewState.dirty.defaulCapacityProviders = removedDefaultCapacityProviders; } } } } public attachEventHandlers(cmd: IEcsServerGroupCommand): void { cmd.subnetChanged = (command: IEcsServerGroupCommand): IServerGroupCommandResult => { const result = this.configureVpcId(command); extend(result.dirty, this.configureLoadBalancerOptions(command).dirty); command.viewState.dirty = command.viewState.dirty || {}; extend(command.viewState.dirty, result.dirty); return result; }; cmd.regionChanged = (command: IEcsServerGroupCommand): IServerGroupCommandResult => { const result: IEcsServerGroupCommandResult = { dirty: {} }; extend(result.dirty, this.configureSubnetPurposes(command).dirty); if (command.region) { extend(result.dirty, command.subnetChanged(command).dirty); this.configureAvailabilityZones(command); this.configureAvailableEcsClusters(command); this.configureAvailableSubnetTypes(command); this.configureAvailableSecurityGroups(command); this.configureAvailableSecrets(command); this.configureAvailableServiceDiscoveryRegistries(command); this.setCapacityProviderDetails(command); } return result; }; cmd.subnetTypeChanged = (command: IEcsServerGroupCommand): IServerGroupCommandResult => { const result: IEcsServerGroupCommandResult = { dirty: {} }; this.configureAvailableSecurityGroups(command); return result; }; cmd.clusterChanged = (command: IEcsServerGroupCommand): void => { command.moniker = NameUtils.getMoniker(command.application, command.stack, command.freeFormDetails); }; cmd.credentialsChanged = (command: IEcsServerGroupCommand): IServerGroupCommandResult => { const result: IEcsServerGroupCommandResult = { dirty: {} }; const backingData = command.backingData; if (command.credentials) { this.configureAvailableIamRoles(command); this.configureAvailableEcsClusters(command); this.configureAvailableSubnetTypes(command); this.configureAvailableSecurityGroups(command); this.configureAvailableSecrets(command); this.configureAvailableServiceDiscoveryRegistries(command); this.configureAvailableRegions(command); this.setCapacityProviderDetails(command); if (!some(backingData.filtered.regions, { name: command.region })) { command.region = null; result.dirty.region = true; } else { extend(result.dirty, command.regionChanged(command).dirty); } } else { command.region = null; } return result; }; cmd.placementStrategyNameChanged = (command: IEcsServerGroupCommand): IServerGroupCommandResult => { const result: IEcsServerGroupCommandResult = { dirty: {} }; command.placementStrategySequence = this.placementStrategyService.getPredefinedStrategy( command.placementStrategyName, ); return result; }; this.applyOverrides('attachEventHandlers', cmd); } } export const ECS_SERVER_GROUP_CONFIGURATION_SERVICE = 'spinnaker.ecs.serverGroup.configure.service'; module(ECS_SERVER_GROUP_CONFIGURATION_SERVICE, [ LOAD_BALANCER_READ_SERVICE, SECURITY_GROUP_READER, CACHE_INITIALIZER_SERVICE, SERVER_GROUP_COMMAND_REGISTRY_PROVIDER, ]).service('ecsServerGroupConfigurationService', EcsServerGroupConfigurationService);