Commit 342eb1b2 authored by Stavros Sachtouris's avatar Stavros Sachtouris

Warn/raise error when config file is inaccessible

Fixes grnet/kamaki#71

Raise an error if the file is not readable or not existing, warn if
it is not writable, add a description to the error message when
kamaki attempts to write to a non-writable config file.
parent a13a1d2f
...@@ -5,6 +5,15 @@ Unified Changelog file for Kamaki versions >= 0.13 ...@@ -5,6 +5,15 @@ Unified Changelog file for Kamaki versions >= 0.13
.. _Changelog-0.13: .. _Changelog-0.13:
v0.13rc6
========
Bug fixes
---------
* Warn or raise errors when the configuration file is inaccessible
[grnet/kamaki#71]
v0.13rc5 v0.13rc5
======== ========
......
...@@ -178,8 +178,8 @@ def _setup_logging(debug=False, verbose=False): ...@@ -178,8 +178,8 @@ def _setup_logging(debug=False, verbose=False):
logger.add_stream_logger('kamaki.clients.send', logging.INFO, sfmt) logger.add_stream_logger('kamaki.clients.send', logging.INFO, sfmt)
logger.add_stream_logger('kamaki.clients.recv', logging.INFO, rfmt) logger.add_stream_logger('kamaki.clients.recv', logging.INFO, rfmt)
logger.add_stream_logger(__name__, logging.INFO) logger.add_stream_logger(__name__, logging.INFO)
else: # else:
logger.add_stream_logger(__name__, logging.WARNING) # logger.add_stream_logger(__name__, logging.WARNING)
global kloger global kloger
kloger = logger.get_logger(__name__) kloger = logger.get_logger(__name__)
...@@ -237,7 +237,7 @@ def _init_session(arguments, is_non_api=False): ...@@ -237,7 +237,7 @@ def _init_session(arguments, is_non_api=False):
if ca_file: if ca_file:
https.patch_with_certs(ca_file) https.patch_with_certs(ca_file)
else: else:
warn = red('WARNING: CA certifications path not set (insecure) ') warn = red('CA certifications path not set (insecure) ')
kloger.warning(warn) kloger.warning(warn)
https.patch_ignore_ssl(ignore_ssl) https.patch_ignore_ssl(ignore_ssl)
...@@ -341,12 +341,12 @@ def init_cached_authenticator(config_argument, cloud, logger): ...@@ -341,12 +341,12 @@ def init_cached_authenticator(config_argument, cloud, logger):
_cnf.write() _cnf.write()
if tokens: if tokens:
return astakos, help_message return astakos, help_message
logger.warning('WARNING: cloud.%s.token is now empty' % cloud) logger.warning('cloud.%s.token is now empty' % cloud)
help_message = [ help_message = [
'To set a new token:', 'To set a new token:',
' kamaki config set cloud.%s.token NEW_TOKEN'] ' kamaki config set cloud.%s.token NEW_TOKEN']
except AssertionError as ae: except AssertionError as ae:
logger.warning('WARNING: Failed to load authenticator [%s]' % ae) logger.warning('Failed to load authenticator [%s]' % ae)
return None, help_message return None, help_message
...@@ -528,6 +528,9 @@ def main(func): ...@@ -528,6 +528,9 @@ def main(func):
for i, a in enumerate(internal_argv): for i, a in enumerate(internal_argv):
argv[i] = a argv[i] = a
logger.add_stream_logger(
__name__, logging.WARNING,
fmt='%(levelname)s (%(name)s): %(message)s')
_config_arg = ConfigArgument('Path to config file') _config_arg = ConfigArgument('Path to config file')
parser = ArgumentParseManager(exe, arguments=dict( parser = ArgumentParseManager(exe, arguments=dict(
config=_config_arg, config=_config_arg,
......
...@@ -41,6 +41,7 @@ import dateutil.tz ...@@ -41,6 +41,7 @@ import dateutil.tz
import dateutil.parser import dateutil.parser
from time import mktime from time import mktime
from sys import stderr from sys import stderr
import os.path
from logging import getLogger from logging import getLogger
from argparse import ( from argparse import (
...@@ -107,7 +108,7 @@ class Argument(object): ...@@ -107,7 +108,7 @@ class Argument(object):
assert name.count(' ') == 0, '%s: Invalid parse name "%s"' % ( assert name.count(' ') == 0, '%s: Invalid parse name "%s"' % (
self, name) self, name)
msg = '%s: Invalid parse name "%s" should start with a "-"' % ( msg = '%s: Invalid parse name "%s" should start with a "-"' % (
self, name) self, name)
assert name.startswith('-'), msg assert name.startswith('-'), msg
self.default = default or None self.default = default or None
...@@ -149,6 +150,10 @@ class ConfigArgument(Argument): ...@@ -149,6 +150,10 @@ class ConfigArgument(Argument):
@value.setter @value.setter
def value(self, config_file): def value(self, config_file):
if config_file: if config_file:
if not os.path.exists(config_file):
raiseCLIError(
'Config file "%s" does not exist' % config_file,
importance=3)
self._value = Config(config_file) self._value = Config(config_file)
self.file_path = config_file self.file_path = config_file
elif self.file_path: elif self.file_path:
...@@ -248,8 +253,8 @@ class BooleanArgument(ValueArgument): ...@@ -248,8 +253,8 @@ class BooleanArgument(ValueArgument):
v = new_value.lower() v = new_value.lower()
if v not in ('true', 'false'): if v not in ('true', 'false'):
raise CLIInvalidArgument( raise CLIInvalidArgument(
'Invalid value %s=%s' % (self.lvalue, new_value), details=[ 'Invalid value %s=%s' % (self.lvalue, new_value),
'Usage:', '%s=<true|false>' % self.lvalue]) details=['Usage:', '%s=<true|false>' % self.lvalue])
self._value = bool(v == 'true') self._value = bool(v == 'true')
...@@ -488,9 +493,9 @@ class StatusArgument(ValueArgument): ...@@ -488,9 +493,9 @@ class StatusArgument(ValueArgument):
new_status = new_status.upper() new_status = new_status.upper()
if new_status not in self.valid_states: if new_status not in self.valid_states:
raise CLIInvalidArgument( raise CLIInvalidArgument(
'Invalid argument %s' % new_status, details=[ 'Invalid argument %s' % new_status,
'Usage: ' details=['Usage:', '%s=[%s]' % (
'%s=[%s]' % (self.lvalue, '|'.join(self.valid_states))]) self.lvalue, '|'.join(self.valid_states))])
self._value = new_status self._value = new_status
...@@ -621,7 +626,7 @@ class ArgumentParseManager(object): ...@@ -621,7 +626,7 @@ class ArgumentParseManager(object):
next = cur + lt_all - lt_pn next = cur + lt_all - lt_pn
ret += prefix ret += prefix
ret += ('{:<%s}' % (lt_all - lt_pn)).format(arg.help[cur:next]) ret += ('{:<%s}' % (lt_all - lt_pn)).format(arg.help[cur:next])
cur, finish = next, '\n%s' % tab2 cur = next
return ret + '\n' return ret + '\n'
@staticmethod @staticmethod
......
...@@ -35,8 +35,9 @@ from mock import patch, call ...@@ -35,8 +35,9 @@ from mock import patch, call
from unittest import TestCase from unittest import TestCase
from StringIO import StringIO from StringIO import StringIO
from datetime import datetime from datetime import datetime
from tempfile import NamedTemporaryFile
from kamaki.cli import argument, errors from kamaki.cli import argument, errors, CLIError
from kamaki.cli.config import Config from kamaki.cli.config import Config
...@@ -109,11 +110,20 @@ class ConfigArgument(TestCase): ...@@ -109,11 +110,20 @@ class ConfigArgument(TestCase):
def test_value(self): def test_value(self):
c = argument._config_arg c = argument._config_arg
self.assertEqual(c.value, None) self.assertEqual(c.value, None)
exp = '/some/random/path'
c.value = exp wrong_path = '/some/random/path'
self.assertTrue(isinstance(c.value, Config)) raises_error = False
self.assertEqual(c.file_path, exp) try:
self.assertEqual(c.value.path, exp) c.value = wrong_path
except CLIError:
raises_error = True
self.assertTrue(raises_error)
with NamedTemporaryFile() as f:
c.value = f.name
self.assertTrue(isinstance(c.value, Config))
self.assertEqual(c.file_path, f.name)
self.assertEqual(c.value.path, f.name)
def test_get(self): def test_get(self):
c = argument._config_arg c = argument._config_arg
...@@ -337,8 +347,7 @@ class KeyValueArgument(TestCase): ...@@ -337,8 +347,7 @@ class KeyValueArgument(TestCase):
( (
('k1=v1 v2', 'k3=', 'k 4=v4'), ('k1=v1 v2', 'k3=', 'k 4=v4'),
{'k1': 'v1 v2', 'k3': '', 'k 4': 'v4'}), {'k1': 'v1 v2', 'k3': '', 'k 4': 'v4'}),
(('k=v1', 'k=v2', 'k=v3'), {'k': 'v3'}) (('k=v1', 'k=v2', 'k=v3'), {'k': 'v3'})):
):
kva.value = kvpairs kva.value = kvpairs
old.update(exp) old.update(exp)
assert_dicts_are_equal(self, kva.value, old) assert_dicts_are_equal(self, kva.value, old)
......
...@@ -76,8 +76,8 @@ def fall_back(func): ...@@ -76,8 +76,8 @@ def fall_back(func):
try: try:
inp = func(self, inp) inp = func(self, inp)
except Exception as e: except Exception as e:
log.warning('WARNING: Error while running %s: %s' % (func, e)) log.warning('Error while running %s: %s' % (func, e))
log.warning('\tWARNING: Kamaki will use original data to go on') log.warning('Kamaki will use original data to go on')
finally: finally:
return inp return inp
return wrap return wrap
......
...@@ -1643,7 +1643,6 @@ class container_create(_PithosAccount): ...@@ -1643,7 +1643,6 @@ class container_create(_PithosAccount):
metadata=self['meta'], metadata=self['meta'],
success=(201, )) success=(201, ))
except ClientError as ce: except ClientError as ce:
print 'WHAAAA?'
if ce.status in (202, ): if ce.status in (202, ):
raise CLIError( raise CLIError(
'Container %s alread exists' % self.container, details=[ 'Container %s alread exists' % self.container, details=[
......
...@@ -39,7 +39,7 @@ from collections import defaultdict ...@@ -39,7 +39,7 @@ from collections import defaultdict
from ConfigParser import RawConfigParser, NoOptionError, NoSectionError, Error from ConfigParser import RawConfigParser, NoOptionError, NoSectionError, Error
from re import match from re import match
from kamaki.cli.errors import CLISyntaxError from kamaki.cli.errors import CLISyntaxError, CLIError
from kamaki import __version__ from kamaki import __version__
try: try:
...@@ -138,6 +138,19 @@ class Config(RawConfigParser): ...@@ -138,6 +138,19 @@ class Config(RawConfigParser):
def __init__(self, path=None, with_defaults=True): def __init__(self, path=None, with_defaults=True):
RawConfigParser.__init__(self, dict_type=OrderedDict) RawConfigParser.__init__(self, dict_type=OrderedDict)
self.path = path or os.environ.get(CONFIG_ENV, CONFIG_PATH) self.path = path or os.environ.get(CONFIG_ENV, CONFIG_PATH)
# Check if self.path is accessible
abspath = os.path.abspath(self.path)
if not os.path.exists(self.path):
log.warning('Config file %s does not exist' % abspath)
elif os.access(self.path, os.R_OK):
if not os.access(self.path, os.W_OK):
log.warning('Config file %s is not writable' % abspath)
else:
raise CLIError(
'Config file %s is inaccessible' % abspath,
importance=3, details=['No read permissions for this file'])
self._overrides = defaultdict(dict) self._overrides = defaultdict(dict)
if with_defaults: if with_defaults:
self._load_defaults() self._load_defaults()
...@@ -289,7 +302,12 @@ class Config(RawConfigParser): ...@@ -289,7 +302,12 @@ class Config(RawConfigParser):
""" """
:returns: (float) version of the config file or 0.9 if unrecognized :returns: (float) version of the config file or 0.9 if unrecognized
""" """
from kamaki.cli import logger
# Ignore logs from "checker" logger
logger.deactivate(__name__)
checker = Config(self.path, with_defaults=False) checker = Config(self.path, with_defaults=False)
logger.activate(__name__)
sections = checker.sections() sections = checker.sections()
# log.debug('Config file heuristic 1: old global section ?') # log.debug('Config file heuristic 1: old global section ?')
if 'global' in sections: if 'global' in sections:
...@@ -425,6 +443,10 @@ class Config(RawConfigParser): ...@@ -425,6 +443,10 @@ class Config(RawConfigParser):
f.write(HEADER.lstrip()) f.write(HEADER.lstrip())
f.flush() f.flush()
RawConfigParser.write(self, f) RawConfigParser.write(self, f)
except IOError as ioe:
raise CLIError(
'Cannot write to config file %s' % os.path.abspath(self.path),
importance=3, details=[type(ioe), ioe, ])
finally: finally:
if CLOUD_PREFIX not in self.sections(): if CLOUD_PREFIX not in self.sections():
self.add_section(CLOUD_PREFIX) self.add_section(CLOUD_PREFIX)
......
...@@ -234,7 +234,7 @@ class Config(TestCase): ...@@ -234,7 +234,7 @@ class Config(TestCase):
_cnf = Config(path=f.name) _cnf = Config(path=f.name)
self.assertEqual( self.assertEqual(
sorted(['global.%s = %s' % sample for sample in extras]), sorted(['global.%s = %s' % sample for sample in extras]),
sorted(_cnf.rescue_old_file())) sorted(_cnf.rescue_old_file()))
def test_guess_version(self): def test_guess_version(self):
from kamaki.cli.config import Config from kamaki.cli.config import Config
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment