Merge lp:~marcoceppi/pyjuju/ssh-key-opt into lp:pyjuju

Proposed by Marco Ceppi
Status: Needs review
Proposed branch: lp:~marcoceppi/pyjuju/ssh-key-opt
Merge into: lp:pyjuju
Diff against target: 220 lines (+76/-12)
5 files modified
juju/control/__init__.py (+11/-4)
juju/providers/common/tests/test_utils.py (+29/-0)
juju/providers/common/utils.py (+17/-2)
juju/state/sshclient.py (+10/-2)
juju/state/tests/test_sshclient.py (+9/-4)
To merge this branch: bzr merge lp:~marcoceppi/pyjuju/ssh-key-opt
Reviewer Review Type Date Requested Status
Clint Byrum (community) Needs Fixing
Review via email: mp+125714@code.launchpad.net

Description of the change

Allow for public and private key setting (depending on the nature of the subcommand) to be set via the command line and the -i --identitiy-file opt.

This also adds a generalized "create_parser" method to allow for buried, deep down methods to access command line options.

To post a comment you must log in.
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

making the provider code depend on sys.argv is quite odd. what's the goal/value add?

Revision history for this message
Clint Byrum (clint-fewbar) wrote :

I think its more than odd, I don't think its appropriate to access sys.argv from that deep within juju. It should not be a challenge to have the argument parsed in juju.control.* where it makes sense, and then pushed down into the providers the same way other parameters are.

review: Needs Fixing

Unmerged revisions

585. By Marco Ceppi

Remove ~ from 'args' for common provider test

584. By Marco Ceppi

Now with unit tests and code coverage

583. By Marco Ceppi

sshclient doesn't need argparse anymore

582. By Marco Ceppi

Allow uses to specify which 'public-key' to bootstrap with, -i overrides config options

581. By Marco Ceppi

Make create_parser method and use that instead of defining it each time

580. By Marco Ceppi

Allow for passing ssh private keys via -i flag

579. By Marco Ceppi

Added -i flag

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'juju/control/__init__.py'
--- juju/control/__init__.py 2012-03-30 19:12:05 +0000
+++ juju/control/__init__.py 2012-09-22 03:13:42 +0000
@@ -96,10 +96,9 @@
96 return (result, ) * tuple_size96 return (result, ) * tuple_size
97 return format97 return format
9898
9999def create_parser(formatter, **kw):
100def setup_parser(subcommands, **kw):
101 """Setup a command line argument/option parser."""100 """Setup a command line argument/option parser."""
102 parser = JujuParser(formatter_class=JujuFormatter, **kw)101 parser = JujuParser(formatter_class=formatter, **kw)
103 parser.add_argument(102 parser.add_argument(
104 "--verbose", "-v", default=False,103 "--verbose", "-v", default=False,
105 action="store_true",104 action="store_true",
@@ -109,8 +108,16 @@
109 "--log-file", "-l", default=sys.stderr, type=argparse.FileType('a'),108 "--log-file", "-l", default=sys.stderr, type=argparse.FileType('a'),
110 help="Log output to file")109 help="Log output to file")
111110
111 parser.add_argument(
112 "--identity-file", "-i", help="SSH key to authenticate with")
113
114 """This should probably take care of subparsers too, but I can't get
115 it to work properly, so it's just doing the "main" ones for now"""
116 return parser
117
118def setup_parser(subcommands, **kw):
119 parser = create_parser(JujuFormatter, **kw)
112 subparsers = parser.add_subparsers()120 subparsers = parser.add_subparsers()
113
114 for module in subcommands:121 for module in subcommands:
115 configure_subparser = getattr(module, "configure_subparser", None)122 configure_subparser = getattr(module, "configure_subparser", None)
116 passthrough = getattr(module, "passthrough", None)123 passthrough = getattr(module, "passthrough", None)
117124
=== modified file 'juju/providers/common/tests/test_utils.py'
--- juju/providers/common/tests/test_utils.py 2012-09-10 03:20:20 +0000
+++ juju/providers/common/tests/test_utils.py 2012-09-22 03:13:42 +0000
@@ -29,6 +29,35 @@
29 key_data = get_user_authorized_keys(config)29 key_data = get_user_authorized_keys(config)
30 self.assertEqual(data, key_data)30 self.assertEqual(data, key_data)
3131
32 def test_key_public_identity_file_retrieval(self):
33 """
34 The F{juju.providers.common.get_user_authorized_keys} will retrieve
35 a key's contents from disk OR the commandline and return the key data.
36 """
37 data = "through the looking glass"
38 self.makeFile(
39 data,
40 path=os.path.join(self.tmp_home, ".ssh", "identity_test.pub"))
41 key_data = get_user_authorized_keys({},
42 args=["juju", "-i", "%s/.ssh/identity_test.pub" % self.tmp_home])
43 self.assertEqual(data, key_data)
44
45 def test_key_private_identity_file_retrieval(self):
46 """
47 The F{juju.providers.common.get_user_authorized_keys} will retrieve
48 a key's contents from disk OR the commandline and return the key data.
49 """
50 data = "wabbits"
51 self.makeFile(
52 "wong %s" % data,
53 path=os.path.join(self.tmp_home, ".ssh", "identity_crisis"))
54 self.makeFile(
55 data,
56 path=os.path.join(self.tmp_home, ".ssh", "identity_crisis.pub"))
57 key_data = get_user_authorized_keys({},
58 args=["juju", "-i", "%s/.ssh/identity_crisis" % self.tmp_home])
59 self.assertEqual(data, key_data)
60
32 def test_content_utilized(self):61 def test_content_utilized(self):
33 """62 """
34 The entire key content can be specified via configuration.63 The entire key content can be specified via configuration.
3564
=== modified file 'juju/providers/common/utils.py'
--- juju/providers/common/utils.py 2012-09-10 03:20:20 +0000
+++ juju/providers/common/utils.py 2012-09-22 03:13:42 +0000
@@ -1,11 +1,12 @@
1import logging1import logging
2import os2import os
33import sys
44
5from twisted.python.failure import Failure5from twisted.python.failure import Failure
66
7from juju.lib import serializer7from juju.lib import serializer
8from juju.errors import JujuError, ProviderInteractionError8from juju.errors import JujuError, ProviderInteractionError
9from juju.control import create_parser, JujuFormatter
910
10log = logging.getLogger("juju.common")11log = logging.getLogger("juju.common")
1112
@@ -50,7 +51,7 @@
50# lines in the data obtained, and then the call site can add each51# lines in the data obtained, and then the call site can add each
51# individual key to CloudInit.52# individual key to CloudInit.
52#53#
53def get_user_authorized_keys(config):54def get_user_authorized_keys(config, args=sys.argv):
54 """Locate a public key for the user.55 """Locate a public key for the user.
5556
56 If neither "authorized-keys" nor "authorized-keys-path" is57 If neither "authorized-keys" nor "authorized-keys-path" is
@@ -67,6 +68,20 @@
67 :raises: :exc:`LookupError` if an SSH public key is not found.68 :raises: :exc:`LookupError` if an SSH public key is not found.
68 """69 """
69 key_names = ["id_dsa.pub", "id_rsa.pub", "identity.pub"]70 key_names = ["id_dsa.pub", "id_rsa.pub", "identity.pub"]
71
72 parser = create_parser(JujuFormatter)
73 options, extra = parser.parse_known_args(args[1:])
74
75 if options.identity_file:
76 # This could be a private key, or a weirdly named public key
77 # so check if there is a .pub for this file first.
78 identity_file = os.path.expanduser(options.identity_file)
79 if os.path.exists(identity_file):
80 if os.path.exists("%s.pub" % identity_file):
81 identity_file = "%s.pub" % identity_file
82
83 return open(identity_file).read()
84
70 if config.get("authorized-keys"):85 if config.get("authorized-keys"):
71 return config["authorized-keys"]86 return config["authorized-keys"]
7287
7388
=== modified file 'juju/state/sshclient.py'
--- juju/state/sshclient.py 2011-12-07 21:13:52 +0000
+++ juju/state/sshclient.py 2012-09-22 03:13:42 +0000
@@ -1,4 +1,5 @@
1import re1import re
2import sys
2import socket3import socket
3import time4import time
45
@@ -9,11 +10,11 @@
9from juju.state.security import SecurityPolicyConnection10from juju.state.security import SecurityPolicyConnection
10from juju.state.sshforward import forward_port, ClientTunnelProtocol11from juju.state.sshforward import forward_port, ClientTunnelProtocol
1112
13from juju.control import create_parser, JujuFormatter
12from .utils import PortWatcher, get_open_port14from .utils import PortWatcher, get_open_port
1315
14SERVER_RE = re.compile("^(\S+):(\d+)$")16SERVER_RE = re.compile("^(\S+):(\d+)$")
1517
16
17class SSHClient(SecurityPolicyConnection):18class SSHClient(SecurityPolicyConnection):
18 """19 """
19 A ZookeeperClient which will internally handle an SSH tunnel20 A ZookeeperClient which will internally handle an SSH tunnel
@@ -40,6 +41,7 @@
40 """41 """
41 hostname, port = self._parse_servers(server or self._servers)42 hostname, port = self._parse_servers(server or self._servers)
42 start_time = time.time()43 start_time = time.time()
44 identity_file = self._parse_identity(sys.argv[1:])
4345
44 # Determine which port we'll be using.46 # Determine which port we'll be using.
45 local_port = get_open_port()47 local_port = get_open_port()
@@ -55,7 +57,8 @@
55 protocol = ClientTunnelProtocol(self, tunnel_error)57 protocol = ClientTunnelProtocol(self, tunnel_error)
56 self._process = forward_port(58 self._process = forward_port(
57 self.remote_user, local_port, hostname, int(port),59 self.remote_user, local_port, hostname, int(port),
58 process_protocol=protocol, share=share)60 private_key_path=identity_file, process_protocol=protocol,
61 share=share)
5962
60 # Wait for the tunneled port to open.63 # Wait for the tunneled port to open.
61 try:64 try:
@@ -91,6 +94,11 @@
91 hostname, port = match.groups()94 hostname, port = match.groups()
92 return hostname, port95 return hostname, port
9396
97 def _parse_identity(self, args):
98 parser = create_parser(JujuFormatter)
99 options, extra = parser.parse_known_args(args)
100 return options.identity_file
101
94 @inlineCallbacks102 @inlineCallbacks
95 def connect(self, server=None, timeout=60, share=False):103 def connect(self, server=None, timeout=60, share=False):
96 """Probe ZK is accessible via ssh tunnel, return client on success."""104 """Probe ZK is accessible via ssh tunnel, return client on success."""
97105
=== modified file 'juju/state/tests/test_sshclient.py'
--- juju/state/tests/test_sshclient.py 2011-12-07 04:55:29 +0000
+++ juju/state/tests/test_sshclient.py 2012-09-22 03:13:42 +0000
@@ -82,7 +82,8 @@
82 # First, get the forwarding going, targetting the remote82 # First, get the forwarding going, targetting the remote
83 # address provided.83 # address provided.
84 forward("ubuntu", MATCH_PORT, "remote", 2181,84 forward("ubuntu", MATCH_PORT, "remote", 2181,
85 process_protocol=MATCH_PROTOCOL, share=False)85 private_key_path=None, process_protocol=MATCH_PROTOCOL,
86 share=False)
86 self.mocker.result(process)87 self.mocker.result(process)
8788
88 # Next ensure the port check succeeds89 # Next ensure the port check succeeds
@@ -127,7 +128,9 @@
127128
128 protocol = self.mocker.mock()129 protocol = self.mocker.mock()
129 forward("ubuntu", MATCH_PORT, "remote", 2181,130 forward("ubuntu", MATCH_PORT, "remote", 2181,
130 process_protocol=MATCH_PROTOCOL, share=False)131 private_key_path=None, process_protocol=MATCH_PROTOCOL,
132 share=False)
133
131 self.mocker.result(protocol)134 self.mocker.result(protocol)
132135
133 thread(MATCH_FUNC)136 thread(MATCH_FUNC)
@@ -151,7 +154,8 @@
151154
152 protocol = self.mocker.mock()155 protocol = self.mocker.mock()
153 forward("ubuntu", MATCH_PORT, "remote", 2181,156 forward("ubuntu", MATCH_PORT, "remote", 2181,
154 process_protocol=MATCH_PROTOCOL, share=False)157 private_key_path=None, process_protocol=MATCH_PROTOCOL,
158 share=False)
155 self.mocker.result(protocol)159 self.mocker.result(protocol)
156160
157 thread(MATCH_FUNC)161 thread(MATCH_FUNC)
@@ -175,7 +179,8 @@
175179
176 protocol = self.mocker.mock()180 protocol = self.mocker.mock()
177 forward("ubuntu", MATCH_PORT, "remote", 2181,181 forward("ubuntu", MATCH_PORT, "remote", 2181,
178 process_protocol=MATCH_PROTOCOL, share=False)182 private_key_path=None, process_protocol=MATCH_PROTOCOL,
183 share=False)
179 self.mocker.result(protocol)184 self.mocker.result(protocol)
180185
181 thread(MATCH_FUNC)186 thread(MATCH_FUNC)

Subscribers

People subscribed via source and target branches

to status/vote changes: