Tip top, looks good. There are some things that still need doing I think, but it's the right approach. But first, a Panella service announcement:
I guess there are two ways of addressing this bug:
1. Finding each place that reports CalledProcessError (explicitly or
by catching a superclass), and update them to treat
CalledProcessError specially, e.g. by logging e.output too.
2. Finding each place we use check_call() or check_output() and
switching them to use wrapped_check_{call,output}(), and 'fixing'
the exception to include the log output when it's dumped later on.
3. Push a change upstream into Python to fix CalledProcessError to be
like #2.
That's 3 things.
Only #1 and #2 are feasible really, and you've chosen #2 (after asking
all of us, I might add). It's sad when we can't use the standard
library unadorned, but the str/unicode crap in Python 2.x kind of
forces our hand. It's easier to find uses of check_{call,output} in
the codebase than it is to find those points where exceptions leak out
into log files and suchlike, so #2 is the better approach, even though
it's less "pretty" than #1 to my mind.
This long ramble mainly for my own benefit is now over.
[1]
wrapped_check_call and wrapped_check_output are ugly names... but I
can't think of anything better. They talk about mechanism and
implementation instead of what we expect to gain from their use.
I also have problems with ExternalActionError's name. Sorry :-/ This
is a hard problem. I think it's the "Action" bit; it sounds too
specific, and not Unix enough. How about "ExternalProcessError"?
Tip top, looks good. There are some things that still need doing I think, but it's the right approach. But first, a Panella service announcement:
I guess there are two ways of addressing this bug:
1. Finding each place that reports CalledProcessError (explicitly or sError specially, e.g. by logging e.output too.
by catching a superclass), and update them to treat
CalledProces
2. Finding each place we use check_call() or check_output() and check_{ call,output} (), and 'fixing'
switching them to use wrapped_
the exception to include the log output when it's dumped later on.
3. Push a change upstream into Python to fix CalledProcessError to be
like #2.
That's 3 things.
Only #1 and #2 are feasible really, and you've chosen #2 (after asking
all of us, I might add). It's sad when we can't use the standard
library unadorned, but the str/unicode crap in Python 2.x kind of
forces our hand. It's easier to find uses of check_{call,output} in
the codebase than it is to find those points where exceptions leak out
into log files and suchlike, so #2 is the better approach, even though
it's less "pretty" than #1 to my mind.
This long ramble mainly for my own benefit is now over.
[1]
wrapped_check_call and wrapped_ check_output are ugly names... but I
can't think of anything better. They talk about mechanism and
implementation instead of what we expect to gain from their use.
Maybe:
check_call --> call_and_check and_check
check_output --> call_capture_
?
[2]
I also have problems with ExternalActionE rror's name. Sorry :-/ This sError" ?
is a hard problem. I think it's the "Action" bit; it sounds too
specific, and not Unix enough. How about "ExternalProces
[3]
+from provisioningser ver.utils import (
+ read_text_file,
+ )
Try:
utilities/ format- new-and- modified- import -r submit:
and it'll unwrap that. It'll also fix imports in a few other modules
too.
[4]
+class TestCallDnsSecK eygen(MAASTestC ase): call_dnssec_ keygen. """ external_ script( self): subprocess, 'check_output') get("PATH" , "").split( os.pathsep) "/usr/sbin" ) pathsep. join(path) ) keygen( target_ dir) assert_ called_ with([
+ """Tests for omshell.
+
+ def test_runs_
+ check_output = self.patch(
+ target_dir = self.make_dir()
+ path = os.environ.
+ path.append(
+ env = dict(os.environ, PATH=os.
+ call_dnssec_
+ check_output.
+ 'dnssec-keygen', '-r', '/dev/urandom', '-a', 'HMAC-MD5',
+ '-b', '512', '-n', 'HOST', '-K', target_dir, '-q', 'omapi_key'
+ ], env=env)
Woah, where did that come from? :)
If it's only called once, you could do:
...
Also, if the env isn't /that/ important here, you could do:
from mock import ANY
check_ output. assert_ called_ with([
'dnssec- keygen' , '-r', '/dev/urandom', '-a', 'HMAC-MD5',
'-b', '512', '-n', 'HOST', '-K', target_dir, '-q', 'omapi_key'
], env=ANY)
[5]
+ recorder = self.patch(tasks, 'wrapped_ check_call' , Mock())
Not your code, but you can remove the Mock() bit here; it's done
automatically by patch():
recorder = self.patch(tasks, 'wrapped_ check_call' )
[6]
+ try: check_call( command, *args, **kwargs) CalledProcessEr ror as error: rror(error. returncode, error.cmd, error.output)
+ return subprocess.
+ except subprocess.
+ raise ExternalActionE
You can totally cheat here ;)
try: check_call( command, *args, **kwargs) CalledProcessEr ror as error:
error. __class_ _ = ExternalActionError
return subprocess.
except subprocess.
raise
[7]
+ def __str__(self):
+ return self.__unicode__()
This ain't good; __str__ should return bytes.
I have a different implementation in mind:
{{{ rror(CalledProc essError) :
class ExternalActionE
"""Raised when there's a problem calling an external command.
Unlike CalledProcessError, ExternalActionE rror.__ str__() contains
the output of the failed external process, if available.
"""
@staticmethod string) : decode( "ascii" , "replace")
def _to_unicode(
if isinstance(string, bytes):
return string.
else:
return unicode(string)
@staticmethod encode( "ascii" , "replace")
string. decode( "ascii" )
return "<non-ASCII string>"
return string
def _to_ascii(string):
if isinstance(string, unicode):
return string.
else:
string = bytes(string)
try:
except UnicodeDecodeError:
else:
def __unicode__(self): quote(self. _to_unicode( part)) for part in self.cmd) unicode( self.output)
cmd = u" ".join(
output = self._to_
return u"Command `%s` returned non-zero exit status %d:\n%s" % (
cmd, self.returncode, output)
def __str__(self): quote(self. _to_ascii( part)) for part in self.cmd) ascii(self. output)
cmd = b" ".join(
output = self._to_
return b"Command `%s` returned non-zero exit status %d:\n%s" % (
cmd, self.returncode, output)
}}}
Yep, it's a big one, and it'll need a little more testing, but I
suspect it's going to be more useful overall.