Skip to content

Commit

Permalink
CRAYSAT-1896: Add Jinja2 template support to more bootprep fields
Browse files Browse the repository at this point in the history
Add support for Jinja2 template rendering to several more properties in
the `sat bootprep` input file. The following properties now support
Jinja2 templating:

- configurations[].layers[].git.commit
- configurations[].layers[].git.url
- configurations[].layers[].product.commit
- All properties under images[].base.ims
- session_templates[].image.ims.id

Allowing Jinja2 template rendering for these fields is helpful in
writing automated test cases for `sat bootprep` that can run on any
system. These values will vary from system to system, so it's helpful to
pass this information in to the bootprep input file as a variable.

Test Description:
Added unit tests pass. Will also be tested on a system with bootprep
input files that use Jinja templates in these properties.
  • Loading branch information
haasken-hpe committed Nov 25, 2024
1 parent 054e7bd commit 49b5c01
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 2 deletions.
3 changes: 3 additions & 0 deletions sat/cli/bootprep/input/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ class GitInputConfigurationLayer(InputConfigurationLayer):
and a branch or a commit hash.
"""
@property
@jinja_rendered
def clone_url(self):
# The 'url' property is required by the schema
return self.layer_data['git']['url']
Expand All @@ -197,6 +198,7 @@ def branch(self):
return self.layer_data['git'].get('branch')

@property
@jinja_rendered
def commit(self):
# The 'commit' property is optional
return self.layer_data['git'].get('commit')
Expand Down Expand Up @@ -271,6 +273,7 @@ def branch(self):
return self.layer_data['product'].get('branch')

@cached_property
@jinja_rendered
def commit(self):
# The 'commit' property is optional
return self.layer_data['product'].get('commit')
Expand Down
1 change: 1 addition & 0 deletions sat/cli/bootprep/input/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@ def base_is_recipe(self):
class IMSInputImageV2(IMSInputImage):
"""An input image that specifies a base IMS image/recipe under the 'ims' property under the 'base' property."""
@property
@jinja_rendered
def ims_data(self):
"""dict: the data that defines the base IMS image or recipe"""
return self.image_data['base']['ims']
Expand Down
1 change: 1 addition & 0 deletions sat/cli/bootprep/input/session_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ def ims_image_name(self):
return get_val_by_path(self.data, 'image.ims.name')

@property
@jinja_rendered
def ims_image_id(self):
"""str or None: the id specified for the IMS image, or None if not specified"""
return get_val_by_path(self.data, 'image.ims.id')
Expand Down
43 changes: 42 additions & 1 deletion tests/cli/bootprep/input/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,18 @@ def test_clone_url_property(self):
self.mock_cfs_client)
self.assertEqual(self.branch_layer_data['git']['url'], layer.clone_url)

def test_clone_url_property_jinja_template(self):
"""Test the clone_url property when the clone URL uses Jinja2 templating"""
repo_name = 'foo-config-management'
self.jinja_env.globals['test'] = {
'repo_name': repo_name
}

self.branch_layer_data['git']['url'] = 'https://api-gw-service-nmn.local/vcs/cray/{{test.repo_name}}.git'
layer = GitInputConfigurationLayer(self.branch_layer_data, 0, self.jinja_env,
self.mock_cfs_client)
self.assertEqual(f'https://api-gw-service-nmn.local/vcs/cray/{repo_name}.git', layer.clone_url)

def test_branch_property_present(self):
"""Test the branch property when the branch is in the layer data"""
layer = GitInputConfigurationLayer(self.branch_layer_data, 0, self.jinja_env,
Expand Down Expand Up @@ -231,6 +243,20 @@ def test_commit_property_not_present(self):
self.mock_cfs_client)
self.assertIsNone(layer.commit)

def test_commit_property_jinja_template(self):
"""Test the commit property when the commit uses Jinja2 templating"""
commit_hash = 'abc1234'
self.jinja_env.globals['test'] = {
'commit_hash': commit_hash
}

self.commit_layer_data['git']['commit'] = '{{test.commit_hash}}'

layer = GitInputConfigurationLayer(self.commit_layer_data, 0, self.jinja_env,
self.mock_cfs_client)

self.assertEqual(commit_hash, layer.commit)

def test_validate_playbook_cfs_v3(self):
"""Test the validate_playbook_specified_with_cfs_v3 method with a CFSV3Client and present playbook"""
mock_cfs_v3_client = Mock(spec=CFSV3Client)
Expand Down Expand Up @@ -423,7 +449,7 @@ def test_branch_property_not_present(self):

def test_branch_property_jinja_template(self):
"""Test the branch property when the branch uses Jinja2 templating"""
# Have to double the literal brackets that make up the Jinja2 variable reference
# Use non f-string to include the literal brackets that make up the Jinja2 variable reference
self.branch_layer_data['product']['branch'] = 'integration-{{' + f'{self.product_name}.version' + '}}'

layer = ProductInputConfigurationLayer(self.branch_layer_data, 0, self.jinja_env,
Expand All @@ -439,6 +465,21 @@ def test_commit_property_not_present(self):
"""Test the commit property when a branch is in the layer data"""
self.assertIsNone(self.branch_layer.commit)

def test_commit_property_jinja_template(self):
"""Test the commit property when the commit uses Jinja2 templating"""
commit_hash = 'abc1234'
self.jinja_env.globals['test'] = {
'commit_hash': commit_hash
}

# Use non f-string to include the literal brackets that make up the Jinja2 variable reference
self.commit_layer_data['product']['commit'] = '{{test.commit_hash}}'

layer = ProductInputConfigurationLayer(self.commit_layer_data, 0, self.jinja_env,
self.mock_cfs_client, self.mock_product_catalog)

self.assertEqual(commit_hash, layer.commit)

def test_commit_property_resolve_branches(self):
"""Test the commit property when resolving branches and commit specified in the input file"""
with self.patch_resolve_branches(True):
Expand Down
52 changes: 51 additions & 1 deletion tests/cli/bootprep/input/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,60 @@

from sat.apiclient.ims import IMSClient
from sat.cli.bootprep.errors import ImageCreateError
from sat.cli.bootprep.input.image import ProductInputImage
from sat.cli.bootprep.input.image import IMSInputImageV2, ProductInputImage
from sat.cli.bootprep.input.instance import InputInstance


class TestIMSInputImageV2(unittest.TestCase):
"""Tests for IMSInputImageV2"""
def setUp(self):
self.mock_input_instance = Mock(spec=InputInstance)
self.mock_product_catalog = Mock(spec=ProductCatalog)
self.mock_cfs_client = Mock(spec=CFSClientBase)
self.mock_ims_client = Mock(spec=IMSClient)
self.jinja_env = Environment()

def test_ims_data(self):
"""Test the ims_data property of IMSInputImageV2"""
base_type = 'image'
image_id = 'abcdef12'
input_data = {
'name': 'my-image',
'base': {
'ims': {
'type': base_type,
'id': image_id
}
}
}
ims_input_image = IMSInputImageV2(input_data, 0, self.mock_input_instance, self.jinja_env,
self.mock_product_catalog, self.mock_ims_client,
self.mock_cfs_client)
self.assertEqual({'type': base_type, 'id': image_id}, ims_input_image.ims_data)

def test_ims_data_jinja_rendered(self):
"""Test the ims_data property of IMSInputImageV2 when it uses variable substitution"""
image_id = '1234abcd'
base_type = 'recipe'
self.jinja_env.globals['test'] = {
'id': image_id,
'type': base_type
}
input_data = {
'name': 'my-image',
'base': {
'ims': {
'type': '{{test.type}}',
'id': '{{test.id}}'
}
}
}
ims_input_image = IMSInputImageV2(input_data, 0, self.mock_input_instance, self.jinja_env,
self.mock_product_catalog, self.mock_ims_client,
self.mock_cfs_client)
self.assertEqual({'type': base_type, 'id': image_id}, ims_input_image.ims_data)


class TestProductInputImage(unittest.TestCase):
"""Tests for ProductInputImage"""

Expand Down
60 changes: 60 additions & 0 deletions tests/cli/bootprep/input/test_session_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,66 @@ def get_input_and_expected_bos_data(self, name='my-session-template',

return input_data, bos_payload

def test_ims_image_name(self):
"""Test the ims_image_name property"""
image_name = 'my-example-image'
input_data, _ = self.get_input_and_expected_bos_data(image_name=image_name)
input_session_template = self.simplified_session_template_v2(
input_data, self.input_instance, 0, self.jinja_env,
self.bos_client, self.cfs_client, self.ims_client
)
self.assertEqual(image_name, input_session_template.ims_image_name)

def test_ims_image_name_jinja_rendered(self):
"""Test the ims_image_name property with Jinja rendering"""
image_name = 'some-image'
self.jinja_env.globals['test'] = {
'image_name': image_name
}
image_property = '{{test.image_name}}'
input_data, _ = self.get_input_and_expected_bos_data(image_name=image_property)
input_session_template = self.simplified_session_template_v2(
input_data, self.input_instance, 0, self.jinja_env,
self.bos_client, self.cfs_client, self.ims_client
)
self.assertEqual(image_name, input_session_template.ims_image_name)

def test_ims_image_id(self):
"""Test the ims_image_id property"""
image_id = 'abcdef12345'
input_data = {
'name': 'my-session-template',
'image': {'ims': {'id': image_id}},
'configuration': 'my-configuration',
'bos_parameters': {}
}

input_session_template = self.simplified_session_template_v2(
input_data, self.input_instance, 0, self.jinja_env,
self.bos_client, self.cfs_client, self.ims_client
)
self.assertEqual(image_id, input_session_template.ims_image_id)

def test_ims_image_id_jinja_rendered(self):
"""Test the ims_image_id property with Jinja rendering"""
image_id = 'abcdef12345'
self.jinja_env.globals['test'] = {
'image_id': image_id
}
id_property = '{{test.image_id}}'
input_data = {
'name': 'my-session-template',
'image': {'ims': {'id': id_property}},
'configuration': 'my-configuration',
'bos_parameters': {}
}

input_session_template = self.simplified_session_template_v2(
input_data, self.input_instance, 0, self.jinja_env,
self.bos_client, self.cfs_client, self.ims_client
)
self.assertEqual(image_id, input_session_template.ims_image_id)

def test_get_create_item_data_no_arch(self):
"""Test get_create_item_data method with no architecture specified"""
input_data, expected_bos_data = self.get_input_and_expected_bos_data()
Expand Down

0 comments on commit 49b5c01

Please sign in to comment.