diff --git a/sample/missing-properties.yml b/sample/missing-properties.yml index ebb4d56b..5b854e4c 100644 --- a/sample/missing-properties.yml +++ b/sample/missing-properties.yml @@ -5,6 +5,9 @@ city: New Jersey country: country_us username: SpongeBob password: { 'secret': SquarePants } +my_creds: + identity: username + password: p4ssw0rd app2_persistence_store_type: none # p-mysql # Works, but turned off because this is not deployed in our PCF environments dynamic_service_plans: - name: plan1 diff --git a/sample/tile.yml b/sample/tile.yml index 93e0a21a..9f9f58a8 100644 --- a/sample/tile.yml +++ b/sample/tile.yml @@ -46,6 +46,10 @@ forms: default: true - name: country_elsewhere label: Elsewhere + - name: my_creds + type: simple_credentials + label: Some Credentials + description: Some credentials - name: account-info-1 label: Account Info description: Example Account Information Form diff --git a/tile_generator/config.py b/tile_generator/config.py index 0eb40398..56e006c3 100644 --- a/tile_generator/config.py +++ b/tile_generator/config.py @@ -55,7 +55,8 @@ # ways depending on the package type. We normalize all these so that the rest of # the code can rely on a single format # -# Normalize Jobs - Ensure that job type and template are set for every job +# Normalize Jobs - Ensure that job type, template, and properties are set for +# every job package_types = { 'app': { 'flags': [ 'is_cf', 'requires_cf_cli', 'is_app' ] }, @@ -145,6 +146,7 @@ def normalize_jobs(self): for job in release.get('jobs', []): job['type'] = job.get('type', job['name']) job['template'] = job.get('template', job['type']) + job['properties'] = job.get('properties', {}) def release_for_package(self, package): release_name = package['name'] if package.get('is_bosh_release', False) else self['name'] diff --git a/tile_generator/config_unittest.py b/tile_generator/config_unittest.py index 039faeee..b38f0896 100644 --- a/tile_generator/config_unittest.py +++ b/tile_generator/config_unittest.py @@ -324,6 +324,17 @@ def test_purge_service_broker_is_overridden(self): config.add_defaults() self.assertFalse(config['purge_service_brokers']) + def test_normalize_jobs_default_job_properties(self): + config = Config({ + 'releases': [{ + 'jobs': [{ + 'name': 'my-job' + }] + }] + }) + config.normalize_jobs() + self.assertEqual(config['releases'][0]['jobs'][0]['properties'], {}) + @mock.patch('os.path.getsize') class TestVMDiskSize(unittest.TestCase): def test_min_vm_disk_size(self, mock_getsize): diff --git a/tile_generator/template.py b/tile_generator/template.py index 5597a409..048ccd13 100644 --- a/tile_generator/template.py +++ b/tile_generator/template.py @@ -55,6 +55,41 @@ def render_plans_json(input): ' %>\n' ' export ' + input.upper() + '=<%= Shellwords.escape JSON.dump(plans) %>') +def render_property_value(property): + complex_types = ( + 'simple_credentials', + 'rsa_cert_credentials', + 'rsa_pkey_credentials', + 'salted_credentials', + 'selector', + ) + if property['type'] in complex_types: + return 'properties.{}.marshal_dump.to_json'.format(property['name']) + else: + return 'properties.' + property['name'] + +def render_property(property): + """Render a property for bosh manifest, according to its type.""" + # This ain't the prettiest thing, but it should get the job done. + # I don't think we have anything more elegant available at bosh-manifest-generation time. + # See https://docs.pivotal.io/partners/product-template-reference.html for list + property_fields = { + 'simple_credentials': ['identity', 'password'], + 'rsa_cert_credentials': [ 'private_key_pem', 'cert_pem', 'public_key_pem', 'cert_and_private_key_pems' ], + 'rsa_pkey_credentials': [ 'private_key_pem', 'public_key_pem', 'public_key_openssh', 'public_key_fingerprint' ], + 'salted_credentials': [ 'salt', 'identity', 'password' ], + 'selector': [ 'value', 'selected_option', 'nested context' ], + } + if 'type' in property and property['type'] in property_fields: + fields = {} + for field in property_fields[property['type']]: + fields[field] = '(( .properties.{}.{} ))'.format(property['name'], field) + out = { property['name']: fields } + else: + # Other types use 'value'. + out = { property['name']: '(( .properties.{}.value ))'.format(property['name']) } + return out + @contextfilter def render(context, input): template = Template(input) @@ -67,6 +102,8 @@ def render(context, input): TEMPLATE_ENVIRONMENT.filters['yaml'] = render_yaml TEMPLATE_ENVIRONMENT.filters['shell_string'] = render_shell_string TEMPLATE_ENVIRONMENT.filters['plans_json'] = render_plans_json +TEMPLATE_ENVIRONMENT.filters['property'] = render_property +TEMPLATE_ENVIRONMENT.filters['property_value'] = render_property_value TEMPLATE_ENVIRONMENT.filters['render'] = render def render(target_path, template_file, config): diff --git a/tile_generator/template_unittest.py b/tile_generator/template_unittest.py new file mode 100644 index 00000000..b9066ed8 --- /dev/null +++ b/tile_generator/template_unittest.py @@ -0,0 +1,41 @@ +# tile-generator +# +# Copyright (c) 2015-Present Pivotal Software, Inc. 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. + +from __future__ import absolute_import, division, print_function +import unittest +from . import template + +class TestPropertyFilter(unittest.TestCase): + def test_property_filter_simple_property(self): + property = { + 'name': 'prop', + 'type': 'string', + } + result = template.render_property(property) + self.assertEqual(result, {'prop': '(( .properties.prop.value ))' }) + + def test_property_filter_complex_property(self): + property = { + 'name': 'prop', + 'type': 'simple_credentials', + } + result = template.render_property(property) + self.assertEqual(result, { + 'prop': { + 'identity': '(( .properties.prop.identity ))', + 'password': '(( .properties.prop.password ))', + } + }) diff --git a/tile_generator/templates/jobs/deploy-all.sh.erb b/tile_generator/templates/jobs/deploy-all.sh.erb index 7039e2c4..2be2a0f3 100644 --- a/tile_generator/templates/jobs/deploy-all.sh.erb +++ b/tile_generator/templates/jobs/deploy-all.sh.erb @@ -45,7 +45,7 @@ function import_opsmgr_variables() { {% endfor %} {% for property in context.all_properties %} - export {{ property.name | upper }}={{ ( 'properties.' + property.name ) | shell_string }} + export {{ property.name | upper }}={{ property | property_value | shell_string }} {% endfor %} {% for package in context.packages if package.type == 'docker-bosh' %} export {{ package.name | upper }}_HOST={{ ( 'properties.' + package.name + '.host' ) | shell_string }} diff --git a/tile_generator/templates/tile/metadata.yml b/tile_generator/templates/tile/metadata.yml index 17e60639..876fc006 100644 --- a/tile_generator/templates/tile/metadata.yml +++ b/tile_generator/templates/tile/metadata.yml @@ -479,7 +479,7 @@ job_types: user: (( .{{ job.name }}.app_credentials.identity )) password: (( .{{ job.name }}.app_credentials.password )) {% for property in all_properties %} - {{ property.name }}: (( .properties.{{ property.name }}.value )) + {{ property | property | yaml | indent }} {% endfor %} {% if dynamic_service_plans %} dynamic_service_plans: (( .properties.dynamic_service_plans.value ))