Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initialize Django module for one-off management commands #1

Closed
wants to merge 14 commits into from
Prev Previous commit
Next Next commit
Initialize tests
Set up some simple tests for the package configuration.
  • Loading branch information
Jean Cochrane committed Feb 28, 2019
commit a46dc8d93acfab5050e99dc7483f9f48944cc333
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down Expand Up @@ -115,4 +113,7 @@ venv.bak/
dmypy.json

# Pyre type checker
pyre/
pyre/

# Editors
.vscode
14 changes: 11 additions & 3 deletions ecsmanage/management/commands/ecsmanage.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ def add_arguments(self, parser):
'-e', '--env',
type=str,
default='default',
help="Environment to run the task in, as defined in ECSMANAGE_ENVIRONMENTS."
help=(
'Environment to run the task in, as defined in'
'ECSMANAGE_ENVIRONMENTS.'
)
)

parser.add_argument(
Expand Down Expand Up @@ -74,7 +77,7 @@ def parse_config(self):
'ECSMANAGE_ENVIRONMENTS (environments include: '
f'{settings.ECSMANAGE_ENVIRONMENTS.keys()})'
)

config = {
'TASK_DEFINITION_NAME': '',
'CLUSTER_NAME': '',
Expand Down Expand Up @@ -164,7 +167,12 @@ def get_subnet(self, subnet_tags):
subnet = self.parse_response(subnet_response, 'Subnets', 0)
return subnet['SubnetId']

def run_task(self, config, task_def_arn, security_group_id, subnet_id, cmd):
def run_task(self,
config,
task_def_arn,
security_group_id,
subnet_id,
cmd):
"""
Run a task for a given task definition ARN using the given security
group and subnets, and return the task ID.
Expand Down
9 changes: 4 additions & 5 deletions scripts/test
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Options:
--lint Run shell and Python linters.
--app Run app tests.
"
}

function run_linters() {
# Lint Bash scripts.
Expand All @@ -25,14 +26,12 @@ function run_linters() {
fi

# Lint Python scripts.
# Lint Python scripts.
docker-compose run --rm --no-deps \
--entrypoint flake8 app \
--exclude *.pyc
./.venv/bin/flake8 --exclude ./*.pyc,./.venv
}

function run_tests() {
docker-compose run --rm --entrypoint python app manage.py test --noinput
PYTHONPATH="./tests/" DJANGO_SETTINGS_MODULE='settings_test' \
./.venv/bin/django-admin test --noinput
}

if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
Expand Down
35 changes: 35 additions & 0 deletions scripts/update
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash

set -e

if [[ -n "${ECSMANAGE_DEBUG}" ]]; then
set -x
fi

function usage() {
echo -n \
"Usage: $(basename "$0")

Install the package for testing.
"
}

if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
if [ "${1:-}" = "--help" ]; then
usage
else
if ! [ -x "$(command -v python3)" ]; then
echo "Error: python3 is not installed."
exit 1j
elif ! [ -x "$(command -v pip3)" ]; then
echo "Error: pip3 is not installed."
exit 1
else
if ! [ -d ".venv" ]; then
python3 -m venv .venv
else
./.venv/bin/pip3 install -e ".[tests]"
fi
fi
fi
fi
7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
README = readme.read()

description = (
'Run any Django management command on an AWS Elastic Container Service (ECS)'
'cluster.'
'Run any Django management command on an AWS Elastic Container Service'
'(ECS) cluster.'
)

# allow setup.py to be run from any path
Expand All @@ -29,6 +29,7 @@
'Django >=1.11, <=2.1',
'boto3 >=1.9.0'
],
extras_require={'tests': ['moto >= 1.3.3', 'flake8 >= 3.7.7']},
classifiers=[
'Environment :: Web Environment',
'Framework :: Django',
Expand All @@ -43,4 +44,4 @@
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
],
)
)
File renamed without changes.
4 changes: 4 additions & 0 deletions tests/settings_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""
Django settings for testing purposes.
"""
SECRET_KEY = 'testing!'
93 changes: 93 additions & 0 deletions tests/test_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from django.core.management import call_command
from django.core.management.base import CommandError
from django.test import SimpleTestCase


class ConfigurationTestCase(SimpleTestCase):
"""
Test the configuration of settings for the management command.
"""
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests only check user configurations. I tried to use Moto to mock Boto3 for integration tests, but I ran into two main problems:

  1. Not all EC2 endpoints we use are fully implemented, e.g. describe_subnets
  2. Inscrutable errors showing that mocks were not being applied, as documented in failure to mock aws auth - NoCredentialsError getmoto/moto#2069

If 2) above can be overcome, then we could likely support at least partial tests for endpoints that aren't blocked by 1).

def test_failure_when_no_settings(self):
"""
Test that the command throws an error when the ECSMANAGE_ENVIRONMENTS
setting does not exist.
"""
with self.assertRaises(CommandError):
call_command('ecsmanage', 'help')

def test_failure_when_missing_environment(self):
"""
Test that the command throws an error when the environment passed to
the CLI does not exist in ECSMANAGE_ENVIRONMENTS.
"""
ECSMANAGE_ENVIRONMENTS = {
'staging': {},
'production': {}
}
with self.assertRaises(CommandError):
with self.settings(ECSMANAGE_ENVIRONMENTS=ECSMANAGE_ENVIRONMENTS):
call_command('ecsmanage', 'help', env='foobar')

def test_failure_when_no_task_def_name(self):
"""
Test that the command throws an error when the configuration is missing
a task definition name.
"""
ECSMANAGE_ENVIRONMENTS = {
'staging': {
'CLUSTER_NAME': 'foo',
'SECURITY_GROUP_TAGS': {},
'SUBNET_TAGS': {},
},
}
with self.assertRaises(CommandError):
with self.settings(ECSMANAGE_ENVIRONMENTS=ECSMANAGE_ENVIRONMENTS):
call_command('ecsmanage', 'help', env='staging')

def test_failure_when_no_cluster_name(self):
"""
Test that the command throws an error when the configuration is missing
a cluster name.
"""
ECSMANAGE_ENVIRONMENTS = {
'staging': {
'TASK_DEFINITION_NAME': 'foo',
'SECURITY_GROUP_TAGS': {},
'SUBNET_TAGS': {},
},
}
with self.assertRaises(CommandError):
with self.settings(ECSMANAGE_ENVIRONMENTS=ECSMANAGE_ENVIRONMENTS):
call_command('ecsmanage', 'help', env='staging')

def test_failure_when_no_security_group_tags(self):
"""
Test that the command throws an error when the configuration is missing
security group tags.
"""
ECSMANAGE_ENVIRONMENTS = {
'staging': {
'TASK_DEFINITION_NAME': 'foo',
'CLUSTER_NAME': 'bar',
'SUBNET_TAGS': {},
},
}
with self.assertRaises(CommandError):
with self.settings(ECSMANAGE_ENVIRONMENTS=ECSMANAGE_ENVIRONMENTS):
call_command('ecsmanage', 'help', env='staging')

def test_failure_when_no_subnet_tags(self):
"""
Test that the command throws an error when the configuration is missing
subnet tags.
"""
ECSMANAGE_ENVIRONMENTS = {
'staging': {
'TASK_DEFINITION_NAME': 'foo',
'CLUSTER_NAME': 'bar',
'SECURITY_GROUP_TAGS': {},
},
}
with self.assertRaises(CommandError):
with self.settings(ECSMANAGE_ENVIRONMENTS=ECSMANAGE_ENVIRONMENTS):
call_command('ecsmanage', 'help', env='staging')