def test_basic(self, cfngin_context, runway_context): """Test resolution of a basic lookup.""" name = "/test/param" value = "test value" cfngin_stubber = cfngin_context.add_stubber("ssm") runway_stubber = runway_context.add_stubber("ssm") cfngin_var = Variable("test_var", "${ssm %s}" % name, variable_type="cfngin") runway_var = Variable("test_var", "${ssm %s}" % name, variable_type="runway") for stubber in [cfngin_stubber, runway_stubber]: stubber.add_response( "get_parameter", get_parameter_response(name, value), get_parameter_request(name), ) with cfngin_stubber as cfn_stub, runway_stubber as rw_stub: cfngin_var.resolve(context=cfngin_context) assert cfngin_var.value == value runway_var.resolve(context=runway_context) assert runway_var.value == value cfn_stub.assert_no_pending_responses() rw_stub.assert_no_pending_responses()
def test_resolve_variables(self): """Test resolve variables.""" class TestBlueprint(Blueprint): """Test blueprint.""" VARIABLES = { "Param1": { "default": 0, "type": int }, "Param2": { "type": str }, } blueprint = TestBlueprint(name="test", context=MagicMock()) variables = [ Variable("Param1", 1, 'cfngin'), Variable("Param2", "${output other-stack::Output}", 'cfngin'), Variable("Param3", 3, 'cfngin'), ] variables[1]._value._resolve("Test Output") blueprint.resolve_variables(variables) self.assertEqual(blueprint.resolved_variables["Param1"], 1) self.assertEqual(blueprint.resolved_variables["Param2"], "Test Output") self.assertIsNone(blueprint.resolved_variables.get("Param3"))
def test_get_parameter_values(self): """Test get parameter values.""" class TestBlueprint(Blueprint): """Test blueprint.""" VARIABLES = { "Param1": { "type": int }, "Param2": { "type": CFNString }, } blueprint = TestBlueprint(name="test", context=MagicMock()) variables = [ Variable("Param1", 1, 'cfngin'), Variable("Param2", "Value", 'cfngin') ] blueprint.resolve_variables(variables) variables = blueprint.get_variables() self.assertEqual(len(variables), 2) parameters = blueprint.get_parameter_values() self.assertEqual(len(parameters), 1) self.assertEqual(parameters["Param2"], "Value")
def test_basic(self, cfngin_context, runway_context): """Test resolution of a basic lookup.""" name = '/test/param' value = 'test value' cfngin_stubber = cfngin_context.add_stubber('ssm') runway_stubber = runway_context.add_stubber('ssm') cfngin_var = Variable('test_var', '${ssm %s}' % name, variable_type='cfngin') runway_var = Variable('test_var', '${ssm %s}' % name, variable_type='runway') for stubber in [cfngin_stubber, runway_stubber]: stubber.add_response('get_parameter', get_parameter_response(name, value), get_parameter_request(name)) with cfngin_stubber as cfn_stub, runway_stubber as rw_stub: cfngin_var.resolve(context=cfngin_context) assert cfngin_var.value == value runway_var.resolve(context=runway_context) assert runway_var.value == value cfn_stub.assert_no_pending_responses() rw_stub.assert_no_pending_responses()
def to_json(self, variables=None): """Render the blueprint and return the template in json form. Args: variables (Optional[Dict[str, Any]]): Dictionary providing/overriding variable values. Returns: str: Rhe rendered CFN JSON template. """ variables_to_resolve = [] if variables: for key, value in variables.items(): variables_to_resolve.append(Variable(key, value, 'cfngin')) for k in self.get_parameter_definitions(): if not variables or k not in variables: # The provided value for a CFN parameter has no effect in this # context (generating the CFN template), so any string can be # provided for its value - just needs to be something variables_to_resolve.append( Variable(k, 'unused_value', 'cfngin')) self.resolve_variables(variables_to_resolve) return self.render_template()[1]
def test_dependencies(self, mocker: MockerFixture) -> None: """Test dependencies.""" assert Variable("Param", "val").dependencies == set() mocker.patch.object(VariableValue, "parse_obj", return_value=MagicMock(dependencies={"test"})) assert Variable("Param", "val").dependencies == {"test"}
def test_resolve_variable_allowed_values(self): """Test resolve variable allowed values.""" var_name = "testVar" var_def = {"type": str, "allowed_values": ["allowed"]} provided_variable = Variable(var_name, "not_allowed", "cfngin") blueprint_name = "testBlueprint" with self.assertRaises(ValueError): resolve_variable(var_name, var_def, provided_variable, blueprint_name) provided_variable = Variable(var_name, "allowed", "cfngin") value = resolve_variable(var_name, var_def, provided_variable, blueprint_name) self.assertEqual(value, "allowed")
def test_j2_to_json(self): """Verify jinja2 template parsing.""" expected_json = json.dumps( { "AWSTemplateFormatVersion": "2010-09-09", "Description": "TestTemplate", "Parameters": { "Param1": { "Type": "String" }, "Param2": { "Default": "default", "Type": "CommaDelimitedList" }, }, "Resources": { "Dummy": { "Type": "AWS::CloudFormation::WaitConditionHandle" } }, "Outputs": { "DummyId": { "Value": "dummy-bar-param1val-foo-1234" } }, }, sort_keys=True, indent=4, ) blueprint = RawTemplateBlueprint( name="stack1", context=mock_context( extra_config_args={ "stacks": [{ "name": "stack1", "template_path": "unused", "variables": { "Param1": "param1val", "bar": "foo" }, }] }, environment={"foo": "bar"}, ), raw_template_path=RAW_J2_TEMPLATE_PATH, ) blueprint.resolve_variables([ Variable("Param1", "param1val", "cfngin"), Variable("bar", "foo", "cfngin"), ]) self.assertEqual(expected_json, blueprint.to_json())
def test_resolve_variable_allowed_values() -> None: """Test resolve_variable.""" var_name = "testVar" var_def: BlueprintVariableTypeDef = {"type": str, "allowed_values": ["allowed"]} with pytest.raises(ValueError): resolve_variable( var_name, var_def, Variable(var_name, "not_allowed", "cfngin"), "test" ) assert ( resolve_variable( var_name, var_def, Variable(var_name, "allowed", "cfngin"), "test" ) == "allowed" )
def test_resolve_lookups_list_unknown_lookup(self): """Test resolve lookups list unknown lookup.""" with self.assertRaises(UnknownLookupType): Variable("MyVar", [ "${bad_lookup foo}", "random string", ])
def test_resolve_variable_provided_not_resolved(mocker: MockerFixture) -> None: """Test resolve_variable.""" mocker.patch("runway.variables.CFNGIN_LOOKUP_HANDLERS", {"mock": Mock()}) with pytest.raises(UnresolvedBlueprintVariable): resolve_variable( "name", {"type": str}, Variable("name", "${mock abc}", "cfngin"), "test" )
def test_value_lookup_to_dict(self): """Variable lookups should be resolvable to a dict value.""" var = Variable('test', '${env dict_val}', 'runway') var.resolve(CONTEXT) # need to use data prop to compare the MutableMap contents self.assertEqual(var.value.data, VALUE['dict_val'])
def test_default(self, runway_context): """Test resolution of a default value.""" name = "/test/param" value = "test value" stubber = runway_context.add_stubber("ssm") var = Variable( "test_var", "${ssm /test/invalid::load=json, default=${ssm %s}}" % name, variable_type="runway", ) stubber.add_response( "get_parameter", get_parameter_response(name, value), get_parameter_request(name), ) stubber.add_client_error( "get_parameter", "ParameterNotFound", expected_params=get_parameter_request("/test/invalid"), ) with stubber as stub: var.resolve(context=runway_context) assert var.value == value stub.assert_no_pending_responses()
def test_resolve_variables_lookup_returns_non_string(self): """Test resolve variables lookup returns non string.""" class TestBlueprint(Blueprint): """Test blueprint.""" VARIABLES = { "Param1": { "type": list }, } def return_list_something(*_args, **_kwargs): """Return list something.""" return ["something"] register_lookup_handler("custom", return_list_something) blueprint = TestBlueprint(name="test", context=MagicMock()) variables = [ Variable("Param1", "${custom non-string-return-val}", 'cfngin') ] for var in variables: var._value.resolve({}, {}) blueprint.resolve_variables(variables) self.assertEqual(blueprint.resolved_variables["Param1"], ["something"])
def test_resolve_variables_lookup_returns_troposphere_obj(self): """Test resolve variables lookup returns troposphere obj.""" class TestBlueprint(Blueprint): """Test blueprint.""" VARIABLES = { "Param1": { "type": Base64 }, } def return_obj(*_args, **_kwargs): """Return object.""" return Base64("test") register_lookup_handler("custom", return_obj) blueprint = TestBlueprint(name="test", context=MagicMock()) variables = [ Variable("Param1", "${custom non-string-return-val}", 'cfngin') ] for var in variables: var._value.resolve({}, {}) blueprint.resolve_variables(variables) self.assertEqual(blueprint.resolved_variables["Param1"].data, Base64("test").data)
def test_env_vars_config_unresolved( self, fx_deployments: YamlLoaderDeployment, mocker: MockerFixture, runway_context: MockRunwayContext, ) -> None: """Test env_vars_config unresolved.""" expected = {"key": "val"} mocker.patch.object(Deployment, "_Deployment__merge_env_vars", MagicMock(return_value=None)) mocker.patch.object( RunwayDeploymentDefinition, "env_vars", PropertyMock(side_effect=[ UnresolvedVariable( Variable("test", "something", variable_type="runway"), MagicMock(), ), expected, ]), create=True, ) mocker.patch.object(RunwayDeploymentDefinition, "_env_vars", PropertyMock(), create=True) raw_deployment: Dict[str, Any] = cast(Dict[str, Any], fx_deployments.get("min_required")) deployment = RunwayDeploymentDefinition.parse_obj(raw_deployment) obj = Deployment(context=runway_context, definition=deployment) assert obj.env_vars_config == expected obj.definition._env_vars.resolve.assert_called_once()
def test_simple_lookup(self) -> None: """Test simple lookup.""" var = Variable("Param1", "${test query}") assert isinstance(var._value, VariableValueLookup) var.resolve(MagicMock(), MagicMock()) assert var.resolved is True assert var.value == "resolved"
def test_value_complex_str(self): """Multiple lookups should be usable within a single string.""" var = Variable('test', 'the ${env what} was ${env test}ful', 'runway') var.resolve(CONTEXT) self.assertEqual( var.value, 'the {} was {}ful'.format(VALUE['what'], VALUE['test']))
def test_variable_replace_lookups_mixed(self): """Test variable replace lookups mixed.""" value = { "something": [ "${output fakeStack::FakeOutput}", "other", ], "here": { "other": "${output fakeStack::FakeOutput2}", "same": "${output fakeStack::FakeOutput}", "mixed": "something:${output fakeStack::FakeOutput3}", }, } var = Variable("Param1", value) var._value["something"][0]._resolve("resolved") var._value["here"]["other"]._resolve("resolved2") var._value["here"]["same"]._resolve("resolved") var._value["here"]["mixed"][1]._resolve("resolved3") self.assertEqual( var.value, { "something": [ "resolved", "other", ], "here": { "other": "resolved2", "same": "resolved", "mixed": "something:resolved3", }, })
def test_value_complex_str(self): """Multiple lookups should be usable within a single string.""" var = Variable("test", "the ${env what} was ${env test}ful", "runway") var.resolve(CONTEXT) self.assertEqual( var.value, "the {} was {}ful".format(VALUE["what"], VALUE["test"]))
def test_troposphere(self, cfngin_context): """Test with troposphere object like returned from lambda hook.""" bucket = 'test-bucket' s3_key = 'lambda_functions/my_function' cfngin_context.set_hook_data( 'lambda', {'my_function': Code(S3Bucket=bucket, S3Key=s3_key)}) var_bucket = Variable('test', '${hook_data lambda.my_function::' 'load=troposphere,get=S3Bucket}', variable_type='cfngin') var_key = Variable('test', '${hook_data lambda.my_function::get=S3Key}', variable_type='cfngin') var_bucket.resolve(cfngin_context) var_key.resolve(cfngin_context) assert var_bucket.value == bucket assert var_key.value == s3_key
def resolve_troposphere_var(tpe: Any, value: Any, **kwargs: Any) -> Any: """Resolve troposphere var.""" return resolve_variable( "name", {"type": TroposphereType(tpe, **kwargs)}, Variable("name", value, "cfngin"), "test", )
def test_init(self, variable_type: VariableTypeLiteralTypeDef) -> None: """Test __init__.""" obj = Variable("Param", "val", variable_type) assert obj.name == "Param" assert obj.variable_type == variable_type assert obj._raw_value == "val" assert obj._value.value == "val" assert obj._value.variable_type == variable_type
def test_value_simple_str(self): """Test value for a simple string without lookups.""" var = Variable('test', 'success') self.assertTrue(var.resolved, msg='when no lookup is used, it should ' 'be automatically marked as resolved') self.assertEqual(var.value, 'success')
def test_resolve_lookups_list_failed_variable_lookup(self): """Test resolve lookups list failed variable lookup.""" variable = Variable("MyVar", [ "random string", "${output foo::bar}", "random string", ]) self.resolve_lookups_with_output_handler_raise_valueerror(variable)
def _resolve_troposphere_var(self, tpe, value, **kwargs): """Resolve troposphere var.""" var_name = "testVar" var_def = {"type": TroposphereType(tpe, **kwargs)} provided_variable = Variable(var_name, value, "cfngin") blueprint_name = "testBlueprint" return resolve_variable(var_name, var_def, provided_variable, blueprint_name)
def test_handle(self, cfngin_context): """Test handle with simple usage.""" cfngin_context.set_hook_data('fake_hook', {'nested': { 'result': 'good' }}) var_top = Variable('test', '${hook_data fake_hook}', variable_type='cfngin') var_nested = Variable('test', '${hook_data fake_hook.nested.result}', variable_type='cfngin') var_top.resolve(cfngin_context) var_nested.resolve(cfngin_context) assert var_top.value == {'nested': {'result': 'good'}} assert var_nested.value == 'good'
def test_default(self, cfngin_context): """Test handle with a default value.""" cfngin_context.set_hook_data('fake_hook', {'nested': { 'result': 'good' }}) var_top = Variable('test', '${hook_data bad_hook::default=something}', variable_type='cfngin') var_nested = Variable('test', '${hook_data fake_hook.bad.' 'result::default=something,load=json,get=key}', variable_type='cfngin') var_top.resolve(cfngin_context) var_nested.resolve(cfngin_context) assert var_top.value == 'something' assert var_nested.value == 'something'
def test_create_template_passes(self) -> None: """Test create template passes.""" ctx = CfnginContext() blueprint = Repositories("test_repo", ctx) blueprint.resolve_variables( [Variable("Repositories", ["repo1", "repo2"], "cfngin")]) blueprint.create_template() self.assertRenderedBlueprint(blueprint)
def test_create_template_passes(self): """Test create template passes.""" ctx = Context({'namespace': 'test'}) blueprint = Repositories('test_repo', ctx) blueprint.resolve_variables( [Variable('Repositories', ["repo1", "repo2"], 'cfngin')]) blueprint.create_template() self.assertRenderedBlueprint(blueprint)