diff --git a/oslo_policy/generator.py b/oslo_policy/generator.py index 4e1489cf..c6ecd5b2 100644 --- a/oslo_policy/generator.py +++ b/oslo_policy/generator.py @@ -14,8 +14,10 @@ import logging import sys import textwrap import warnings +import yaml from oslo_config import cfg +from oslo_serialization import jsonutils import stevedore from oslo_policy import policy @@ -45,6 +47,12 @@ ENFORCER_OPTS = [ 'which to look for a policy.Enforcer.'), ] +UPGRADE_OPTS = [ + cfg.StrOpt('policy', + required=True, + help='Path to the policy file which need to be updated.') +] + def get_policies_dict(namespaces): """Find the options available via the given namespaces. @@ -330,6 +338,48 @@ def generate_policy(args=None): _generate_policy(conf.namespace, conf.output_file) +def _upgrade_policies(policies, default_policies): + old_policies_keys = list(policies.keys()) + for section in sorted(default_policies.keys()): + rule_defaults = default_policies[section] + for rule_default in rule_defaults: + if (rule_default.deprecated_rule and + rule_default.deprecated_rule.name in old_policies_keys): + policies[rule_default.name] = policies.pop( + rule_default.deprecated_rule.name) + LOG.info('The name of policy %(old_name)s has been upgraded to' + '%(new_name)', + {'old_name': rule_default.deprecated_rule.name, + 'new_name': rule_default.name}) + + +def upgrade_policy(args=None): + logging.basicConfig(level=logging.WARN) + conf = cfg.ConfigOpts() + conf.register_cli_opts(GENERATOR_OPTS + RULE_OPTS + UPGRADE_OPTS) + conf.register_opts(GENERATOR_OPTS + RULE_OPTS + UPGRADE_OPTS) + conf(args) + with open(conf.policy, 'r') as input_data: + policies = policy.parse_file_contents(input_data.read()) + default_policies = get_policies_dict(conf.namespace) + + _upgrade_policies(policies, default_policies) + + if conf.output_file: + if conf.format == 'yaml': + yaml.safe_dump(policies, open(conf.output_file, 'w'), + default_flow_style=False) + elif conf.format == 'json': + jsonutils.dump(policies, open(conf.output_file, 'w'), + indent=4) + else: + if conf.format == 'yaml': + sys.stdout.write(yaml.safe_dump(policies, + default_flow_style=False)) + elif conf.format == 'json': + sys.stdout.write(jsonutils.dumps(policies, indent=4)) + + def list_redundant(args=None): logging.basicConfig(level=logging.WARN) conf = cfg.ConfigOpts() diff --git a/oslo_policy/tests/test_generator.py b/oslo_policy/tests/test_generator.py index 06d9de9c..98355ffd 100644 --- a/oslo_policy/tests/test_generator.py +++ b/oslo_policy/tests/test_generator.py @@ -16,10 +16,12 @@ import mock from oslo_config import cfg import stevedore import testtools +import yaml from oslo_policy import generator from oslo_policy import policy from oslo_policy.tests import base +from oslo_serialization import jsonutils OPTS = {'base_rules': [policy.RuleDefault('admin', 'is_admin:True', @@ -572,3 +574,104 @@ class ListRedundantTestCase(base.PolicyBaseTestCase): opt1 = matches[1] self.assertEqual('"owner"', opt1[0]) self.assertEqual('"project_id:%(project_id)s"', opt1[1]) + + +class UpgradePolicyTestCase(base.PolicyBaseTestCase): + def setUp(self): + super(UpgradePolicyTestCase, self).setUp() + policy_json_contents = jsonutils.dumps({ + "deprecated_name": "rule:admin" + }) + self.create_config_file('policy.json', policy_json_contents) + deprecated_policy = policy.DeprecatedRule( + name='deprecated_name', + check_str='rule:admin' + ) + self.new_policy = policy.DocumentedRuleDefault( + name='new_policy_name', + check_str='rule:admin', + description='test_policy', + operations=[{'path': '/test', 'method': 'GET'}], + deprecated_rule=deprecated_policy, + deprecated_reason='test', + deprecated_since='Stein' + ) + self.extensions = [] + ext = stevedore.extension.Extension(name='test_upgrade', + entry_point=None, + plugin=None, + obj=[self.new_policy]) + self.extensions.append(ext) + + def test_upgrade_policy_json_file(self): + test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( + extensions=self.extensions, namespace='test_upgrade') + with mock.patch('stevedore.named.NamedExtensionManager', + return_value=test_mgr): + testargs = ['olsopolicy-policy-upgrade', + '--policy', + self.get_config_file_fullname('policy.json'), + '--namespace', 'test_upgrade', + '--output-file', + self.get_config_file_fullname('new_policy.json'), + '--format', 'json'] + with mock.patch('sys.argv', testargs): + generator.upgrade_policy() + new_file = self.get_config_file_fullname('new_policy.json') + new_policy = jsonutils.loads(open(new_file, 'r').read()) + self.assertIsNotNone(new_policy.get('new_policy_name')) + self.assertIsNone(new_policy.get('deprecated_name')) + + def test_upgrade_policy_yaml_file(self): + test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( + extensions=self.extensions, namespace='test_upgrade') + with mock.patch('stevedore.named.NamedExtensionManager', + return_value=test_mgr): + testargs = ['olsopolicy-policy-upgrade', + '--policy', + self.get_config_file_fullname('policy.json'), + '--namespace', 'test_upgrade', + '--output-file', + self.get_config_file_fullname('new_policy.yaml'), + '--format', 'yaml'] + with mock.patch('sys.argv', testargs): + generator.upgrade_policy() + new_file = self.get_config_file_fullname('new_policy.yaml') + new_policy = yaml.safe_load(open(new_file, 'r')) + self.assertIsNotNone(new_policy.get('new_policy_name')) + self.assertIsNone(new_policy.get('deprecated_name')) + + def test_upgrade_policy_json_stdout(self): + test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( + extensions=self.extensions, namespace='test_upgrade') + stdout = self._capture_stdout() + with mock.patch('stevedore.named.NamedExtensionManager', + return_value=test_mgr): + testargs = ['olsopolicy-policy-upgrade', + '--policy', + self.get_config_file_fullname('policy.json'), + '--namespace', 'test_upgrade', + '--format', 'json'] + with mock.patch('sys.argv', testargs): + generator.upgrade_policy() + expected = '''{ + "new_policy_name": "rule:admin" +}''' + self.assertEqual(expected, stdout.getvalue()) + + def test_upgrade_policy_yaml_stdout(self): + test_mgr = stevedore.named.NamedExtensionManager.make_test_instance( + extensions=self.extensions, namespace='test_upgrade') + stdout = self._capture_stdout() + with mock.patch('stevedore.named.NamedExtensionManager', + return_value=test_mgr): + testargs = ['olsopolicy-policy-upgrade', + '--policy', + self.get_config_file_fullname('policy.json'), + '--namespace', 'test_upgrade', + '--format', 'yaml'] + with mock.patch('sys.argv', testargs): + generator.upgrade_policy() + expected = '''new_policy_name: rule:admin +''' + self.assertEqual(expected, stdout.getvalue()) diff --git a/releasenotes/notes/add-policy-upgrade-command-a65bc4f760e5d8b1.yaml b/releasenotes/notes/add-policy-upgrade-command-a65bc4f760e5d8b1.yaml new file mode 100644 index 00000000..433ee088 --- /dev/null +++ b/releasenotes/notes/add-policy-upgrade-command-a65bc4f760e5d8b1.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``oslopolicy-policy-upgrade`` command to help operators upgrade their + self-defined policy file to new release format. It will upgrade the + deprected policy name with the new name. diff --git a/setup.cfg b/setup.cfg index 256faee3..c6c7bdb2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,6 +38,7 @@ console_scripts = oslopolicy-sample-generator = oslo_policy.generator:generate_sample oslopolicy-policy-generator = oslo_policy.generator:generate_policy oslopolicy-list-redundant = oslo_policy.generator:list_redundant + oslopolicy-policy-upgrade = oslo_policy.generator:upgrade_policy oslo.policy.rule_checks = http = oslo_policy._external:HttpCheck