Repository: zalando-stups/pierone-cli

Issue:
pierone fails with backtrace when the CVE status is COULDNT_FIGURE_OUT
```
Traceback (most recent call last):
  File "/usr/local/bin/pierone", line 11, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.4/dist-packages/pierone/cli.py", line 485, in main
    cli()
  File "/usr/local/lib/python3.4/dist-packages/click/core.py", line 716, in __call__
    return self.main(*args, **kwargs)
  File "/usr/local/lib/python3.4/dist-packages/click/core.py", line 696, in main
    rv = self.invoke(ctx)
  File "/usr/local/lib/python3.4/dist-packages/click/core.py", line 1060, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/local/lib/python3.4/dist-packages/click/core.py", line 889, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/local/lib/python3.4/dist-packages/click/core.py", line 534, in invoke
    return callback(*args, **kwargs)
  File "/usr/local/lib/python3.4/dist-packages/click/decorators.py", line 27, in new_func
    return f(get_current_context().obj, *args, **kwargs)
  File "/usr/local/lib/python3.4/dist-packages/pierone/cli.py", line 313, in cves
    installed_software = get_clair_features(config.get('clair_url'), artifact_tag.get('clair_id'), token)
  File "/usr/local/lib/python3.4/dist-packages/pierone/cli.py", line 235, in get_clair_features
    return r.json()['Layer']['Features']
KeyError: 'Features'
```

Discussion:

Patch:
diff --git a/pierone/cli.py b/pierone/cli.py
index 1af5790..50dba86 100644
--- a/pierone/cli.py
+++ b/pierone/cli.py
@@ -232,7 +232,8 @@ def get_clair_features(url, layer_id, access_token):
         return []
     else:
         r.raise_for_status()
-    return r.json()['Layer']['Features']
+
+    return r.json()['Layer'].get('Features', [])
 
 
 @cli.command()


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 6282253..087d27d 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -221,6 +221,61 @@ def test_cves(monkeypatch, tmpdir):
         assert re.match('[^\n]+\n[^\n]+HIGH', result.output), 'Results should be ordered by highest priority'
 
 
+def test_no_cves_found(monkeypatch, tmpdir):
+    pierone_service_payload = [
+        # Former pierone payload
+        {
+            'name': '1.0',
+            'created_by': 'myuser',
+            'created': '2015-08-20T08:14:59.432Z'
+        },
+        # New pierone payload with clair but no information about CVEs
+        {
+            "name": "1.1",
+            "created": "2016-05-19T15:23:41.065Z",
+            "created_by": "myuser",
+            "image": "sha256:here",
+            "clair_id": None,
+            "severity_fix_available": None,
+            "severity_no_fix_available": None
+        },
+        # New pierone payload with clair input and info about CVEs
+        {
+            "name": "1.2",
+            "created": "2016-05-23T13:29:17.753Z",
+            "created_by": "myuser",
+            "image": "sha256:here",
+            "clair_id": "sha256:here",
+            "severity_fix_available": "High",
+            "severity_no_fix_available": "Medium"
+        }
+    ]
+
+    no_cves_clair_payload = {
+        "Layer": {
+            "Name": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
+            "NamespaceName": "ubuntu:16.04",
+            "ParentName": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
+            "IndexedByVersion": 2
+        }
+    }
+
+    response = MagicMock()
+    response.json.side_effect = [
+        pierone_service_payload,
+        no_cves_clair_payload
+    ]
+
+    runner = CliRunner()
+    monkeypatch.setattr('stups_cli.config.load_config', lambda x: {'url': 'foobar', 'clair_url': 'barfoo'})
+    monkeypatch.setattr('zign.api.get_token', MagicMock(return_value='tok123'))
+    monkeypatch.setattr('os.path.expanduser', lambda x: x.replace('~', str(tmpdir)))
+    monkeypatch.setattr('pierone.api.session.get', MagicMock(return_value=response))
+    with runner.isolated_filesystem():
+        result = runner.invoke(cli, ['cves', 'myteam', 'myart', '1.2'], catch_exceptions=False)
+        assert re.match('^[^\n]+\n$', result.output), 'No results should be shown'
+
+
 def test_latest(monkeypatch, tmpdir):
     response = MagicMock()
     response.json.return_value = [