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
1=== modified file 'juju/control/__init__.py'
2--- juju/control/__init__.py 2012-03-30 19:12:05 +0000
3+++ juju/control/__init__.py 2012-09-22 03:13:42 +0000
4@@ -96,10 +96,9 @@
5 return (result, ) * tuple_size
6 return format
7
8-
9-def setup_parser(subcommands, **kw):
10+def create_parser(formatter, **kw):
11 """Setup a command line argument/option parser."""
12- parser = JujuParser(formatter_class=JujuFormatter, **kw)
13+ parser = JujuParser(formatter_class=formatter, **kw)
14 parser.add_argument(
15 "--verbose", "-v", default=False,
16 action="store_true",
17@@ -109,8 +108,16 @@
18 "--log-file", "-l", default=sys.stderr, type=argparse.FileType('a'),
19 help="Log output to file")
20
21+ parser.add_argument(
22+ "--identity-file", "-i", help="SSH key to authenticate with")
23+
24+ """This should probably take care of subparsers too, but I can't get
25+ it to work properly, so it's just doing the "main" ones for now"""
26+ return parser
27+
28+def setup_parser(subcommands, **kw):
29+ parser = create_parser(JujuFormatter, **kw)
30 subparsers = parser.add_subparsers()
31-
32 for module in subcommands:
33 configure_subparser = getattr(module, "configure_subparser", None)
34 passthrough = getattr(module, "passthrough", None)
35
36=== modified file 'juju/providers/common/tests/test_utils.py'
37--- juju/providers/common/tests/test_utils.py 2012-09-10 03:20:20 +0000
38+++ juju/providers/common/tests/test_utils.py 2012-09-22 03:13:42 +0000
39@@ -29,6 +29,35 @@
40 key_data = get_user_authorized_keys(config)
41 self.assertEqual(data, key_data)
42
43+ def test_key_public_identity_file_retrieval(self):
44+ """
45+ The F{juju.providers.common.get_user_authorized_keys} will retrieve
46+ a key's contents from disk OR the commandline and return the key data.
47+ """
48+ data = "through the looking glass"
49+ self.makeFile(
50+ data,
51+ path=os.path.join(self.tmp_home, ".ssh", "identity_test.pub"))
52+ key_data = get_user_authorized_keys({},
53+ args=["juju", "-i", "%s/.ssh/identity_test.pub" % self.tmp_home])
54+ self.assertEqual(data, key_data)
55+
56+ def test_key_private_identity_file_retrieval(self):
57+ """
58+ The F{juju.providers.common.get_user_authorized_keys} will retrieve
59+ a key's contents from disk OR the commandline and return the key data.
60+ """
61+ data = "wabbits"
62+ self.makeFile(
63+ "wong %s" % data,
64+ path=os.path.join(self.tmp_home, ".ssh", "identity_crisis"))
65+ self.makeFile(
66+ data,
67+ path=os.path.join(self.tmp_home, ".ssh", "identity_crisis.pub"))
68+ key_data = get_user_authorized_keys({},
69+ args=["juju", "-i", "%s/.ssh/identity_crisis" % self.tmp_home])
70+ self.assertEqual(data, key_data)
71+
72 def test_content_utilized(self):
73 """
74 The entire key content can be specified via configuration.
75
76=== modified file 'juju/providers/common/utils.py'
77--- juju/providers/common/utils.py 2012-09-10 03:20:20 +0000
78+++ juju/providers/common/utils.py 2012-09-22 03:13:42 +0000
79@@ -1,11 +1,12 @@
80 import logging
81 import os
82-
83+import sys
84
85 from twisted.python.failure import Failure
86
87 from juju.lib import serializer
88 from juju.errors import JujuError, ProviderInteractionError
89+from juju.control import create_parser, JujuFormatter
90
91 log = logging.getLogger("juju.common")
92
93@@ -50,7 +51,7 @@
94 # lines in the data obtained, and then the call site can add each
95 # individual key to CloudInit.
96 #
97-def get_user_authorized_keys(config):
98+def get_user_authorized_keys(config, args=sys.argv):
99 """Locate a public key for the user.
100
101 If neither "authorized-keys" nor "authorized-keys-path" is
102@@ -67,6 +68,20 @@
103 :raises: :exc:`LookupError` if an SSH public key is not found.
104 """
105 key_names = ["id_dsa.pub", "id_rsa.pub", "identity.pub"]
106+
107+ parser = create_parser(JujuFormatter)
108+ options, extra = parser.parse_known_args(args[1:])
109+
110+ if options.identity_file:
111+ # This could be a private key, or a weirdly named public key
112+ # so check if there is a .pub for this file first.
113+ identity_file = os.path.expanduser(options.identity_file)
114+ if os.path.exists(identity_file):
115+ if os.path.exists("%s.pub" % identity_file):
116+ identity_file = "%s.pub" % identity_file
117+
118+ return open(identity_file).read()
119+
120 if config.get("authorized-keys"):
121 return config["authorized-keys"]
122
123
124=== modified file 'juju/state/sshclient.py'
125--- juju/state/sshclient.py 2011-12-07 21:13:52 +0000
126+++ juju/state/sshclient.py 2012-09-22 03:13:42 +0000
127@@ -1,4 +1,5 @@
128 import re
129+import sys
130 import socket
131 import time
132
133@@ -9,11 +10,11 @@
134 from juju.state.security import SecurityPolicyConnection
135 from juju.state.sshforward import forward_port, ClientTunnelProtocol
136
137+from juju.control import create_parser, JujuFormatter
138 from .utils import PortWatcher, get_open_port
139
140 SERVER_RE = re.compile("^(\S+):(\d+)$")
141
142-
143 class SSHClient(SecurityPolicyConnection):
144 """
145 A ZookeeperClient which will internally handle an SSH tunnel
146@@ -40,6 +41,7 @@
147 """
148 hostname, port = self._parse_servers(server or self._servers)
149 start_time = time.time()
150+ identity_file = self._parse_identity(sys.argv[1:])
151
152 # Determine which port we'll be using.
153 local_port = get_open_port()
154@@ -55,7 +57,8 @@
155 protocol = ClientTunnelProtocol(self, tunnel_error)
156 self._process = forward_port(
157 self.remote_user, local_port, hostname, int(port),
158- process_protocol=protocol, share=share)
159+ private_key_path=identity_file, process_protocol=protocol,
160+ share=share)
161
162 # Wait for the tunneled port to open.
163 try:
164@@ -91,6 +94,11 @@
165 hostname, port = match.groups()
166 return hostname, port
167
168+ def _parse_identity(self, args):
169+ parser = create_parser(JujuFormatter)
170+ options, extra = parser.parse_known_args(args)
171+ return options.identity_file
172+
173 @inlineCallbacks
174 def connect(self, server=None, timeout=60, share=False):
175 """Probe ZK is accessible via ssh tunnel, return client on success."""
176
177=== modified file 'juju/state/tests/test_sshclient.py'
178--- juju/state/tests/test_sshclient.py 2011-12-07 04:55:29 +0000
179+++ juju/state/tests/test_sshclient.py 2012-09-22 03:13:42 +0000
180@@ -82,7 +82,8 @@
181 # First, get the forwarding going, targetting the remote
182 # address provided.
183 forward("ubuntu", MATCH_PORT, "remote", 2181,
184- process_protocol=MATCH_PROTOCOL, share=False)
185+ private_key_path=None, process_protocol=MATCH_PROTOCOL,
186+ share=False)
187 self.mocker.result(process)
188
189 # Next ensure the port check succeeds
190@@ -127,7 +128,9 @@
191
192 protocol = self.mocker.mock()
193 forward("ubuntu", MATCH_PORT, "remote", 2181,
194- process_protocol=MATCH_PROTOCOL, share=False)
195+ private_key_path=None, process_protocol=MATCH_PROTOCOL,
196+ share=False)
197+
198 self.mocker.result(protocol)
199
200 thread(MATCH_FUNC)
201@@ -151,7 +154,8 @@
202
203 protocol = self.mocker.mock()
204 forward("ubuntu", MATCH_PORT, "remote", 2181,
205- process_protocol=MATCH_PROTOCOL, share=False)
206+ private_key_path=None, process_protocol=MATCH_PROTOCOL,
207+ share=False)
208 self.mocker.result(protocol)
209
210 thread(MATCH_FUNC)
211@@ -175,7 +179,8 @@
212
213 protocol = self.mocker.mock()
214 forward("ubuntu", MATCH_PORT, "remote", 2181,
215- process_protocol=MATCH_PROTOCOL, share=False)
216+ private_key_path=None, process_protocol=MATCH_PROTOCOL,
217+ share=False)
218 self.mocker.result(protocol)
219
220 thread(MATCH_FUNC)

Subscribers

People subscribed via source and target branches

to status/vote changes: