Пример #1
0
    def validate_entry_point(self, config_module, path_to_plugin):
        """Validates a plugin's entry point. Returns True or throws a PluginValidationError

        An entry point is considered valid if the config has an entry with key PLUGIN_ENTRY
        and the value is a path to either a file or the name of a runnable Python module.

        :param config_module: The previously loaded configuration for the plugin
        :param path_to_plugin: The path to the root of the plugin
        """
        if config_module is None:
            raise PluginValidationError(
                "Configuration is None. This is not allowed.")

        if path_to_plugin is None:
            raise PluginValidationError(
                "Path to Plugin is None. This is not allowed.")

        if not hasattr(config_module, self.ENTRY_POINT_KEY):
            raise PluginValidationError(
                "No %s defined in the plugin configuration." %
                self.ENTRY_POINT_KEY)

        entry_point = getattr(config_module, self.ENTRY_POINT_KEY)

        if isfile(join(path_to_plugin, entry_point)):
            return
        elif entry_point.startswith("-m "):
            pkg_path = join(path_to_plugin, entry_point[3:])
            if (isdir(pkg_path) and isfile(join(pkg_path, "__init__.py"))
                    and isfile(join(pkg_path, "__main__.py"))):
                return
        else:
            raise PluginValidationError(
                "The %s must be a Python script or a runnable Python package: %s"
                % (self.ENTRY_POINT_KEY, entry_point))
Пример #2
0
    def validate_plugin_config(self, path_to_plugin):
        """Validates that there is a beer.conf file in the path_to_plugin

        :param path_to_plugin: Path to the plugin
        :return: True if beer.conf exists
        :raises: PluginValidationError if beer.conf is not found
        """
        if path_to_plugin is None:
            raise PluginValidationError("Attempted to validate plugin config, "
                                        "but the plugin_path is None.")

        path_to_config = join(path_to_plugin, self.CONFIG_NAME)

        if not isfile(path_to_config):
            raise PluginValidationError(
                "Could not validate config file. It does not exist.")

        config_module = self._load_plugin_config(path_to_plugin)
        self.validate_required_config_keys(config_module)
        self.logger.debug("Required keys are present.")
        self.validate_entry_point(config_module, path_to_plugin)
        self.logger.debug("Validated Plugin Entry Point successfully.")
        self.validate_instances_and_args(config_module)
        self.logger.debug(
            "Validated plugin instances & arguments successfully.")
        self.validate_plugin_environment(config_module)
        self.logger.debug("Validated Plugin Environment successfully.")
        return True
Пример #3
0
    def validate_plugin_environment(self, config_module):
        """Validates ENVIRONMENT if specified.

        ENVIRONMENT must be a dictionary of Strings to Strings. Otherwise it is invalid.

        :param config_module:
        :return: True if valid
        :raises: PluginValidationError if something goes wrong while validating
        """
        if config_module is None:
            self.logger.error("Configuration is None. This is not allowed.")
            raise PluginValidationError(
                "Configuration is None. This is not allowed.")

        if hasattr(config_module, "ENVIRONMENT"):
            env = config_module.ENVIRONMENT
            if not isinstance(env, dict):
                self.logger.error(
                    "Invalid ENVIRONMENT specified: %s. This argument must "
                    "be a dictionary.",
                    env,
                )
                raise PluginValidationError(
                    "Invalid ENVIRONMENT specified: %s. This argument "
                    "must be a dictionary." % env)

            for key, value in env.items():
                if not isinstance(key, str):
                    self.logger.error(
                        "Invalid Key: %s specified for plugin environment. "
                        "This must be a String.",
                        key,
                    )
                    raise PluginValidationError(
                        "Invalid Key: %s specified for plugin "
                        "environment. This must be a String." % key)

                if key.startswith("BG_"):
                    self.logger.error(
                        "Invalid key: %s specified for plugin environment. The 'BG_' prefix is a "
                        "special case for beer-garden only environment variables. You will have to "
                        "pick another name. Sorry for the inconvenience." %
                        key)
                    raise PluginValidationError(
                        "Invalid key: %s specified for plugin environment. The 'BG_' prefix "
                        "is a special case for beer-garden only environment variables. You will "
                        "have to pick another name. Sorry for the inconvenience."
                        % key)

                if not isinstance(value, str):
                    self.logger.error(
                        "Invalid Key: %s specified for plugin environment. This must be a String.",
                        value,
                    )
                    raise PluginValidationError(
                        "Invalid Value: %s specified for plugin environment. This must be a "
                        "String." % value)

        return True
Пример #4
0
    def validate_required_config_keys(self, config_module):
        if config_module is None:
            raise PluginValidationError(
                "Configuration is None. This is not allowed.")

        for key in self.REQUIRED_KEYS:
            if not hasattr(config_module, key):
                raise PluginValidationError(
                    "Required key '%s' is not present. "
                    "This is not allowed." % key)
Пример #5
0
    def validate_plugin_path(self, path_to_plugin):
        """Validates that a plugin path is actually a path and not a single file."""
        if path_to_plugin is None or not isdir(path_to_plugin):
            raise PluginValidationError('Plugin path "%s" is not a directory' %
                                        path_to_plugin)

        return True
Пример #6
0
    def validate_instances_and_args(self, config_module):
        if config_module is None:
            raise PluginValidationError(
                "Configuration is None. This is not allowed.")

        plugin_args = getattr(config_module, self.ARGS_KEY, None)
        instances = getattr(config_module, self.INSTANCES_KEY, None)

        if instances is not None and not isinstance(instances, list):
            raise PluginValidationError(
                "'%s' entry was not None or a list. This is invalid. "
                "Got: %s" % (self.INSTANCES_KEY, instances))

        if plugin_args is None:
            return True
        elif isinstance(plugin_args, list):
            return self.validate_individual_plugin_arguments(plugin_args)
        elif isinstance(plugin_args, dict):
            for instance_name, instance_args in plugin_args.items():
                if instances is not None and instance_name not in instances:
                    raise PluginValidationError(
                        "'%s' contains key '%s' but that instance is not specified in the '%s'"
                        "entry." %
                        (self.ARGS_KEY, instance_name, self.INSTANCES_KEY))
                self.validate_individual_plugin_arguments(instance_args)

            if instances:
                for instance_name in instances:
                    if instance_name not in plugin_args.keys():
                        raise PluginValidationError(
                            "'%s' contains key '%s' but that instance is not specified in the "
                            "'%s' entry." %
                            (self.INSTANCES_KEY, instance_name, self.ARGS_KEY))

            return True
        else:
            raise PluginValidationError(
                "'%s' entry was not a list or dictionary. This is invalid. "
                "Got: %s" % (self.ARGS_KEY, plugin_args))
Пример #7
0
    def validate_individual_plugin_arguments(self, plugin_args):
        """Validates an individual PLUGIN_ARGS entry"""

        if plugin_args is not None and not isinstance(plugin_args, list):
            self.logger.error(
                "Invalid Plugin Argument Specified. It was not a list or None. "
                "This is not allowed")
            raise PluginValidationError(
                "Invalid Plugin Argument Specified: %s. It was not a "
                "list or None. This is not allowed." % plugin_args)

        if isinstance(plugin_args, list):
            for plugin_arg in plugin_args:
                if not isinstance(plugin_arg, str):
                    self.logger.error(
                        "Invalid plugin argument: %s - this argument must be a string",
                        plugin_arg,
                    )
                    raise PluginValidationError(
                        "Invalid plugin argument: %s - this argument must be a string."
                        % plugin_arg)

        return True
Пример #8
0
class LocalPluginValidatorTest(unittest.TestCase):
    def setUp(self):
        self.validator = LocalPluginValidator()

    @patch(
        "bartender.local_plugins.validator.LocalPluginValidator.validate_plugin_path",
        Mock(),
    )
    @patch(
        "bartender.local_plugins.validator.LocalPluginValidator.validate_plugin_config",
        Mock(),
    )
    def test_validate_plugin_calls(self):
        rv = self.validator.validate_plugin("/path/to/plugin")
        self.assertEqual(self.validator.validate_plugin_path.call_count, 1)
        self.assertEqual(self.validator.validate_plugin_config.call_count, 1)
        self.assertEqual(rv, True)

    @patch(
        "bartender.local_plugins.validator.LocalPluginValidator.validate_plugin_path",
        Mock(side_effect=PluginValidationError("foo")),
    )
    def test_validate_plugin_error(self):
        rv = self.validator.validate_plugin("/path/to/plugin")
        self.assertEqual(rv, False)

    @patch("bartender.local_plugins.validator.isfile", Mock(return_value=True))
    @patch(
        "bartender.local_plugins.validator.LocalPluginValidator.validate_plugin_path",
        Mock(),
    )
    @patch(
        "bartender.local_plugins.validator.LocalPluginValidator.validate_entry_point",
        Mock(),
    )
    @patch(
        "bartender.local_plugins.validator.LocalPluginValidator.validate_instances_and_args",
        Mock(),
    )
    @patch(
        "bartender.local_plugins.validator.LocalPluginValidator.validate_plugin_environment",
        Mock(),
    )
    @patch("bartender.local_plugins.validator.load_source")
    def test_validate_plugin_load_plugin_config(self, mock_load):
        mock_load.return_value = {}
        self.validator.validate_plugin("/path/to/plugin")
        mock_load.assert_called_with(
            "BGPLUGINCONFIG", "/path/to/plugin/%s" % self.validator.CONFIG_NAME
        )

    @patch("bartender.local_plugins.validator.isfile", Mock(return_value=True))
    @patch(
        "bartender.local_plugins.validator.LocalPluginValidator.validate_plugin_path",
        Mock(),
    )
    @patch(
        "bartender.local_plugins.validator.LocalPluginValidator.validate_entry_point",
        Mock(),
    )
    @patch(
        "bartender.local_plugins.validator.LocalPluginValidator.validate_instances_and_args",
        Mock(),
    )
    @patch(
        "bartender.local_plugins.validator.LocalPluginValidator.validate_plugin_environment",
        Mock(),
    )
    @patch("bartender.local_plugins.validator.load_source")
    def test_validate_remove_plugin_config_from_sys_modules(self, mock_load):
        def side_effect(module_name, value):
            sys.modules[module_name] = value

        mock_load.side_effect = side_effect
        self.validator.validate_plugin("/path/to/plugin")
        self.assertNotIn("BGPLUGINCONFIG", sys.modules)

    def test_validate_plugin_path_bad(self):
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_plugin_path,
            "/path/to/non-existant/foo",
        )

    def test_validate_plugin_path_none(self):
        self.assertRaises(
            PluginValidationError, self.validator.validate_plugin_path, None
        )

    def test_validate_plugin_path_good(self):
        current_directory = os.path.dirname(os.path.abspath(__file__))
        self.assertTrue(self.validator.validate_plugin_path(current_directory))

    def test_validate_plugin_config_none(self):
        self.assertRaises(
            PluginValidationError, self.validator.validate_plugin_config, None
        )

    @patch("bartender.local_plugins.validator.isfile")
    def test_validate_plugin_config_not_a_file(self, isfile_mock):
        isfile_mock.return_value = False
        self.assertRaises(
            PluginValidationError, self.validator.validate_plugin_config, "not_a_file"
        )
        isfile_mock.assert_called_with("not_a_file/%s" % self.validator.CONFIG_NAME)

    def test_validate_entry_point_none_config_module(self):
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_entry_point,
            None,
            "/path/to/plugin",
        )

    def test_validate_entry_point_none_path_to_plugin(self):
        self.assertRaises(
            PluginValidationError, self.validator.validate_entry_point, {}, None
        )

    def test_validate_entry_point_no_entry_point_key(self):
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_entry_point,
            Mock(spec=[]),
            "/path/to/plugin",
        )

    def test_validate_entry_point_bad_entry_point(self):
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_entry_point,
            Mock(spec=[self.validator.ENTRY_POINT_KEY], PLUGIN_ENTRY="not_a_file"),
            "/path/to/plugin",
        )

    @patch("bartender.local_plugins.validator.isfile", Mock(return_value=True))
    def test_validate_entry_point_good_file(self):
        self.validator.validate_entry_point(
            Mock(PLUGIN_ENTRY="is_totally_a_file"), "/path/to/plugin"
        )

    @patch("bartender.local_plugins.validator.isdir", Mock(return_value=True))
    @patch("bartender.local_plugins.validator.isfile")
    def test_validate_entry_point_good_package(self, isfile_mock):
        def is_special_file(name):
            return "__init__" in name or "__main__" in name

        isfile_mock.side_effect = is_special_file

        self.validator.validate_entry_point(
            Mock(PLUGIN_ENTRY="-m plugin"), "/path/to/plugin"
        )

    def test_validate_individual_plugin_arguments_not_none_or_list(self):
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_individual_plugin_arguments,
            "notalistornone",
        )

    def test_validate_individual_plugin_arguments_none(self):
        self.assertTrue(self.validator.validate_individual_plugin_arguments(None))

    def test_validate_individual_plugin_arguments_bad_list(self):
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_individual_plugin_arguments,
            [{"foo": "bar"}],
        )

    def test_validate_individual_plugin_arguments_good_list(self):
        self.assertTrue(self.validator.validate_individual_plugin_arguments(["good"]))

    def test_validate_plugin_environment_none_config(self):
        self.assertRaises(
            PluginValidationError, self.validator.validate_plugin_environment, None
        )

    def test_validate_plugin_environment_no_environment(self):
        self.assertTrue(self.validator.validate_plugin_environment(Mock(spec=[])))

    def test_validate_plugin_environment_bad_environment(self):
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_plugin_environment,
            Mock(ENVIRONMENT="notadict"),
        )

    def test_validate_plugin_environment_bad_key(self):
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_plugin_environment,
            Mock(ENVIRONMENT={1: "int_key_not_allowed"}),
        )

    def test_validate_plugin_environment_with_bg_prefix_key(self):
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_plugin_environment,
            Mock(ENVIRONMENT={"BG_foo": "that_key_is_not_allowed"}),
        )

    def test_validate_plugin_environment_bad_value(self):
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_plugin_environment,
            Mock(ENVIRONMENT={"foos": ["foo1", "foo2"]}),
        )

    def test_validate_plugin_environment_good(self):
        self.assertTrue(
            self.validator.validate_plugin_environment(Mock(ENVIRONMENT={"foo": "bar"}))
        )

    @patch("bartender.local_plugins.validator.isfile", Mock(return_value=True))
    @patch("bartender.local_plugins.validator.LocalPluginValidator._load_plugin_config")
    def test_validate_plugin_config_missing_required_key(self, load_config_mock):
        config_module = Mock(
            VERSION="0.0.1",
            PLUGIN_ENTRY="/path/to/entry.py",
            spec=["VERSION", "PLUGIN_ENTRY"],
        )
        load_config_mock.return_value = config_module
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_plugin_config,
            "/path/to/beer.conf",
        )

    @patch("bartender.local_plugins.validator.isfile", Mock(return_value=True))
    @patch("bartender.local_plugins.validator.LocalPluginValidator._load_plugin_config")
    def test_validate_plugin_config_good(self, load_config_mock):
        config_module = Mock(
            NAME="name",
            VERSION="0.0.1",
            PLUGIN_ENTRY="/path/to/entry.py",
            spec=["NAME", "VERSION", "PLUGIN_ENTRY"],
        )
        load_config_mock.return_value = config_module
        self.assertTrue(self.validator.validate_plugin_config("/path/to/beer.conf"))

    def test_validate_instances_and_args_none_config_module(self):
        self.assertRaises(
            PluginValidationError, self.validator.validate_instances_and_args, None
        )

    def test_validate_instances_and_args_both_none(self):
        config_module = Mock(spec=[])
        self.assertTrue(
            PluginValidationError,
            self.validator.validate_instances_and_args(config_module),
        )

    def test_validate_instances_and_args_invalid_instances(self):
        config_module = Mock(INSTANCES="THIS_IS_WRONG", PLUGIN_ARGS=None)
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_instances_and_args,
            config_module,
        )

    def test_validate_instances_and_args_good_args(self):
        config_module = Mock(INSTANCES=None, PLUGIN_ARGS=["foo", "bar"])
        self.assertTrue(self.validator.validate_instances_and_args(config_module))

    def test_validate_instances_and_args_invalid_args(self):
        config_module = Mock(INSTANCES=None, PLUGIN_ARGS="THIS IS WRONG")
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_instances_and_args,
            config_module,
        )

    def test_validate_instances_and_args_invalid_plugin_arg_key(self):
        config_module = Mock(INSTANCES=["foo"], PLUGIN_ARGS={"bar": ["arg1"]})
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_instances_and_args,
            config_module,
        )

    def test_validate_instances_and_args_missing_plugin_arg_key(self):
        config_module = Mock(INSTANCES=["foo"], PLUGIN_ARGS={})
        self.assertRaises(
            PluginValidationError,
            self.validator.validate_instances_and_args,
            config_module,
        )

    def test_validate_instance_and_args_both_provided_good(self):
        config_module = Mock(INSTANCES=["foo"], PLUGIN_ARGS={"foo": ["arg1"]})
        self.assertTrue(self.validator.validate_instances_and_args(config_module))