Repository: zalando-stups/senza

Issue:
ACM Cert lookup fails with KeyError
```
Generating Cloud Formation template.. EXCEPTION OCCURRED: 'Serial'
Unknown Error: 'Serial'.
Please create an issue with the content of /tmp/senza-traceback-078eseqg

$ cat /tmp/senza-traceback-078eseqg
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/senza/error_handling.py", line 69, in __call__
    self.function(*args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/click/core.py", line 716, in __call__
    return self.main(*args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/click/core.py", line 696, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.5/dist-packages/click/core.py", line 1060, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python3.5/dist-packages/click/core.py", line 889, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python3.5/dist-packages/click/core.py", line 534, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.5/dist-packages/senza/cli.py", line 555, 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 638, in create_cf_template
    data = evaluate(definition.copy(), args, account_info, force)
  File "/usr/local/lib/python3.5/dist-packages/senza/cli.py", line 239, in evaluate
    definition = componentfn(definition, configuration, args, info, force, account_info)
  File "/usr/local/lib/python3.5/dist-packages/senza/components/weighted_dns_elastic_load_balancer.py", line 29, in component_weighted_dns_elastic_load_balancer
    return component_elastic_load_balancer(definition, configuration, args, info, force, account_info)
  File "/usr/local/lib/python3.5/dist-packages/senza/components/elastic_load_balancer.py", line 110, in component_elastic_load_balancer
    listeners = configuration.get('Listeners') or get_listeners(subdomain, main_zone, configuration)
  File "/usr/local/lib/python3.5/dist-packages/senza/components/elastic_load_balancer.py", line 48, in get_listeners
    reverse=True)
  File "/usr/local/lib/python3.5/dist-packages/senza/manaus/acm.py", line 173, in get_certificates
    certificate = ACMCertificate.get_by_arn(arn)
  File "/usr/local/lib/python3.5/dist-packages/senza/manaus/acm.py", line 110, in get_by_arn
    return cls.from_boto_dict(certificate)
  File "/usr/local/lib/python3.5/dist-packages/senza/manaus/acm.py", line 83, in from_boto_dict
    serial = certificate['Serial']
KeyError: 'Serial'
```

The cert has status "'VALIDATION_TIMED_OUT" in the error case.

Discussion:

Patch:
diff --git a/senza/manaus/acm.py b/senza/manaus/acm.py
index 0c16faa..ad918e8 100644
--- a/senza/manaus/acm.py
+++ b/senza/manaus/acm.py
@@ -80,16 +80,16 @@ class ACMCertificate:
         arn = certificate['CertificateArn']
         subject_alternative_name = certificate['SubjectAlternativeNames']
         domain_validation_options = certificate['DomainValidationOptions']
-        serial = certificate['Serial']
         subject = certificate['Subject']
-        issuer = certificate['Issuer']
         created_at = certificate['CreatedAt']
-        issued_at = certificate['IssuedAt']
         status = certificate['Status']
-        not_before = certificate['NotBefore']
-        not_after = certificate['NotAfter']
         signature_algorithm = certificate['SignatureAlgorithm']
         in_use_by = certificate['InUseBy']
+        serial = certificate.get('Serial')
+        issuer = certificate.get('Issuer')
+        issued_at = certificate.get('IssuedAt')
+        not_before = certificate.get('NotBefore')
+        not_after = certificate.get('NotAfter')
 
         revoked_at = certificate.get('RevokedAt')
         revocation_reason = certificate.get('RevocationReason')


Test patch:
diff --git a/tests/test_manaus/test_acm.py b/tests/test_manaus/test_acm.py
index 13691ed..51e12d4 100644
--- a/tests/test_manaus/test_acm.py
+++ b/tests/test_manaus/test_acm.py
@@ -85,6 +85,24 @@ CERT2 = {'CertificateArn': 'arn:aws:acm:eu-west-1:cert2',
                                      '*.senza.aws.example.net',
                                      '*.app.example.net']}
 
+CERT_VALIDATION_TIMED_OUT = {
+    'KeyAlgorithm': 'RSA-2048',
+    'DomainName': 'alpha.example.org',
+    'InUseBy': [],
+    'CreatedAt': datetime(2016, 7,  11, 15,  15,  30),
+    'SubjectAlternativeNames': ['alpha.example.org'],
+    'SignatureAlgorithm': 'SHA256WITHRSA',
+    'Status': 'VALIDATION_TIMED_OUT',
+    'DomainValidationOptions': [{'DomainName': 'alpha.example.org',
+                                 'ValidationEmails': ['administrator@alpha.example.org',
+                                    'hostmaster@alpha.example.org',
+                                    'admin@alpha.example.org',
+                                    'webmaster@alpha.example.org',
+                                    'postmaster@alpha.example.org'],
+                                 'ValidationDomain': 'alpha.example.org'}],
+    'CertificateArn': 'arn:aws:acm:eu-central-1:123123:certificate/f8a0fa1a-381b-44b6-ab10-1b94ba1480a1',
+    'Subject': 'CN=alpha.example.org'}
+
 
 def test_certificate_valid():
     certificate1 = ACMCertificate.from_boto_dict(CERT1)
@@ -108,6 +126,9 @@ def test_certificate_valid():
     assert not certificate1_revoked.is_valid(when=datetime(2013, 4, 2, 10, 11, 12,
                                                            tzinfo=timezone.utc))
 
+    cert_invalid = ACMCertificate.from_boto_dict(CERT_VALIDATION_TIMED_OUT)
+    assert not cert_invalid.is_valid()
+
 
 def test_certificate_comparison():
     cert2 = CERT1.copy()