Repository: deis/deis

Issue:
deis keys:add with argument not yet implemented
If an argument is provided, this command currently does nothing, but the spec says it should upload the key to the server.

https://github.com/opdemand/deis/blob/master/client/deis.py#L1211



Discussion:

Resolving patch:
diff --git a/client/deis.py b/client/deis.py
--- a/client/deis.py
+++ b/client/deis.py
@@ -43,6 +43,7 @@
 """
 
 from __future__ import print_function
+from collections import namedtuple
 from cookielib import MozillaCookieJar
 from getpass import getpass
 from itertools import cycle
@@ -55,6 +56,7 @@
 import re
 import subprocess
 import sys
+import tempfile
 import time
 import urlparse
 import webbrowser
@@ -63,7 +65,6 @@
 from docopt import docopt
 from docopt import DocoptExit
 import requests
-import tempfile
 
 __version__ = '0.3.1'
 
@@ -1214,57 +1215,60 @@ def keys_add(self, args):
 
         Usage: deis keys:add [<key>]
         """
+
+        Key = namedtuple('Key', 'path name type str comment')
+
+        def parse_key(path):
+            """Parse an SSH public key path into a Key namedtuple."""
+            name = path.split(os.path.sep)[-1]
+            with open(path) as f:
+                data = f.read()
+                match = re.match(r'^(ssh-...) ([^ ]+) ?(.*)', data)
+                if not match:
+                    print("Could not parse SSH public key {0}".format(name))
+                    return
+                key_type, key_str, key_comment = match.groups()
+                return Key(path, name, key_type, key_str, key_comment)
+
         path = args.get('<key>')
         if not path:
+            # find public keys and prompt the user to pick one
             ssh_dir = os.path.expanduser('~/.ssh')
-            pubkeys = glob.glob(os.path.join(ssh_dir, '*.pub'))
-            pubkeys_list = []
-            if not pubkeys:
+            pubkey_paths = glob.glob(os.path.join(ssh_dir, '*.pub'))
+            if not pubkey_paths:
                 print('No SSH public keys found')
                 return
+            pubkeys_list = [parse_key(k) for k in pubkey_paths]
             print('Found the following SSH public keys:')
-            for i, k in enumerate(pubkeys):
-                key = k.split(os.path.sep)[-1]
-                with open(k) as f:
-                    data = f.read()
-                    match = re.match(r'^(ssh-...) ([^ ]+) ?(.*)', data)
-                    if not match:
-                        print("Could not parse SSH public key {0}".format(key))
-                        return
-                    key_type, key_str, _key_comment = match.groups()
-                    pubkeys_list.append([k, key, key_type, key_str, _key_comment])
-                    print("{0}) {1} {2}".format(i + 1, key, _key_comment))
-
+            for i, key_ in enumerate(pubkeys_list):
+                print("{}) {} {}".format(i + 1, key_.name, key_.comment))
             inp = raw_input('Which would you like to use with Deis? ')
             try:
                 selected_key = pubkeys_list[int(inp) - 1]
-                path = selected_key[0]
             except:
                 print('Aborting')
                 return
-
-            if not selected_key[4]:
-                body = {
-                    'id': selected_key[1].replace('.pub', ''),
-                    'public': "{0} {1}".format(
-                        selected_key[2], selected_key[3]
-                    )
-                }
-                sys.stdout.write("Uploading {} to Deis...".format(selected_key[1]))
-            else:
-                body = {
-                    'id': selected_key[4],
-                    'public': "{0} {1}".format(
-                        selected_key[2], selected_key[3]
-                    )
-                }
-                sys.stdout.write("Uploading {} to Deis...".format(selected_key[4]))
-            sys.stdout.flush()
-            response = self._dispatch('post', '/api/keys', json.dumps(body))
-            if response.status_code == requests.codes.created:  # @UndefinedVariable
-                print('done')
-            else:
-                raise ResponseError(response)
+        else:
+            # check the specified key format
+            selected_key = parse_key(path)
+            if not selected_key:
+                return
+        # Upload the key to Deis
+        if selected_key.comment:
+            key_id = selected_key.comment
+        else:
+            key_id = selected_key.name.replace('.pub', '')
+        body = {
+            'id': key_id,
+            'public': "{} {}".format(selected_key.type, selected_key.str)
+        }
+        sys.stdout.write("Uploading {} to Deis...".format(key_id))
+        sys.stdout.flush()
+        response = self._dispatch('post', '/api/keys', json.dumps(body))
+        if response.status_code == requests.codes.created:  # @UndefinedVariable
+            print('done')
+        else:
+            raise ResponseError(response)
 
     def keys_list(self, args):
         """


Test patch:
diff --git a/client/tests/test_keys.py b/client/tests/test_keys.py
--- a/client/tests/test_keys.py
+++ b/client/tests/test_keys.py
@@ -7,8 +7,32 @@
 
 from __future__ import unicode_literals
 from unittest import TestCase
+import glob
+import os.path
+
+import pexpect
+
+from .utils import DEIS
+from .utils import purge
+from .utils import register
 
 
 class KeysTest(TestCase):
 
-    pass
+    @classmethod
+    def setUpClass(cls):
+        cls.username, cls.password = register(False, False)
+
+    @classmethod
+    def tearDownClass(cls):
+        purge(cls.username, cls.password)
+
+    def test_add(self):
+        # test adding a specified key--the "choose a key" path is well
+        # covered in utils.register()
+        ssh_dir = os.path.expanduser('~/.ssh')
+        pubkey = glob.glob(os.path.join(ssh_dir, '*.pub'))[0]
+        child = pexpect.spawn("{} keys:add {}".format(DEIS, pubkey))
+        child.expect('Uploading')
+        child.expect('...done')
+        child.expect(pexpect.EOF)
diff --git a/client/tests/utils.py b/client/tests/utils.py
--- a/client/tests/utils.py
+++ b/client/tests/utils.py
@@ -85,7 +85,7 @@ def random_repo():
     return name, EXAMPLES[name]
 
 
-def register():
+def register(add_keys=True, add_providers=True):
     """Register a new Deis user from the command line."""
     username = "autotester-{}".format(uuid4().hex[:4])
     password = 'password'
@@ -136,28 +136,30 @@ def register():
     child.expect("Logged in as {}".format(username))
     child.expect(pexpect.EOF)
     # add keys
-    child = pexpect.spawn("{} keys:add".format(DEIS))
-    child.expect('Which would you like to use with Deis')
-    for index, key in re.findall('(\d)\) ([ \S]+)', child.before):
-        if username in key:
-            child.sendline(index)
-            break
-    child.expect('Uploading')
-    child.expect('...done')
-    child.expect(pexpect.EOF)
+    if add_keys:
+        child = pexpect.spawn("{} keys:add".format(DEIS))
+        child.expect('Which would you like to use with Deis')
+        for index, key in re.findall('(\d)\) ([ \S]+)', child.before):
+            if username in key:
+                child.sendline(index)
+                break
+        child.expect('Uploading')
+        child.expect('...done')
+        child.expect(pexpect.EOF)
     # discover providers
-    child = pexpect.spawn("{} providers:discover".format(DEIS))
-    opt = child.expect(['Import EC2 credentials\? \(y/n\) :',
-                       'No EC2 credentials discovered.'])
-    if opt == 0:
-        child.sendline('y')
-    opt = child.expect(['Import Rackspace credentials\? \(y/n\) :',
-                       'No Rackspace credentials discovered.'])
-    if opt == 0:
-        child.sendline('y')
-    opt = child.expect(['Import DigitalOcean credentials\? \(y/n\) :',
-                       'No DigitalOcean credentials discovered.'])
-    if opt == 0:
-        child.sendline('y')
-    child.expect(pexpect.EOF)
+    if add_providers:
+        child = pexpect.spawn("{} providers:discover".format(DEIS))
+        opt = child.expect(['Import EC2 credentials\? \(y/n\) :',
+                           'No EC2 credentials discovered.'])
+        if opt == 0:
+            child.sendline('y')
+        opt = child.expect(['Import Rackspace credentials\? \(y/n\) :',
+                           'No Rackspace credentials discovered.'])
+        if opt == 0:
+            child.sendline('y')
+        opt = child.expect(['Import DigitalOcean credentials\? \(y/n\) :',
+                           'No DigitalOcean credentials discovered.'])
+        if opt == 0:
+            child.sendline('y')
+        child.expect(pexpect.EOF)
     return username, password