def test_execute_plan_no_persist(self) -> None: """Test execute plan with no persistent graph.""" context = CfnginContext(config=self.config) context.put_persistent_graph = mock.MagicMock() vpc = Stack(definition=generate_definition("vpc", 1), context=context) bastion = Stack( definition=generate_definition("bastion", 1, requires=[vpc.name]), context=context, ) calls: List[str] = [] def _launch_stack(stack: Stack, status: Optional[Status] = None) -> Status: calls.append(stack.fqn) return COMPLETE graph = Graph.from_steps( [Step(vpc, fn=_launch_stack), Step(bastion, fn=_launch_stack)] ) plan = Plan(description="Test", graph=graph, context=context) plan.execute(walk) self.assertEqual(calls, ["namespace-vpc.1", "namespace-bastion.1"]) context.put_persistent_graph.assert_not_called()
def test_execute_plan_graph_locked(self) -> None: """Test execute plan with locked persistent graph.""" context = CfnginContext(config=self.config) context._persistent_graph = Graph.from_dict({"stack1": []}, context) context._persistent_graph_lock_code = "1111" plan = Plan(description="Test", graph=Graph(), context=context) print(plan.locked) with self.assertRaises(PersistentGraphLocked): plan.execute()
def mock_context( namespace: str = "default", extra_config_args: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> CfnginContext: """Mock context.""" config_args = {"namespace": namespace} if extra_config_args: config_args.update(extra_config_args) config = CfnginConfig.parse_obj(config_args) if kwargs.get("environment"): return CfnginContext(config=config, **kwargs) return CfnginContext(config=config, environment={}, **kwargs)
def test_plan_targeted(self) -> None: """Test plan targeted.""" context = CfnginContext(config=self.config) vpc = Stack(definition=generate_definition("vpc", 1), context=context) bastion = Stack( definition=generate_definition("bastion", 1, requires=[vpc.name]), context=context, ) context.stack_names = [vpc.name] graph = Graph.from_steps([Step(vpc, fn=None), Step(bastion, fn=None)]) plan = Plan(description="Test", graph=graph, context=context) self.assertEqual({vpc.name: set()}, plan.graph.to_dict())
def setUp(self) -> None: """Run before tests.""" self.provider = MagicMock() self.context = CfnginContext(parameters={ "namespace": "test", "env_var": "val_in_env" })
def _get_context( self, extra_config_args: Optional[Dict[str, Any]] = None, **kwargs: Any ) -> CfnginContext: """Get context.""" config = { "namespace": "namespace", "stacks": [ {"name": "vpc", "template_path": "."}, {"name": "bastion", "requires": ["vpc"], "template_path": "."}, { "name": "instance", "requires": ["vpc", "bastion"], "template_path": ".", }, { "name": "db", "requires": ["instance", "vpc", "bastion"], "template_path": ".", }, {"name": "other", "requires": ["db"], "template_path": "."}, ], } if extra_config_args: config.update(extra_config_args) return CfnginContext(config=CfnginConfig.parse_obj(config), **kwargs)
def test_execute_plan(self) -> None: """Test execute plan.""" context = CfnginContext(config=self.config) context.put_persistent_graph = mock.MagicMock() vpc = Stack(definition=generate_definition("vpc", 1), context=context) bastion = Stack( definition=generate_definition("bastion", 1, requires=[vpc.name]), context=context, ) removed = Stack( definition=generate_definition("removed", 1, requires=[]), context=context ) context._persistent_graph = Graph.from_steps([Step(removed)]) calls: List[str] = [] def _launch_stack(stack: Stack, status: Optional[Status] = None) -> Status: calls.append(stack.fqn) return COMPLETE def _destroy_stack(stack: Stack, status: Optional[Status] = None) -> Status: calls.append(stack.fqn) return COMPLETE graph = Graph.from_steps( [ Step(removed, fn=_destroy_stack), Step(vpc, fn=_launch_stack), Step(bastion, fn=_launch_stack), ] ) plan = Plan(description="Test", graph=graph, context=context) plan.context._persistent_graph_lock_code = plan.lock_code # type: ignore plan.execute(walk) # the order these are appended changes between python2/3 self.assertIn("namespace-vpc.1", calls) self.assertIn("namespace-bastion.1", calls) self.assertIn("namespace-removed.1", calls) context.put_persistent_graph.assert_called() # order is different between python2/3 so can't compare dicts result_graph_dict = context.persistent_graph.to_dict() # type: ignore self.assertEqual(2, len(result_graph_dict)) self.assertEqual(set(), result_graph_dict.get("vpc.1")) self.assertEqual(set(["vpc.1"]), result_graph_dict.get("bastion.1")) self.assertIsNone(result_graph_dict.get("namespace-removed.1"))
def setUp(self) -> None: """Run before tests.""" self.context = CfnginContext( config=CfnginConfig.parse_obj({ "namespace": "test", "cfngin_bucket": "test" })) self.provider = mock_provider(region="us-east-1")
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_stacks_exists(cfngin_context: CfnginContext) -> None: """Test CloudFormation Stacks exists.""" client = cfngin_context.get_session(region="us-east-1").client("cloudformation") assert cfngin_context.stacks, "no stacks found in context/config" for stack in cfngin_context.stacks: assert client.describe_stacks(StackName=stack.fqn)[ "Stacks" ], f"unable to descrive stack: {stack.fqn}"
def test_create_template_fails(self) -> None: """Test create template fails.""" ctx = CfnginContext() blueprint = Repositories("test_repo", ctx) blueprint.resolve_variables( [Variable("Repositories", ["repo1", "repo2", "repo3"], "cfngin")]) blueprint.create_template() with self.assertRaises(AssertionError): self.assertRenderedBlueprint(blueprint)
def setUp(self) -> None: """Run before tests.""" self.context = CfnginContext( config=CfnginConfig.parse_obj({"namespace": "namespace"})) self.provider = MockProvider() self.deploy_action = deploy.Action( self.context, provider_builder=MockProviderBuilder( provider=self.provider), # type: ignore )
def test_stacks_not_exists(cfngin_context: CfnginContext) -> None: """Test CloudFormation Stacks don't exists.""" client = cfngin_context.get_session(region="us-east-1").client("cloudformation") assert cfngin_context.stacks, "no stacks found in context/config" for stack in cfngin_context.stacks: try: assert not client.describe_stacks(StackName=stack.fqn)[ "Stacks" ], f"stack exists: {stack.fqn}" except client.exceptions.ClientError as exc: assert "does not exist" in str(exc)
def get_principal_arn(context: CfnginContext) -> str: """Return ARN of current session principle.""" # looking up caller identity session = context.get_session() sts_client = session.client("sts") caller_identity_arn = sts_client.get_caller_identity()["Arn"] if caller_identity_arn.split(":")[2] == "iam" and ( caller_identity_arn.split(":")[5].startswith("user/") ): return caller_identity_arn # user arn return assumed_role_to_principle(caller_identity_arn)
def setUp(self) -> None: """Run before tests.""" self.context = CfnginContext( config=CfnginConfig.parse_obj({ "namespace": "test", "cfngin_bucket": "test" })) self.provider = mock_provider(region="us-east-1") self.mock_process = MockProcess() self.popen_mock = mock.patch("runway.cfngin.hooks.command.Popen", return_value=self.mock_process).start()
def cfngin_context( cd_test_dir: Path, cfngin_config: CfnginConfig, runway_config: RunwayConfig, runway_context: RunwayContext, ) -> CfnginContext: """Return CFNgin context.""" return CfnginContext( config=cfngin_config, config_path=cd_test_dir / "cfngin.yml", deploy_environment=runway_context.env, parameters=runway_config.deployments[0].parameters, )
def invoke( context: CfnginContext, *, expected_status_code: int = 200, function_name: str, **_: Any, ) -> bool: """Invoke AWS Lambda Function and check the response.""" LOGGER.info("invoking %s", function_name) assert (context.get_session().client("lambda").invoke( FunctionName=function_name, InvocationType="RequestResponse")["StatusCode"] == expected_status_code ) LOGGER.info("%s returned %s", function_name, expected_status_code) return True
def setUp(self) -> None: """Run before tests.""" self.count = 0 self.config = CfnginConfig.parse_obj({"namespace": "namespace"}) self.context = CfnginContext(config=self.config) class FakeLookup(LookupHandler): """False Lookup.""" # pylint: disable=arguments-differ @classmethod def handle(cls, value: str, *__args: Any, **__kwargs: Any) -> str: # type: ignore """Perform the lookup.""" return "test" register_lookup_handler("noop", FakeLookup)
def setUp(self) -> None: """Run before tests.""" self.sd = {"name": "test"} # pylint: disable=invalid-name self.config = CfnginConfig.parse_obj({"namespace": "namespace"}) self.context = CfnginContext(config=self.config) self.stack = Stack(definition=generate_definition("vpc", 1), context=self.context) class FakeLookup(LookupHandler): """False Lookup.""" # pylint: disable=arguments-differ,unused-argument @classmethod def handle(cls, value: str, *__args: Any, **__kwargs: Any) -> str: # type: ignore """Perform the lookup.""" return "test" register_lookup_handler("noop", FakeLookup)
def delete_prefix( context: CfnginContext, *, bucket_name: str, delimiter: str = "/", prefix: str, **_: Any, ) -> bool: """Delete all objects with prefix.""" if not Bucket(context, bucket_name): LOGGER.warning("bucket '%s' does not exist or you do not have access to it") return True bucket = context.get_session().resource("s3").Bucket(bucket_name) LOGGER.info( "deleting objects from s3://%s%s%s...", bucket_name, delimiter, prefix, ) bucket.object_versions.filter(Delimiter=delimiter, Prefix=prefix).delete() return True
def test_path_relative(self) -> None: """Test path relative.""" with self.temp_directory_with_files(["test/test.py"]) as temp_dir: results = self.run_hook( functions={"MyFunction": { "path": "test" }}, context=CfnginContext( config=CfnginConfig.parse_obj({ "namespace": "test", "cfngin_bucket": "test" }), config_path=Path(str(temp_dir.path)), ), ) self.assertIsNotNone(results) code = results.get("MyFunction") self.assertIsInstance(code, Code) self.assert_s3_zip_file_list(code.S3Bucket, code.S3Key, ["test.py"])
def _get_context(self, extra_config_args: Optional[Dict[str, Any]] = None, **kwargs: Any) -> CfnginContext: """Get context.""" config: Dict[str, Any] = { "namespace": "namespace", "stacks": [ { "name": "vpc", "template_path": "." }, { "name": "bastion", "template_path": ".", "variables": { "test": "${output vpc::something}" }, }, { "name": "db", "template_path": ".", "variables": { "test": "${output vpc::something}", "else": "${output bastion::something}", }, }, { "name": "other", "template_path": ".", "variables": {} }, ], } if extra_config_args: config.update(extra_config_args) return CfnginContext(config=CfnginConfig.parse_obj(config), **kwargs)
def setUp(self) -> None: """Run before tests.""" self.provider = MagicMock() self.context = CfnginContext( config=CfnginConfig.parse_obj({"namespace": "ns"}))
self.variables["NodeAutoScalingGroupMaxSize"].ref, NoValue, ), LaunchConfigurationName=nodelaunchconfig.ref(), MinSize=self.variables["NodeAutoScalingGroupMinSize"].ref, MaxSize=self.variables["NodeAutoScalingGroupMaxSize"].ref, VPCZoneIdentifier=self.variables["Subnets"].ref, Tags=[ autoscaling.Tag( "Name", Sub("${ClusterName}-${NodeGroupName}-Node"), True ), autoscaling.Tag( Sub("kubernetes.io/cluster/${ClusterName}"), "owned", True ), ], UpdatePolicy=UpdatePolicy( AutoScalingRollingUpdate=AutoScalingRollingUpdate( MinInstancesInService="1", MaxBatchSize="1" ) ), ) ) # Helper section to enable easy blueprint -> template generation # (just run `python <thisfile>` to output the json) if __name__ == "__main__": from runway.context import CfnginContext print(NodeGroup("test", CfnginContext(parameters={"namespace": "test"})).to_json())
AssumeRolePolicyDocument=PolicyDocument( Statement=[ Statement( Effect=Allow, Action=[awacs.sts.AssumeRole], Principal=Principal("AWS", TESTING_ACCOUNT_ID), ) ] ), Description="Role used for cross account testing in runway", ManagedPolicyArns=["arn:aws:iam::aws:policy/AdministratorAccess"], RoleName=Join( "-", [ "runway-integration-test-role", self.variables["EnvironmentName"].ref, ], ), ) ) if __name__ == "__main__": from runway.context import CfnginContext print( CrossAccountRole( "test", CfnginContext(parameters={"namespace": "test"}) ).to_json() )
], ), Statement( Action=[ awacs.dynamodb.GetItem, awacs.dynamodb.PutItem, awacs.dynamodb.DeleteItem, ], Effect=Allow, Resource=[terraformlocktable.get_att("Arn")], ), ], ), )) self.template.add_output( Output( "PolicyArn", Description="Managed policy Arn", Value=managementpolicy.ref(), )) # Helper section to enable easy blueprint -> template generation # (just run `python <thisfile>` to output the json) if __name__ == "__main__": from runway.context import CfnginContext print( TfState("test", CfnginContext(parameters={"namespace": "test"})).to_json())
def setUp(self) -> None: """Run before tests.""" self.ctx = CfnginContext() self.prov = MagicMock() self.blueprint = MagicMock()