diff --git a/doc/source/template_guide/hot_spec.rst b/doc/source/template_guide/hot_spec.rst index 80925f92ea..d01864d38e 100644 --- a/doc/source/template_guide/hot_spec.rst +++ b/doc/source/template_guide/hot_spec.rst @@ -437,6 +437,7 @@ default value defined as nested elements. constraints: immutable: + tags: param name The name of the parameter. @@ -478,6 +479,11 @@ immutable set to ``true`` and the parameter value is changed. This attribute is optional and defaults to ``false``. +tags + A list of strings to specify the category of a parameter. This value is + used to categorize a parameter so that users can group the parameters. + This attribute is optional. + The table below describes all currently supported types with examples: +----------------------+-------------------------------+------------------+ diff --git a/heat/engine/api.py b/heat/engine/api.py index c19b4f9f53..8cf958ff5e 100644 --- a/heat/engine/api.py +++ b/heat/engine/api.py @@ -524,6 +524,9 @@ def format_validate_parameter(param): if param.user_value: res[rpc_api.PARAM_VALUE] = param.user_value + if param.tags(): + res[rpc_api.PARAM_TAG] = param.tags() + _build_parameter_constraints(res, param) return res diff --git a/heat/engine/hot/parameters.py b/heat/engine/hot/parameters.py index 1438288924..309c5e6b75 100644 --- a/heat/engine/hot/parameters.py +++ b/heat/engine/hot/parameters.py @@ -79,6 +79,22 @@ class HOTParamSchema(parameters.Schema): raise exception.InvalidSchemaError( message=_("No constraint expressed")) + @classmethod + def _constraints(cls, param_name, schema_dict): + constraints = schema_dict.get(cls.CONSTRAINTS) + if constraints is None: + return + + if not isinstance(constraints, list): + raise exception.InvalidSchemaError( + message=_("Invalid parameter constraints for parameter " + "%s, expected a list") % param_name) + + for constraint in constraints: + cls._check_dict(constraint, PARAM_CONSTRAINTS, + 'parameter constraints') + yield cls._constraint_from_def(constraint) + @classmethod def from_dict(cls, param_name, schema_dict): """Return a Parameter Schema object from a legacy schema dictionary. @@ -89,27 +105,12 @@ class HOTParamSchema(parameters.Schema): """ cls._validate_dict(param_name, schema_dict) - def constraints(): - constraints = schema_dict.get(cls.CONSTRAINTS) - if constraints is None: - return - - if not isinstance(constraints, list): - raise exception.InvalidSchemaError( - message=_("Invalid parameter constraints for parameter " - "%s, expected a list") % param_name) - - for constraint in constraints: - cls._check_dict(constraint, PARAM_CONSTRAINTS, - 'parameter constraints') - yield cls._constraint_from_def(constraint) - # make update_allowed true by default on TemplateResources # as the template should deal with this. return cls(schema_dict[cls.TYPE], description=schema_dict.get(HOTParamSchema.DESCRIPTION), default=schema_dict.get(HOTParamSchema.DEFAULT), - constraints=list(constraints()), + constraints=list(cls._constraints(param_name, schema_dict)), hidden=schema_dict.get(HOTParamSchema.HIDDEN, False), label=schema_dict.get(HOTParamSchema.LABEL), immutable=schema_dict.get(HOTParamSchema.IMMUTABLE, False)) @@ -131,6 +132,35 @@ class HOTParamSchema20170224(HOTParamSchema): constraint) +class HOTParamSchema20180302(HOTParamSchema20170224): + + KEYS_20180302 = (TAGS,) = ('tags',) + KEYS = HOTParamSchema20170224.KEYS + KEYS_20180302 + + PARAMETER_KEYS = KEYS + + @classmethod + def from_dict(cls, param_name, schema_dict): + """Return a Parameter Schema object from a legacy schema dictionary. + + :param param_name: name of the parameter owning the schema; used + for more verbose logging + :type param_name: str + """ + cls._validate_dict(param_name, schema_dict) + + # make update_allowed true by default on TemplateResources + # as the template should deal with this. + return cls(schema_dict[cls.TYPE], + description=schema_dict.get(HOTParamSchema.DESCRIPTION), + default=schema_dict.get(HOTParamSchema.DEFAULT), + constraints=list(cls._constraints(param_name, schema_dict)), + hidden=schema_dict.get(HOTParamSchema.HIDDEN, False), + label=schema_dict.get(HOTParamSchema.LABEL), + immutable=schema_dict.get(HOTParamSchema.IMMUTABLE, False), + tags=schema_dict.get(HOTParamSchema20180302.TAGS)) + + class HOTParameters(parameters.Parameters): PSEUDO_PARAMETERS = ( PARAM_STACK_ID, PARAM_STACK_NAME, PARAM_REGION, PARAM_PROJECT_ID diff --git a/heat/engine/hot/template.py b/heat/engine/hot/template.py index 0a18f4f8bd..4a54615c24 100644 --- a/heat/engine/hot/template.py +++ b/heat/engine/hot/template.py @@ -695,3 +695,5 @@ class HOTemplate20180302(HOTemplate20170901): 'yaql': hot_funcs.Yaql, 'contains': hot_funcs.Contains } + + param_schema_class = parameters.HOTParamSchema20180302 diff --git a/heat/engine/parameters.py b/heat/engine/parameters.py index 8a4d19a403..2222c6034e 100644 --- a/heat/engine/parameters.py +++ b/heat/engine/parameters.py @@ -42,10 +42,10 @@ class Schema(constr.Schema): KEYS = ( TYPE, DESCRIPTION, DEFAULT, SCHEMA, CONSTRAINTS, HIDDEN, - LABEL, IMMUTABLE + LABEL, IMMUTABLE, TAGS, ) = ( 'Type', 'Description', 'Default', 'Schema', 'Constraints', 'NoEcho', - 'Label', 'Immutable' + 'Label', 'Immutable', 'Tags', ) PARAMETER_KEYS = PARAMETER_KEYS @@ -59,7 +59,8 @@ class Schema(constr.Schema): ) def __init__(self, data_type, description=None, default=None, schema=None, - constraints=None, hidden=False, label=None, immutable=False): + constraints=None, hidden=False, label=None, immutable=False, + tags=None): super(Schema, self).__init__(data_type=data_type, description=description, default=default, @@ -69,6 +70,7 @@ class Schema(constr.Schema): label=label, immutable=immutable) self.hidden = hidden + self.tags = tags # Schema class validates default value for lists assuming list type. For # comma delimited list string supported in parameters Schema class, the @@ -129,6 +131,11 @@ class Schema(constr.Schema): message=_("Missing parameter type for parameter: %s") % param_name) + if not isinstance(schema_dict.get(cls.TAGS, []), list): + raise exception.InvalidSchemaError( + message=_("Tags property should be a list for parameter: %s") % + param_name) + @classmethod def from_dict(cls, param_name, schema_dict): """Return a Parameter Schema object from a legacy schema dictionary. @@ -276,6 +283,10 @@ class Parameter(object): """Return the label or param name.""" return self.schema.label or self.name + def tags(self): + """Return the tags associated with the parameter""" + return self.schema.tags or [] + def has_default(self): """Return whether the parameter has a default value.""" return (self.schema.default is not None or diff --git a/heat/rpc/api.py b/heat/rpc/api.py index bffe783d73..90cdc81776 100644 --- a/heat/rpc/api.py +++ b/heat/rpc/api.py @@ -193,13 +193,13 @@ VALIDATE_PARAM_KEYS = ( PARAM_MIN_LENGTH, PARAM_MAX_VALUE, PARAM_MIN_VALUE, PARAM_STEP, PARAM_OFFSET, PARAM_DESCRIPTION, PARAM_CONSTRAINT_DESCRIPTION, PARAM_LABEL, - PARAM_CUSTOM_CONSTRAINT, PARAM_VALUE + PARAM_CUSTOM_CONSTRAINT, PARAM_VALUE, PARAM_TAG ) = ( 'Type', 'Default', 'NoEcho', 'AllowedValues', 'AllowedPattern', 'MaxLength', 'MinLength', 'MaxValue', 'MinValue', 'Step', 'Offset', 'Description', 'ConstraintDescription', 'Label', - 'CustomConstraint', 'Value' + 'CustomConstraint', 'Value', 'Tags' ) VALIDATE_PARAM_TYPES = ( diff --git a/heat/tests/test_validate.py b/heat/tests/test_validate.py index 128e093f32..2db962150a 100644 --- a/heat/tests/test_validate.py +++ b/heat/tests/test_validate.py @@ -924,6 +924,46 @@ resources: external_id: foobar ''' +test_template_hot_parameter_tags_older = ''' +heat_template_version: 2013-05-23 + +parameters: + KeyName: + type: string + description: Name of an existing key pair to use for the instance + label: Nova KeyPair Name + tags: + - feature1 + - feature2 + +''' + +test_template_hot_parameter_tags_pass = ''' +heat_template_version: 2018-03-02 + +parameters: + KeyName: + type: string + description: Name of an existing key pair to use for the instance + label: Nova KeyPair Name + tags: + - feature1 + - feature2 + +''' + +test_template_hot_parameter_tags_fail = ''' +heat_template_version: 2018-03-02 + +parameters: + KeyName: + type: string + description: Name of an existing key pair to use for the instance + label: Nova KeyPair Name + tags: feature + +''' + class ValidateTest(common.HeatTestCase): def setUp(self): @@ -1859,3 +1899,39 @@ parameter_groups: self.ctx, t, {}) self.assertEqual(dependencies.CircularDependencyException, exc.exc_info[0]) + + def test_validate_hot_parameter_tags_older(self): + t = template_format.parse(test_template_hot_parameter_tags_older) + + exc = self.assertRaises(dispatcher.ExpectedException, + self.engine.validate_template, + self.ctx, t, {}) + self.assertEqual(exception.InvalidSchemaError, + exc.exc_info[0]) + + def test_validate_hot_parameter_tags_pass(self): + t = template_format.parse(test_template_hot_parameter_tags_pass) + + res = dict(self.engine.validate_template(self.ctx, t, {})) + parameters = res['Parameters'] + + expected = {'KeyName': { + 'Description': 'Name of an existing key pair to use for the ' + 'instance', + 'NoEcho': 'false', + 'Label': 'Nova KeyPair Name', + 'Type': 'String', + 'Tags': [ + 'feature1', + 'feature2' + ]}} + self.assertEqual(expected, parameters) + + def test_validate_hot_parameter_tags_fail(self): + t = template_format.parse(test_template_hot_parameter_tags_fail) + + exc = self.assertRaises(dispatcher.ExpectedException, + self.engine.validate_template, + self.ctx, t, {}) + self.assertEqual(exception.InvalidSchemaError, + exc.exc_info[0]) diff --git a/releasenotes/notes/parameter-tags-148ef065616f92fc.yaml b/releasenotes/notes/parameter-tags-148ef065616f92fc.yaml new file mode 100644 index 0000000000..87f67ba3c5 --- /dev/null +++ b/releasenotes/notes/parameter-tags-148ef065616f92fc.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Added a new schema property tags, to parameters, to categorize + parameters based on features.