diff --git a/CHANGES.txt b/CHANGES.txt
index e47ae317..d3a7bec0 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,45 @@
+1.5
+
+Features
+- Platform-independent user data provider option (formatted like a properties file)
+- Ability to run Asgard in one AWS account and AssumeRole to manage a different account
+- Automated deployment can accept arbitrary user-chosen steps
+- Each application can have an optional application group
+- Each application can have optional tags
+- Enable configurable maximum number of ASGs per cluster
+- REST endpoints for retrieving a deployment workflow plan JSON blob, and for starting a workflow with that JSON
+- Additional ways to configure Asgard to get AWS credentials, including from an IAM profile
+- Updated list of AWS instance types
+- Links to security groups include both name and ID
+- Made it possible to skip cache loading during cache loading, using runtime flag API
+- Support for HTTPS ELB listeners (Thanks Greg Dziemidowicz)
+- Configurable AWS API socket timeout value
+- Shared visibility all of in-memory running tasks across Asgard instances in a cluster
+- Filterable instance reservations by offering type
+
+
+Infrastructure
+- Turn off noisy, legacy task completion emails
+- Introduced AngularJS for auto deployment screens, with Grunt build and JavaScript unit tests
+- Asgard's health check should fail while initializing, but then should pass forever, for desired load balancer behavior
+- Asgard can register with Eureka service for conventional discoverability
+- "Wither" function to have Asgard delete itself after it finishes all running in-memory tasks
+- Separate SimpleDB service from Application service
+- Store SWF tokens in database
+- Ability to set arbitrary headers via RestClientService (Thanks e0d)
+- Upgraded frigga library to allow for letters in version strings of AMI appversion tags
+- Converted more JUnit tests to Spock
+
+
+Bug Fixes
+- List of VPCs for security group creation only includes VPCs that have conventionally labeled subnets
+- List of ELBs over 400 can be listed successfully
+- List of RDS DB instances over 100 can be listed successfully
+- Rolling push can work with spot instances (Thanks Dale Wijnand)
+- Force U.S. dollars for Amazon-listed currency amounts (Thanks Dale Wijnand)
+- Allow an initial size of 0 for creating the next ASG in a cluster
+
+
1.4.2
Features
diff --git a/app/scripts/controllers/deployment/detail.js b/app/scripts/controllers/deployment/detail.js
index 08c302d5..ec178fe6 100644
--- a/app/scripts/controllers/deployment/detail.js
+++ b/app/scripts/controllers/deployment/detail.js
@@ -4,18 +4,21 @@ angular.module('asgardApp')
.controller('DeploymentDetailCtrl', function ($scope, $routeParams, $http, $timeout) {
var deploymentId = $routeParams.deploymentId;
var shouldPoll = true;
+ $scope.readOnlyDeploymentSteps = true;
+ $scope.targetAsgTypes = ["Previous", "Next"];
var retrieveDeployment = function() {
$http.get('deployment/show/' + deploymentId + '.json').success(function(data, status, headers, config) {
$scope.deployment = data;
shouldPoll = !$scope.deployment.done;
- var text ='';
+ var text = '';
angular.forEach($scope.deployment.log, function(value) {
text = text + value + '\n';
});
$scope.logText = text;
});
};
+
var poll = function() {
retrieveDeployment();
if (shouldPoll) {
@@ -24,6 +27,14 @@ angular.module('asgardApp')
};
poll();
+ $scope.getLogForStep = function(stepIndex) {
+ return $scope.deployment.logForSteps[stepIndex];
+ };
+
+ $scope.stepUrl = function(type) {
+ return '/views/deployment/' + type + 'Step.html';
+ };
+
$scope.encodedWorkflowExecutionIds = function() {
var runId = $scope.deployment.workflowExecution.runId;
var workflowId = $scope.deployment.workflowExecution.workflowId;
@@ -42,6 +53,27 @@ angular.module('asgardApp')
judgeDeployment('proceed');
};
+ $scope.getCurrentStep = function() {
+ return $scope.deployment.logForSteps.length - 1;
+ };
+
+ $scope.getStepStatus = function(stepIndex) {
+ var currentStep = $scope.getCurrentStep();
+ if (stepIndex < currentStep) {
+ return "success";
+ }
+ if (stepIndex === currentStep) {
+ if ($scope.deployment.status === "completed" && currentStep === $scope.deployment.steps.length - 1) {
+ return "success";
+ }
+ if ($scope.deployment.status !== "running") {
+ return "failure";
+ }
+ return "running";
+ }
+ return "queued";
+ };
+
var judgeDeployment = function(judgment) {
$http.post('deployment/' + judgment, {
id: deploymentId,
diff --git a/app/scripts/controllers/deployment/new.js b/app/scripts/controllers/deployment/new.js
index ba944b2f..5eccb5ee 100644
--- a/app/scripts/controllers/deployment/new.js
+++ b/app/scripts/controllers/deployment/new.js
@@ -4,10 +4,221 @@ angular.module("asgardApp")
.controller("DeploymentNewCtrl", function ($scope, $routeParams, $http, $location) {
$scope.clusterName = $routeParams.clusterName;
$scope.hideAdvancedItems = true;
+ $scope.hideJsonSteps = true;
+ $scope.hideHtmlSteps = false;
+ $scope.hideShowMoreAmisLink = false;
+ $scope.targetAsgTypes = ["Previous", "Next"];
+ $scope.count= 0;
+ $scope.selectionsForSubnet = {}
+
+ var isSameStepBeforeOrAfter = function(stepTypeName, index) {
+ if (index > 0 && index < $scope.generated.stepsDisplay.length - 1) {
+ return $scope.generated.stepsDisplay[index - 1].type === stepTypeName || $scope.generated.stepsDisplay[index + 1].type === stepTypeName;
+ }
+ if (index > 1) {
+ return $scope.generated.stepsDisplay[index - 1].type === stepTypeName;
+ }
+ if (index < $scope.generated.stepsDisplay.length) {
+ return $scope.generated.stepsDisplay[index + 1].type === stepTypeName;
+ }
+ return true;
+ };
+
+ var isLastStep = function(index) {
+ return index === $scope.generated.stepsDisplay.length - 1;
+ };
+
+ var isFirstStep = function(index) {
+ return index === 0;
+ };
+
+ var firstIndexOfStepType = function(stepTypeName) {
+ var i, n = $scope.generated.stepsDisplay.length;
+ for (i = 0; i < n; ++i) {
+ var nextStep = $scope.generated.stepsDisplay[i];
+ if ('type' in nextStep && nextStep.type === stepTypeName) {
+ return i;
+ }
+ }
+ return undefined;
+ };
+
+ var isBeforeCreateStep = function(index) {
+ return index < firstIndexOfStepType("CreateAsg");
+ };
+
+ var isAfterDeleteStep = function(index) {
+ return index > firstIndexOfStepType("DeleteAsg");
+ };
+
+ var stepTypes = {
+ "Wait": {
+ display: "Wait",
+ isAllowed: function(index) {
+ return !isSameStepBeforeOrAfter("Wait", index) && !isLastStep(index);
+ },
+ add: function(index) {
+ $scope.generated.stepsDisplay.splice(index, 0, {"type":"Wait", "durationMinutes":60});
+ }
+ },
+ "Judgment": {
+ display: "Judgment",
+ isAllowed: function(index) {
+ return !isSameStepBeforeOrAfter("Judgment", index) && !isLastStep(index);
+ },
+ add: function(index) {
+ $scope.generated.stepsDisplay.splice(index, 0, {"type":"Judgment", "durationMinutes":120});
+ }
+ },
+ "ResizeAsg": {
+ display: "Resize",
+ isAllowed: function(index) {
+ return !isBeforeCreateStep(index) && !isAfterDeleteStep(index);
+ },
+ add: function(index) {
+ $scope.generated.stepsDisplay.splice(index, 0,
+ {"type":"Resize", "targetAsg":"Next", "capacity":0, "startUpTimeoutMinutes":40});
+ }
+ },
+ "DisableAsg": {
+ display: "Disable",
+ isAllowed: function(index) {
+ return !isBeforeCreateStep(index) && !isAfterDeleteStep(index);
+ },
+ add: function(index) {
+ $scope.generated.stepsDisplay.splice(index, 0, {"type":"DisableAsg", "targetAsg":"Previous"});
+ }
+ },
+ "EnableAsg": {
+ display: "Enable",
+ isAllowed: function(index) {
+ return !isBeforeCreateStep(index) && !isAfterDeleteStep(index);
+ },
+ add: function(index) {
+ $scope.generated.stepsDisplay.splice(index, 0, {"type":"EnableAsg", "targetAsg":"Next"});
+ }
+ },
+ "DeleteAsg": {
+ display: "Delete",
+ isAllowed: function(index) {
+ return !isBeforeCreateStep(index) && !isAfterDeleteStep(index) && !firstIndexOfStepType("DeleteAsg")
+ && isLastStep(index);
+ },
+ add: function(index) {
+ $scope.generated.stepsDisplay.splice(index, 0, {"type":"DeleteAsg", "targetAsg":"Previous"});
+ }
+ }
+ };
+
+ $scope.stepTypeNames = Object.keys(stepTypes);
+
+ $scope.isStepAllowed = function(stepTypeName, index) {
+ return stepTypes[stepTypeName].isAllowed(index);
+ };
+
+ $scope.stepTypeDisplay = function(stepTypeName) {
+ return stepTypes[stepTypeName].display;
+ };
+
+ $scope.addStep = function(stepTypeName, index) {
+ resetStepsDisplay();
+ stepTypes[stepTypeName].add(index);
+ $scope.generated.stepsDisplay.splice(index, 0, {showSteps: false});
+ };
+
+ $scope.removeStep = function(index) {
+ resetStepsDisplay();
+ $scope.generated.stepsDisplay.splice(index, 2);
+ };
+
+ var resetStepsDisplay = function() {
+ var i, n = $scope.generated.stepsDisplay.length;
+ for (i = 0; i < n; ++i) {
+ var nextStep = $scope.generated.stepsDisplay[i];
+ if ('showSteps' in nextStep) {
+ nextStep.showSteps = false
+ }
+ }
+ };
+
+ var initStepsDisplay = function() {
+ $scope.generated = {};
+ $scope.generated.stepsDisplay = [{showSteps: false}];
+ var i, n = $scope.deploymentOptions.steps.length;
+ for (i = 0; i < n; ++i) {
+ var nextStep = $scope.deploymentOptions.steps[i];
+ $scope.generated.stepsDisplay.push(nextStep);
+ $scope.generated.stepsDisplay.push({showSteps: false});
+ }
+ };
+
+ $scope.editJsonSteps = function() {
+ $scope.hideHtmlSteps = true;
+ };
+
+ $scope.saveJsonSteps = function() {
+ $scope.jsonStepsParseError = null;
+ var jsonSteps;
+ try {
+ jsonSteps = angular.fromJson($scope.generated.jsonSteps);
+ } catch(e) {
+ $scope.jsonStepsParseError = e.stack;
+ return;
+ }
+ var steps = [];
+ var i, n = jsonSteps.length;
+ for (i = 0; i < n; ++i) {
+ var nextStep = jsonSteps[i];
+ steps.push(nextStep);
+ }
+ $scope.deploymentOptions.steps = steps;
+ initStepsDisplay();
+ $scope.hideHtmlSteps = false;
+ };
+
+ var constructStepsFromDisplay = function() {
+ var steps = [];
+ var i, n = $scope.generated.stepsDisplay.length;
+ for (i = 0; i < n; ++i) {
+ var nextStep = $scope.generated.stepsDisplay[i];
+ if ('type' in nextStep) {
+ steps.push(nextStep);
+ }
+ }
+ $scope.deploymentOptions.steps = angular.fromJson(angular.toJson(steps));
+ };
+
+ $scope.$watch("generated.stepsDisplay", function() {
+ if ($scope.deploymentOptions) {
+ constructStepsFromDisplay();
+ }
+ }, true);
+
+ $scope.$watch("deploymentOptions.steps", function() {
+ if ($scope.deploymentOptions) {
+ var text ='[\n';
+ var i, n = $scope.deploymentOptions.steps.length;
+ for (i = 0; i < n; ++i) {
+ var nextStep = $scope.deploymentOptions.steps[i];
+ text = text + ' ' + angular.toJson(nextStep);
+ if (i < n - 1) {
+ text = text + ',\n';
+ }
+ }
+ text = text + '\n]';
+ $scope.generated.jsonSteps = text;
+ }
+ });
+
+ $scope.toggleShowStepTypes = function(index) {
+ var value = $scope.generated.stepsDisplay[index].showSteps;
+ $scope.generated.stepsDisplay[index].showSteps = !value;
+ };
+
var prepareParams = {
params: {
includeEnvironment: true,
- deploymentTemplateName: "CreateJudgeAndCleanUp"
+ deploymentTemplateName: "CreateAndCleanUpPreviousAsg"
}
};
@@ -16,10 +227,21 @@ angular.module("asgardApp")
$scope.environment = data.environment;
$scope.asgOptions = data.asgOptions;
$scope.lcOptions = data.lcOptions;
- if ($scope.asgOptions) {
- $scope.suspendAZRebalance = $scope.asgOptions.suspendedProcesses.indexOf("AZRebalance") > -1;
- $scope.suspendAddToLoadBalancer = $scope.asgOptions.suspendedProcesses.indexOf("AddToLoadBalancer") > -1;
- }
+ $scope.suspendAZRebalance = $scope.asgOptions.suspendedProcesses.indexOf("AZRebalance") > -1;
+ $scope.suspendAddToLoadBalancer = $scope.asgOptions.suspendedProcesses.indexOf("AddToLoadBalancer") > -1;
+ initStepsDisplay();
+ angular.forEach($scope.environment.subnetPurposes.concat(""), function(value) {
+ $scope.selectionsForSubnet[value] = {
+ securityGroups: [],
+ availabilityZones: [],
+ loadBalancerNames: []
+ }
+ });
+ $scope.selectionsForSubnet[$scope.asgOptions.subnetPurpose] = {
+ securityGroups: $scope.lcOptions.securityGroups,
+ availabilityZones: $scope.asgOptions.availabilityZones,
+ loadBalancerNames: $scope.asgOptions.loadBalancerNames
+ };
});
$scope.$watch("asgOptions.subnetPurpose", function() {
@@ -28,6 +250,14 @@ angular.module("asgardApp")
}
});
+ $scope.$watch("selectionsForSubnet[asgOptions.subnetPurpose].securityGroups", function() {
+ if ($scope.asgOptions) {
+ $scope.selectedSecurityGroupNames = $scope.environment.securityGroups.filter(function(value) {
+ return $scope.selectionsForSubnet[$scope.asgOptions.subnetPurpose].securityGroups.indexOf(value.id) !== -1;
+ }).map(function(value) { return value.name })
+ }
+ });
+
$scope.$watch("suspendAZRebalance", function() {
if ($scope.asgOptions) {
toggleSuspendedProcess("AZRebalance", $scope.suspendAZRebalance);
@@ -59,8 +289,21 @@ angular.module("asgardApp")
$scope.hideAdvancedItems = !$scope.hideAdvancedItems
};
+ $scope.toggleJsonSteps = function() {
+ $scope.hideJsonSteps = !$scope.hideJsonSteps
+ };
+
+ $scope.stepUrl = function(type) {
+ return '/views/deployment/' + type + 'Step.html';
+ };
+
$scope.startDeployment = function() {
$scope.startingDeployment = true;
+ constructStepsFromDisplay();
+ var subnetSpecificSelections = $scope.selectionsForSubnet[$scope.asgOptions.subnetPurpose];
+ $scope.lcOptions.securityGroups = subnetSpecificSelections.securityGroups;
+ $scope.asgOptions.availabilityZones = subnetSpecificSelections.availabilityZones;
+ $scope.asgOptions.loadBalancerNames = subnetSpecificSelections.loadBalancerNames;
var deployment = {
deploymentOptions: $scope.deploymentOptions,
asgOptions: $scope.asgOptions,
@@ -76,4 +319,11 @@ angular.module("asgardApp")
});
};
+ $scope.retrieveAllAmis = function() {
+ $http.get("deployment/allAmis/").success(function(data) {
+ $scope.environment.images = data;
+ $scope.hideShowMoreAmisLink = true
+ });
+ };
+
});
diff --git a/app/views/deployment/CreateAsgStep.html b/app/views/deployment/CreateAsgStep.html
new file mode 100644
index 00000000..51c15b70
--- /dev/null
+++ b/app/views/deployment/CreateAsgStep.html
@@ -0,0 +1,10 @@
+
+
+ Creates the new Auto Scaling Group and Launch Configuration with 0 instances.
+
+
diff --git a/app/views/deployment/DeleteAsgStep.html b/app/views/deployment/DeleteAsgStep.html
new file mode 100644
index 00000000..864c48bd
--- /dev/null
+++ b/app/views/deployment/DeleteAsgStep.html
@@ -0,0 +1,13 @@
+
+
Delete {{step.targetAsg}} ASG
+
+
+
+
+ Delete the
+ {{step.targetAsg}}
+ ASG.
+
+
diff --git a/app/views/deployment/DisableAsgStep.html b/app/views/deployment/DisableAsgStep.html
new file mode 100644
index 00000000..9e270d8e
--- /dev/null
+++ b/app/views/deployment/DisableAsgStep.html
@@ -0,0 +1,13 @@
+
+
Disable {{step.targetAsg}} ASG
+
+
+
+
+ This turns off traffic to the
+
+ ASG.
+
+
diff --git a/app/views/deployment/EnableAsgStep.html b/app/views/deployment/EnableAsgStep.html
new file mode 100644
index 00000000..a05a68a2
--- /dev/null
+++ b/app/views/deployment/EnableAsgStep.html
@@ -0,0 +1,13 @@
+
+
Enable {{step.targetAsg}} ASG
+
+
+
+
+ This turns on traffic to the
+
+ ASG and splits traffic between enabled ASGs in the cluster.
+
+
diff --git a/app/views/deployment/JudgmentStep.html b/app/views/deployment/JudgmentStep.html
new file mode 100644
index 00000000..eb45d3cc
--- /dev/null
+++ b/app/views/deployment/JudgmentStep.html
@@ -0,0 +1,19 @@
+
+
Wait For Judgment
+
+
+
+
+ Deployment will be paused and notifications will be sent now and after
+
+ minutes.
+
+
+
+
+
+
+
+
diff --git a/app/views/deployment/ResizeStep.html b/app/views/deployment/ResizeStep.html
new file mode 100644
index 00000000..a5eca39f
--- /dev/null
+++ b/app/views/deployment/ResizeStep.html
@@ -0,0 +1,19 @@
+
+
Resize {{step.targetAsg}} ASG
+
+
+
+
+
diff --git a/app/views/deployment/WaitStep.html b/app/views/deployment/WaitStep.html
new file mode 100644
index 00000000..88d64ec7
--- /dev/null
+++ b/app/views/deployment/WaitStep.html
@@ -0,0 +1,13 @@
+
+
+ Wait
+
+ minutes.
+
+
diff --git a/app/views/deployment/detail.html b/app/views/deployment/detail.html
index 20b72717..0d8267c6 100644
--- a/app/views/deployment/detail.html
+++ b/app/views/deployment/detail.html
@@ -3,59 +3,49 @@ Deployment
-
-
-
-
- Return to Cluster: |
-
- {{deployment.clusterName}}
- |
-
-
- Description: |
- {{deployment.description}} |
-
-
- Workflow Execution Details: |
-
- Workflow Execution
- |
-
-
- Region: |
- {{deployment.regionCode}} |
-
-
- Status: |
- {{deployment.status}} |
-
-
- Start Time: |
- {{deployment.startTime | date: 'yyyy-MM-dd HH:mm:ss Z'}} |
-
-
- Duration: |
- {{deployment.durationString}} |
-
-
- Owner: |
- {{deployment.owner}} |
-
-
- Log: |
-
-
- |
-
-
-
+
+
+
+
+
Region:
+
{{deployment.regionCode}}
+
+
+
Status:
+
{{deployment.status}}
+
+
+
Start Time:
+
{{deployment.startTime | date: 'yyyy-MM-dd HH:mm:ss Z'}}
+
+
+
Duration:
+
{{deployment.durationString}}
+
+
+
Owner:
+
{{deployment.owner}}
+
+
+
diff --git a/app/views/deployment/new.html b/app/views/deployment/new.html
index e0f46ce5..9d0f31c6 100644
--- a/app/views/deployment/new.html
+++ b/app/views/deployment/new.html
@@ -16,14 +16,10 @@ Deploy Next ASG for Cluster '{{clusterName}}'
-
-
{{environment.nextGroupName}}
-
-
-
- Advanced Options
-
+
+ Advanced Options
+
{{environment.nextGroupName}}
@@ -54,7 +50,7 @@
{{environment.nextGroupName}}
+ Show more AMIs
@@ -80,8 +76,10 @@
{{environment.nextGroupName}}
-
@@ -214,7 +212,8 @@
{{environment.nextGroupName}}
-
@@ -247,8 +246,9 @@
{{environment.nextGroupName}}
-
{{selectedLoadBalancerName}}
+
@@ -273,86 +273,29 @@ Deployment Configuration
-
Steps
+
+ JSON
+
+
Steps
-
-
-
-
Create ASG
-
- Wait
-
- minutes before creation.
-
-
-
-
-
-
-
-
-
-
- Deployment will be paused and notifications will be sent now and after
-
- minutes.
-
-
-
-
-
-
- Deployment will be paused and notifications will be sent now and after
-
- minutes.
-
-
-
-
-
-
-
-
-
- This turns off traffic to the previous ASG and sends full traffic to the new ASG.
-
-
-
-
-
- Deployment will be paused and notifications will be sent now and after
-
- minutes.
-
-
-
-
-
+
+
+
{{jsonStepsParseError}}
+
+
+
+
+
+
+
-
-
-
- Delete the previous ASG.
-
+
+
+
diff --git a/application.properties b/application.properties
index c7178e9c..f35ed7b2 100644
--- a/application.properties
+++ b/application.properties
@@ -1,6 +1,6 @@
#Grails Metadata file
-#Wed Aug 07 12:22:31 PST 2013
+#Wed Jun 05 11:45:00 PST 2014
app.grails.version=2.2.4
app.name=asgard
app.servlet.version=2.4
-app.version=1.4.2
+app.version=1.5
diff --git a/grails-app/conf/Config.groovy b/grails-app/conf/Config.groovy
index 700fd13b..99b5f69c 100644
--- a/grails-app/conf/Config.groovy
+++ b/grails-app/conf/Config.groovy
@@ -73,12 +73,14 @@ log4j = {
error 'com.amazonaws.services.simpleworkflow.flow.worker.DecisionTaskPoller'
environments {
- development {
+ def devConfig = {
console name: 'stdout', layout: pattern(conversionPattern: '[%d{ISO8601}] %c{4} %m%n')
root {
info 'stdout'
}
}
+ development devConfig
+ mcetestLocalDev devConfig
}
}
diff --git a/grails-app/conf/spring/resources.groovy b/grails-app/conf/spring/resources.groovy
index d5f7771b..42b807c4 100644
--- a/grails-app/conf/spring/resources.groovy
+++ b/grails-app/conf/spring/resources.groovy
@@ -19,9 +19,9 @@ import com.google.common.base.CaseFormat
import com.netflix.asgard.CachedMapBuilder
import com.netflix.asgard.Caches
import com.netflix.asgard.CsiAsgAnalyzer
-import com.netflix.asgard.DefaultAdvancedUserDataProvider
-import com.netflix.asgard.DefaultUserDataProvider
-import com.netflix.asgard.NetflixAdvancedUserDataProvider
+import com.netflix.asgard.userdata.DefaultAdvancedUserDataProvider
+import com.netflix.asgard.userdata.DefaultUserDataProvider
+import com.netflix.asgard.userdata.NetflixAdvancedUserDataProvider
import com.netflix.asgard.NoOpAsgAnalyzer
import com.netflix.asgard.Region
import com.netflix.asgard.ServiceInitLoggingBeanPostProcessor
@@ -34,6 +34,7 @@ import com.netflix.asgard.deployment.DeploymentActivitiesImpl
import com.netflix.asgard.eureka.EurekaClientHolder
import com.netflix.asgard.model.CsiScheduledAnalysisFactory
import com.netflix.asgard.server.DeprecatedServerNames
+import com.netflix.asgard.userdata.PropertiesUserDataProvider
import groovy.io.FileType
beans = {
@@ -56,6 +57,10 @@ beans = {
objectMapper(ObjectMapper)
+ propertiesUserDataProvider(PropertiesUserDataProvider) { bean ->
+ bean.lazyInit = true
+ }
+
defaultUserDataProvider(DefaultUserDataProvider) { bean ->
bean.lazyInit = true
}
diff --git a/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy b/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy
index 051cebca..6204e155 100644
--- a/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy
+++ b/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy
@@ -39,6 +39,7 @@ import com.netflix.asgard.model.GroupedInstance
import com.netflix.asgard.model.InstancePriceType
import com.netflix.asgard.model.SubnetTarget
import com.netflix.asgard.model.Subnets
+import com.netflix.frigga.Names
import com.netflix.grails.contextParam.ContextParam
import grails.converters.JSON
import grails.converters.XML
@@ -488,13 +489,13 @@ class AutoScalingController {
}
def generateName() {
- withFormat {
+ request.withFormat {
json {
if (params.appName) {
try {
String groupName = Relationships.buildGroupName(params, true)
- List
envVars = Relationships.labeledEnvironmentVariables(groupName,
- configService.userDataVarPrefix)
+ List envVars = Relationships.labeledEnvVarsMap(Names.parseName(groupName),
+ configService.userDataVarPrefix).collect { k, v -> "${k}=${v}" }
Map result = [groupName: groupName, envVars: envVars]
render(result as JSON)
} catch (Exception e) {
diff --git a/grails-app/controllers/com/netflix/asgard/DeploymentController.groovy b/grails-app/controllers/com/netflix/asgard/DeploymentController.groovy
index e655ca1e..52e6dbf5 100644
--- a/grails-app/controllers/com/netflix/asgard/DeploymentController.groovy
+++ b/grails-app/controllers/com/netflix/asgard/DeploymentController.groovy
@@ -77,11 +77,7 @@ class DeploymentController {
if (!deployment) {
Requests.renderNotFound('Deployment', id, this)
} else {
- withFormat {
- html { return [ deployment : deployment ] }
- xml { new XML(deployment).render(response) }
- json { new JSON(deployment).render(response) }
- }
+ render objectMapper.writer().writeValueAsString(deployment)
}
}
@@ -147,6 +143,7 @@ class DeploymentController {
asgOptions.with {
autoScalingGroupName = null
launchConfigurationName = null
+ subnetPurpose = subnetPurpose ?: ""
}
LaunchConfiguration lc = awsAutoScalingService.getLaunchConfiguration(userContext,
@@ -159,6 +156,10 @@ class DeploymentController {
instanceMonitoringIsEnabled = instanceMonitoringIsEnabled != null ? instanceMonitoringIsEnabled :
configService.enableInstanceMonitoring
blockDeviceMappings = null // SWF can not handle serializing this, and Asgard builds them per instance type.
+ securityGroups = lcOptions.securityGroups.collect {
+ // all security groups should be ids rather than names
+ awsEc2Service.getSecurityGroup(userContext, it)
+ }.sort { it.groupName }.collect { it.groupId }
}
Map attributes = [
@@ -167,7 +168,7 @@ class DeploymentController {
]
DeploymentTemplate deploymentTemplate = DeploymentTemplate.of(deploymentTemplateName)
if (deploymentTemplate) {
- DeploymentWorkflowOptions deploymentOptions = deploymentTemplate.deployment
+ DeploymentWorkflowOptions deploymentOptions = deploymentTemplate.getDeployment(asgOptions.desiredCapacity)
String groupName = lastGroup.autoScalingGroupName
String appName = Relationships.appNameFromGroupName(groupName)
String email = applicationService.getEmailFromApp(userContext, appName)
@@ -214,8 +215,7 @@ class DeploymentController {
price: it.monthlyLinuxOnDemandPrice ? it.monthlyLinuxOnDemandPrice + '/mo' : '']
},
securityGroups: effectiveSecurityGroups.collect {
- [id: it.groupId, name: it.groupName, selection: it.vpcId ? it.groupId : it.groupName,
- vpcId: it.vpcId ?: '']
+ [id: it.groupId, name: it.groupName, vpcId: it.vpcId ?: '']
},
images: images.sort { it.imageLocation.toLowerCase() }.collect {
[id: it.imageId, imageLocation: it.imageLocation]
@@ -227,6 +227,18 @@ class DeploymentController {
render objectMapper.writer().writeValueAsString(attributes)
}
+ /**
+ * @return all AMIs for account
+ */
+ def allAmis() {
+ UserContext userContext = UserContext.of(request)
+ Collection images = awsEc2Service.getAccountImages(userContext)
+ List