-
Notifications
You must be signed in to change notification settings - Fork 900
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1852a0b
commit ad1747e
Showing
88 changed files
with
5,258 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { ICredentials } from '@spinnaker/core'; | ||
|
||
export interface ITitusCredentials extends ICredentials { | ||
awsAccount: string; | ||
awsVpc: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './ITitusCredentials'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import {module} from 'angular'; | ||
import {HELP_CONTENTS_REGISTRY, HelpContentsRegistry} from '@spinnaker/core'; | ||
|
||
const helpContents: {[key: string]: string} = { | ||
'titus.deploy.runtimeLimitSecs': '<p>Maximum amount of time (in seconds) a batch job is allowed to run</p>', | ||
'titus.deploy.retries': '<p>Number of times to retry this job</p>', | ||
'titus.deploy.propertyFile': '<p>(Optional) Configures the name to the file used to pass in properties to later stages in the Spinnaker pipeline. The file must be saved into the /logs directory during execution</p>', | ||
'titus.deploy.iamProfile': 'AWS IAM instance profile to assign to this service', | ||
'titus.deploy.capacityGroup': 'Used by Titus to ensure capacity guarantees, defaults to the application name if not provided', | ||
'titus.deploy.network': 'Amount of networking bandwidth to allocate in Mbps', | ||
'titus.deploy.allocateIP': 'If selected, specifies an IP to be allocated for each of your job’s containers', | ||
'titus.deploy.softConstraints': 'Soft constraints are enforced on a best efforts basis. For example, if tasks can’t be perfectly balanced across zones, the best available balance is achieved without keeping the tasks pending for execution.', | ||
'titus.deploy.hardConstraints': 'Constraints must be met and tasks will not be launched if constraint can’t be perfectly met', | ||
'titus.deploy.efs': 'if completed, allows you to specify an EFS volume to attach to each Task that gets created for the Job', | ||
'titus.deploy.mountPoint': '(Required) A valid directory to mount the volume, e.g, <samp>/efs</samp>. Invalid locations are <samp>/</samp>, <samp>/data</samp>, and <samp>/logs</samp> as these are reserved directories.', | ||
'titus.deploy.efsId': '(Required) The EFS file system ID, e.g. <samp> fs-0208c74b</samp>.', | ||
'titus.job.waitForCompletion': 'if unchecked, marks the stage as successful right away without waiting for the job to complete', | ||
'titus.bake.fromGitTrigger': 'If checked, gets git details from the specified git trigger. The pipeline will fail when ran manually', | ||
'titus.bake.repositoryUrl': 'Url to the git repository containing the code to create the Docker image from, <samp>ssh://git@stash.corp.netflix.com:7999/SPKR/orca.git</samp> or <samp>ssh://git@github.com/spinnaker/orca.git</samp>', | ||
'titus.bake.repositoryHash': '(Optional) The hashcode to the git commit for the image', | ||
'titus.bake.repositoryBranch': '(Optional) The branch in git to build the image from', | ||
'titus.bake.repositoryDirectory': '(Optional) If specified, will build the image from the Dockerfile contained in this directory. Default to project root.', | ||
'titus.bake.imageOrganization': '(Optional) The organization to which this image belongs to, e.g. <samp>spinnaker</samp> for <samp>spinnaker/igor</samp>Defaults to none.', | ||
'titus.bake.imageName': '(Optional) The name for the image, e.g. <samp>igor</samp> for <samp>spinnaker/igor</samp>Defaults to [git project name].[git repo name].', | ||
'titus.bake.tags': '(Optional) Comma separated. By default, the <samp>latest</samp> tag is updated. Adds additional tags to label this image <samp>1.0.0-unstable,1.0.0-rc1</samp>', | ||
'titus.bake.buildParameters': '(Optional) Build time variables to be passed to the Docker image. These are the set of values passed to --build-args in the command line.', | ||
'titus.serverGroup.traffic': ` | ||
<p>Enables the "inService" scaling process, which is used by Spinnaker and discovery services to determine if the server group is enabled.</p> | ||
<p>Will be automatically enabled when any non "custom" deployment strategy is selected.</p>`, | ||
'titus.deploy.securityGroups': 'AWS Security Groups to assign to this service. Security groups are set only if <samp>Allocate IP?</samp> has been selected and are assigned to the Titus AWS Elastic Network Interface.', | ||
'titus.job.securityGroups': 'AWS Security Groups to assign to this job', | ||
|
||
}; | ||
|
||
export const TITUS_HELP = 'spinnaker.titus.help.contents'; | ||
module(TITUS_HELP, [HELP_CONTENTS_REGISTRY]) | ||
.run((helpContentsRegistry: HelpContentsRegistry) => { | ||
Object.keys(helpContents).forEach(key => helpContentsRegistry.register(key, helpContents[key])); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,260 @@ | ||
'use strict'; | ||
|
||
const angular = require('angular'); | ||
import { defaults, filter } from 'lodash'; | ||
|
||
import { | ||
ACCOUNT_SERVICE, | ||
CLOUD_PROVIDER_REGISTRY, | ||
CONFIRMATION_MODAL_SERVICE, | ||
INSTANCE_READ_SERVICE, | ||
INSTANCE_WRITE_SERVICE, | ||
RECENT_HISTORY_SERVICE | ||
} from '@spinnaker/core'; | ||
|
||
module.exports = angular.module('spinnaker.instance.detail.titus.controller', [ | ||
require('angular-ui-router').default, | ||
require('angular-ui-bootstrap'), | ||
ACCOUNT_SERVICE, | ||
INSTANCE_WRITE_SERVICE, | ||
INSTANCE_READ_SERVICE, | ||
CONFIRMATION_MODAL_SERVICE, | ||
RECENT_HISTORY_SERVICE, | ||
CLOUD_PROVIDER_REGISTRY, | ||
]) | ||
.controller('titusInstanceDetailsCtrl', function ($scope, $q, $state, $uibModal, accountService, | ||
instanceWriter, confirmationModalService, recentHistoryService, | ||
cloudProviderRegistry, instanceReader, instance, app) { | ||
|
||
// needed for standalone instances | ||
$scope.detailsTemplateUrl = cloudProviderRegistry.getValue('titus', 'instance.detailsTemplateUrl'); | ||
|
||
$scope.state = { | ||
loading: true, | ||
standalone: app.isStandalone | ||
}; | ||
|
||
$scope.application = app; | ||
|
||
function extractHealthMetrics(instance, latest) { | ||
// do not backfill on standalone instances | ||
if (app.isStandalone) { | ||
instance.health = latest.health; | ||
} | ||
|
||
instance.health = instance.health || []; | ||
var displayableMetrics = instance.health.filter( | ||
function(metric) { | ||
return metric.state !== 'Unknown'; | ||
}); | ||
|
||
// backfill details where applicable | ||
if (latest.health) { | ||
displayableMetrics.forEach(function (metric) { | ||
var detailsMatch = latest.health.filter(function (latestHealth) { | ||
return latestHealth.type === metric.type; | ||
}); | ||
if (detailsMatch.length) { | ||
defaults(metric, detailsMatch[0]); | ||
} | ||
}); | ||
} | ||
$scope.healthMetrics = displayableMetrics; | ||
} | ||
|
||
function retrieveInstance() { | ||
var extraData = {}; | ||
var instanceSummary, loadBalancers, account, region, vpcId; | ||
app.serverGroups.data.some(function (serverGroup) { | ||
return serverGroup.instances.some(function (possibleInstance) { | ||
if (possibleInstance.id === instance.instanceId) { | ||
instanceSummary = possibleInstance; | ||
loadBalancers = serverGroup.loadBalancers; | ||
account = serverGroup.account; | ||
region = serverGroup.region; | ||
extraData.serverGroup = serverGroup.name; | ||
return true; | ||
} | ||
}); | ||
}); | ||
|
||
if (instanceSummary && account && region) { | ||
extraData.account = account; | ||
extraData.region = region; | ||
recentHistoryService.addExtraDataToLatest('instances', extraData); | ||
return instanceReader.getInstanceDetails(account, region, instance.instanceId).then(function(details) { | ||
$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.loadBalancers = loadBalancers; | ||
$scope.baseIpAddress = $scope.instance.placement.containerIp || $scope.instance.placement.host; | ||
$scope.instance.externalIpAddress = $scope.instance.placement.host; | ||
getBastionAddressForAccount($scope.instance.account, $scope.instance.region); | ||
}, | ||
autoClose | ||
); | ||
} | ||
|
||
if (!instanceSummary) { | ||
$scope.instanceIdNotFound = instance.instanceId; | ||
$scope.state.loading = false; | ||
} | ||
return $q.when(null); | ||
} | ||
|
||
function autoClose() { | ||
if ($scope.$$destroyed) { | ||
return; | ||
} | ||
$state.params.allowModalToStayOpen = true; | ||
$state.go('^', null, {location: 'replace'}); | ||
} | ||
|
||
this.canRegisterWithLoadBalancer = function() { | ||
return false; | ||
}; | ||
|
||
this.canDeregisterFromLoadBalancer = function() { | ||
return false; | ||
}; | ||
|
||
this.canRegisterWithDiscovery = function() { | ||
let healthMetrics = $scope.instance.health || []; | ||
var discoveryHealth = healthMetrics.filter(function(health) { | ||
return health.type === 'Discovery'; | ||
}); | ||
return discoveryHealth.length ? discoveryHealth[0].state === 'OutOfService' : false; | ||
}; | ||
|
||
this.terminateInstance = function terminateInstance() { | ||
var instance = $scope.instance; | ||
instance.instanceId = instance.id; | ||
var taskMonitor = { | ||
application: app, | ||
title: 'Terminating ' + instance.instanceId, | ||
onTaskComplete: function() { | ||
if ($state.includes('**.instanceDetails', {id: instance.instanceId})) { | ||
$state.go('^'); | ||
} | ||
} | ||
}; | ||
|
||
var submitMethod = function () { | ||
let params = {cloudProvider: 'titus'}; | ||
if (instance.serverGroup) { | ||
params.managedInstanceGroupName = instance.serverGroup; | ||
} | ||
return instanceWriter.terminateInstance(instance, app, params); | ||
}; | ||
|
||
confirmationModalService.confirm({ | ||
header: 'Really terminate ' + instance.id + '?', | ||
buttonText: 'Terminate ' + instance.id, | ||
account: instance.account, | ||
provider: 'titus', | ||
taskMonitorConfig: taskMonitor, | ||
submitMethod: submitMethod | ||
}); | ||
}; | ||
|
||
this.registerInstanceWithLoadBalancer = function registerInstanceWithLoadBalancer() { | ||
// Do nothing | ||
}; | ||
|
||
this.deregisterInstanceFromLoadBalancer = function deregisterInstanceFromLoadBalancer() { | ||
// Do nothing | ||
}; | ||
|
||
this.enableInstanceInDiscovery = function enableInstanceInDiscovery() { | ||
var instance = $scope.instance; | ||
instance.instanceId = instance.id; | ||
|
||
var taskMonitor = { | ||
application: app, | ||
title: 'Enabling ' + instance.instanceId + ' in discovery' | ||
}; | ||
|
||
var submitMethod = function () { | ||
return instanceWriter.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() { | ||
var instance = $scope.instance; | ||
instance.instanceId = instance.id; | ||
|
||
var taskMonitor = { | ||
application: app, | ||
title: 'Disabling ' + instance.instanceId + ' in discovery' | ||
}; | ||
|
||
var submitMethod = function () { | ||
return instanceWriter.disableInstanceInDiscovery(instance, app); | ||
}; | ||
|
||
confirmationModalService.confirm({ | ||
header: 'Really disable ' + instance.instanceId + ' in discovery?', | ||
buttonText: 'Disable ' + instance.instanceId, | ||
provider: 'titus', | ||
account: instance.account, | ||
taskMonitorConfig: taskMonitor, | ||
submitMethod: submitMethod | ||
}); | ||
}; | ||
|
||
this.hasHealthState = function hasHealthState(healthProviderType, state) { | ||
let healthMetrics = $scope.instance.health || []; | ||
return (healthMetrics.some(function (health) { | ||
return health.type === healthProviderType && health.state === state; | ||
}) | ||
); | ||
}; | ||
|
||
let getBastionAddressForAccount = (account, region) => { | ||
return accountService.getAccountDetails(account).then((details) => { | ||
this.bastionHost = details.bastionHost || 'unknown'; | ||
this.apiEndpoint = filter(details.regions, {name: region})[0].endpoint; | ||
this.titusUiEndpoint = this.apiEndpoint.replace('titusapi', 'titus-ui').replace('http', 'https').replace('7101', '7001'); | ||
if (region !== 'us-east-1') { | ||
this.bastionStack = '-stack ' + this.apiEndpoint.split('.' + region)[0].replace('http://titusapi.', ''); | ||
} else { | ||
this.bastionStack = ''; | ||
} | ||
|
||
$scope.sshLink = `ssh -t ${this.bastionHost} 'titus-ssh ${this.bastionStack} -region ${$scope.instance.region} -id ${$scope.instance.id}'`; | ||
}); | ||
}; | ||
|
||
this.hasPorts = () => { | ||
return Object.keys($scope.instance.resources.ports).length > 0; | ||
}; | ||
|
||
let initialize = app.isStandalone ? | ||
retrieveInstance() : | ||
app.serverGroups.ready().then(retrieveInstance); | ||
|
||
initialize.then(() => { | ||
// 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; | ||
|
||
} | ||
); |
Oops, something went wrong.