import json
from awscli.testutils import BaseAWSCommandParamsTest, FileCreator
from awscli.customizations.ecs.deploy import (CodeDeployer,
MAX_WAIT_MIN,
TIMEOUT_BUFFER_MIN)
from awscli.customizations.ecs.filehelpers import (get_app_name,
get_deploy_group_name)
class TestDeployCommand(BaseAWSCommandParamsTest):
PREFIX = 'ecs deploy '
TASK_DEFINITION_JSON = {
"family": "test",
"containerDefinitions": [],
"cpu": "256",
"memory": '512'
}
JSON_APPSPEC = """
{
"version": 0.0,
"resources": [{
"TestService": {
"type": "AWS::ECS::Service",
"properties": {
"taskDefinition": "arn:aws:ecs::123:task-definition:1",
"loadBalancerInfo": {
"containerName": "web",
"containerPort": 80
}
}
}
}]
}
"""
BAD_APPSPEC = """
version: 0.0
resources:
- TestService:
type: AWS::Lambda::Function
properties:
name: myFunction
"""
YAML_APPSPEC = """
version: 0.0
resources:
- TestService:
type: AWS::ECS::Service
properties:
taskDefinition: arn:aws:ecs::123:task-definition:1
loadBalancerInfo:
containerName: web
containerPort: 80
"""
APPSPEC_DICT = {
"version": 0.0,
"resources": [{
"TestService": {
"type": "AWS::ECS::Service",
"properties": {
"taskDefinition": "arn:aws:ecs::123:task-definition:1",
"loadBalancerInfo": {
"containerName": "web",
"containerPort": 80
}
}
}
}]
}
def setUp(self):
super(TestDeployCommand, self).setUp()
files = FileCreator()
self.task_def_file = files.create_file(
'taskDef.json', json.dumps(self.TASK_DEFINITION_JSON), mode='w')
self.appspec_file = files.create_file(
'appspec.yaml', self.YAML_APPSPEC, mode='w')
self.appspec_file_json = files.create_file(
'appspec.json', self.JSON_APPSPEC, mode='w')
self.service_name = 'serviceTest'
self.service_arn = 'arn:aws:ecs:::service/serviceTest'
self.cluster_name = 'default'
self.cluster_arn = 'arn:aws:ecs:::cluster/default'
self.application_name = get_app_name(
self.service_name, self.cluster_name, None)
self.deployment_group_name = get_deploy_group_name(
self.service_name, self.cluster_name, None)
self.missing_properties_appspec = files.create_file(
'appspec_bad.yaml', self.BAD_APPSPEC, mode='w')
self.task_definition_arn = \
'arn:aws:ecs::1234567890:task-definition\\test:2'
self.deployment_id = 'd-1234567XX'
self.mock_deployer = CodeDeployer(None, self.APPSPEC_DICT)
self.mock_deployer.update_task_def_arn(self.task_definition_arn)
self.expected_stdout = ("Successfully registered new ECS task "
"definition " + self.task_definition_arn + "\n"
"Successfully created deployment " +
self.deployment_id + "\n"
"Waiting for " + self.deployment_id +
" to succeed (will wait up to 30 minutes)..."
"\nSuccessfully deployed "
+ self.task_definition_arn + " to service '"
+ self.service_name + "'\n")
def tearDown(self):
super(TestDeployCommand, self).tearDown()
def test_deploy_with_defaults(self):
cmdline = self.PREFIX
cmdline += '--service ' + self.service_name
cmdline += ' --task-definition ' + self.task_def_file
cmdline += ' --codedeploy-appspec ' + self.appspec_file
expected_create_deployment_params = \
self.mock_deployer._get_create_deploy_request(
self.application_name, self.deployment_group_name)
self.parsed_responses = self._get_parsed_responses(
self.cluster_name,
self.application_name,
self.deployment_group_name)
expected_params = self._get_expected_params(
self.service_name,
self.cluster_name,
self.application_name,
self.deployment_group_name,
expected_create_deployment_params)
stdout, _, _ = self.assert_params_list_for_cmd(
cmdline, expected_params, None)
self.assertEqual(stdout, self.expected_stdout)
def test_deploy_with_default_arns(self):
cmdline = self.PREFIX
cmdline += '--service ' + self.service_arn
cmdline += ' --cluster ' + self.cluster_arn
cmdline += ' --task-definition ' + self.task_def_file
cmdline += ' --codedeploy-appspec ' + self.appspec_file
expected_create_deployment_params = \
self.mock_deployer._get_create_deploy_request(
self.application_name, self.deployment_group_name)
self.parsed_responses = self._get_parsed_responses(
self.cluster_name,
self.application_name,
self.deployment_group_name)
expected_params = self._get_expected_params(
self.service_arn,
self.cluster_arn,
self.application_name,
self.deployment_group_name,
expected_create_deployment_params)
stdout, _, _ = self.assert_params_list_for_cmd(
cmdline, expected_params, None)
self.assertEqual(stdout, self.expected_stdout)
def test_deploy_with_json_appspec(self):
cmdline = self.PREFIX
cmdline += '--service ' + self.service_name
cmdline += ' --task-definition ' + self.task_def_file
cmdline += ' --codedeploy-appspec ' + self.appspec_file_json
expected_create_deployment_params = \
self.mock_deployer._get_create_deploy_request(
self.application_name, self.deployment_group_name)
self.parsed_responses = self._get_parsed_responses(
self.cluster_name,
self.application_name,
self.deployment_group_name)
expected_params = self._get_expected_params(
self.service_name,
self.cluster_name,
self.application_name,
self.deployment_group_name,
expected_create_deployment_params)
stdout, _, _ = self.assert_params_list_for_cmd(
cmdline, expected_params, None)
self.assertEqual(stdout, self.expected_stdout)
def test_deploy_with_custom_timeout(self):
cmdline = self.PREFIX
cmdline += '--service ' + self.service_name
cmdline += ' --task-definition ' + self.task_def_file
cmdline += ' --codedeploy-appspec ' + self.appspec_file
expected_create_deployment_params = \
self.mock_deployer._get_create_deploy_request(
self.application_name, self.deployment_group_name)
custom_deployment_grp_response = {
'deploymentGroupInfo': {
'applicationName': self.application_name,
'deploymentGroupName': self.deployment_group_name,
'computePlatform': 'ECS',
'blueGreenDeploymentConfiguration': {
'deploymentReadyOption': {
'waitTimeInMinutes': 5
},
'terminateBlueInstancesOnDeploymentSuccess': {
'terminationWaitTimeInMinutes': 60
}
},
'ecsServices': [{
'serviceName': self.service_name,
'clusterName': self.cluster_name
}]
}
}
custom_timeout = str(60 + 5 + TIMEOUT_BUFFER_MIN)
self.parsed_responses = self._get_parsed_responses(
self.cluster_name,
self.application_name,
self.deployment_group_name)
self.parsed_responses[2] = custom_deployment_grp_response
expected_params = self._get_expected_params(
self.service_name,
self.cluster_name,
self.application_name,
self.deployment_group_name,
expected_create_deployment_params)
expected_stdout = ("Successfully registered new ECS task "
"definition " + self.task_definition_arn + "\n"
"Successfully created deployment " +
self.deployment_id + "\n"
"Waiting for " + self.deployment_id +
" to succeed (will wait up to " + custom_timeout
+ " minutes)...\nSuccessfully deployed "
+ self.task_definition_arn + " to service '"
+ self.service_name + "'\n")
stdout, _, _ = self.assert_params_list_for_cmd(
cmdline, expected_params, None)
self.assertEqual(stdout, expected_stdout)
def test_deploy_with_max_timeout(self):
cmdline = self.PREFIX
cmdline += '--service ' + self.service_name
cmdline += ' --task-definition ' + self.task_def_file
cmdline += ' --codedeploy-appspec ' + self.appspec_file
expected_create_deployment_params = \
self.mock_deployer._get_create_deploy_request(
self.application_name, self.deployment_group_name)
custom_deployment_grp_response = {
'deploymentGroupInfo': {
'applicationName': self.application_name,
'deploymentGroupName': self.deployment_group_name,
'computePlatform': 'ECS',
'blueGreenDeploymentConfiguration': {
'deploymentReadyOption': {
'waitTimeInMinutes': 90
},
'terminateBlueInstancesOnDeploymentSuccess': {
'terminationWaitTimeInMinutes': 300
}
},
'ecsServices': [{
'serviceName': self.service_name,
'clusterName': self.cluster_name
}]
}
}
max_timeout = str(MAX_WAIT_MIN)
self.parsed_responses = self._get_parsed_responses(
self.cluster_name,
self.application_name,
self.deployment_group_name)
self.parsed_responses[2] = custom_deployment_grp_response
expected_params = self._get_expected_params(
self.service_name,
self.cluster_name,
self.application_name,
self.deployment_group_name,
expected_create_deployment_params)
expected_stdout = ("Successfully registered new ECS task "
"definition " + self.task_definition_arn + "\n"
"Successfully created deployment " +
self.deployment_id + "\n"
"Waiting for " + self.deployment_id +
" to succeed (will wait up to " + max_timeout
+ " minutes)...\nSuccessfully deployed "
+ self.task_definition_arn + " to service '"
+ self.service_name + "'\n")
stdout, _, _ = self.assert_params_list_for_cmd(
cmdline, expected_params, None)
self.assertEqual(stdout, expected_stdout)
def test_deploy_with_optional_values(self):
custom_app = 'myOtherApp'
custom_dgp = 'myOtherDgp'
custom_cluster = 'myOtherCluster'
cmdline = self.PREFIX
cmdline += '--service ' + self.service_name
cmdline += ' --task-definition ' + self.task_def_file
cmdline += ' --codedeploy-appspec ' + self.appspec_file
cmdline += ' --cluster ' + self.cluster_name
cmdline += ' --codedeploy-application ' + custom_app
cmdline += ' --codedeploy-deployment-group ' + custom_dgp
cmdline += ' --cluster ' + custom_cluster
expected_create_deployment_params = \
self.mock_deployer._get_create_deploy_request(
custom_app, custom_dgp)
self.parsed_responses = self._get_parsed_responses(
custom_cluster, custom_app, custom_dgp)
expected_params = self._get_expected_params(
self.service_name, custom_cluster, custom_app,
custom_dgp, expected_create_deployment_params)
stdout, _, _ = self.assert_params_list_for_cmd(
cmdline, expected_params, None)
self.assertEqual(stdout, self.expected_stdout)
def test_deploy_error_missing_appspec_property(self):
cmdline = self.PREFIX
cmdline += '--service ' + self.service_name
cmdline += ' --task-definition ' + self.task_def_file
cmdline += ' --codedeploy-appspec ' + self.missing_properties_appspec
self.parsed_responses = [
{
'services': [{
'serviceArn': self.service_arn,
'serviceName': self.service_name,
'clusterArn': 'arn:aws:ecs:::cluster/' + self.cluster_name
}]
},
{
'application': {
'applicationId': '876uyh6-45tdfg',
'applicationName': self.application_name,
'computePlatform': 'ECS'
}
},
{
'deploymentGroupInfo': {
'applicationName': self.application_name,
'deploymentGroupName': self.deployment_group_name,
'computePlatform': 'ECS',
'blueGreenDeploymentConfiguration': {
'deploymentReadyOption': {
'waitTimeInMinutes': 5
},
'terminateBlueInstancesOnDeploymentSuccess': {
'terminationWaitTimeInMinutes': 10
}
},
'ecsServices': [{
'serviceName': self.service_name,
'clusterName': self.cluster_name
}]
}
},
{
'taskDefinition': {
'taskDefinitionArn': self.task_definition_arn,
'family': 'test',
'containerDefinitions': []
}
}
]
expected_params = [
{
'operation': 'DescribeServices',
'params': {
'cluster': self.cluster_name,
'services': [self.service_name]
}
},
{
'operation': 'GetApplication',
'params': {'applicationName': self.application_name}
},
{
'operation': 'GetDeploymentGroup',
'params': {
'applicationName': self.application_name,
'deploymentGroupName': self.deployment_group_name
}
},
{
'operation': 'RegisterTaskDefinition',
'params': self.TASK_DEFINITION_JSON
}
]
expected_stdout = ("Successfully registered new ECS task "
"definition " + self.task_definition_arn + "\n")
expected_stderr = ("\nError: Resource 'properties' must "
"include property 'taskDefinition'\n")
stdout, stderr, _ = self.assert_params_list_for_cmd(
cmdline, expected_params, 255)
self.assertEqual(stdout, expected_stdout)
self.assertEqual(stderr, expected_stderr)
def test_deploy_error_invalid_platform(self):
cmdline = self.PREFIX
cmdline += '--service ' + self.service_name
cmdline += ' --task-definition ' + self.task_def_file
cmdline += ' --codedeploy-appspec ' + self.missing_properties_appspec
self.parsed_responses = [
{
'services': [{
'serviceArn': self.service_arn,
'serviceName': self.service_name,
'clusterArn': 'arn:aws:ecs:::cluster/' + self.cluster_name
}]
},
{
'application': {
'applicationId': '876uyh6-45tdfg',
'applicationName': self.application_name,
'computePlatform': 'Server'
}
},
{
'deploymentGroupInfo': {
'applicationName': self.application_name,
'deploymentGroupName': self.deployment_group_name,
'computePlatform': 'ECS',
'ecsServices': [{
'serviceName': self.service_name,
'clusterName': self.cluster_name
}]
}
}
]
expected_params = [
{
'operation': 'DescribeServices',
'params': {
'cluster': self.cluster_name,
'services': [self.service_name]
}
},
{
'operation': 'GetApplication',
'params': {'applicationName': self.application_name}
},
{
'operation': 'GetDeploymentGroup',
'params': {
'applicationName': self.application_name,
'deploymentGroupName': self.deployment_group_name
}
}
]
expected_stderr = ("\nError: Application '" + self.application_name +
"' must support 'ECS' compute platform\n")
stdout, stderr, _ = self.assert_params_list_for_cmd(
cmdline, expected_params, 255)
self.assertEqual('', stdout)
self.assertEqual(expected_stderr, stderr)
def assert_params_list_for_cmd(self, cmd, params, expected_rc=0,
stderr_contains=None):
stdout, stderr, rc = self.run_cmd(cmd, expected_rc)
if stderr_contains is not None:
self.assertIn(stderr_contains, stderr)
self.assertEqual(len(self.operations_called), len(params))
for i, param in enumerate(params):
self.assertEqual(
self.operations_called[i][0].name, param['operation'])
self.assertEqual(
self.operations_called[i][1], param['params'])
return stdout, stderr, rc
def _get_expected_params(self, service_name, cluster_name, app_name,
dgp_name, create_deployment_params):
return [
{
'operation': 'DescribeServices',
'params': {
'cluster': cluster_name,
'services': [service_name]
}
},
{
'operation': 'GetApplication',
'params': {'applicationName': app_name}
},
{
'operation': 'GetDeploymentGroup',
'params': {
'applicationName': app_name,
'deploymentGroupName': dgp_name
}
},
{
'operation': 'RegisterTaskDefinition',
'params': self.TASK_DEFINITION_JSON
},
{
'operation': 'CreateDeployment',
'params': create_deployment_params
},
{
'operation': 'GetDeployment',
'params': {
'deploymentId': self.deployment_id
}
}
]
def _get_parsed_responses(self, cluster_name, app_name, dgp_name):
return [
{
'services': [{
'serviceArn': self.service_arn,
'serviceName': self.service_name,
'clusterArn': 'arn:aws:ecs:::cluster/' + cluster_name
}]
},
{
'application': {
'applicationId': '876uyh6-45tdfg',
'applicationName': app_name,
'computePlatform': 'ECS'
}
},
{
'deploymentGroupInfo': {
'applicationName': app_name,
'deploymentGroupName': dgp_name,
'computePlatform': 'ECS',
'blueGreenDeploymentConfiguration': {
'deploymentReadyOption': {
'waitTimeInMinutes': 5
},
'terminateBlueInstancesOnDeploymentSuccess': {
'terminationWaitTimeInMinutes': 10
}
},
'ecsServices': [{
'serviceName': self.service_name,
'clusterName': cluster_name
}]
}
},
{
'taskDefinition': {
'taskDefinitionArn': self.task_definition_arn,
'family': 'test',
'containerDefinitions': []
}
},
{
'deploymentId': self.deployment_id
},
{
'deploymentInfo': {
'applicationName': app_name,
'status': 'Succeeded'
}
}
]