Merge lp:~bcsaller/charms/trusty/cloudfoundry/cmd-refactor into lp:~cf-charmers/charms/trusty/cloudfoundry/trunk

Proposed by Benjamin Saller
Status: Merged
Merged at revision: 165
Proposed branch: lp:~bcsaller/charms/trusty/cloudfoundry/cmd-refactor
Merge into: lp:~cf-charmers/charms/trusty/cloudfoundry/trunk
Diff against target: 342 lines (+144/-75)
2 files modified
cfdeploy (+24/-9)
cloudfoundry/utils.py (+120/-66)
To merge this branch: bzr merge lp:~bcsaller/charms/trusty/cloudfoundry/cmd-refactor
Reviewer Review Type Date Requested Status
Cloud Foundry Charmers Pending
Review via email: mp+245153@code.launchpad.net

Description of the change

Refactor Command/Commander. Uniform response object, native bool cmp for exit status.
Create a JUJU_REPO in tmp with the current dir linked in for cloudfoundry charm deploy
  to work around oddness with links

To post a comment you must log in.
164. By Benjamin Saller

keyboard interrupt support

165. By Benjamin Saller

less aggressive reset

166. By Benjamin Saller

finally fixed

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'cfdeploy'
--- cfdeploy 2014-12-03 23:14:23 +0000
+++ cfdeploy 2014-12-19 18:42:26 +0000
@@ -29,8 +29,9 @@
2929
3030
31def prepare_runtime():31def prepare_runtime():
32 in_venv = os.environ.get('VIRTUAL_ENV', None)
32 activate = "bin/activate_this.py"33 activate = "bin/activate_this.py"
33 if not verify_dep('virtualenv'):34 if not verify_dep('virtualenv') and not in_venv:
34 print "(sudo) installing python-virtualenv package."35 print "(sudo) installing python-virtualenv package."
35 try:36 try:
36 subprocess.check_output(37 subprocess.check_output(
@@ -39,12 +40,13 @@
39 except subprocess.CalledProcessError as e:40 except subprocess.CalledProcessError as e:
40 print e.output41 print e.output
41 raise42 raise
42 import virtualenv43 if not in_venv:
43 venv = os.path.join(os.getcwd(), '.venv')44 import virtualenv
44 if not os.path.exists(venv):45 venv = os.path.join(os.getcwd(), '.venv')
45 virtualenv.create_environment(venv, site_packages=False)46 if not os.path.exists(venv):
46 venv_launcher = os.path.join(venv, activate)47 virtualenv.create_environment(venv, site_packages=False)
47 execfile(venv_launcher, dict(__file__=venv_launcher))48 venv_launcher = os.path.join(venv, activate)
49 execfile(venv_launcher, dict(__file__=venv_launcher))
48 install_python_deps()50 install_python_deps()
4951
50prepare_runtime()52prepare_runtime()
@@ -69,6 +71,11 @@
6971
70from progress.bar import Bar72from progress.bar import Bar
7173
74def reset_terminal():
75 subprocess.check_call(['stty', 'sane'])
76 subprocess.check_call(['tput', 'rs2', 'rs3'])
77 print
78
7279
73def setup():80def setup():
74 parser = argparse.ArgumentParser()81 parser = argparse.ArgumentParser()
@@ -100,6 +107,13 @@
100 yield107 yield
101 sys.stderr = _save108 sys.stderr = _save
102109
110def reset_handler(f):
111 def wrapper(*args, **kwargs):
112 try:
113 f(*args, **kwargs)
114 finally:
115 reset_terminal()
116 return wrapper
103117
104def show_reconciler():118def show_reconciler():
105 with devnull():119 with devnull():
@@ -136,13 +150,14 @@
136 print "Installing CF CLI (this requires sudo access)"150 print "Installing CF CLI (this requires sudo access)"
137 sh.sudo('dpkg', '-i', 'cf-cli_amd64.deb')151 sh.sudo('dpkg', '-i', 'cf-cli_amd64.deb')
138152
139153@reset_handler
140def main():154def main():
141 options = setup()155 options = setup()
142 if options.log:156 if options.log:
143 root_logger = logging.getLogger()157 root_logger = logging.getLogger()
144 root_logger.handlers = [logging.FileHandler('cfdeploy.log', 'w')]158 root_logger.handlers = [logging.FileHandler('cfdeploy.log', 'w')]
145 root_logger.setLevel(logging.DEBUG)159 root_logger.setLevel(logging.DEBUG)
160 sh.set_log(root_logger)
146 install_deps()161 install_deps()
147162
148 bar = ProgressBar('Deploying CloudFoundry', max=10)163 bar = ProgressBar('Deploying CloudFoundry', max=10)
@@ -169,7 +184,7 @@
169 bar.next(message="Opening Admin Console")184 bar.next(message="Opening Admin Console")
170 show_webadmin()185 show_webadmin()
171 bar.finish()186 bar.finish()
172187 subprocess.check_call(['tput', 'rs2', 'rs3'])
173 print "You should now be logged into a running CF deployment."188 print "You should now be logged into a running CF deployment."
174 if which('cf'):189 if which('cf'):
175 print "The 'cf' command line client is installed and available."190 print "The 'cf' command line client is installed and available."
176191
=== modified file 'cloudfoundry/utils.py'
--- cloudfoundry/utils.py 2014-12-09 22:56:46 +0000
+++ cloudfoundry/utils.py 2014-12-19 18:42:26 +0000
@@ -4,9 +4,9 @@
4import logging4import logging
5import os5import os
6import re6import re
7import shutil
7import socket8import socket
8import subprocess9import subprocess
9import sys
10import tempfile10import tempfile
11import time11import time
12from contextlib import contextmanager12from contextlib import contextmanager
@@ -17,7 +17,7 @@
1717
18from charmhelpers import fetch18from charmhelpers import fetch
1919
2020log = logging.getLogger('utils')
21ip_pat = re.compile('^(\d{1,3}\.){3}\d{1,3}$')21ip_pat = re.compile('^(\d{1,3}\.){3}\d{1,3}$')
22_client = None # cached client connection22_client = None # cached client connection
2323
@@ -168,52 +168,115 @@
168 return conf168 return conf
169169
170170
171def command(*base_args, **kwargs):171class ProcessResult(object):
172 check = kwargs.pop('check', False)172 def __init__(self, command, exit_code, stdout, stderr):
173 throw = kwargs.pop('throw', True)173 self.command = command
174174 self.exit_code = exit_code
175 def callable_command(*args, **kws):175 self.stdout = stdout
176 kwargs.update(kws)176 self.stderr = stderr
177 all_args = base_args + args177
178 logging.debug(' '.join(all_args))178 def __repr__(self):
179 return '<ProcessResult "%s":%s>' % (self.command, self.exit_code)
180
181 @property
182 def cmd(self):
183 return ' '.join(self.command)
184
185 @property
186 def output(self):
187 result = ''
188 if self.stdout:
189 result += self.stdout
190 if self.stderr:
191 result += self.stderr
192 return result.strip()
193
194 @property
195 def json(self):
196 if self.stdout:
197 return json.loads(self.stdout)
198 return None
199
200 def __eq__(self, other):
201 return self.exit_code == other
202
203 def __bool__(self):
204 return self.exit_code == 0
205
206 def throw_on_error(self):
207 if not bool(self):
208 raise subprocess.CalledProcessError(
209 self.exit_code, self.command, output=self.output)
210
211
212class Process(object):
213 def __init__(self, command=None, throw=False, log=log, **kwargs):
214 if isinstance(command, str):
215 command = (command, )
216 self.command = command
217 self._throw_on_error = False
218 self.log = log
219 self._kw = kwargs
220
221 def throw_on_error(self, throw=True):
222 self._throw_on_error = throw
223 return self
224
225 def __call__(self, *args, **kw):
226 kwargs = dict(stdout=subprocess.PIPE,
227 stderr=subprocess.STDOUT)
228 if self._kw:
229 kwargs.update(self._kw)
230 kwargs.update(kw)
231 if self.command:
232 all_args = self.command + args
233 else:
234 all_args = args
179 if 'env' not in kwargs:235 if 'env' not in kwargs:
180 kwargs['env'] = os.environ236 kwargs['env'] = os.environ
181 logging.debug("invoke: %s", all_args)237 p = subprocess.Popen(all_args, **kwargs)
238 stdout, stderr = p.communicate()
239 self.log.debug(stdout)
240 stdout = stdout.strip()
241 if stderr is not None:
242 stderr = stderr.strip()
243 self.log.debug(stderr)
244 exit_code = p.poll()
245 result = ProcessResult(all_args, exit_code, stdout, stderr)
246 self.log.debug("process: %s (%d)", result.cmd, result.exit_code)
247 if self._throw_on_error and not bool(self):
248 result.throw_on_error()
249 return result
182250
183 p = subprocess.Popen(all_args,251command = Process
184 stdout=subprocess.PIPE,
185 stderr=subprocess.STDOUT,
186 **kwargs)
187 output, _ = p.communicate()
188 ret_code = p.poll()
189 logging.debug('output: %s', output)
190 if check is True:
191 if ret_code != 0 and throw:
192 raise subprocess.CalledProcessError(ret_code, all_args, output=output)
193 return ret_code
194 else:
195 return output.strip()
196 return callable_command
197252
198253
199class Commander(object):254class Commander(object):
255 def __init__(self, log=log):
256 self.log = log
257
258 def set_log(self, logger):
259 self.log = logger
260
200 def __getattr__(self, key):261 def __getattr__(self, key):
201 return command(key)262 return command((key,), log=self.log)
202263
203 def check(self, *args, **kwargs):264 def check(self, *args, **kwargs):
204 return command(*args, check=True, **kwargs)265 kwargs.update({'log': self.log})
266 return command(args, **kwargs).throw_on_error()
205267
206 def __call__(self, cmd, **kwargs):268 def __call__(self, *args, **kwargs):
207 subprocess.check_output(cmd, shell=True, **kwargs)269 kwargs.update({'log': self.log})
270 return command(args, shell=True, **kwargs)
208271
209272
210sh = Commander()273sh = Commander()
211dig = command('dig', '+short')274dig = command('dig', '+short')
212api_endpoints = sh.check('juju', 'api-endpoints', throw=False)275api_endpoints = sh('juju', 'api-endpoints')
213276
214277
215def current_env():278def current_env():
216 return sh.juju('switch')279 return sh.juju('switch').output
217280
218281
219def get_jenv():282def get_jenv():
@@ -285,9 +348,9 @@
285348
286349
287def juju_state_server():350def juju_state_server():
288 if api_endpoints() != 0:351 if not api_endpoints():
289 return False352 return False
290 endpoints = json.loads(sh.juju('api-endpoints', '--format=json'))353 endpoints = sh.juju('api-endpoints', '--format=json').json
291 for ep in endpoints:354 for ep in endpoints:
292 host, port = ep.split(':', 1)355 host, port = ep.split(':', 1)
293 result = socket_open(host, int(port))356 result = socket_open(host, int(port))
@@ -374,9 +437,8 @@
374437
375438
376def bootstrap():439def bootstrap():
377 if not os.path.exists(get_jenv()) or api_endpoints() != 0:440 if not os.path.exists(get_jenv()) or not api_endpoints():
378 juju = sh.check('juju', throw=False)441 return bool(sh.juju('bootstrap', '--upload-tools'))
379 return juju('bootstrap') != 0
380 return True442 return True
381443
382444
@@ -386,22 +448,16 @@
386 return data['password']448 return data['password']
387449
388450
389def get_repo_path():451@contextmanager
390 candidates = [os.getcwd(), os.environ.get('JUJU_REPOSITORY')]452def cf_repo():
391 for repo_path in candidates:453 """Context manager to ensure we are deploying the cloudfoundry charm
392 if not repo_path:454 containing this instance of cfdeploy."""
393 continue455 repo = tempfile.mkdtemp()
394 if 'trusty' in repo_path:456 repo_path = os.path.join(repo, "trusty")
395 repo_path, _ = os.path.split(repo_path)457 os.mkdir(repo_path)
396 if repo_path.endswith('trusty'):458 os.symlink(os.path.abspath(os.getcwd()), os.path.join(repo_path, "cloudfoundry"))
397 repo_path = os.path.dirname(repo_path)459 yield repo
398 if os.path.exists(os.path.join(repo_path, 'trusty', 'cloudfoundry')):460 shutil.rmtree(repo)
399 return repo_path
400 sys.stderr.write(
401 'Unable to determine valid repository path.\n'
402 'Please set JUJU_REPOSITORY and/or ensure that directory containing\n'
403 'the charm includes the series.\n')
404 sys.exit(1)
405461
406462
407def deploy(**config):463def deploy(**config):
@@ -421,20 +477,18 @@
421 with open(fn, 'w') as fp:477 with open(fn, 'w') as fp:
422 yaml.dump({'cloudfoundry': config}, fp)478 yaml.dump({'cloudfoundry': config}, fp)
423479
424 repo_path = get_repo_path()480 with cf_repo() as repo_path:
425481 args = ['deploy', '--config=%s' % fn,
426 args = ['deploy', '--config=%s' % fn,482 '--repository=%s' % repo_path]
427 '--repository=%s' % repo_path]483 if constraints:
428 if constraints:484 args.append('--constraints=%s' % constraints)
429 args.append('--constraints=%s' % constraints)485 args.append('local:trusty/cloudfoundry')
430 args.append('local:trusty/cloudfoundry')486 if not sh.juju(*args):
431 juju = sh.check('juju', throw=False)487 return False
432 if juju(*args) != 0:488 time.sleep(5)
433 return False489 if not sh.juju('expose', 'cloudfoundry'):
434 time.sleep(5)490 return False
435 if juju('expose', 'cloudfoundry') != 0:491 os.unlink(fn)
436 return False
437 os.unlink(fn)
438 return True492 return True
439493
440494

Subscribers

People subscribed via source and target branches