@spotinst/spinnaker-deck
Version:
Spinnaker-Deck service, forked with support to Spotinst
281 lines (247 loc) • 10.5 kB
JavaScript
;
import * as angular from 'angular';
import _ from 'lodash';
import { AccountService, INSTANCE_TYPE_SERVICE } from '@spinnaker/core';
import { ECS_SERVER_GROUP_CONFIGURATION_SERVICE } from './serverGroupConfiguration.service';
export const ECS_SERVERGROUP_CONFIGURE_SERVERGROUPCOMMANDBUILDER_SERVICE =
'spinnaker.ecs.serverGroupCommandBuilder.service';
export const name = ECS_SERVERGROUP_CONFIGURE_SERVERGROUPCOMMANDBUILDER_SERVICE; // for backwards compatibility
angular
.module(ECS_SERVERGROUP_CONFIGURE_SERVERGROUPCOMMANDBUILDER_SERVICE, [
INSTANCE_TYPE_SERVICE,
ECS_SERVER_GROUP_CONFIGURATION_SERVICE,
])
.factory('ecsServerGroupCommandBuilder', [
'$q',
'instanceTypeService',
'ecsServerGroupConfigurationService',
function ($q, instanceTypeService, ecsServerGroupConfigurationService) {
function reconcileUpstreamImages(image, upstreamImages) {
if (image.fromContext) {
const matchingImage = upstreamImages.find((otherImage) => image.stageId === otherImage.stageId);
if (matchingImage) {
image.cluster = matchingImage.cluster;
image.pattern = matchingImage.pattern;
image.repository = matchingImage.repository;
return image;
} else {
return null;
}
} else if (image.fromTrigger) {
const matchingImage = upstreamImages.find((otherImage) => {
return (
image.registry === otherImage.registry &&
image.repository === otherImage.repository &&
image.tag === otherImage.tag
);
});
if (matchingImage) {
return image;
} else {
return null;
}
} else {
return image;
}
}
function findUpstreamImages(current, all, visited = {}) {
// This actually indicates a loop in the stage dependencies.
if (visited[current.refId]) {
return [];
} else {
visited[current.refId] = true;
}
let result = [];
if (current.type === 'findImageFromTags') {
result.push({
fromContext: true,
imageLabelOrSha: current.imageLabelOrSha,
stageId: current.refId,
});
}
current.requisiteStageRefIds.forEach(function (id) {
const next = all.find((stage) => stage.refId === id);
if (next) {
result = result.concat(findUpstreamImages(next, all, visited));
}
});
return result;
}
function findTriggerImages(triggers) {
const result = triggers
.filter((trigger) => {
return trigger.type === 'docker';
})
.map((trigger) => {
return {
fromTrigger: true,
repository: trigger.repository,
account: trigger.account,
organization: trigger.organization,
registry: trigger.registry,
tag: trigger.tag,
};
});
return result;
}
function buildNewServerGroupCommand(application, defaults) {
defaults = defaults || {};
const credentialsLoader = AccountService.getCredentialsKeyedByAccount('ecs');
const defaultCredentials = defaults.account || application.defaultCredentials.ecs;
const defaultRegion = defaults.region || application.defaultRegions.ecs;
const preferredZonesLoader = AccountService.getAvailabilityZonesForAccountAndRegion(
'ecs',
defaultCredentials,
defaultRegion,
);
return $q
.all({
preferredZones: preferredZonesLoader,
credentialsKeyedByAccount: credentialsLoader,
})
.then(function (asyncData) {
const availabilityZones = asyncData.preferredZones;
let defaultIamRole = 'None (No IAM role)';
defaultIamRole = defaultIamRole.replace('{{application}}', application.name);
const defaultImageCredentials = 'None (No registry credentials)';
const command = {
application: application.name,
credentials: defaultCredentials,
region: defaultRegion,
strategy: '',
capacity: {
min: 1,
max: 1,
desired: 1,
},
launchType: 'EC2',
healthCheckType: 'EC2',
selectedProvider: 'ecs',
iamRole: defaultIamRole,
dockerImageCredentialsSecret: defaultImageCredentials,
availabilityZones: availabilityZones,
subnetType: '',
securityGroups: [],
healthCheckGracePeriodSeconds: '',
placementConstraints: [],
placementStrategyName: '',
taskDefinitionArtifact: {},
useTaskDefinitionArtifact: false,
placementStrategySequence: [],
serviceDiscoveryAssociations: [],
ecsClusterName: '',
targetGroup: '',
copySourceScalingPoliciesAndActions: true,
preferSourceCapacity: true,
useSourceCapacity: true,
viewState: {
useAllImageSelection: false,
useSimpleCapacity: true,
usePreferredZones: true,
mode: defaults.mode || 'create',
disableStrategySelection: true,
dirty: {},
},
};
if (
application.attributes &&
application.attributes.platformHealthOnlyShowOverride &&
application.attributes.platformHealthOnly
) {
command.interestingHealthProviderNames = ['ecs'];
}
return command;
});
}
function buildServerGroupCommandFromPipeline(application, originalCluster, current, pipeline) {
const pipelineCluster = _.cloneDeep(originalCluster);
const region = Object.keys(pipelineCluster.availabilityZones)[0];
// var instanceTypeCategoryLoader = instanceTypeService.getCategoryForInstanceType('ecs', pipelineCluster.instanceType);
const commandOptions = { account: pipelineCluster.account, region: region };
return buildNewServerGroupCommand(application, commandOptions).then(function (command) {
const zones = pipelineCluster.availabilityZones[region];
const usePreferredZones = zones.join(',') === command.availabilityZones.join(',');
let contextImages = findUpstreamImages(current, pipeline.stages) || [];
contextImages = contextImages.concat(findTriggerImages(pipeline.triggers));
if (command.docker && command.docker.image) {
command.docker.image = reconcileUpstreamImages(command.docker.image, contextImages);
}
const viewState = {
instanceProfile: undefined,
disableImageSelection: true,
useSimpleCapacity:
pipelineCluster.capacity.min === pipelineCluster.capacity.max &&
pipelineCluster.useSourceCapacity !== true,
usePreferredZones: usePreferredZones,
mode: 'editPipeline',
submitButtonLabel: 'Done',
templatingEnabled: true,
existingPipelineCluster: true,
dirty: {},
contextImages: contextImages,
pipeline: pipeline,
currentStage: current,
};
const viewOverrides = {
region: region,
credentials: pipelineCluster.account,
availabilityZones: pipelineCluster.availabilityZones[region],
viewState: viewState,
};
pipelineCluster.strategy = pipelineCluster.strategy || '';
return angular.extend({}, command, pipelineCluster, viewOverrides);
});
}
// Only used to prepare view requiring template selecting
function buildNewServerGroupCommandForPipeline(current, pipeline) {
let contextImages = findUpstreamImages(current, pipeline.stages) || [];
contextImages = contextImages.concat(findTriggerImages(pipeline.triggers));
return $q.when({
viewState: {
requiresTemplateSelection: true,
// applies viewState overrides after template selection
overrides: {
viewState: {
mode: 'editPipeline',
contextImages: contextImages,
pipeline: pipeline,
currentStage: current,
},
},
},
});
}
function buildUpdateServerGroupCommand(serverGroup) {
const command = {
type: 'modifyAsg',
asgs: [{ asgName: serverGroup.name, region: serverGroup.region }],
healthCheckType: serverGroup.asg.healthCheckType,
credentials: serverGroup.account,
};
ecsServerGroupConfigurationService.configureUpdateCommand(command);
return command;
}
function buildServerGroupCommandFromExisting(application, serverGroup, mode = 'clone') {
// do NOT copy: deployment strategy. DO copy: account, region, cluster name, stack
// TODO: query for & pull in ECS-specific data that would be useful, e.g, network mode, launch type
const commandOptions = { account: serverGroup.account, region: serverGroup.region };
return buildNewServerGroupCommand(application, commandOptions).then(function (command) {
command.credentials = serverGroup.account;
command.app = serverGroup.moniker.app;
command.stack = serverGroup.moniker.stack;
command.region = serverGroup.region;
command.ecsClusterName = serverGroup.ecsCluster;
command.capacity = serverGroup.capacity;
command.viewState.mode = mode;
return command;
});
}
return {
buildNewServerGroupCommand: buildNewServerGroupCommand,
buildServerGroupCommandFromExisting: buildServerGroupCommandFromExisting,
buildNewServerGroupCommandForPipeline: buildNewServerGroupCommandForPipeline,
buildServerGroupCommandFromPipeline: buildServerGroupCommandFromPipeline,
buildUpdateServerGroupCommand: buildUpdateServerGroupCommand,
};
},
]);