Repository: zalando-stups/senza

Issue:
Exception in senza create witout senza init
senza throws exception when security group is missing. It can check this condition and suggest using "senza init" to create the group.
```
EXCEPTION OCCURRED: Security Group "app-something" does not exist
Unknown Error: Security Group "app-something" does not exist.

Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/senza/error_handling.py", line 104, in __call__
    self.function(*args, **kwargs)
  File "/home/someuser/.local/lib/python3.5/site-packages/click/core.py", line 716, in __call__
    return self.main(*args, **kwargs)
  File "/home/someuser/.local/lib/python3.5/site-packages/click/core.py", line 696, in main
    rv = self.invoke(ctx)
  File "/home/someuser/.local/lib/python3.5/site-packages/click/core.py", line 1060, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/someuser/.local/lib/python3.5/site-packages/click/core.py", line 889, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/someuser/.local/lib/python3.5/site-packages/click/core.py", line 534, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/senza/cli.py", line 576, in create
    data = create_cf_template(definition, region, version, parameter, force, parameter_file)
  File "/usr/local/lib/python3.5/dist-packages/senza/cli.py", line 659, in create_cf_template
    data = evaluate(definition.copy(), args, account_info, force)
  File "/usr/local/lib/python3.5/dist-packages/senza/cli.py", line 238, in evaluate
    definition = componentfn(definition, configuration, args, info, force, account_info)
  File "/usr/local/lib/python3.5/dist-packages/senza/components/taupage_auto_scaling_group.py", line 81, in component_taupage_auto_scaling_group
    definition = component_auto_scaling_group(definition, configuration, args, info, force, account_info)
  File "/usr/local/lib/python3.5/dist-packages/senza/components/auto_scaling_group.py", line 99, in component_auto_scaling_group
    resolve_security_groups(configuration["SecurityGroups"], args.region)
  File "/usr/local/lib/python3.5/dist-packages/senza/aws.py", line 126, in resolve_security_groups
    result.append(resolve_security_group(security_group, region))
  File "/usr/local/lib/python3.5/dist-packages/senza/aws.py", line 119, in resolve_security_group
    raise ValueError('Security Group "{}" does not exist'.format(security_group))
ValueError: Security Group "app-something" does not exist
```


Comments:

Patch:
diff --git a/senza/aws.py b/senza/aws.py
index 0f87a7e..e07f65d 100644
--- a/senza/aws.py
+++ b/senza/aws.py
@@ -9,6 +9,7 @@ import yaml
 from botocore.exceptions import ClientError
 from click import FileError
 
+from .exceptions import SecurityGroupNotFound
 from .manaus.boto_proxy import BotoClientProxy
 from .manaus.utils import extract_client_error_code
 from .stack_references import check_file_exceptions
@@ -108,14 +109,14 @@ def resolve_security_group(security_group, region: str):
     if isinstance(security_group, dict):
         sg = resolve_referenced_resource(security_group, region)
         if not sg:
-            raise ValueError('Referenced Security Group "{}" does not exist'.format(security_group))
+            raise SecurityGroupNotFound(security_group)
         return sg
     elif security_group.startswith('sg-'):
         return security_group
     else:
         sg = get_security_group(region, security_group)
         if not sg:
-            raise ValueError('Security Group "{}" does not exist'.format(security_group))
+            raise SecurityGroupNotFound(security_group)
         return sg.id
 
 
diff --git a/senza/error_handling.py b/senza/error_handling.py
index e56378b..32ffb02 100644
--- a/senza/error_handling.py
+++ b/senza/error_handling.py
@@ -15,7 +15,7 @@ from clickclick import fatal_error
 from raven import Client
 
 from .configuration import configuration
-from .exceptions import InvalidDefinition, PiuNotFound
+from .exceptions import InvalidDefinition, PiuNotFound, SecurityGroupNotFound
 from .manaus.exceptions import (ELBNotFound, HostedZoneNotFound, InvalidState,
                                 RecordNotFound)
 from .manaus.utils import extract_client_error_code
@@ -112,7 +112,7 @@ class HandleExceptions:
             sys.stdout.flush()
             if is_credentials_expired_error(client_error):
                 die_fatal_error('AWS credentials have expired.\n'
-                                'Use the "mai" command line tool to get a new '
+                                'Use the "zaws" command line tool to get a new '
                                 'temporary access key.')
             elif is_access_denied_error(client_error):
                 die_fatal_error(
@@ -136,6 +136,10 @@ class HandleExceptions:
         except (ELBNotFound, HostedZoneNotFound, RecordNotFound,
                 InvalidDefinition, InvalidState) as error:
             die_fatal_error(error)
+        except SecurityGroupNotFound as error:
+            message = ("{}\nRun `senza init` to (re-)create "
+                       "the security group.").format(error)
+            die_fatal_error(message)
         except Exception as unknown_exception:
             # Catch All
             self.die_unknown_error(unknown_exception)
diff --git a/senza/exceptions.py b/senza/exceptions.py
index 5b57980..ff84c81 100644
--- a/senza/exceptions.py
+++ b/senza/exceptions.py
@@ -41,3 +41,15 @@ class InvalidDefinition(SenzaException):
     def __str__(self):
         return ("{path} is not a valid senza definition: "
                 "{reason}".format_map(vars(self)))
+
+
+class SecurityGroupNotFound(SenzaException):
+    """
+    Exception raised when a Security Group is not found
+    """
+
+    def __init__(self, security_group: str):
+        self.security_group = security_group
+
+    def __str__(self):
+        return 'Security Group "{}" does not exist.'.format(self.security_group)


Test patch:
diff --git a/tests/test_error_handling.py b/tests/test_error_handling.py
index 2df4395..c3c367f 100644
--- a/tests/test_error_handling.py
+++ b/tests/test_error_handling.py
@@ -7,7 +7,7 @@ import botocore.exceptions
 import senza.error_handling
 import yaml
 from pytest import fixture, raises
-from senza.exceptions import PiuNotFound
+from senza.exceptions import PiuNotFound, SecurityGroupNotFound
 from senza.manaus.exceptions import ELBNotFound, InvalidState
 
 
@@ -225,6 +225,18 @@ def test_yaml_error(capsys):
     assert 'Please quote all variable values' in err
 
 
+def test_sg_not_found(capsys):
+    func = MagicMock(side_effect=SecurityGroupNotFound('my-app'))
+
+    with raises(SystemExit):
+        senza.error_handling.HandleExceptions(func)()
+
+    out, err = capsys.readouterr()
+
+    assert err == ('Security Group "my-app" does not exist.\n'
+                   'Run `senza init` to (re-)create the security group.\n')
+
+
 def test_unknown_error(capsys, mock_tempfile, mock_raven):
     senza.error_handling.sentry = senza.error_handling.setup_sentry(None)
     func = MagicMock(side_effect=Exception("something"))