diff --git a/contrib/lint.py b/contrib/lint.py new file mode 100644 index 0000000000..02c022b194 --- /dev/null +++ b/contrib/lint.py @@ -0,0 +1,104 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +""" +libcloud linter + +This script checks the libcloud codebase for registered drivers that don't +comply to libcloud API guidelines. +""" + +import inspect +import os + +import libcloud + +import libcloud.compute.providers +from libcloud.compute.base import NodeDriver + +import libcloud.dns.providers +from libcloud.dns.base import DNSDriver + +import libcloud.loadbalancer.providers +from libcloud.loadbalancer.base import Driver + +import libcloud.storage.providers +from libcloud.storage.base import StorageDriver + + +modules = [ + (libcloud.compute.providers, NodeDriver), + (libcloud.dns.providers, DNSDriver), + (libcloud.loadbalancer.providers, Driver), + (libcloud.storage.providers, StorageDriver), + ] + + +warnings = set() + +def warning(obj, warning): + source_file = os.path.relpath(inspect.getsourcefile(obj), os.path.dirname(libcloud.__file__)) + source_line = inspect.getsourcelines(obj)[1] + if (source_file, source_line, warning) in warnings: + # When the error is actually caused by a mixin or base class we can get dupes... + return + warnings.add((source_file, source_line, warning)) + print source_file, source_line, warning + +for providers, base, in modules: + core_api = {} + for name, value in inspect.getmembers(base, inspect.ismethod): + if name.startswith("_"): + continue + + if name.startswith("ex_"): + warning(value, "Core driver shouldn't haveex_ methods") + continue + + args = core_api[name] = inspect.getargspec(value) + + for arg in args.args: + if arg.startswith("ex_"): + warning(value, "Core driver method shouldnt have ex_ arguments") + + + for driver_id in providers.DRIVERS.keys(): + driver = providers.get_driver(driver_id) + + for name, value in inspect.getmembers(driver, inspect.ismethod): + if name.startswith("_"): + continue + + if not name.startswith("ex_") and not name in core_api: + warning(value, "'%s' should be prefixed with ex_ or be private as it is not a core API" % name) + continue + + + # Only validate arguments of core API's + if name.startswith("ex_"): + continue + + argspec = inspect.getargspec(value) + + core_args = set(core_api[name].args) + driver_args = set(argspec.args) + + for missing in core_args - driver_args: + warning(value, "Core API function should support arg '%s' but doesn't" % missing) + + for extra in driver_args - core_args: + if not extra.startswith("ex_"): + warning(value, "Core API function shouldn't take arg '%s'. Should it be prefixed with ex_?" % extra) +