From 792564e4019b91e59055b2812c110993fb5d818b Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Thu, 2 Apr 2015 12:34:42 -0700 Subject: [PATCH] Bring over the 'safeyaml' from bzr This will likely also be very useful to use since it ensures we safely use/parse yaml. Change-Id: Ibc0f7dee46d2f199e2d65d9bc6444bfa72383704 --- cloudinit/logging.py | 82 ++++++++++++++++++++++++++++++++ cloudinit/safeyaml.py | 40 ++++++++++++++++ cloudinit/shell.py | 9 ++-- cloudinit/tests/test_safeyaml.py | 29 +++++++++++ cloudinit/util.py | 26 +++++----- 5 files changed, 169 insertions(+), 17 deletions(-) create mode 100644 cloudinit/logging.py create mode 100644 cloudinit/safeyaml.py create mode 100644 cloudinit/tests/test_safeyaml.py diff --git a/cloudinit/logging.py b/cloudinit/logging.py new file mode 100644 index 00000000..f3baa31e --- /dev/null +++ b/cloudinit/logging.py @@ -0,0 +1,82 @@ +# Copyright 2015 Canonical Ltd. +# This file is part of cloud-init. See LICENCE file for license information. +# +# vi: ts=4 expandtab + +from __future__ import absolute_import + +import logging +import sys + +_BASE = __name__.split(".", 1)[0] + +# Add a BLATHER level, this matches the multiprocessing utils.py module (and +# kazoo and others) that declares a similar level, this level is for +# information that is even lower level than regular DEBUG and gives out so +# much runtime information that it is only useful by low-level/certain users... +BLATHER = 5 + +# Copy over *select* attributes to make it easy to use this module. +CRITICAL = logging.CRITICAL +DEBUG = logging.DEBUG +ERROR = logging.ERROR +FATAL = logging.FATAL +INFO = logging.INFO +NOTSET = logging.NOTSET +WARN = logging.WARN +WARNING = logging.WARNING + + +class _BlatherLoggerAdapter(logging.LoggerAdapter): + + def blather(self, msg, *args, **kwargs): + """Delegate a blather call to the underlying logger.""" + self.log(BLATHER, msg, *args, **kwargs) + + def warn(self, msg, *args, **kwargs): + """Delegate a warning call to the underlying logger.""" + self.warning(msg, *args, **kwargs) + + +# TODO(harlowja): we should remove when we no longer have to support 2.6... +if sys.version_info[0:2] == (2, 6): + + class _FixedBlatherLoggerAdapter(_BlatherLoggerAdapter): + """Ensures isEnabledFor() exists on adapters that are created.""" + + def isEnabledFor(self, level): + return self.logger.isEnabledFor(level) + + _BlatherLoggerAdapter = _FixedBlatherLoggerAdapter + + # Taken from python2.7 (same in python3.4)... + class _NullHandler(logging.Handler): + """This handler does nothing. + + It's intended to be used to avoid the + "No handlers could be found for logger XXX" one-off warning. This is + important for library code, which may contain code to log events. If a + user of the library does not configure logging, the one-off warning + might be produced; to avoid this, the library developer simply needs + to instantiate a _NullHandler and add it to the top-level logger of the + library module or package. + """ + + def handle(self, record): + """Stub.""" + + def emit(self, record): + """Stub.""" + + def createLock(self): + self.lock = None + +else: + _NullHandler = logging.NullHandler + + +def getLogger(name=_BASE, extra=None): + logger = logging.getLogger(name) + if not logger.handlers: + logger.addHandler(_NullHandler()) + return _BlatherLoggerAdapter(logger, extra=extra) diff --git a/cloudinit/safeyaml.py b/cloudinit/safeyaml.py new file mode 100644 index 00000000..a8f9d1ba --- /dev/null +++ b/cloudinit/safeyaml.py @@ -0,0 +1,40 @@ +# Copyright 2015 Canonical Ltd. +# This file is part of cloud-init. See LICENCE file for license information. +# +# vi: ts=4 expandtab + +import yaml as _yaml + +from cloudinit import util + + +YAMLError = _yaml.YAMLError + + +def load(path): + """Load yaml string from a path and return the data represented. + + Exception will be raised if types other than the following are found: + dict, int, float, string, list, unicode + """ + return loads(util.load_file(path)) + + +def loads(blob): + """Load yaml string and return the data represented. + + Exception will be raised if types other than the following are found: + dict, int, float, string, list, unicode + """ + return _yaml.safe_load(blob) + + +def dumps(obj): + """Dumps an object back into a yaml string.""" + formatted = _yaml.safe_dump(obj, + line_break="\n", + indent=4, + explicit_start=True, + explicit_end=True, + default_flow_style=False) + return formatted diff --git a/cloudinit/shell.py b/cloudinit/shell.py index 24b64e9e..5b347da4 100644 --- a/cloudinit/shell.py +++ b/cloudinit/shell.py @@ -1,9 +1,12 @@ # Copyright 2015 Canonical Ltd. -# Copyright 2015 Cloudbase Solutions Srl # This file is part of cloud-init. See LICENCE file for license information. +# +# vi: ts=4 expandtab def main(): - print("Hello World\n") + print("Run cloudinit run!") -# vi: ts=4 expandtab + +if __name__ == '__main__': + main() diff --git a/cloudinit/tests/test_safeyaml.py b/cloudinit/tests/test_safeyaml.py new file mode 100644 index 00000000..8b064765 --- /dev/null +++ b/cloudinit/tests/test_safeyaml.py @@ -0,0 +1,29 @@ +# Copyright 2015 Canonical Ltd. +# This file is part of cloud-init. See LICENCE file for license information. +# +# vi: ts=4 expandtab + +from cloudinit import safeyaml as yaml +from cloudinit import test + + +class TestSafeYaml(test.TestCase): + def test_simple(self): + blob = '\nk1: one\nk2: two' + expected = {'k1': "one", 'k2': "two"} + self.assertEqual(yaml.loads(blob), expected) + + def test_bogus_raises_exception(self): + badyaml = "1\n 2:" + self.assertRaises(yaml.YAMLError, yaml.loads, badyaml) + + def test_unsafe_types(self): + # should not load complex types + unsafe_yaml = "!!python/object:__builtin__.object {}" + self.assertRaises(yaml.YAMLError, yaml.loads, unsafe_yaml) + + def test_python_unicode_not_allowed(self): + # python/unicode is not allowed + # in the past this type was allowed, but not now, so explicit test. + blob = "{k1: !!python/unicode 'my unicode', k2: my string}" + self.assertRaises(yaml.YAMLError, yaml.loads, blob) diff --git a/cloudinit/util.py b/cloudinit/util.py index 5986ed7e..368ebeb6 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -1,19 +1,17 @@ -# Copyright (C) 2015 Canonical Ltd. -# Copyright (C) 2015 Cloudbase Solutions Srl +# Copyright 2015 Canonical Ltd. +# This file is part of cloud-init. See LICENCE file for license information. # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# vi: ts=4 expandtab -__all__ = ('abstractclassmethod', ) +from cloudinit import logging + +LOG = logging.getLogger(__name__) + + +def load_file(path, encoding='utf8'): + LOG.blather("Loading file from path '%s' (%s)", path, encoding) + with open(path, 'rb') as fh: + return fh.read().decode(encoding) class abstractclassmethod(classmethod):