diff --git a/expandybird/expansion/expansion.py b/expandybird/expansion/expansion.py index f786b844b..162ebbcd0 100755 --- a/expandybird/expansion/expansion.py +++ b/expandybird/expansion/expansion.py @@ -140,12 +140,13 @@ def _ProcessResource(resource, imports, env, validate_schema=False): # A template resource, which contains sub-resources. expanded_template = ExpandTemplate(resource, imports, env, validate_schema) - if expanded_template['resources'] is not None: + if expanded_template['resources']: _ValidateUniqueNames(expanded_template['resources'], resource['type']) # Process all sub-resources of this template. for resource_to_process in expanded_template['resources']: - processed_resource = _ProcessResource(resource_to_process, imports, env) + processed_resource = _ProcessResource(resource_to_process, imports, env, + validate_schema) # Append all sub-resources to the config resources, and the resulting # layout of sub-resources. @@ -228,7 +229,7 @@ def ExpandTemplate(resource, imports, env, validate_schema=False): resource['properties'] = schema_validation.Validate( properties, schema, source_file, imports) except schema_validation.ValidationErrors as e: - raise ExpansionError(resource, e.message) + raise ExpansionError(resource['name'], e.message) if source_file.endswith('jinja'): expanded_template = ExpandJinja( diff --git a/expandybird/expansion/expansion_test.py b/expandybird/expansion/expansion_test.py new file mode 100644 index 000000000..264d5e272 --- /dev/null +++ b/expandybird/expansion/expansion_test.py @@ -0,0 +1,504 @@ +###################################################################### +# Copyright 2015 The Kubernetes Authors All rights reserved. +# 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. +###################################################################### +"""Basic unit tests for template expansion library.""" + +import expansion +import os +import unittest +import yaml + + +def GetFilePath(): + """Find our source and data files.""" + return os.path.dirname(os.path.abspath(__file__)) + + +def ReadTestFile(filename): + """Returns contents of a file from the test/ directory.""" + + full_path = GetFilePath() + '/../test/templates/' + filename + test_file = open(full_path, 'r') + return test_file.read() + + +def GetTestBasePath(filename): + """Returns the base path of a file from the testdata/ directory.""" + + full_path = GetFilePath() + '/../test/templates/' + filename + return os.path.dirname(full_path) + + +class ExpansionTest(unittest.TestCase): + """Tests basic functionality of the template expansion library.""" + + EMPTY_RESPONSE = 'config:\n resources: []\nlayout:\n resources: []\n' + + def testEmptyExpansion(self): + template = '' + expanded_template = expansion.Expand( + template) + + self.assertEqual('', expanded_template) + + def testNoResourcesList(self): + template = 'imports: [ test.import ]' + expanded_template = expansion.Expand( + template) + + self.assertEqual(self.EMPTY_RESPONSE, expanded_template) + + def testResourcesListEmpty(self): + template = 'resources:' + expanded_template = expansion.Expand( + template) + + self.assertEqual(self.EMPTY_RESPONSE, expanded_template) + + def testSimpleNoExpansionTemplate(self): + template = ReadTestFile('simple.yaml') + + expanded_template = expansion.Expand( + template) + + result_file = ReadTestFile('simple_result.yaml') + self.assertEquals(result_file, expanded_template) + + def testJinjaExpansion(self): + template = ReadTestFile('jinja_template.yaml') + + imports = {} + imports['jinja_template.jinja'] = ReadTestFile('jinja_template.jinja') + + expanded_template = expansion.Expand( + template, imports) + + result_file = ReadTestFile('jinja_template_result.yaml') + + self.assertEquals(result_file, expanded_template) + + def testJinjaWithNoParamsExpansion(self): + template = ReadTestFile('jinja_noparams.yaml') + + imports = {} + imports['jinja_noparams.jinja'] = ReadTestFile('jinja_noparams.jinja') + + expanded_template = expansion.Expand( + template, imports) + + result_file = ReadTestFile('jinja_noparams_result.yaml') + + self.assertEquals(result_file, expanded_template) + + def testPythonWithNoParamsExpansion(self): + template = ReadTestFile('python_noparams.yaml') + + imports = {} + imports['python_noparams.py'] = ReadTestFile('python_noparams.py') + + expanded_template = expansion.Expand( + template, imports) + + result_file = ReadTestFile('python_noparams_result.yaml') + + self.assertEquals(result_file, expanded_template) + + def testPythonExpansion(self): + template = ReadTestFile('python_template.yaml') + + imports = {} + imports['python_template.py'] = ReadTestFile('python_template.py') + + expanded_template = expansion.Expand( + template, imports) + + result_file = ReadTestFile('python_template_result.yaml') + + self.assertEquals(result_file, expanded_template) + + def testPythonAndJinjaExpansion(self): + template = ReadTestFile('python_and_jinja_template.yaml') + + imports = {} + imports['python_and_jinja_template.py'] = ReadTestFile( + 'python_and_jinja_template.py') + + imports['python_and_jinja_template.jinja'] = ReadTestFile( + 'python_and_jinja_template.jinja') + + expanded_template = expansion.Expand( + template, imports) + + result_file = ReadTestFile('python_and_jinja_template_result.yaml') + + self.assertEquals(result_file, expanded_template) + + def testNoImportErrors(self): + template = 'resources: \n- type: something.jinja\n name: something' + expansion.Expand(template, {}) + + def testInvalidConfig(self): + template = ReadTestFile('invalid_config.yaml') + + try: + expansion.Expand( + template) + self.fail('Expansion should fail') + except expansion.ExpansionError as e: + self.assertNotIn(os.path.basename(expansion.__name__), e.message, + 'Do not leak internals') + + def testJinjaWithImport(self): + template = ReadTestFile('jinja_template_with_import.yaml') + + imports = {} + imports['jinja_template_with_import.jinja'] = ReadTestFile( + 'jinja_template_with_import.jinja') + imports['helpers/common.jinja'] = ReadTestFile( + 'helpers/common.jinja') + + yaml_template = yaml.safe_load(template) + + expanded_template = expansion.Expand( + str(yaml_template), imports) + + result_file = ReadTestFile('jinja_template_with_import_result.yaml') + + self.assertEquals(result_file, expanded_template) + + def testJinjaWithInlinedFile(self): + template = ReadTestFile('jinja_template_with_inlinedfile.yaml') + + imports = {} + imports['jinja_template_with_inlinedfile.jinja'] = ReadTestFile( + 'jinja_template_with_inlinedfile.jinja') + imports['helpers/common.jinja'] = ReadTestFile( + 'helpers/common.jinja') + + imports['description_text.txt'] = ReadTestFile('description_text.txt') + + yaml_template = yaml.safe_load(template) + + expanded_template = expansion.Expand( + str(yaml_template), imports) + + result_file = ReadTestFile('jinja_template_with_inlinedfile_result.yaml') + + self.assertEquals(result_file, expanded_template) + + def testPythonWithImport(self): + template = ReadTestFile('python_template_with_import.yaml') + + imports = {} + imports['python_template_with_import.py'] = ReadTestFile( + 'python_template_with_import.py') + + imports['helpers/common.py'] = ReadTestFile('helpers/common.py') + imports['helpers/extra/common2.py'] = ReadTestFile( + 'helpers/extra/common2.py') + imports['helpers/extra'] = ReadTestFile('helpers/extra/__init__.py') + + yaml_template = yaml.safe_load(template) + + expanded_template = expansion.Expand( + str(yaml_template), imports) + + result_file = ReadTestFile('python_template_with_import_result.yaml') + + self.assertEquals(result_file, expanded_template) + + def testPythonWithInlinedFile(self): + template = ReadTestFile('python_template_with_inlinedfile.yaml') + + imports = {} + imports['python_template_with_inlinedfile.py'] = ReadTestFile( + 'python_template_with_inlinedfile.py') + + imports['helpers/common.py'] = ReadTestFile('helpers/common.py') + imports['helpers/extra/common2.py'] = ReadTestFile( + 'helpers/extra/common2.py') + + imports['description_text.txt'] = ReadTestFile('description_text.txt') + + yaml_template = yaml.safe_load(template) + + expanded_template = expansion.Expand( + str(yaml_template), imports) + + result_file = ReadTestFile( + 'python_template_with_inlinedfile_result.yaml') + + self.assertEquals(result_file, expanded_template) + + def testPythonWithEnvironment(self): + template = ReadTestFile('python_template_with_env.yaml') + + imports = {} + imports['python_template_with_env.py'] = ReadTestFile( + 'python_template_with_env.py') + + env = {'project': 'my-project'} + + expanded_template = expansion.Expand( + template, imports, env) + + result_file = ReadTestFile('python_template_with_env_result.yaml') + self.assertEquals(result_file, expanded_template) + + def testJinjaWithEnvironment(self): + template = ReadTestFile('jinja_template_with_env.yaml') + + imports = {} + imports['jinja_template_with_env.jinja'] = ReadTestFile( + 'jinja_template_with_env.jinja') + + env = {'project': 'test-project', 'deployment': 'test-deployment'} + + expanded_template = expansion.Expand( + template, imports, env) + + result_file = ReadTestFile('jinja_template_with_env_result.yaml') + + self.assertEquals(result_file, expanded_template) + + def testMissingNameErrors(self): + template = 'resources: \n- type: something.jinja\n' + + try: + expansion.Expand(template, {}) + self.fail('Expansion should fail') + except expansion.ExpansionError as e: + self.assertTrue('not have a name' in e.message) + + def testDuplicateNamesErrors(self): + template = ReadTestFile('duplicate_names.yaml') + + try: + expansion.Expand(template, {}) + self.fail('Expansion should fail') + except expansion.ExpansionError as e: + self.assertTrue(("Resource name 'my_instance' is not unique" + " in config.") in e.message) + + def testDuplicateNamesInSubtemplates(self): + template = ReadTestFile('duplicate_names_in_subtemplates.yaml') + + imports = {} + imports['duplicate_names_in_subtemplates.jinja'] = ReadTestFile( + 'duplicate_names_in_subtemplates.jinja') + + try: + expansion.Expand( + template, imports) + self.fail('Expansion should fail') + except expansion.ExpansionError as e: + self.assertTrue('not unique in duplicate_names_in_subtemplates.jinja' + in e.message) + + def testDuplicateNamesMixedLevel(self): + template = ReadTestFile('duplicate_names_mixed_level.yaml') + + imports = {} + imports['duplicate_names_B.jinja'] = ReadTestFile( + 'duplicate_names_B.jinja') + imports['duplicate_names_C.jinja'] = ReadTestFile( + 'duplicate_names_C.jinja') + + expanded_template = expansion.Expand( + template, imports) + + result_file = ReadTestFile('duplicate_names_mixed_level_result.yaml') + + self.assertEquals(result_file, expanded_template) + + def testDuplicateNamesParentChild(self): + template = ReadTestFile('duplicate_names_parent_child.yaml') + + imports = {} + imports['duplicate_names_B.jinja'] = ReadTestFile( + 'duplicate_names_B.jinja') + + expanded_template = expansion.Expand( + template, imports) + + result_file = ReadTestFile('duplicate_names_parent_child_result.yaml') + + self.assertEquals(result_file, expanded_template) + # Note, this template will fail in the frontend for duplicate resource names + + def testTemplateReturnsEmpty(self): + template = ReadTestFile('no_resources.yaml') + + imports = {} + imports['no_resources.py'] = ReadTestFile( + 'no_resources.py') + + try: + expansion.Expand( + template, imports) + self.fail('Expansion should fail') + except expansion.ExpansionError as e: + self.assertIn('Template did not return a \'resources:\' field.', + e.message) + self.assertIn('no_resources.py', e.message) + + def testJinjaDefaultsSchema(self): + # Loop 1000 times to make sure we don't rely on dictionary ordering. + for unused_x in range(0, 1000): + template = ReadTestFile('jinja_defaults.yaml') + + imports = {} + imports['jinja_defaults.jinja'] = ReadTestFile( + 'jinja_defaults.jinja') + imports['jinja_defaults.jinja.schema'] = ReadTestFile( + 'jinja_defaults.jinja.schema') + + expanded_template = expansion.Expand( + template, imports, + validate_schema=True) + + result_file = ReadTestFile('jinja_defaults_result.yaml') + + self.assertEquals(result_file, expanded_template) + + def testPythonDefaultsOverrideSchema(self): + template = ReadTestFile('python_schema.yaml') + + imports = {} + imports['python_schema.py'] = ReadTestFile('python_schema.py') + imports['python_schema.py.schema'] = ReadTestFile('python_schema.py.schema') + + env = {'project': 'my-project'} + + expanded_template = expansion.Expand( + template, imports, env=env, + validate_schema=True) + + result_file = ReadTestFile('python_schema_result.yaml') + + self.assertEquals(result_file, expanded_template) + + def testJinjaMissingRequiredPropertySchema(self): + template = ReadTestFile('jinja_missing_required.yaml') + + imports = {} + imports['jinja_missing_required.jinja'] = ReadTestFile( + 'jinja_missing_required.jinja') + imports['jinja_missing_required.jinja.schema'] = ReadTestFile( + 'jinja_missing_required.jinja.schema') + + try: + expansion.Expand( + template, imports, + validate_schema=True) + self.fail('Expansion error expected') + except expansion.ExpansionError as e: + self.assertIn('Invalid properties', e.message) + self.assertIn("'important' is a required property", e.message) + self.assertIn('jinja_missing_required_resource_name', e.message) + + def testJinjaErrorFileMessage(self): + template = ReadTestFile('jinja_unresolved.yaml') + + imports = {} + imports['jinja_unresolved.jinja'] = ReadTestFile('jinja_unresolved.jinja') + + try: + expansion.Expand( + template, imports, + validate_schema=False) + self.fail('Expansion error expected') + except expansion.ExpansionError as e: + self.assertIn('jinja_unresolved.jinja', e.message) + + def testJinjaMultipleErrorsSchema(self): + template = ReadTestFile('jinja_multiple_errors.yaml') + + imports = {} + imports['jinja_multiple_errors.jinja'] = ReadTestFile( + 'jinja_multiple_errors.jinja') + imports['jinja_multiple_errors.jinja.schema'] = ReadTestFile( + 'jinja_multiple_errors.jinja.schema') + + try: + expansion.Expand( + template, imports, + validate_schema=True) + self.fail('Expansion error expected') + except expansion.ExpansionError as e: + self.assertIn('Invalid properties', e.message) + self.assertIn("'a string' is not of type 'integer'", e.message) + self.assertIn("'d' is not one of ['a', 'b', 'c']", e.message) + self.assertIn("'longer than 10 chars' is too long", e.message) + self.assertIn("{'multipleOf': 2} is not allowed for 6", e.message) + + def testPythonBadSchema(self): + template = ReadTestFile('python_bad_schema.yaml') + + imports = {} + imports['python_bad_schema.py'] = ReadTestFile( + 'python_bad_schema.py') + imports['python_bad_schema.py.schema'] = ReadTestFile( + 'python_bad_schema.py.schema') + + try: + expansion.Expand( + template, imports, + validate_schema=True) + self.fail('Expansion error expected') + except expansion.ExpansionError as e: + self.assertIn('Invalid schema', e.message) + self.assertIn("'int' is not valid under any of the given schemas", + e.message) + self.assertIn("'maximum' is a dependency of u'exclusiveMaximum'", + e.message) + self.assertIn("10 is not of type u'boolean'", e.message) + self.assertIn("'not a list' is not of type u'array'", e.message) + + def testNoProperties(self): + template = ReadTestFile('no_properties.yaml') + + imports = {} + imports['no_properties.py'] = ReadTestFile( + 'no_properties.py') + + expanded_template = expansion.Expand( + template, imports, + validate_schema=True) + + result_file = ReadTestFile('no_properties_result.yaml') + + self.assertEquals(result_file, expanded_template) + + def testNestedTemplateSchema(self): + template = ReadTestFile('use_helper.yaml') + + imports = {} + imports['use_helper.jinja'] = ReadTestFile( + 'use_helper.jinja') + imports['use_helper.jinja.schema'] = ReadTestFile( + 'use_helper.jinja.schema') + imports['helper.jinja'] = ReadTestFile( + 'helper.jinja') + imports['helper.jinja.schema'] = ReadTestFile( + 'helper.jinja.schema') + + expanded_template = expansion.Expand( + template, imports, + validate_schema=True) + + result_file = ReadTestFile('use_helper_result.yaml') + + self.assertEquals(result_file, expanded_template) + +if __name__ == '__main__': + unittest.main() diff --git a/expandybird/expansion/schema_validation.py b/expandybird/expansion/schema_validation.py index e361d1e17..f82b94ff8 100644 --- a/expandybird/expansion/schema_validation.py +++ b/expandybird/expansion/schema_validation.py @@ -26,13 +26,12 @@ PROPERTIES = "properties" # This validator will set default values in properties. # This does not return a complete set of errors; use only for setting defaults. # Pass this object a schema to get a validator for that schema. -DEFAULT_VALIDATOR = schema_validation_utils.OnlyValidateProperties( - schema_validation_utils.ExtendWithDefault(jsonschema.Draft4Validator)) +DEFAULT_SETTER = schema_validation_utils.ExtendWithDefault( + jsonschema.Draft4Validator) -# This is a regular validator, use after using the DEFAULT_VALIDATOR +# This is a regular validator, use after using the DEFAULT_SETTER # Pass this object a schema to get a validator for that schema. -VALIDATOR = schema_validation_utils.OnlyValidateProperties( - jsonschema.Draft4Validator) +VALIDATOR = jsonschema.Draft4Validator # This is a validator using the default Draft4 metaschema, # use it to validate user schemas. @@ -61,29 +60,61 @@ IMPORT_SCHEMA_VALIDATOR = jsonschema.Draft4Validator( yaml.safe_load(IMPORT_SCHEMA)) +def _ValidateSchema(schema, validating_imports, schema_name, template_name): + """Validate that the passed in schema file is correctly formatted. + + Args: + schema: contents of the schema file + validating_imports: boolean, if we should validate the 'imports' + section of the schema + schema_name: name of the schema file to validate + template_name: name of the template whose properties are being validated + + Raises: + ValidationErrors: A list of ValidationError errors that occured when + validating the schema file + """ + schema_errors = [] + + # Validate the syntax of the optional "imports:" section of the schema + if validating_imports: + schema_errors.extend(IMPORT_SCHEMA_VALIDATOR.iter_errors(schema)) + + # Validate the syntax of the jsonSchema section of the schema + try: + schema_errors.extend(SCHEMA_VALIDATOR.iter_errors(schema)) + except jsonschema.RefResolutionError as e: + # Calls to iter_errors could throw a RefResolution exception + raise ValidationErrors(schema_name, template_name, + [e], is_schema_error=True) + + if schema_errors: + raise ValidationErrors(schema_name, template_name, + schema_errors, is_schema_error=True) + + def Validate(properties, schema_name, template_name, imports): """Given a set of properties, validates it against the given schema. Args: properties: dict, the properties to be validated schema_name: name of the schema file to validate - template_name: name of the template whose's properties are being validated - imports: map from string to string, the map of imported files names - and contents + template_name: name of the template whose properties are being validated + imports: the map of imported files names to file contents Returns: Dict containing the validated properties, with defaults filled in Raises: ValidationErrors: A list of ValidationError errors that occurred when - validating the properties and schema + validating the properties and schema, + or if the schema file was not found """ if schema_name not in imports: raise ValidationErrors(schema_name, template_name, - ["Could not find schema file '" - + schema_name + "'."]) - else: - raw_schema = imports[schema_name] + ["Could not find schema file '%s'." % schema_name]) + + raw_schema = imports[schema_name] if properties is None: properties = {} @@ -91,33 +122,14 @@ def Validate(properties, schema_name, template_name, imports): schema = yaml.safe_load(raw_schema) # If the schema is empty, do nothing. - if schema is None: + if not schema: return properties - schema_errors = [] validating_imports = IMPORTS in schema and schema[IMPORTS] - # Validate the syntax of the optional "imports:" section of the schema - if validating_imports: - schema_errors.extend(list(IMPORT_SCHEMA_VALIDATOR.iter_errors(schema))) - - # Validate the syntax of the optional "properties:" section of the schema - if PROPERTIES in schema and schema[PROPERTIES]: - try: - schema_errors.extend( - list(SCHEMA_VALIDATOR.iter_errors(schema[PROPERTIES]))) - except jsonschema.RefResolutionError as e: - # Calls to iter_errors could throw a RefResolution exception - raise ValidationErrors(schema_name, template_name, - list(e), is_schema_error=True) - - if schema_errors: - raise ValidationErrors(schema_name, template_name, - schema_errors, is_schema_error=True) + # If this doesn't raise any exceptions, we can assume we have a valid schema + _ValidateSchema(schema, validating_imports, schema_name, template_name) - ###### - # Assume we have a valid schema - ###### errors = [] # Validate that all files specified as "imports:" were included @@ -131,24 +143,25 @@ def Validate(properties, schema_name, template_name, imports): import_name = import_object["path"] if import_name not in imports: - errors.append(("File '" + import_name + "' requested in schema '" - + schema_name + "' but not included with imports.")) + errors.append(("File '%s' requested in schema '%s' " + "but not included with imports." + % (import_name, schema_name))) try: - # This code block uses DEFAULT_VALIDATOR and VALIDATOR for two very + # This code block uses DEFAULT_SETTER and VALIDATOR for two very # different purposes. - # DEFAULT_VALIDATOR is based on JSONSchema 4, but uses modified validators: + # DEFAULT_SETTER is based on JSONSchema 4, but uses modified validators: # - The 'required' validator does nothing # - The 'properties' validator sets default values on user properties # With these changes, the validator does not report errors correctly. # # So, we do error reporting in two steps: - # 1) Use DEFAULT_VALIDATOR to set default values in the user's properties + # 1) Use DEFAULT_SETTER to set default values in the user's properties # 2) Use the unmodified VALIDATOR to report all of the errors # Calling iter_errors mutates properties in place, adding default values. # You must call list()! This is a generator, not a function! - list(DEFAULT_VALIDATOR(schema).iter_errors(properties)) + list(DEFAULT_SETTER(schema).iter_errors(properties)) # Now that we have default values, validate the properties errors.extend(list(VALIDATOR(schema).iter_errors(properties))) @@ -158,7 +171,7 @@ def Validate(properties, schema_name, template_name, imports): except jsonschema.RefResolutionError as e: # Calls to iter_errors could throw a RefResolution exception raise ValidationErrors(schema_name, template_name, - list(e), is_schema_error=True) + [e], is_schema_error=True) except TypeError as e: raise ValidationErrors( schema_name, template_name, @@ -191,7 +204,7 @@ class ValidationErrors(Exception): message = "Invalid properties for '%s':\n" % self.template_name for error in self.errors: - if type(error) is jsonschema.exceptions.ValidationError: + if isinstance(error, jsonschema.exceptions.ValidationError): error_message = error.message location = list(error.path) if location and len(location): diff --git a/expandybird/expansion/schema_validation_test.py b/expandybird/expansion/schema_validation_test.py new file mode 100644 index 000000000..3f8f4b38f --- /dev/null +++ b/expandybird/expansion/schema_validation_test.py @@ -0,0 +1,616 @@ +###################################################################### +# Copyright 2015 The Kubernetes Authors All rights reserved. +# 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. +###################################################################### + +import os +import unittest +import schema_validation +import yaml + +INVALID_PROPERTIES = "Invalid properties for 'template.py'" + + +def GetFilePath(): + """Find our source and data files.""" + return os.path.dirname(os.path.abspath(__file__)) + + +def ReadTestFile(filename): + """Returns contents of a file from the testdata/ directory.""" + + full_path = os.path.join(GetFilePath(), '..', 'test', 'schemas', filename) + return open(full_path, 'r').read() + + +def RawValidate(raw_properties, schema_name, raw_schema): + return ImportsRawValidate(raw_properties, schema_name, + {schema_name: raw_schema}) + + +def ImportsRawValidate(raw_properties, schema_name, import_map): + """Takes raw properties, calls validate and returns yaml properties.""" + properties = yaml.safe_load(raw_properties) + return schema_validation.Validate(properties, schema_name, 'template.py', + import_map) + + +class SchemaValidationTest(unittest.TestCase): + """Tests of the schema portion of the template expansion library.""" + + def testDefaults(self): + schema_name = 'defaults.jinja.schema' + schema = ReadTestFile(schema_name) + empty_properties = '' + expected_properties = """ + alpha: alpha + one: 1 + """ + self.assertEqual(yaml.safe_load(expected_properties), + RawValidate(empty_properties, schema_name, schema)) + + def testNestedDefaults(self): + schema_name = 'nested_defaults.py.schema' + schema = ReadTestFile(schema_name) + properties = """ + zone: us-central1-a + disks: + - name: backup # diskType and sizeGb set by default + - name: cache # sizeGb set by default + diskType: pd-ssd + - name: data # Nothing set by default + diskType: pd-ssd + sizeGb: 150 + - name: swap # diskType set by default + sizeGb: 200 + """ + expected_properties = """ + zone: us-central1-a + disks: + - sizeGb: 100 + diskType: pd-standard + name: backup + - sizeGb: 100 + diskType: pd-ssd + name: cache + - sizeGb: 150 + diskType: pd-ssd + name: data + - sizeGb: 200 + diskType: pd-standard + name: swap + """ + self.assertEqual(yaml.safe_load(expected_properties), + RawValidate(properties, schema_name, schema)) + + def testNestedRefDefaults(self): + schema_name = 'ref_nested_defaults.py.schema' + schema = ReadTestFile(schema_name) + properties = """ + zone: us-central1-a + disks: + - name: backup # diskType and sizeGb set by default + - name: cache # sizeGb set by default + diskType: pd-ssd + - name: data # Nothing set by default + diskType: pd-ssd + sizeGb: 150 + - name: swap # diskType set by default + sizeGb: 200 + """ + expected_properties = """ + zone: us-central1-a + disks: + - sizeGb: 100 + diskType: pd-standard + name: backup + - sizeGb: 100 + diskType: pd-ssd + name: cache + - sizeGb: 150 + diskType: pd-ssd + name: data + - sizeGb: 200 + diskType: pd-standard + name: swap + """ + self.assertEqual(yaml.safe_load(expected_properties), + RawValidate(properties, schema_name, schema)) + + def testInvalidDefault(self): + schema_name = 'invalid_default.jinja.schema' + schema = ReadTestFile(schema_name) + empty_properties = '' + try: + RawValidate(empty_properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn(INVALID_PROPERTIES, e.message) + self.assertIn("'string' is not of type 'integer' at ['number']", + e.message) + + def testRequiredDefault(self): + schema_name = 'required_default.jinja.schema' + schema = ReadTestFile(schema_name) + empty_properties = '' + expected_properties = """ + name: my_name + """ + self.assertEqual(yaml.safe_load(expected_properties), + RawValidate(empty_properties, schema_name, schema)) + + def testRequiredDefaultReference(self): + schema_name = 'req_default_ref.py.schema' + schema = ReadTestFile(schema_name) + empty_properties = '' + + try: + RawValidate(empty_properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn(INVALID_PROPERTIES, e.message) + self.assertIn("'my_name' is not of type 'integer' at ['number']", + e.message) + + def testDefaultReference(self): + schema_name = 'default_ref.jinja.schema' + schema = ReadTestFile(schema_name) + empty_properties = '' + expected_properties = 'number: 1' + + self.assertEqual(yaml.safe_load(expected_properties), + RawValidate(empty_properties, schema_name, schema)) + + def testMissingQuoteInReference(self): + schema_name = 'missing_quote.py.schema' + schema = ReadTestFile(schema_name) + properties = 'number: 1' + + try: + RawValidate(properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(2, len(e.errors)) + self.assertIn("Invalid schema '%s'" % schema_name, e.message) + self.assertIn("type 'NoneType' is not iterable", e.message) + self.assertIn('around your reference', e.message) + + def testRequiredPropertyMissing(self): + schema_name = 'required.jinja.schema' + schema = ReadTestFile(schema_name) + empty_properties = '' + try: + RawValidate(empty_properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn(INVALID_PROPERTIES, e.message) + self.assertIn("'name' is a required property", e.errors[0].message) + + def testRequiredPropertyValid(self): + schema_name = 'required.jinja.schema' + schema = ReadTestFile(schema_name) + properties = """ + name: my-name + """ + self.assertEqual(yaml.safe_load(properties), + RawValidate(properties, schema_name, schema)) + + def testMultipleErrors(self): + schema_name = 'defaults.py.schema' + schema = ReadTestFile(schema_name) + properties = """ + one: not a number + alpha: 12345 + """ + try: + RawValidate(properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(2, len(e.errors)) + self.assertIn(INVALID_PROPERTIES, e.message) + self.assertIn("'not a number' is not of type 'integer' at ['one']", + e.message) + self.assertIn("12345 is not of type 'string' at ['alpha']", e.message) + + def testNumbersValid(self): + schema_name = 'numbers.py.schema' + schema = ReadTestFile(schema_name) + properties = """ + minimum0: 0 + exclusiveMin0: 1 + maximum10: 10 + exclusiveMax10: 9 + even: 20 + odd: 21 + """ + self.assertEquals(yaml.safe_load(properties), + RawValidate(properties, schema_name, schema)) + + def testNumbersInvalid(self): + schema_name = 'numbers.py.schema' + schema = ReadTestFile(schema_name) + properties = """ + minimum0: -1 + exclusiveMin0: 0 + maximum10: 11 + exclusiveMax10: 10 + even: 21 + odd: 20 + """ + try: + RawValidate(properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(6, len(e.errors)) + self.assertIn(INVALID_PROPERTIES, e.message) + self.assertIn("-1 is less than the minimum of 0 at ['minimum0']", + e.message) + self.assertIn(('0 is less than or equal to the minimum of 0' + " at ['exclusiveMin0']"), e.message) + self.assertIn("11 is greater than the maximum of 10 at ['maximum10']", + e.message) + self.assertIn(('10 is greater than or equal to the maximum of 10' + " at ['exclusiveMax10']"), e.message) + self.assertIn("21 is not a multiple of 2 at ['even']", e.message) + self.assertIn("{'multipleOf': 2} is not allowed for 20 at ['odd']", + e.message) + + def testReference(self): + schema_name = 'reference.jinja.schema' + schema = ReadTestFile(schema_name) + properties = """ + odd: 6 + """ + try: + RawValidate(properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn('even', e.message) + self.assertIn('is not allowed for 6', e.message) + + def testBadSchema(self): + schema_name = 'bad.jinja.schema' + schema = ReadTestFile(schema_name) + empty_properties = '' + try: + RawValidate(empty_properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(2, len(e.errors)) + self.assertIn("Invalid schema '%s'" % schema_name, e.message) + self.assertIn("u'minimum' is a dependency of u'exclusiveMinimum'", + e.message) + self.assertIn("0 is not of type u'boolean'", e.message) + + def testInvalidReference(self): + schema_name = 'invalid_reference.py.schema' + schema = ReadTestFile(schema_name) + properties = 'odd: 1' + try: + RawValidate(properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn("Invalid schema '%s'" % schema_name, e.message) + self.assertIn('Unresolvable JSON pointer', e.message) + + def testInvalidReferenceInSchema(self): + schema_name = 'invalid_reference_schema.py.schema' + schema = ReadTestFile(schema_name) + empty_properties = '' + try: + RawValidate(empty_properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn("Invalid schema '%s'" % schema_name, e.message) + self.assertIn('Unresolvable JSON pointer', e.message) + + def testMetadata(self): + schema_name = 'metadata.py.schema' + schema = ReadTestFile(schema_name) + properties = """ + one: 2 + alpha: beta + """ + self.assertEquals(yaml.safe_load(properties), + RawValidate(properties, schema_name, schema)) + + def testInvalidInput(self): + schema_name = 'schema' + schema = """ + info: + title: Invalid Input + properties: invalid + """ + properties = """ + one: 2 + alpha: beta + """ + try: + RawValidate(properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn("Invalid schema '%s'" % schema_name, e.message) + self.assertIn("'invalid' is not of type u'object'", e.message) + + def testPattern(self): + schema_name = 'schema' + schema = r""" + properties: + bad-zone: + pattern: \w+-\w+-\w+ + zone: + pattern: \w+-\w+-\w+ + """ + properties = """ + bad-zone: abc + zone: us-central1-a + """ + try: + RawValidate(properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn('Invalid properties', e.message) + self.assertIn("'abc' does not match", e.message) + self.assertIn('bad-zone', e.message) + + def testUniqueItems(self): + schema_name = 'schema' + schema = """ + properties: + bad-list: + type: array + uniqueItems: true + list: + type: array + uniqueItems: true + """ + properties = """ + bad-list: + - a + - b + - a + list: + - a + - b + - c + """ + try: + RawValidate(properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn('Invalid properties', e.message) + self.assertIn('has non-unique elements', e.message) + self.assertIn('bad-list', e.message) + + def testUniqueItemsOnString(self): + schema_name = 'schema' + schema = """ + properties: + ok-string: + type: string + uniqueItems: true + string: + type: string + uniqueItems: true + """ + properties = """ + ok-string: aaa + string: abc + """ + self.assertEquals(yaml.safe_load(properties), + RawValidate(properties, schema_name, schema)) + + def testRequiredTopLevel(self): + schema_name = 'schema' + schema = """ + info: + title: Invalid Input + required: + - name + """ + properties = """ + one: 2 + alpha: beta + """ + try: + RawValidate(properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn(INVALID_PROPERTIES, e.message) + self.assertIn("'name' is a required property", e.message) + + def testEmptySchemaProperties(self): + schema_name = 'schema' + schema = """ + info: + title: Empty Input + properties: + """ + properties = """ + one: 2 + alpha: beta + """ + try: + RawValidate(properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn("Invalid schema '%s'" % schema_name, e.message) + self.assertIn("None is not of type u'object' at [u'properties']", + e.message) + + def testNoInput(self): + schema = """ + info: + title: No other sections + """ + properties = """ + one: 2 + alpha: beta + """ + self.assertEquals(yaml.safe_load(properties), + RawValidate(properties, 'schema', schema)) + + def testEmptySchema(self): + schema = '' + properties = """ + one: 2 + alpha: beta + """ + self.assertEquals(yaml.safe_load(properties), + RawValidate(properties, 'schema', schema)) + + def testImportPathSchema(self): + schema = """ + imports: + - path: a + - path: path/to/b + name: b + """ + properties = """ + one: 2 + alpha: beta + """ + + import_map = {'schema': schema, + 'a': '', + 'b': ''} + + self.assertEquals(yaml.safe_load(properties), + ImportsRawValidate(properties, 'schema', import_map)) + + def testImportSchemaMissing(self): + schema = '' + empty_properties = '' + + try: + properties = yaml.safe_load(empty_properties) + schema_validation.Validate(properties, 'schema', 'template', + {'wrong_name': schema}) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn("Could not find schema file 'schema'", e.message) + + def testImportsMalformedNotAList(self): + schema_name = 'schema' + schema = """ + imports: not-a-list + """ + empty_properties = '' + + try: + RawValidate(empty_properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn("Invalid schema '%s'" % schema_name, e.message) + self.assertIn("is not of type 'array' at ['imports']", e.message) + + def testImportsMalformedMissingPath(self): + schema_name = 'schema' + schema = """ + imports: + - name: no_path.yaml + """ + empty_properties = '' + + try: + RawValidate(empty_properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn("Invalid schema '%s'" % schema_name, e.message) + self.assertIn("'path' is a required property", e.message) + + def testImportsMalformedNonunique(self): + schema_name = 'schema' + schema = """ + imports: + - path: a.yaml + name: a + - path: a.yaml + name: a + """ + empty_properties = '' + + try: + RawValidate(empty_properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn("Invalid schema '%s'" % schema_name, e.message) + self.assertIn('non-unique elements', e.message) + + def testImportsMalformedAdditionalProperties(self): + schema_name = 'schema' + schema = """ + imports: + - path: a.yaml + gnome: a + """ + empty_properties = '' + + try: + RawValidate(empty_properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(1, len(e.errors)) + self.assertIn("Invalid schema '%s'" % schema_name, e.message) + self.assertIn('Additional properties are not allowed' + " ('gnome' was unexpected)", e.message) + + def testImportAndInputErrors(self): + schema = """ + imports: + - path: file + required: + - name + """ + empty_properties = '' + + try: + RawValidate(empty_properties, 'schema', schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(2, len(e.errors)) + self.assertIn("'file' requested in schema 'schema'", e.message) + self.assertIn("'name' is a required property", e.message) + + def testImportAndInputSchemaErrors(self): + schema_name = 'schema' + schema = """ + imports: not-a-list + required: not-a-list + """ + empty_properties = '' + + try: + RawValidate(empty_properties, schema_name, schema) + self.fail('Validation should fail') + except schema_validation.ValidationErrors as e: + self.assertEqual(2, len(e.errors)) + self.assertIn("Invalid schema '%s'" % schema_name, e.message) + self.assertIn("is not of type 'array' at ['imports']", e.message) + self.assertIn("is not of type u'array' at [u'required']", e.message) + +if __name__ == '__main__': + unittest.main() diff --git a/expandybird/expansion/schema_validation_utils.py b/expandybird/expansion/schema_validation_utils.py index b374824eb..2aac82677 100644 --- a/expandybird/expansion/schema_validation_utils.py +++ b/expandybird/expansion/schema_validation_utils.py @@ -15,35 +15,10 @@ import jsonschema -DEFAULT = "default" -PROPERTIES = "properties" -REF = "$ref" -REQUIRED = "required" - - -def OnlyValidateProperties(validator_class): - """Takes a validator and makes it process only the 'properties' top level. - - Args: - validator_class: A class to add a new validator to - - Returns: - A validator_class that will validate properties against things - under the top level "properties" field - """ - - def PropertiesValidator(unused_validator, inputs, instance, schema): - if inputs is None: - inputs = {} - for error in validator_class(schema).iter_errors(instance, inputs): - yield error - - # This makes sure the only keyword jsonschema will validate is 'properties' - new_validators = ClearValidatorMap(validator_class.VALIDATORS) - new_validators.update({PROPERTIES: PropertiesValidator}) - - return jsonschema.validators.extend( - validator_class, new_validators) +DEFAULT = 'default' +PROPERTIES = 'properties' +REF = '$ref' +REQUIRED = 'required' def ExtendWithDefault(validator_class): @@ -55,33 +30,33 @@ def ExtendWithDefault(validator_class): Returns: A validator_class that will set default values and ignore required fields """ + validate_properties = validator_class.VALIDATORS['properties'] - def SetDefaultsInProperties(validator, properties, instance, unused_schema): - if properties is None: - properties = {} - SetDefaults(validator, properties, instance) + def SetDefaultsInProperties(validator, user_schema, user_properties, + parent_schema): + SetDefaults(validator, user_schema or {}, user_properties, parent_schema, + validate_properties) return jsonschema.validators.extend( validator_class, {PROPERTIES: SetDefaultsInProperties, REQUIRED: IgnoreKeyword}) -def SetDefaults(validator, properties, instance): +def SetDefaults(validator, user_schema, user_properties, parent_schema, + validate_properties): """Populate the default values of properties. Args: - validator: A generator that validates the "properties" keyword - properties: User properties on which to set defaults - instance: Piece of user schema containing "properties" + validator: A generator that validates the "properties" keyword of the schema + user_schema: Schema which might define defaults, might be a nested part of + the entire schema file. + user_properties: User provided values which we are setting defaults on + parent_schema: Schema object that contains the schema being evaluated on + this pass, user_schema. + validate_properties: Validator function, called recursively. """ - if not properties: - return - - for dm_property, subschema in properties.iteritems(): - # If the property already has a value, we don't need it's default - if dm_property in instance: - return + for schema_property, subschema in user_schema.iteritems(): # The ordering of these conditions assumes that '$ref' blocks override # all other schema info, which is what the jsonschema library assumes. @@ -89,17 +64,21 @@ def SetDefaults(validator, properties, instance): # see if that reference defines a 'default' value if REF in subschema: out = ResolveReferencedDefault(validator, subschema[REF]) - instance.setdefault(dm_property, out) + user_properties.setdefault(schema_property, out) # Otherwise, see if the subschema has a 'default' value elif DEFAULT in subschema: - instance.setdefault(dm_property, subschema[DEFAULT]) + user_properties.setdefault(schema_property, subschema[DEFAULT]) + + # Recursively apply defaults. This is a generator, so we must wrap with list() + list(validate_properties(validator, user_schema, + user_properties, parent_schema)) def ResolveReferencedDefault(validator, ref): """Resolves a reference, and returns any default value it defines. Args: - validator: A generator the validates the "$ref" keyword + validator: A generator that validates the "$ref" keyword ref: The target of the "$ref" keyword Returns: @@ -110,14 +89,6 @@ def ResolveReferencedDefault(validator, ref): return resolved[DEFAULT] -def ClearValidatorMap(validators): - """Remaps all JsonSchema validators to make them do nothing.""" - ignore_validators = {} - for keyword in validators: - ignore_validators.update({keyword: IgnoreKeyword}) - return ignore_validators - - def IgnoreKeyword( unused_validator, unused_required, unused_instance, unused_schema): """Validator for JsonSchema that does nothing.""" diff --git a/expandybird/test/schemas/bad.jinja.schema b/expandybird/test/schemas/bad.jinja.schema new file mode 100644 index 000000000..825f8dcf7 --- /dev/null +++ b/expandybird/test/schemas/bad.jinja.schema @@ -0,0 +1,9 @@ +info: + title: Schema with a lots of errors in it + +imports: + +properties: + exclusiveMin: + type: integer + exclusiveMinimum: 0 diff --git a/expandybird/test/schemas/default_ref.jinja.schema b/expandybird/test/schemas/default_ref.jinja.schema new file mode 100644 index 000000000..51f83d2c8 --- /dev/null +++ b/expandybird/test/schemas/default_ref.jinja.schema @@ -0,0 +1,14 @@ +info: + title: Schema with a property that has a referenced default value + +imports: + +properties: + number: + $ref: '#/level/mult' + +level: + mult: + type: integer + multipleOf: 1 + default: 1 diff --git a/expandybird/test/schemas/defaults.jinja.schema b/expandybird/test/schemas/defaults.jinja.schema new file mode 100644 index 000000000..bcb7ee34e --- /dev/null +++ b/expandybird/test/schemas/defaults.jinja.schema @@ -0,0 +1,12 @@ +info: + title: Schema with properties that have default values + +imports: + +properties: + one: + type: integer + default: 1 + alpha: + type: string + default: alpha diff --git a/expandybird/test/schemas/defaults.py.schema b/expandybird/test/schemas/defaults.py.schema new file mode 100644 index 000000000..bcb7ee34e --- /dev/null +++ b/expandybird/test/schemas/defaults.py.schema @@ -0,0 +1,12 @@ +info: + title: Schema with properties that have default values + +imports: + +properties: + one: + type: integer + default: 1 + alpha: + type: string + default: alpha diff --git a/expandybird/test/schemas/invalid_default.jinja.schema b/expandybird/test/schemas/invalid_default.jinja.schema new file mode 100644 index 000000000..e60d11148 --- /dev/null +++ b/expandybird/test/schemas/invalid_default.jinja.schema @@ -0,0 +1,11 @@ +info: + title: Schema with a required integer property that has a default string value + +imports: + +required: + - number +properties: + number: + type: integer + default: string diff --git a/expandybird/test/schemas/invalid_reference.py.schema b/expandybird/test/schemas/invalid_reference.py.schema new file mode 100644 index 000000000..7c3fa3e10 --- /dev/null +++ b/expandybird/test/schemas/invalid_reference.py.schema @@ -0,0 +1,10 @@ +info: + title: Schema with references to something that doesnt exist + +imports: + +properties: + odd: + type: integer + not: + $ref: '#/wheeeeeee' diff --git a/expandybird/test/schemas/invalid_reference_schema.py.schema b/expandybird/test/schemas/invalid_reference_schema.py.schema new file mode 100644 index 000000000..6c824568c --- /dev/null +++ b/expandybird/test/schemas/invalid_reference_schema.py.schema @@ -0,0 +1,8 @@ +info: + title: Schema with references to something that doesnt exist + +imports: + +properties: + odd: + $ref: '#/wheeeeeee' diff --git a/expandybird/test/schemas/metadata.py.schema b/expandybird/test/schemas/metadata.py.schema new file mode 100644 index 000000000..3d6e1e346 --- /dev/null +++ b/expandybird/test/schemas/metadata.py.schema @@ -0,0 +1,20 @@ +info: + title: Schema with properties that have extra metadata + +imports: + +properties: + one: + type: integer + default: 1 + metadata: + gcloud: is great! + compute: is awesome + alpha: + type: string + default: alpha + metadata: + - you + - can + - do + - anything diff --git a/expandybird/test/schemas/missing_quote.py.schema b/expandybird/test/schemas/missing_quote.py.schema new file mode 100644 index 000000000..ddd4b5bfd --- /dev/null +++ b/expandybird/test/schemas/missing_quote.py.schema @@ -0,0 +1,11 @@ +info: + title: Schema with references + +imports: + +properties: + number: + $ref: #/number + +number: + type: integer diff --git a/expandybird/test/schemas/nested_defaults.py.schema b/expandybird/test/schemas/nested_defaults.py.schema new file mode 100644 index 000000000..b5288c91b --- /dev/null +++ b/expandybird/test/schemas/nested_defaults.py.schema @@ -0,0 +1,33 @@ +info: + title: VM with Disks + author: Kubernetes + description: Creates a single vm, then attaches disks to it. + +required: +- zone + +properties: + zone: + type: string + description: GCP zone + default: us-central1-a + disks: + type: array + items: + type: object + required: + - name + properties: + name: + type: string + description: Suffix for this disk + sizeGb: + type: integer + default: 100 + diskType: + type: string + enum: + - pd-standard + - pd-ssd + default: pd-standard + additionalProperties: false diff --git a/expandybird/test/schemas/numbers.py.schema b/expandybird/test/schemas/numbers.py.schema new file mode 100644 index 000000000..eff245182 --- /dev/null +++ b/expandybird/test/schemas/numbers.py.schema @@ -0,0 +1,27 @@ +info: + title: Schema with a lots of number properties and restrictions + +imports: + +properties: + minimum0: + type: integer + minimum: 0 + exclusiveMin0: + type: integer + minimum: 0 + exclusiveMinimum: true + maximum10: + type: integer + maximum: 10 + exclusiveMax10: + type: integer + maximum: 10 + exclusiveMaximum: true + even: + type: integer + multipleOf: 2 + odd: + type: integer + not: + multipleOf: 2 diff --git a/expandybird/test/schemas/ref_nested_defaults.py.schema b/expandybird/test/schemas/ref_nested_defaults.py.schema new file mode 100644 index 000000000..80813b73d --- /dev/null +++ b/expandybird/test/schemas/ref_nested_defaults.py.schema @@ -0,0 +1,36 @@ +info: + title: VM with Disks + author: Kubernetes + description: Creates a single vm, then attaches disks to it. + +required: +- zone + +properties: + zone: + type: string + description: GCP zone + default: us-central1-a + disks: + type: array + items: + $ref: '#/disk' + +disk: + type: object + required: + - name + properties: + name: + type: string + description: Suffix for this disk + sizeGb: + type: integer + default: 100 + diskType: + type: string + enum: + - pd-standard + - pd-ssd + default: pd-standard + additionalProperties: false diff --git a/expandybird/test/schemas/reference.jinja.schema b/expandybird/test/schemas/reference.jinja.schema new file mode 100644 index 000000000..e90251c39 --- /dev/null +++ b/expandybird/test/schemas/reference.jinja.schema @@ -0,0 +1,14 @@ +info: + title: Schema with references + +imports: + +properties: + odd: + type: integer + not: + $ref: '#/even' + + +even: + multipleOf: 2 diff --git a/expandybird/test/schemas/req_default_ref.py.schema b/expandybird/test/schemas/req_default_ref.py.schema new file mode 100644 index 000000000..08b1da3e9 --- /dev/null +++ b/expandybird/test/schemas/req_default_ref.py.schema @@ -0,0 +1,14 @@ +info: + title: Schema with a required property that has a referenced default value + +imports: + +required: + - number +properties: + number: + $ref: '#/default_val' + +default_val: + type: integer + default: my_name diff --git a/expandybird/test/schemas/required.jinja.schema b/expandybird/test/schemas/required.jinja.schema new file mode 100644 index 000000000..94c8e39f8 --- /dev/null +++ b/expandybird/test/schemas/required.jinja.schema @@ -0,0 +1,10 @@ +info: + title: Schema with a required property + +imports: + +required: + - name +properties: + name: + type: string diff --git a/expandybird/test/schemas/required_default.jinja.schema b/expandybird/test/schemas/required_default.jinja.schema new file mode 100644 index 000000000..d739e2c20 --- /dev/null +++ b/expandybird/test/schemas/required_default.jinja.schema @@ -0,0 +1,11 @@ +info: + title: Schema with a required property that has a default value + +imports: + +required: + - name +properties: + name: + type: string + default: my_name diff --git a/expandybird/test/templates/description_text.txt b/expandybird/test/templates/description_text.txt new file mode 100644 index 000000000..33e5dea2e --- /dev/null +++ b/expandybird/test/templates/description_text.txt @@ -0,0 +1 @@ +"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." diff --git a/expandybird/test/templates/duplicate_names.yaml b/expandybird/test/templates/duplicate_names.yaml new file mode 100644 index 000000000..f7386f186 --- /dev/null +++ b/expandybird/test/templates/duplicate_names.yaml @@ -0,0 +1,9 @@ +resources: +- type: compute.v1.instance + name: my_instance + properties: + zone: test-zone-a +- type: compute.v1.instance + name: my_instance + properties: + zone: test-zone-b diff --git a/expandybird/test/templates/duplicate_names_B.jinja b/expandybird/test/templates/duplicate_names_B.jinja new file mode 100644 index 000000000..8ae8a7923 --- /dev/null +++ b/expandybird/test/templates/duplicate_names_B.jinja @@ -0,0 +1,5 @@ +resources: +- type: compute.v1.instance + name: B + properties: + zone: test-zone-b diff --git a/expandybird/test/templates/duplicate_names_C.jinja b/expandybird/test/templates/duplicate_names_C.jinja new file mode 100644 index 000000000..3aff269fe --- /dev/null +++ b/expandybird/test/templates/duplicate_names_C.jinja @@ -0,0 +1,5 @@ +resources: +- type: compute.v1.instance + name: C + properties: + zone: test-zone-c diff --git a/expandybird/test/templates/duplicate_names_in_subtemplates.jinja b/expandybird/test/templates/duplicate_names_in_subtemplates.jinja new file mode 100644 index 000000000..f7386f186 --- /dev/null +++ b/expandybird/test/templates/duplicate_names_in_subtemplates.jinja @@ -0,0 +1,9 @@ +resources: +- type: compute.v1.instance + name: my_instance + properties: + zone: test-zone-a +- type: compute.v1.instance + name: my_instance + properties: + zone: test-zone-b diff --git a/expandybird/test/templates/duplicate_names_in_subtemplates.yaml b/expandybird/test/templates/duplicate_names_in_subtemplates.yaml new file mode 100644 index 000000000..06e8a263c --- /dev/null +++ b/expandybird/test/templates/duplicate_names_in_subtemplates.yaml @@ -0,0 +1,5 @@ +imports: ["duplicate_names_in_subtemplates.jinja"] + +resources: +- name: subtemplate + type: duplicate_names_in_subtemplates.jinja diff --git a/expandybird/test/templates/duplicate_names_mixed_level.yaml b/expandybird/test/templates/duplicate_names_mixed_level.yaml new file mode 100644 index 000000000..7cb82e5e7 --- /dev/null +++ b/expandybird/test/templates/duplicate_names_mixed_level.yaml @@ -0,0 +1,7 @@ +imports: ["duplicate_names_B.jinja", "duplicate_names_C.jinja"] + +resources: +- name: A + type: duplicate_names_B.jinja +- name: B + type: duplicate_names_C.jinja diff --git a/expandybird/test/templates/duplicate_names_mixed_level_result.yaml b/expandybird/test/templates/duplicate_names_mixed_level_result.yaml new file mode 100644 index 000000000..de34fa47a --- /dev/null +++ b/expandybird/test/templates/duplicate_names_mixed_level_result.yaml @@ -0,0 +1,22 @@ +config: + resources: + - name: B + properties: + zone: test-zone-b + type: compute.v1.instance + - name: C + properties: + zone: test-zone-c + type: compute.v1.instance +layout: + resources: + - name: A + resources: + - name: B + type: compute.v1.instance + type: duplicate_names_B.jinja + - name: B + resources: + - name: C + type: compute.v1.instance + type: duplicate_names_C.jinja diff --git a/expandybird/test/templates/duplicate_names_parent_child.yaml b/expandybird/test/templates/duplicate_names_parent_child.yaml new file mode 100644 index 000000000..4c3ae6bf1 --- /dev/null +++ b/expandybird/test/templates/duplicate_names_parent_child.yaml @@ -0,0 +1,7 @@ +imports: ["duplicate_names_B.jinja"] + +resources: +- name: A + type: duplicate_names_B.jinja +- name: B + type: compute.v1.instance diff --git a/expandybird/test/templates/duplicate_names_parent_child_result.yaml b/expandybird/test/templates/duplicate_names_parent_child_result.yaml new file mode 100644 index 000000000..8384a72ee --- /dev/null +++ b/expandybird/test/templates/duplicate_names_parent_child_result.yaml @@ -0,0 +1,17 @@ +config: + resources: + - name: B + properties: + zone: test-zone-b + type: compute.v1.instance + - name: B + type: compute.v1.instance +layout: + resources: + - name: A + resources: + - name: B + type: compute.v1.instance + type: duplicate_names_B.jinja + - name: B + type: compute.v1.instance diff --git a/expandybird/test/templates/helper.jinja b/expandybird/test/templates/helper.jinja new file mode 100644 index 000000000..d174617f4 --- /dev/null +++ b/expandybird/test/templates/helper.jinja @@ -0,0 +1,5 @@ +resources: +- name: helper + type: bar + properties: + test: {{ properties["foobar"] }} diff --git a/expandybird/test/templates/helper.jinja.schema b/expandybird/test/templates/helper.jinja.schema new file mode 100644 index 000000000..7bbdf5b5e --- /dev/null +++ b/expandybird/test/templates/helper.jinja.schema @@ -0,0 +1,4 @@ +properties: + foobar: + type: string + default: Use this schema diff --git a/expandybird/test/templates/helpers/common.jinja b/expandybird/test/templates/helpers/common.jinja new file mode 100644 index 000000000..056435742 --- /dev/null +++ b/expandybird/test/templates/helpers/common.jinja @@ -0,0 +1,3 @@ +{%- macro GenerateMachineName(prefix='', suffix='') -%} + {{ prefix + "-" + suffix }} +{%- endmacro %} diff --git a/expandybird/test/templates/helpers/common.py b/expandybird/test/templates/helpers/common.py new file mode 100644 index 000000000..16088dbeb --- /dev/null +++ b/expandybird/test/templates/helpers/common.py @@ -0,0 +1,8 @@ +# Copyright 2014 Google Inc. All Rights Reserved. + +"""Dummy helper methods invoked in other constructors.""" + + +def GenerateMachineName(prefix, suffix): + """Generates name of a VM.""" + return prefix + "-" + suffix diff --git a/expandybird/test/templates/helpers/extra/__init__.py b/expandybird/test/templates/helpers/extra/__init__.py new file mode 100644 index 000000000..27e204d9a --- /dev/null +++ b/expandybird/test/templates/helpers/extra/__init__.py @@ -0,0 +1 @@ +"""Package marker file.""" diff --git a/expandybird/test/templates/helpers/extra/common2.py b/expandybird/test/templates/helpers/extra/common2.py new file mode 100644 index 000000000..17ab8209e --- /dev/null +++ b/expandybird/test/templates/helpers/extra/common2.py @@ -0,0 +1,8 @@ +# Copyright 2014 Google Inc. All Rights Reserved. + +"""Dummy helper methods invoked in other constructors.""" + + +def GenerateMachineSize(): + """Generates size of a VM.""" + return "big" diff --git a/expandybird/test/templates/invalid_config.yaml b/expandybird/test/templates/invalid_config.yaml new file mode 100644 index 000000000..d2e04dfb4 --- /dev/null +++ b/expandybird/test/templates/invalid_config.yaml @@ -0,0 +1,2 @@ +resources: +- name: foo properties: bar: baz diff --git a/expandybird/test/templates/jinja_defaults.jinja b/expandybird/test/templates/jinja_defaults.jinja new file mode 100644 index 000000000..74837f2a2 --- /dev/null +++ b/expandybird/test/templates/jinja_defaults.jinja @@ -0,0 +1,16 @@ +resources: +- type: compute.v1.instance + name: vm-created-by-cloud-config-{{ properties["deployment"] }} + properties: + zone: {{ properties["zone"] }} + machineType: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/zones/{{ properties["zone"] }}/machineTypes/f1-micro + disks: + - deviceName: boot + type: PERSISTENT + boot: true + autoDelete: true + initializeParams: + diskName: disk-created-by-cloud-config-{{ properties["deployment"] }} + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/global/networks/default diff --git a/expandybird/test/templates/jinja_defaults.jinja.schema b/expandybird/test/templates/jinja_defaults.jinja.schema new file mode 100644 index 000000000..7cf4ae108 --- /dev/null +++ b/expandybird/test/templates/jinja_defaults.jinja.schema @@ -0,0 +1,18 @@ +info: + title: Schema for a basic jinja template that includes default values + +imports: + +properties: + foo: + description: blah + type: string + zone: + type: string + default: test-zone + project: + type: string + default: test-project + deployment: + type: string + default: test-deployment diff --git a/expandybird/test/templates/jinja_defaults.yaml b/expandybird/test/templates/jinja_defaults.yaml new file mode 100644 index 000000000..1b3ee64f4 --- /dev/null +++ b/expandybird/test/templates/jinja_defaults.yaml @@ -0,0 +1,9 @@ +imports: +- path: "jinja_defaults.jinja" +- path: "jinja_defaults.jinja.schema" + +resources: +- name: jinja_defaults_name + type: jinja_defaults.jinja + properties: + foo: bar diff --git a/expandybird/test/templates/jinja_defaults_result.yaml b/expandybird/test/templates/jinja_defaults_result.yaml new file mode 100644 index 000000000..60c03b408 --- /dev/null +++ b/expandybird/test/templates/jinja_defaults_result.yaml @@ -0,0 +1,29 @@ +config: + resources: + - name: vm-created-by-cloud-config-test-deployment + properties: + disks: + - autoDelete: true + boot: true + deviceName: boot + initializeParams: + diskName: disk-created-by-cloud-config-test-deployment + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + type: PERSISTENT + machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default + zone: test-zone + type: compute.v1.instance +layout: + resources: + - name: jinja_defaults_name + properties: + deployment: test-deployment + foo: bar + project: test-project + zone: test-zone + resources: + - name: vm-created-by-cloud-config-test-deployment + type: compute.v1.instance + type: jinja_defaults.jinja diff --git a/expandybird/test/templates/jinja_missing_required.jinja b/expandybird/test/templates/jinja_missing_required.jinja new file mode 100644 index 000000000..2247bf2e1 --- /dev/null +++ b/expandybird/test/templates/jinja_missing_required.jinja @@ -0,0 +1,4 @@ +Nothing here because this file should never be called. +The validation will fail before this file is used. + +{{% Invalid diff --git a/expandybird/test/templates/jinja_missing_required.jinja.schema b/expandybird/test/templates/jinja_missing_required.jinja.schema new file mode 100644 index 000000000..387b64dde --- /dev/null +++ b/expandybird/test/templates/jinja_missing_required.jinja.schema @@ -0,0 +1,11 @@ +info: + title: Schema with a required property + +imports: + +required: + - important +properties: + important: + type: string + diff --git a/expandybird/test/templates/jinja_missing_required.yaml b/expandybird/test/templates/jinja_missing_required.yaml new file mode 100644 index 000000000..412bccf8e --- /dev/null +++ b/expandybird/test/templates/jinja_missing_required.yaml @@ -0,0 +1,9 @@ +imports: +- path: "jinja_missing_required.jinja" +- path: "jinja_missing_required.jinja.schema" + +resources: +- name: jinja_missing_required_resource_name + type: jinja_missing_required.jinja + properties: + less-important: an optional property diff --git a/expandybird/test/templates/jinja_multiple_errors.jinja b/expandybird/test/templates/jinja_multiple_errors.jinja new file mode 100644 index 000000000..2247bf2e1 --- /dev/null +++ b/expandybird/test/templates/jinja_multiple_errors.jinja @@ -0,0 +1,4 @@ +Nothing here because this file should never be called. +The validation will fail before this file is used. + +{{% Invalid diff --git a/expandybird/test/templates/jinja_multiple_errors.jinja.schema b/expandybird/test/templates/jinja_multiple_errors.jinja.schema new file mode 100644 index 000000000..5d73e125d --- /dev/null +++ b/expandybird/test/templates/jinja_multiple_errors.jinja.schema @@ -0,0 +1,22 @@ +info: + title: Schema with several rules + +imports: + +properties: + number: + type: integer + short-string: + type: string + maxLength: 10 + odd: + type: integer + not: + multipleOf: 2 + abc: + type: string + enum: + - a + - b + - c + diff --git a/expandybird/test/templates/jinja_multiple_errors.yaml b/expandybird/test/templates/jinja_multiple_errors.yaml new file mode 100644 index 000000000..0fc663628 --- /dev/null +++ b/expandybird/test/templates/jinja_multiple_errors.yaml @@ -0,0 +1,12 @@ +imports: +- path: "jinja_multiple_errors.jinja" +- path: "jinja_multiple_errors.jinja.schema" + +resources: +- name: jinja_multiple_errors + type: jinja_multiple_errors.jinja + properties: + number: a string + short-string: longer than 10 chars + odd: 6 + abc: d diff --git a/expandybird/test/templates/jinja_noparams.jinja b/expandybird/test/templates/jinja_noparams.jinja new file mode 100644 index 000000000..9b0ec9476 --- /dev/null +++ b/expandybird/test/templates/jinja_noparams.jinja @@ -0,0 +1,19 @@ +resources: +{% for name in ['name1', 'name2'] %} +- type: compute.v1.instance + name: {{ name }} + properties: + zone: test-zone + machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro + disks: + - deviceName: boot + type: PERSISTENT + boot: true + autoDelete: true + initializeParams: + diskName: disk-created-by-cloud-config-test-deployment + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default +{% endfor %} + diff --git a/expandybird/test/templates/jinja_noparams.yaml b/expandybird/test/templates/jinja_noparams.yaml new file mode 100644 index 000000000..7debc85e2 --- /dev/null +++ b/expandybird/test/templates/jinja_noparams.yaml @@ -0,0 +1,6 @@ +imports: ["jinja_noparams.jinja"] + +resources: +- name: jinja_noparams_name + type: jinja_noparams.jinja + diff --git a/expandybird/test/templates/jinja_noparams_result.yaml b/expandybird/test/templates/jinja_noparams_result.yaml new file mode 100644 index 000000000..5a5f0e52c --- /dev/null +++ b/expandybird/test/templates/jinja_noparams_result.yaml @@ -0,0 +1,41 @@ +config: + resources: + - name: name1 + properties: + disks: + - autoDelete: true + boot: true + deviceName: boot + initializeParams: + diskName: disk-created-by-cloud-config-test-deployment + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + type: PERSISTENT + machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default + zone: test-zone + type: compute.v1.instance + - name: name2 + properties: + disks: + - autoDelete: true + boot: true + deviceName: boot + initializeParams: + diskName: disk-created-by-cloud-config-test-deployment + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + type: PERSISTENT + machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default + zone: test-zone + type: compute.v1.instance +layout: + resources: + - name: jinja_noparams_name + resources: + - name: name1 + type: compute.v1.instance + - name: name2 + type: compute.v1.instance + type: jinja_noparams.jinja diff --git a/expandybird/test/templates/jinja_template.jinja b/expandybird/test/templates/jinja_template.jinja new file mode 100644 index 000000000..5febf21bd --- /dev/null +++ b/expandybird/test/templates/jinja_template.jinja @@ -0,0 +1,18 @@ +resources: +- type: compute.v1.instance + name: vm-created-by-cloud-config-{{ properties["deployment"] }} + properties: + zone: {{ properties["zone"] }} + machineType: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/zones/{{ properties["zone"] }}/machineTypes/f1-micro + disks: + - deviceName: boot + type: PERSISTENT + boot: true + autoDelete: true + initializeParams: + diskName: disk-created-by-cloud-config-{{ properties["deployment"] }} + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/global/networks/default + + diff --git a/expandybird/test/templates/jinja_template.yaml b/expandybird/test/templates/jinja_template.yaml new file mode 100644 index 000000000..bc2b2b0db --- /dev/null +++ b/expandybird/test/templates/jinja_template.yaml @@ -0,0 +1,10 @@ +imports: ["jinja_template.jinja"] + +resources: +- name: jinja_template_name + type: jinja_template.jinja + properties: + zone: test-zone + project: test-project + deployment: test-deployment + diff --git a/expandybird/test/templates/jinja_template_result.yaml b/expandybird/test/templates/jinja_template_result.yaml new file mode 100644 index 000000000..10a36fa56 --- /dev/null +++ b/expandybird/test/templates/jinja_template_result.yaml @@ -0,0 +1,28 @@ +config: + resources: + - name: vm-created-by-cloud-config-test-deployment + properties: + disks: + - autoDelete: true + boot: true + deviceName: boot + initializeParams: + diskName: disk-created-by-cloud-config-test-deployment + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + type: PERSISTENT + machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default + zone: test-zone + type: compute.v1.instance +layout: + resources: + - name: jinja_template_name + properties: + deployment: test-deployment + project: test-project + zone: test-zone + resources: + - name: vm-created-by-cloud-config-test-deployment + type: compute.v1.instance + type: jinja_template.jinja diff --git a/expandybird/test/templates/jinja_template_with_env.jinja b/expandybird/test/templates/jinja_template_with_env.jinja new file mode 100644 index 000000000..545824e5e --- /dev/null +++ b/expandybird/test/templates/jinja_template_with_env.jinja @@ -0,0 +1,18 @@ +resources: +- type: compute.v1.instance + name: vm-created-by-cloud-config-{{ env["deployment"] }} + properties: + zone: {{ properties["zone"] }} + machineType: https://www.googleapis.com/compute/v1/projects/{{ env["project"] }}/zones/{{ properties["zone"] }}/machineTypes/f1-micro + disks: + - deviceName: boot + type: PERSISTENT + boot: true + autoDelete: true + initializeParams: + diskName: disk-created-by-cloud-config-{{ env["deployment"] }}-{{ env["name"] }}-{{ env["type"] }} + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/{{ env["project"] }}/global/networks/default + + diff --git a/expandybird/test/templates/jinja_template_with_env.yaml b/expandybird/test/templates/jinja_template_with_env.yaml new file mode 100644 index 000000000..58388635c --- /dev/null +++ b/expandybird/test/templates/jinja_template_with_env.yaml @@ -0,0 +1,8 @@ +imports: ["jinja_template_with_env.jinja"] + +resources: +- name: jinja_template_with_env_name + type: jinja_template_with_env.jinja + properties: + zone: test-zone + diff --git a/expandybird/test/templates/jinja_template_with_env_result.yaml b/expandybird/test/templates/jinja_template_with_env_result.yaml new file mode 100644 index 000000000..a69f89ab1 --- /dev/null +++ b/expandybird/test/templates/jinja_template_with_env_result.yaml @@ -0,0 +1,26 @@ +config: + resources: + - name: vm-created-by-cloud-config-test-deployment + properties: + disks: + - autoDelete: true + boot: true + deviceName: boot + initializeParams: + diskName: disk-created-by-cloud-config-test-deployment-jinja_template_with_env_name-jinja_template_with_env.jinja + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + type: PERSISTENT + machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default + zone: test-zone + type: compute.v1.instance +layout: + resources: + - name: jinja_template_with_env_name + properties: + zone: test-zone + resources: + - name: vm-created-by-cloud-config-test-deployment + type: compute.v1.instance + type: jinja_template_with_env.jinja diff --git a/expandybird/test/templates/jinja_template_with_import.jinja b/expandybird/test/templates/jinja_template_with_import.jinja new file mode 100644 index 000000000..b5c125726 --- /dev/null +++ b/expandybird/test/templates/jinja_template_with_import.jinja @@ -0,0 +1,6 @@ +{% import 'helpers/common.jinja' as common %} +resources: +- name: {{ common.GenerateMachineName("myFrontend", "prod") }} + type: compute.v1.instance + properties: + machineSize: big diff --git a/expandybird/test/templates/jinja_template_with_import.yaml b/expandybird/test/templates/jinja_template_with_import.yaml new file mode 100644 index 000000000..d4ec9f327 --- /dev/null +++ b/expandybird/test/templates/jinja_template_with_import.yaml @@ -0,0 +1,5 @@ +imports: ["jinja_template_with_import.jinja", "helpers/common.jinja"] + +resources: +- name: jinja_template_with_import_name + type: jinja_template_with_import.jinja diff --git a/expandybird/test/templates/jinja_template_with_import_result.yaml b/expandybird/test/templates/jinja_template_with_import_result.yaml new file mode 100644 index 000000000..3a3c3b2e5 --- /dev/null +++ b/expandybird/test/templates/jinja_template_with_import_result.yaml @@ -0,0 +1,13 @@ +config: + resources: + - name: myFrontend-prod + properties: + machineSize: big + type: compute.v1.instance +layout: + resources: + - name: jinja_template_with_import_name + resources: + - name: myFrontend-prod + type: compute.v1.instance + type: jinja_template_with_import.jinja diff --git a/expandybird/test/templates/jinja_template_with_inlinedfile.jinja b/expandybird/test/templates/jinja_template_with_inlinedfile.jinja new file mode 100644 index 000000000..01d7642e4 --- /dev/null +++ b/expandybird/test/templates/jinja_template_with_inlinedfile.jinja @@ -0,0 +1,7 @@ +{% import 'helpers/common.jinja' as common %} +resources: +- name: {{ common.GenerateMachineName("myFrontend", "prod") }} + type: compute.v1.instance + properties: + description: {{ imports[properties["description-file"]] }} + machineSize: big diff --git a/expandybird/test/templates/jinja_template_with_inlinedfile.yaml b/expandybird/test/templates/jinja_template_with_inlinedfile.yaml new file mode 100644 index 000000000..e8bec0891 --- /dev/null +++ b/expandybird/test/templates/jinja_template_with_inlinedfile.yaml @@ -0,0 +1,7 @@ +imports: ["jinja_template_with_inlinedfile.jinja", "helpers/common.jinja", "description_text.txt"] + +resources: +- name: jinja_template_with_inlinedfile_name + type: jinja_template_with_inlinedfile.jinja + properties: + description-file: description_text.txt diff --git a/expandybird/test/templates/jinja_template_with_inlinedfile_result.yaml b/expandybird/test/templates/jinja_template_with_inlinedfile_result.yaml new file mode 100644 index 000000000..6f4bf9eee --- /dev/null +++ b/expandybird/test/templates/jinja_template_with_inlinedfile_result.yaml @@ -0,0 +1,21 @@ +config: + resources: + - name: myFrontend-prod + properties: + description: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat + non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + machineSize: big + type: compute.v1.instance +layout: + resources: + - name: jinja_template_with_inlinedfile_name + properties: + description-file: description_text.txt + resources: + - name: myFrontend-prod + type: compute.v1.instance + type: jinja_template_with_inlinedfile.jinja diff --git a/expandybird/test/templates/jinja_unresolved.jinja b/expandybird/test/templates/jinja_unresolved.jinja new file mode 100644 index 000000000..6ad1ed1cd --- /dev/null +++ b/expandybird/test/templates/jinja_unresolved.jinja @@ -0,0 +1,18 @@ +resources: +- type: compute.v1.instance + name: vm-created-by-cloud-config-{{ porcelain["deployment"] }} + properties: + zone: {{ properties["zone"] }} + machineType: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/zones/{{ properties["zone"] }}/machineTypes/f1-micro + disks: + - deviceName: boot + type: PERSISTENT + boot: true + autoDelete: true + initializeParams: + diskName: disk-created-by-cloud-config-{{ properties["deployment"] }} + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/global/networks/default + + diff --git a/expandybird/test/templates/jinja_unresolved.yaml b/expandybird/test/templates/jinja_unresolved.yaml new file mode 100644 index 000000000..8bc31af11 --- /dev/null +++ b/expandybird/test/templates/jinja_unresolved.yaml @@ -0,0 +1,10 @@ +imports: ["jinja_unresolved.jinja"] + +resources: +- name: jinja_template_name + type: jinja_unresolved.jinja + properties: + zone: test-zone + project: test-project + deployment: test-deployment + diff --git a/expandybird/test/templates/no_properties.py b/expandybird/test/templates/no_properties.py new file mode 100644 index 000000000..66cd164c5 --- /dev/null +++ b/expandybird/test/templates/no_properties.py @@ -0,0 +1,5 @@ +"""Return empty resources block.""" + + +def GenerateConfig(_): + return """resources:""" diff --git a/expandybird/test/templates/no_properties.yaml b/expandybird/test/templates/no_properties.yaml new file mode 100644 index 000000000..1c7d29795 --- /dev/null +++ b/expandybird/test/templates/no_properties.yaml @@ -0,0 +1,6 @@ +imports: +- path: "no_properties.py" + +resources: +- name: test-resource + type: no_properties.py diff --git a/expandybird/test/templates/no_properties_result.yaml b/expandybird/test/templates/no_properties_result.yaml new file mode 100644 index 000000000..41ccb5602 --- /dev/null +++ b/expandybird/test/templates/no_properties_result.yaml @@ -0,0 +1,6 @@ +config: + resources: [] +layout: + resources: + - name: test-resource + type: no_properties.py diff --git a/expandybird/test/templates/no_resources.py b/expandybird/test/templates/no_resources.py new file mode 100644 index 000000000..c387ebca0 --- /dev/null +++ b/expandybird/test/templates/no_resources.py @@ -0,0 +1,6 @@ +"""Does nothing.""" + + +def GenerateConfig(_): + """Returns empty string.""" + return '' diff --git a/expandybird/test/templates/no_resources.yaml b/expandybird/test/templates/no_resources.yaml new file mode 100644 index 000000000..d9257d8ea --- /dev/null +++ b/expandybird/test/templates/no_resources.yaml @@ -0,0 +1,6 @@ +imports: +- path: "no_resources.py" + +resources: +- name: test-resource + type: no_resources.py diff --git a/expandybird/test/templates/python_and_jinja_template.jinja b/expandybird/test/templates/python_and_jinja_template.jinja new file mode 100644 index 000000000..8a670b476 --- /dev/null +++ b/expandybird/test/templates/python_and_jinja_template.jinja @@ -0,0 +1,17 @@ +resources: +- type: compute.v1.instance + name: vm-created-by-cloud-config-{{ properties["deployment"] }} + properties: + zone: {{ properties["zone"] }} + machineType: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/zones/{{ properties["zone"] }}/machineTypes/f1-micro + disks: + - deviceName: boot + type: PERSISTENT + boot: true + autoDelete: true + initializeParams: + diskName: disk-created-by-cloud-config-{{ properties["deployment"] }} + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/global/networks/default + diff --git a/expandybird/test/templates/python_and_jinja_template.py b/expandybird/test/templates/python_and_jinja_template.py new file mode 100644 index 000000000..7213f0679 --- /dev/null +++ b/expandybird/test/templates/python_and_jinja_template.py @@ -0,0 +1,37 @@ +# Copyright 2014 Google Inc. All Rights Reserved. + +#% description: Creates a VM running a Salt master daemon in a Docker container. +#% parameters: +#% - name: masterAddress +#% type: string +#% description: Name of the Salt master VM. +#% required: true +#% - name: project +#% type: string +#% description: Name of the Cloud project. +#% required: true +#% - name: zone +#% type: string +#% description: Zone to create the resources in. +#% required: true + +"""Generates config for a VM running a SaltStack master. + +Just for fun this template is in Python, while the others in this +directory are in Jinja2. +""" + + +def GenerateConfig(evaluation_context): + return """ +resources: +- name: python_and_jinja_template_jinja_name + type: python_and_jinja_template.jinja + properties: + zone: %(zone)s + project: %(project)s + deployment: %(master)s + +""" % {"master": evaluation_context.properties["masterAddress"], + "project": evaluation_context.properties["project"], + "zone": evaluation_context.properties["zone"]} diff --git a/expandybird/test/templates/python_and_jinja_template.yaml b/expandybird/test/templates/python_and_jinja_template.yaml new file mode 100644 index 000000000..46daafc27 --- /dev/null +++ b/expandybird/test/templates/python_and_jinja_template.yaml @@ -0,0 +1,9 @@ +imports: ["python_and_jinja_template.jinja", "python_and_jinja_template.py"] + +resources: +- name: python_and_jinja_template_name + type: python_and_jinja_template.py + properties: + masterAddress: master-address + project: my-project + zone: my-zone diff --git a/expandybird/test/templates/python_and_jinja_template_result.yaml b/expandybird/test/templates/python_and_jinja_template_result.yaml new file mode 100644 index 000000000..3d23dcfbf --- /dev/null +++ b/expandybird/test/templates/python_and_jinja_template_result.yaml @@ -0,0 +1,35 @@ +config: + resources: + - name: vm-created-by-cloud-config-master-address + properties: + disks: + - autoDelete: true + boot: true + deviceName: boot + initializeParams: + diskName: disk-created-by-cloud-config-master-address + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + type: PERSISTENT + machineType: https://www.googleapis.com/compute/v1/projects/my-project/zones/my-zone/machineTypes/f1-micro + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default + zone: my-zone + type: compute.v1.instance +layout: + resources: + - name: python_and_jinja_template_name + properties: + masterAddress: master-address + project: my-project + zone: my-zone + resources: + - name: python_and_jinja_template_jinja_name + properties: + deployment: master-address + project: my-project + zone: my-zone + resources: + - name: vm-created-by-cloud-config-master-address + type: compute.v1.instance + type: python_and_jinja_template.jinja + type: python_and_jinja_template.py diff --git a/expandybird/test/templates/python_bad_schema.py b/expandybird/test/templates/python_bad_schema.py new file mode 100644 index 000000000..0ebda1f6c --- /dev/null +++ b/expandybird/test/templates/python_bad_schema.py @@ -0,0 +1,3 @@ +"""Throws an exception.""" + +raise Exception diff --git a/expandybird/test/templates/python_bad_schema.py.schema b/expandybird/test/templates/python_bad_schema.py.schema new file mode 100644 index 000000000..cf5d5d6d0 --- /dev/null +++ b/expandybird/test/templates/python_bad_schema.py.schema @@ -0,0 +1,19 @@ +info: + title: Schema with several errors + +imports: + +properties: + bad-type: + type: int + missing-cond: + type: string + exclusiveMaximum: 10 + odd-string: + type: string + not: + multipleOf: 2 + bad-enum: + type: string + enum: not a list + diff --git a/expandybird/test/templates/python_bad_schema.yaml b/expandybird/test/templates/python_bad_schema.yaml new file mode 100644 index 000000000..d52024b6f --- /dev/null +++ b/expandybird/test/templates/python_bad_schema.yaml @@ -0,0 +1,9 @@ +imports: +- path: "python_bad_schema.py" +- path: "python_bad_schema.py.schema" + +resources: +- name: python_bad_schema + type: python_bad_schema.py + properties: + innocent: true diff --git a/expandybird/test/templates/python_noparams.py b/expandybird/test/templates/python_noparams.py new file mode 100644 index 000000000..5ee3255f2 --- /dev/null +++ b/expandybird/test/templates/python_noparams.py @@ -0,0 +1,14 @@ +# Copyright 2014 Google Inc. All Rights Reserved. + +"""Constructs a VM.""" + + +def GenerateConfig(_): + """Generates config of a VM.""" + return """ +resources: +- name: myBackend + type: compute.v1.instance + properties: + machineSize: big +""" diff --git a/expandybird/test/templates/python_noparams.yaml b/expandybird/test/templates/python_noparams.yaml new file mode 100644 index 000000000..b7abeaf55 --- /dev/null +++ b/expandybird/test/templates/python_noparams.yaml @@ -0,0 +1,9 @@ +imports: ["python_noparams.py"] + +resources: +- name: myFrontend + type: compute.v1.instance + properties: + machineSize: big +- name: python_noparams_name + type: python_noparams.py diff --git a/expandybird/test/templates/python_noparams_result.yaml b/expandybird/test/templates/python_noparams_result.yaml new file mode 100644 index 000000000..944d9018a --- /dev/null +++ b/expandybird/test/templates/python_noparams_result.yaml @@ -0,0 +1,19 @@ +config: + resources: + - name: myFrontend + properties: + machineSize: big + type: compute.v1.instance + - name: myBackend + properties: + machineSize: big + type: compute.v1.instance +layout: + resources: + - name: myFrontend + type: compute.v1.instance + - name: python_noparams_name + resources: + - name: myBackend + type: compute.v1.instance + type: python_noparams.py diff --git a/expandybird/test/templates/python_schema.py b/expandybird/test/templates/python_schema.py new file mode 100644 index 000000000..59d74cfd7 --- /dev/null +++ b/expandybird/test/templates/python_schema.py @@ -0,0 +1,59 @@ +# Copyright 2014 Google Inc. All Rights Reserved. + +#% description: Creates a VM running a Salt master daemon in a Docker container. +#% parameters: +#% - name: masterAddress +#% type: string +#% description: Name of the Salt master VM. +#% required: true +#% - name: project +#% type: string +#% description: Name of the Cloud project. +#% required: true +#% - name: zone +#% type: string +#% description: Zone to create the resources in. +#% required: true + +"""Generates config for a VM running a SaltStack master. + +Just for fun this template is in Python, while the others in this +directory are in Jinja2. +""" + + +def GenerateConfig(evaluation_context): + return """ +resources: +- type: compute.v1.firewall + name: %(master)s-firewall + properties: + network: https://www.googleapis.com/compute/v1/projects/%(project)s/global/networks/default + sourceRanges: [ "0.0.0.0/0" ] + allowed: + - IPProtocol: tcp + ports: [ "4505", "4506" ] +- type: compute.v1.instance + name: %(master)s + properties: + zone: %(zone)s + machineType: https://www.googleapis.com/compute/v1/projects/%(project)s/zones/%(zone)s/machineTypes/f1-micro + disks: + - deviceName: boot + type: PERSISTENT + boot: true + autoDelete: true + initializeParams: + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/%(project)s/global/networks/default + accessConfigs: + - name: External NAT + type: ONE_TO_ONE_NAT + metadata: + items: + - key: startup-script + value: startup-script-value +""" % {"master": evaluation_context.properties["masterAddress"], + "project": evaluation_context.env["project"], + "zone": evaluation_context.properties["zone"]} diff --git a/expandybird/test/templates/python_schema.py.schema b/expandybird/test/templates/python_schema.py.schema new file mode 100644 index 000000000..cb960bcfd --- /dev/null +++ b/expandybird/test/templates/python_schema.py.schema @@ -0,0 +1,14 @@ +info: + title: A simple python template that has a schema. + +imports: + +properties: + masterAddress: + type: string + default: slave-address + description: masterAddress + zone: + type: string + default: not-test-zone + description: zone diff --git a/expandybird/test/templates/python_schema.yaml b/expandybird/test/templates/python_schema.yaml new file mode 100644 index 000000000..94f94d4e5 --- /dev/null +++ b/expandybird/test/templates/python_schema.yaml @@ -0,0 +1,10 @@ +imports: +- path: "python_schema.py" +- path: "python_schema.py.schema" + +resources: +- name: python_schema + type: python_schema.py + properties: + masterAddress: master-address + zone: my-zone diff --git a/expandybird/test/templates/python_schema_result.yaml b/expandybird/test/templates/python_schema_result.yaml new file mode 100644 index 000000000..2b75d97c2 --- /dev/null +++ b/expandybird/test/templates/python_schema_result.yaml @@ -0,0 +1,46 @@ +config: + resources: + - name: master-address-firewall + properties: + allowed: + - IPProtocol: tcp + ports: + - '4505' + - '4506' + network: https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default + sourceRanges: + - 0.0.0.0/0 + type: compute.v1.firewall + - name: master-address + properties: + disks: + - autoDelete: true + boot: true + deviceName: boot + initializeParams: + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + type: PERSISTENT + machineType: https://www.googleapis.com/compute/v1/projects/my-project/zones/my-zone/machineTypes/f1-micro + metadata: + items: + - key: startup-script + value: startup-script-value + networkInterfaces: + - accessConfigs: + - name: External NAT + type: ONE_TO_ONE_NAT + network: https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default + zone: my-zone + type: compute.v1.instance +layout: + resources: + - name: python_schema + properties: + masterAddress: master-address + zone: my-zone + resources: + - name: master-address-firewall + type: compute.v1.firewall + - name: master-address + type: compute.v1.instance + type: python_schema.py diff --git a/expandybird/test/templates/python_template.py b/expandybird/test/templates/python_template.py new file mode 100644 index 000000000..3bda1b331 --- /dev/null +++ b/expandybird/test/templates/python_template.py @@ -0,0 +1,59 @@ +# Copyright 2014 Google Inc. All Rights Reserved. + +#% description: Creates a VM running a Salt master daemon in a Docker container. +#% parameters: +#% - name: masterAddress +#% type: string +#% description: Name of the Salt master VM. +#% required: true +#% - name: project +#% type: string +#% description: Name of the Cloud project. +#% required: true +#% - name: zone +#% type: string +#% description: Zone to create the resources in. +#% required: true + +"""Generates config for a VM running a SaltStack master. + +Just for fun this template is in Python, while the others in this +directory are in Jinja2. +""" + + +def GenerateConfig(evaluation_context): + return """ +resources: +- type: compute.v1.firewall + name: %(master)s-firewall + properties: + network: https://www.googleapis.com/compute/v1/projects/%(project)s/global/networks/default + sourceRanges: [ "0.0.0.0/0" ] + allowed: + - IPProtocol: tcp + ports: [ "4505", "4506" ] +- type: compute.v1.instance + name: %(master)s + properties: + zone: %(zone)s + machineType: https://www.googleapis.com/compute/v1/projects/%(project)s/zones/%(zone)s/machineTypes/f1-micro + disks: + - deviceName: boot + type: PERSISTENT + boot: true + autoDelete: true + initializeParams: + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/%(project)s/global/networks/default + accessConfigs: + - name: External NAT + type: ONE_TO_ONE_NAT + metadata: + items: + - key: startup-script + value: startup-script-value +""" % {"master": evaluation_context.properties["masterAddress"], + "project": evaluation_context.properties["project"], + "zone": evaluation_context.properties["zone"]} diff --git a/expandybird/test/templates/python_template.yaml b/expandybird/test/templates/python_template.yaml new file mode 100644 index 000000000..08ae8b8bb --- /dev/null +++ b/expandybird/test/templates/python_template.yaml @@ -0,0 +1,9 @@ +imports: ["python_template.py"] + +resources: +- name: python_template_name + type: python_template.py + properties: + masterAddress: master-address + project: my-project + zone: my-zone diff --git a/expandybird/test/templates/python_template_result.yaml b/expandybird/test/templates/python_template_result.yaml new file mode 100644 index 000000000..1b82f3fed --- /dev/null +++ b/expandybird/test/templates/python_template_result.yaml @@ -0,0 +1,47 @@ +config: + resources: + - name: master-address-firewall + properties: + allowed: + - IPProtocol: tcp + ports: + - '4505' + - '4506' + network: https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default + sourceRanges: + - 0.0.0.0/0 + type: compute.v1.firewall + - name: master-address + properties: + disks: + - autoDelete: true + boot: true + deviceName: boot + initializeParams: + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + type: PERSISTENT + machineType: https://www.googleapis.com/compute/v1/projects/my-project/zones/my-zone/machineTypes/f1-micro + metadata: + items: + - key: startup-script + value: startup-script-value + networkInterfaces: + - accessConfigs: + - name: External NAT + type: ONE_TO_ONE_NAT + network: https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default + zone: my-zone + type: compute.v1.instance +layout: + resources: + - name: python_template_name + properties: + masterAddress: master-address + project: my-project + zone: my-zone + resources: + - name: master-address-firewall + type: compute.v1.firewall + - name: master-address + type: compute.v1.instance + type: python_template.py diff --git a/expandybird/test/templates/python_template_with_env.py b/expandybird/test/templates/python_template_with_env.py new file mode 100644 index 000000000..59d74cfd7 --- /dev/null +++ b/expandybird/test/templates/python_template_with_env.py @@ -0,0 +1,59 @@ +# Copyright 2014 Google Inc. All Rights Reserved. + +#% description: Creates a VM running a Salt master daemon in a Docker container. +#% parameters: +#% - name: masterAddress +#% type: string +#% description: Name of the Salt master VM. +#% required: true +#% - name: project +#% type: string +#% description: Name of the Cloud project. +#% required: true +#% - name: zone +#% type: string +#% description: Zone to create the resources in. +#% required: true + +"""Generates config for a VM running a SaltStack master. + +Just for fun this template is in Python, while the others in this +directory are in Jinja2. +""" + + +def GenerateConfig(evaluation_context): + return """ +resources: +- type: compute.v1.firewall + name: %(master)s-firewall + properties: + network: https://www.googleapis.com/compute/v1/projects/%(project)s/global/networks/default + sourceRanges: [ "0.0.0.0/0" ] + allowed: + - IPProtocol: tcp + ports: [ "4505", "4506" ] +- type: compute.v1.instance + name: %(master)s + properties: + zone: %(zone)s + machineType: https://www.googleapis.com/compute/v1/projects/%(project)s/zones/%(zone)s/machineTypes/f1-micro + disks: + - deviceName: boot + type: PERSISTENT + boot: true + autoDelete: true + initializeParams: + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/%(project)s/global/networks/default + accessConfigs: + - name: External NAT + type: ONE_TO_ONE_NAT + metadata: + items: + - key: startup-script + value: startup-script-value +""" % {"master": evaluation_context.properties["masterAddress"], + "project": evaluation_context.env["project"], + "zone": evaluation_context.properties["zone"]} diff --git a/expandybird/test/templates/python_template_with_env.yaml b/expandybird/test/templates/python_template_with_env.yaml new file mode 100644 index 000000000..d3bbc26c7 --- /dev/null +++ b/expandybird/test/templates/python_template_with_env.yaml @@ -0,0 +1,8 @@ +imports: ["python_template_with_env.py"] + +resources: +- name: python_template_with_env_name + type: python_template_with_env.py + properties: + masterAddress: master-address + zone: my-zone diff --git a/expandybird/test/templates/python_template_with_env_result.yaml b/expandybird/test/templates/python_template_with_env_result.yaml new file mode 100644 index 000000000..027732c8f --- /dev/null +++ b/expandybird/test/templates/python_template_with_env_result.yaml @@ -0,0 +1,46 @@ +config: + resources: + - name: master-address-firewall + properties: + allowed: + - IPProtocol: tcp + ports: + - '4505' + - '4506' + network: https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default + sourceRanges: + - 0.0.0.0/0 + type: compute.v1.firewall + - name: master-address + properties: + disks: + - autoDelete: true + boot: true + deviceName: boot + initializeParams: + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + type: PERSISTENT + machineType: https://www.googleapis.com/compute/v1/projects/my-project/zones/my-zone/machineTypes/f1-micro + metadata: + items: + - key: startup-script + value: startup-script-value + networkInterfaces: + - accessConfigs: + - name: External NAT + type: ONE_TO_ONE_NAT + network: https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default + zone: my-zone + type: compute.v1.instance +layout: + resources: + - name: python_template_with_env_name + properties: + masterAddress: master-address + zone: my-zone + resources: + - name: master-address-firewall + type: compute.v1.firewall + - name: master-address + type: compute.v1.instance + type: python_template_with_env.py diff --git a/expandybird/test/templates/python_template_with_import.py b/expandybird/test/templates/python_template_with_import.py new file mode 100644 index 000000000..716d2604b --- /dev/null +++ b/expandybird/test/templates/python_template_with_import.py @@ -0,0 +1,20 @@ +# Copyright 2014 Google Inc. All Rights Reserved. + +"""Constructs a VM.""" +import json + +import helpers.common +import helpers.extra.common2 + + +def GenerateConfig(_): + """Generates config of a VM.""" + return """ +resources: +- name: %s + type: compute.v1.instance + properties: + machineSize: %s +""" % (helpers.common.GenerateMachineName( + json.dumps('myFrontend').strip('"'), 'prod'), + helpers.extra.common2.GenerateMachineSize()) diff --git a/expandybird/test/templates/python_template_with_import.yaml b/expandybird/test/templates/python_template_with_import.yaml new file mode 100644 index 000000000..73cce18f1 --- /dev/null +++ b/expandybird/test/templates/python_template_with_import.yaml @@ -0,0 +1,5 @@ +imports: ["python_template_with_import.py", "helpers/common.py", "helpers/common2.py", "helpers/__init__.py"] + +resources: +- name: python_template_with_import_name + type: python_template_with_import.py diff --git a/expandybird/test/templates/python_template_with_import_result.yaml b/expandybird/test/templates/python_template_with_import_result.yaml new file mode 100644 index 000000000..d8e283308 --- /dev/null +++ b/expandybird/test/templates/python_template_with_import_result.yaml @@ -0,0 +1,13 @@ +config: + resources: + - name: myFrontend-prod + properties: + machineSize: big + type: compute.v1.instance +layout: + resources: + - name: python_template_with_import_name + resources: + - name: myFrontend-prod + type: compute.v1.instance + type: python_template_with_import.py diff --git a/expandybird/test/templates/python_template_with_inlinedfile.py b/expandybird/test/templates/python_template_with_inlinedfile.py new file mode 100644 index 000000000..818df3a0e --- /dev/null +++ b/expandybird/test/templates/python_template_with_inlinedfile.py @@ -0,0 +1,22 @@ +# Copyright 2014 Google Inc. All Rights Reserved. + +"""Constructs a VM.""" + +# Verify that both ways of hierarchical imports work. +from helpers import common +import helpers.extra.common2 + + +def GenerateConfig(evaluation_context): + """Generates config of a VM.""" + return """ +resources: +- name: %s + type: compute.v1.instance + properties: + description: %s + machineSize: %s +""" % (common.GenerateMachineName("myFrontend", "prod"), + evaluation_context.imports[ + evaluation_context.properties["description-file"]], + helpers.extra.common2.GenerateMachineSize()) diff --git a/expandybird/test/templates/python_template_with_inlinedfile.yaml b/expandybird/test/templates/python_template_with_inlinedfile.yaml new file mode 100644 index 000000000..8c1d8c38c --- /dev/null +++ b/expandybird/test/templates/python_template_with_inlinedfile.yaml @@ -0,0 +1,7 @@ +imports: ["python_template_with_inlinedfile.py", "helpers/common.py", "helpers/common2.py", "helpers/__init__.py", "description_text.txt"] + +resources: +- name: python_template_with_inlinedfile_name + type: python_template_with_inlinedfile.py + properties: + description-file: description_text.txt diff --git a/expandybird/test/templates/python_template_with_inlinedfile_result.yaml b/expandybird/test/templates/python_template_with_inlinedfile_result.yaml new file mode 100644 index 000000000..92706a0fd --- /dev/null +++ b/expandybird/test/templates/python_template_with_inlinedfile_result.yaml @@ -0,0 +1,21 @@ +config: + resources: + - name: myFrontend-prod + properties: + description: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat + non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + machineSize: big + type: compute.v1.instance +layout: + resources: + - name: python_template_with_inlinedfile_name + properties: + description-file: description_text.txt + resources: + - name: myFrontend-prod + type: compute.v1.instance + type: python_template_with_inlinedfile.py diff --git a/expandybird/test/templates/python_with_exception.py b/expandybird/test/templates/python_with_exception.py new file mode 100644 index 000000000..75fe4c888 --- /dev/null +++ b/expandybird/test/templates/python_with_exception.py @@ -0,0 +1,9 @@ +# Copyright 2014 Google Inc. All Rights Reserved. + +"""A python script that raise exceptions. + +""" + + +def GenerateConfig(unused_context): + raise NameError('No file found') diff --git a/expandybird/test/templates/python_with_exception.yaml b/expandybird/test/templates/python_with_exception.yaml new file mode 100644 index 000000000..fadc23970 --- /dev/null +++ b/expandybird/test/templates/python_with_exception.yaml @@ -0,0 +1,9 @@ +imports: ["python_with_exception.py"] + +resources: +- name: python_with_exception_name + type: python_with_exception.py + properties: + masterAddress: master-address + project: my-project + zone: my-zone diff --git a/expandybird/test/templates/simple.yaml b/expandybird/test/templates/simple.yaml new file mode 100644 index 000000000..5065fa564 --- /dev/null +++ b/expandybird/test/templates/simple.yaml @@ -0,0 +1,18 @@ +resources: +- type: compute.v1.instance + name: vm-created-by-cloud-config-{{ params["deployment"] }} + properties: + zone: test-zone + machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro + disks: + - deviceName: boot + type: PERSISTENT + boot: true + autoDelete: true + initializeParams: + diskName: disk-created-by-cloud-config-test-deployment + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default + + diff --git a/expandybird/test/templates/simple_result.yaml b/expandybird/test/templates/simple_result.yaml new file mode 100644 index 000000000..4353c7194 --- /dev/null +++ b/expandybird/test/templates/simple_result.yaml @@ -0,0 +1,21 @@ +config: + resources: + - name: vm-created-by-cloud-config-{{ params["deployment"] }} + properties: + disks: + - autoDelete: true + boot: true + deviceName: boot + initializeParams: + diskName: disk-created-by-cloud-config-test-deployment + sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 + type: PERSISTENT + machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro + networkInterfaces: + - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default + zone: test-zone + type: compute.v1.instance +layout: + resources: + - name: vm-created-by-cloud-config-{{ params["deployment"] }} + type: compute.v1.instance diff --git a/expandybird/test/templates/use_helper.jinja b/expandybird/test/templates/use_helper.jinja new file mode 100644 index 000000000..c748a8ae4 --- /dev/null +++ b/expandybird/test/templates/use_helper.jinja @@ -0,0 +1,7 @@ +resources: +- name: use-helper + type: foo + properties: + test: {{ properties['barfoo'] }} +- name: use-helper-helper + type: helper.jinja diff --git a/expandybird/test/templates/use_helper.jinja.schema b/expandybird/test/templates/use_helper.jinja.schema new file mode 100644 index 000000000..69986603d --- /dev/null +++ b/expandybird/test/templates/use_helper.jinja.schema @@ -0,0 +1,4 @@ +properties: + barfoo: + type: string + default: Use this schema also diff --git a/expandybird/test/templates/use_helper.yaml b/expandybird/test/templates/use_helper.yaml new file mode 100644 index 000000000..818b0d284 --- /dev/null +++ b/expandybird/test/templates/use_helper.yaml @@ -0,0 +1,3 @@ +resources: +- name: use-helper + type: use_helper.jinja diff --git a/expandybird/test/templates/use_helper_result.yaml b/expandybird/test/templates/use_helper_result.yaml new file mode 100644 index 000000000..51c17afc8 --- /dev/null +++ b/expandybird/test/templates/use_helper_result.yaml @@ -0,0 +1,26 @@ +config: + resources: + - name: use-helper + properties: + test: Use this schema also + type: foo + - name: helper + properties: + test: Use this schema + type: bar +layout: + resources: + - name: use-helper + properties: + barfoo: Use this schema also + resources: + - name: use-helper + type: foo + - name: use-helper-helper + properties: + foobar: Use this schema + resources: + - name: helper + type: bar + type: helper.jinja + type: use_helper.jinja