Improve logging and CLI output

Logging was incomplete, and unhelpful in case of an exception.
Add also a log_file parameter to the configuration file, so that
CLI actions can be logged to a file for audit.

Change-Id: Ifa7a2f2828fa38be8879a4a9c64fe948b95d5fb8
This commit is contained in:
Matthieu Huin 2021-04-27 20:25:08 +02:00
parent 110a460dd2
commit 971b64deaa
3 changed files with 73 additions and 20 deletions

View File

@ -13,3 +13,6 @@ url=https://example.com/zuul/
tenant=mytenant tenant=mytenant
# verify_ssl=False # verify_ssl=False
auth_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 auth_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
# the path must be writable by the user.
log_file=/path/to/log
log_level=DEBUG

View File

@ -99,6 +99,12 @@ class ZuulClient():
def parseArguments(self, args=None): def parseArguments(self, args=None):
self.parser = self.createParser() self.parser = self.createParser()
self.args = self.parser.parse_args(args) self.args = self.parser.parse_args(args)
if (
(self.args.zuul_url and self.args.zuul_config) or
(not self.args.zuul_url and not self.args.zuul_config)
):
raise ArgumentException(
'Either specify --zuul-url or use a config file')
if not getattr(self.args, 'func', None): if not getattr(self.args, 'func', None):
self.parser.print_help() self.parser.print_help()
sys.exit(1) sys.exit(1)
@ -139,9 +145,31 @@ class ZuulClient():
"Unable to locate config file in %s" % locations) "Unable to locate config file in %s" % locations)
def setup_logging(self): def setup_logging(self):
"""Client logging does not rely on conf file""" config_args = dict(
format='%(levelname)-8s - %(message)s'
)
if self.args.verbose: if self.args.verbose:
logging.basicConfig(level=logging.DEBUG) config_args['level'] = logging.DEBUG
else:
config_args['level'] = logging.ERROR
# set logging across all components (urllib etc)
logging.basicConfig(**config_args)
if self.args.zuul_config and\
self.args.zuul_config in self.config.sections():
zuul_conf = self.args.zuul_config
log_file = get_default(self.config,
zuul_conf, 'log_file', None)
if log_file is not None:
fh = logging.FileHandler(log_file)
f_loglevel = get_default(self.config,
zuul_conf, 'log_level', 'INFO')
fh.setLevel(getattr(logging, f_loglevel, 'INFO'))
f_formatter = logging.Formatter(
fmt='%(asctime)s %(name)s %(levelname)-8s - %(message)s',
datefmt='%x %X'
)
fh.setFormatter(f_formatter)
self.log.addHandler(fh)
def _main(self, args=None): def _main(self, args=None):
# TODO make func return specific return codes # TODO make func return specific return codes
@ -161,15 +189,21 @@ class ZuulClient():
print() print()
raise raise
if ret: if ret:
self.log.info('Command %s completed '
'successfully' % self.args.func.__name__)
return 0 return 0
else: else:
self.log.error('Command %s completed '
'with error(s)' % self.args.func.__name__)
return 1 return 1
def main(self): def main(self):
try: try:
sys.exit(self._main()) sys.exit(self._main())
except Exception as e: except Exception as e:
self.log.error(e) self.log.exception(
'Failed with the following exception: %s ' % e
)
sys.exit(1) sys.exit(1)
def _check_tenant_scope(self, client): def _check_tenant_scope(self, client):
@ -225,7 +259,7 @@ class ZuulClient():
node_hold_expiration = self.args.node_hold_expiration node_hold_expiration = self.args.node_hold_expiration
client = self.get_client() client = self.get_client()
self._check_tenant_scope(client) self._check_tenant_scope(client)
r = client.autohold( kwargs = dict(
tenant=self.tenant(), tenant=self.tenant(),
project=self.args.project, project=self.args.project,
job=self.args.job, job=self.args.job,
@ -234,6 +268,8 @@ class ZuulClient():
reason=self.args.reason, reason=self.args.reason,
count=self.args.count, count=self.args.count,
node_hold_expiration=node_hold_expiration) node_hold_expiration=node_hold_expiration)
self.log.info('Invoking autohold with arguments: %s' % kwargs)
r = client.autohold(**kwargs)
return r return r
def add_autohold_delete_subparser(self, subparsers): def add_autohold_delete_subparser(self, subparsers):
@ -249,7 +285,12 @@ class ZuulClient():
def autohold_delete(self): def autohold_delete(self):
client = self.get_client() client = self.get_client()
self._check_tenant_scope(client) self._check_tenant_scope(client)
return client.autohold_delete(self.args.id, self.tenant()) kwargs = dict(
id=self.args.id,
tenant=self.tenant()
)
self.log.info('Invoking autohold-delete with arguments: %s' % kwargs)
return client.autohold_delete(**kwargs)
def add_autohold_info_subparser(self, subparsers): def add_autohold_info_subparser(self, subparsers):
cmd_autohold_info = subparsers.add_parser( cmd_autohold_info = subparsers.add_parser(
@ -337,11 +378,14 @@ class ZuulClient():
def enqueue(self): def enqueue(self):
client = self.get_client() client = self.get_client()
self._check_tenant_scope(client) self._check_tenant_scope(client)
r = client.enqueue( kwargs = dict(
tenant=self.tenant(), tenant=self.tenant(),
pipeline=self.args.pipeline, pipeline=self.args.pipeline,
project=self.args.project, project=self.args.project,
change=self.args.change) change=self.args.change
)
self.log.info('Invoking enqueue with arguments: %s' % kwargs)
r = client.enqueue(**kwargs)
return r return r
def add_enqueue_ref_subparser(self, subparsers): def add_enqueue_ref_subparser(self, subparsers):
@ -372,13 +416,16 @@ class ZuulClient():
def enqueue_ref(self): def enqueue_ref(self):
client = self.get_client() client = self.get_client()
self._check_tenant_scope(client) self._check_tenant_scope(client)
r = client.enqueue_ref( kwargs = dict(
tenant=self.tenant(), tenant=self.tenant(),
pipeline=self.args.pipeline, pipeline=self.args.pipeline,
project=self.args.project, project=self.args.project,
ref=self.args.ref, ref=self.args.ref,
oldrev=self.args.oldrev, oldrev=self.args.oldrev,
newrev=self.args.newrev) newrev=self.args.newrev
)
self.log.info('Invoking enqueue-ref with arguments: %s' % kwargs)
r = client.enqueue_ref(**kwargs)
return r return r
def add_dequeue_subparser(self, subparsers): def add_dequeue_subparser(self, subparsers):
@ -401,12 +448,15 @@ class ZuulClient():
def dequeue(self): def dequeue(self):
client = self.get_client() client = self.get_client()
self._check_tenant_scope(client) self._check_tenant_scope(client)
r = client.dequeue( kwargs = dict(
tenant=self.tenant(), tenant=self.tenant(),
pipeline=self.args.pipeline, pipeline=self.args.pipeline,
project=self.args.project, project=self.args.project,
change=self.args.change, change=self.args.change,
ref=self.args.ref) ref=self.args.ref
)
self.log.info('Invoking dequeue with arguments: %s' % kwargs)
r = client.dequeue(**kwargs)
return r return r
def add_promote_subparser(self, subparsers): def add_promote_subparser(self, subparsers):
@ -424,15 +474,16 @@ class ZuulClient():
def promote(self): def promote(self):
client = self.get_client() client = self.get_client()
self._check_tenant_scope(client) self._check_tenant_scope(client)
r = client.promote( kwargs = dict(
tenant=self.tenant(), tenant=self.tenant(),
pipeline=self.args.pipeline, pipeline=self.args.pipeline,
change_ids=self.args.changes) change_ids=self.args.changes
)
self.log.info('Invoking promote with arguments: %s' % kwargs)
r = client.promote(**kwargs)
return r return r
def get_client(self): def get_client(self):
if self.args.zuul_url and self.args.zuul_config:
raise Exception('Either specify --zuul-url or use a config file')
if self.args.zuul_url: if self.args.zuul_url:
self.log.debug( self.log.debug(
'Using Zuul URL provided as argument to instantiate client') 'Using Zuul URL provided as argument to instantiate client')
@ -559,7 +610,7 @@ class ZuulClient():
key = client.get_key(self.tenant(), self.args.project) key = client.get_key(self.tenant(), self.args.project)
pubkey_file.write(str.encode(key)) pubkey_file.write(str.encode(key))
pubkey_file.close() pubkey_file.close()
self.log.debug('Calling openssl') self.log.debug('Invoking openssl')
ciphertext_chunks = encrypt_with_openssl(pubkey_file.name, ciphertext_chunks = encrypt_with_openssl(pubkey_file.name,
plaintext, plaintext,
self.log) self.log)
@ -644,7 +695,6 @@ class ZuulClient():
def builds(self): def builds(self):
if self.args.voting and self.args.non_voting: if self.args.voting and self.args.non_voting:
raise Exception('--voting and --non-voting are mutually exclusive') raise Exception('--voting and --non-voting are mutually exclusive')
self.log.info('Showing the last {} matches.'.format(self.args.limit))
filters = {'limit': self.args.limit, filters = {'limit': self.args.limit,
'skip': self.args.skip} 'skip': self.args.skip}
if self.args.project: if self.args.project:

View File

@ -39,7 +39,7 @@ def get_default(config, section, option, default=None, expand_user=False):
def encrypt_with_openssl(pubkey_path, plaintext, logger=None): def encrypt_with_openssl(pubkey_path, plaintext, logger=None):
cmd = ['openssl', 'version'] cmd = ['openssl', 'version']
if logger: if logger:
logger.debug('calling "%s"' % ' '.join(cmd)) logger.debug('Invoking "%s"' % ' '.join(cmd))
try: try:
openssl_version = subprocess.check_output( openssl_version = subprocess.check_output(
cmd).split()[1] cmd).split()[1]
@ -48,7 +48,7 @@ def encrypt_with_openssl(pubkey_path, plaintext, logger=None):
cmd = ['openssl', 'rsa', '-text', '-pubin', '-in', pubkey_path] cmd = ['openssl', 'rsa', '-text', '-pubin', '-in', pubkey_path]
if logger: if logger:
logger.debug('calling "%s"' % ' '.join(cmd)) logger.debug('Invoking "%s"' % ' '.join(cmd))
p = subprocess.Popen(cmd, p = subprocess.Popen(cmd,
stdout=subprocess.PIPE) stdout=subprocess.PIPE)
(stdout, stderr) = p.communicate() (stdout, stderr) = p.communicate()
@ -81,7 +81,7 @@ def encrypt_with_openssl(pubkey_path, plaintext, logger=None):
'-oaep', '-pubin', '-inkey', '-oaep', '-pubin', '-inkey',
pubkey_path] pubkey_path]
if logger: if logger:
logger.debug('calling "%s" with each data chunk:' % ' '.join(cmd)) logger.debug('Invoking "%s" with each data chunk:' % ' '.join(cmd))
for count in range(chunks): for count in range(chunks):
chunk = plaintext[int(count * max_bytes): chunk = plaintext[int(count * max_bytes):
int((count + 1) * max_bytes)] int((count + 1) * max_bytes)]