def add_managed_policy(c, ManagedPolicyName, PolicyDocument, model, named=False): cfn_name = c.scrub_name(ManagedPolicyName) kw_args = { "Description": "Managed Policy " + ManagedPolicyName, "PolicyDocument": PolicyDocument, "Groups": [], "Roles": [], "Users": [] } if named: kw_args["ManagedPolicyName"] = ManagedPolicyName if "description" in model: kw_args["Description"] = model["description"] if "groups" in model: kw_args["Groups"] = parse_imports(c, model["groups"]) if "users" in model: kw_args["Users"] = parse_imports(c, model["users"]) if "roles" in model: kw_args["Roles"] = parse_imports(c, model["roles"]) if "retain_on_delete" in model: if model["retain_on_delete"] is True: kw_args["DeletionPolicy"] = "Retain" c.template[c.current_account].add_resource(ManagedPolicy( cfn_name, **kw_args )) if c.config['global']['template_outputs'] == "enabled": c.template[c.current_account].add_output([ Output( cfn_name + "PolicyArn", Description=kw_args["Description"] + " Policy Document ARN", Value=Ref(cfn_name), Export=Export(Sub( "${AWS::StackName}-" + cfn_name + "PolicyArn" )) ) ])
def _create_codebuild_project(self, code_build_role, deploy_key): app_package_build = codebuild.Project( 'AppPackageBuild', Artifacts=codebuild.Artifacts(Type='CODEPIPELINE'), Name=Sub('${ApplicationName}-build'), Environment=codebuild.Environment( ComputeType='BUILD_GENERAL1_SMALL', Image=Ref('CodeBuildImage'), Type='LINUX_CONTAINER', EnvironmentVariables=[ codebuild.EnvironmentVariable( Name='APP_S3_BUCKET', Value=Ref('ApplicationBucket')) ]), ServiceRole=code_build_role.GetAtt('Arn'), EncryptionKey=deploy_key.GetAtt('Arn'), Source=codebuild.Source(Type='CODEPIPELINE'), ) self._t.add_resource(app_package_build) return app_package_build
def _pipeline_role(buckets: Iterable[s3.Bucket]) -> iam.Role: """Build and return the IAM Role resource to be used by CodePipeline to run the pipeline.""" bucket_statements = [ AWS.Statement( Effect=AWS.Allow, Action=[S3.GetBucketVersioning, S3.PutBucketVersioning], Resource=[GetAtt(bucket, "Arn") for bucket in buckets], ), AWS.Statement( Effect=AWS.Allow, Action=[S3.GetObject, S3.PutObject], Resource=[Sub("${{{bucket}.Arn}}/*".format(bucket=bucket.title)) for bucket in buckets], ), ] policy = iam.Policy( "PipelinePolicy", PolicyName="PipelinePolicy", PolicyDocument=AWS.PolicyDocument( Statement=bucket_statements + [ AllowEverywhere(Action=[CLOUDWATCH.Action("*"), IAM.PassRole]), AllowEverywhere(Action=[LAMBDA.InvokeFunction, LAMBDA.ListFunctions]), AllowEverywhere( Action=[ CLOUDFORMATION.CreateStack, CLOUDFORMATION.DeleteStack, CLOUDFORMATION.DescribeStacks, CLOUDFORMATION.UpdateStack, CLOUDFORMATION.CreateChangeSet, CLOUDFORMATION.DeleteChangeSet, CLOUDFORMATION.DescribeChangeSet, CLOUDFORMATION.ExecuteChangeSet, CLOUDFORMATION.SetStackPolicy, CLOUDFORMATION.ValidateTemplate, ] ), AllowEverywhere(Action=[CODEBUILD.BatchGetBuilds, CODEBUILD.StartBuild]), ] ), ) return iam.Role( "CodePipelinesRole", AssumeRolePolicyDocument=_service_assume_role(CODEPIPELINE.prefix), Policies=[policy] )
def create_event_source_mapping(self): t = self.template variables = self.get_variables() mapping = variables["EventSourceMapping"] if mapping: if "FunctionName" in mapping: logger.warn( Sub("FunctionName defined in EventSourceMapping in " "${AWS::StackName}. Overriding.")) mapping["FunctionName"] = self.function.GetAtt("Arn") resource = t.add_resource( awslambda.EventSourceMapping.from_dict("EventSourceMapping", mapping)) if not variables["Role"]: self.add_policy_statements( stream_reader_statements(mapping["EventSourceArn"])) t.add_output(Output("EventSourceMappingId", Value=resource.Ref()))
def elb_attributes(self): ret = [ LoadBalancerAttributes(Key="idle_timeout.timeout_seconds", Value=str(self.idle_timeout_seconds)), LoadBalancerAttributes(Key="routing.http2.enabled", Value="true"), LoadBalancerAttributes( Key="deletion_protection.enabled", Value="true" if self._deletion_protection else "false") ] if self._log_bucket is not None: ret += [ LoadBalancerAttributes(Key="access_logs.s3.enabled", Value="true"), LoadBalancerAttributes(Key="access_logs.s3.bucket", Value=self._log_bucket), LoadBalancerAttributes(Key="access_logs.s3.prefix", Value=Sub("${AWS::StackName}-ElbLogs")) ] return ret
def __init__(self, name, definition, routers, nodes, mesh, settings): """ Method to initialize the Mesh service. :param name: name of the virtual service :param routers: the routers of the mesh :param nodes: the nodes of the mesh :param mesh: the mesh object. """ self.title = NONALPHANUM.sub("", name) self.definition = definition service_node = (nodes[self.definition[NODE_KEY]] if keyisset( NODE_KEY, self.definition) else None) service_router = (routers[self.definition[ROUTER_KEY]] if keyisset( ROUTER_KEY, self.definition) else None) if not service_router and not service_node: raise AttributeError( f"The service {name} has neither nodes or routers defined. Define at least one" ) depends = [] self.node = service_node if service_node else None self.router = service_router if service_router else None self.service = appmesh.VirtualService( f"{NONALPHANUM.sub('', name).title()}VirtualService", DependsOn=depends, MeshName=appmesh_conditions.get_mesh_name(mesh), MeshOwner=appmesh_conditions.set_mesh_owner_id(), VirtualServiceName=Sub( f"{name}.${{ZoneName}}", ZoneName=settings.private_zone.name_value, ), Spec=appmesh.VirtualServiceSpec( Provider=appmesh.VirtualServiceProvider( VirtualNode=appmesh.VirtualNodeServiceProvider( VirtualNodeName=service_node.get_node_param ) if service_node else Ref(AWS_NO_VALUE), VirtualRouter=appmesh.VirtualRouterServiceProvider( VirtualRouterName=GetAtt(service_router. router, "VirtualRouterName") ) if service_router else Ref(AWS_NO_VALUE), )), )
def add_figure_lambda(self): ## Now add to a lambda function: function = Function('FigLambda', CodeUri = '../../protocols', ## assume we are running from the stack config template location. Runtime = 'python3.6', Handler = 'log.eventshandler', Description = 'Lambda Function logging start/stop for NCAP', MemorySize = 128, Timeout = 90, Role = 'arn:aws:iam::739988523141:role/lambda_dataflow', ## TODO: Create this in template Events= {}) figurelamb = self.template.add_resource(function) ## Attach specific permissions to invoke this lambda function as well. cwpermission = Permission('CWPermissions', Action = 'lambda:InvokeFunction', Principal = 'events.amazonaws.com', FunctionName = Ref(figurelamb)) self.template.add_resource(cwpermission) ## Because this lambda function gets invoked by an unknown target, we need to take care of its log group separately. figloggroup = LogGroup('FignameLogGroup',LogGroupName=Sub("/aws/lambda/${FigLambda}")) self.template.add_resource(figloggroup) ## Now we need to configure this function as a potential target. ## Initialize role to send events to cloudwatch with open('policies/cloudwatch_events_assume_role_doc.json','r') as f: cloudwatchassume_role_doc = json.load(f) ## Now get the actual policy: with open('policies/cloudwatch_events_policy_doc.json','r') as f: cloudwatch_policy_doc = json.load(f) cloudwatchpolicy = ManagedPolicy("CloudwatchBusPolicy", Description = Join(" ",["Base Policy for all lambda function roles in",Ref(AWS_STACK_NAME)]), PolicyDocument = cloudwatch_policy_doc) self.template.add_resource(cloudwatchpolicy) ## create the role: cwrole = Role("CloudWatchBusRole", AssumeRolePolicyDocument=cloudwatchassume_role_doc, ManagedPolicyArns = [Ref(cloudwatchpolicy)]) cwrole_attached = self.template.add_resource(cwrole) self.cwrole = cwrole_attached return figurelamb
def create_db_subnet_group(template, db, subnets=None): """ Function to create a subnet group :param troposphere.Template template: the template to add the subnet group to. :param subnets: The subnets to use. :return: group, the DB Subnets Group :rtype: troposphere.rds.DBSubnetGroup """ if not subnets: subnets = STORAGE_SUBNETS group = DBSubnetGroup( f"{db.logical_name}SubnetGroup", template=template, DBSubnetGroupDescription=Sub( f"DB Subnet group for {db.logical_name} in ${{AWS::StackName}}" ), SubnetIds=Ref(subnets), ) return group
def account_arn(service_prefix: str, resource: str) -> Sub: """Build an IAM policy Arn pattern scoping down as for as possible for the specified service. :param service_prefix: Service prefix string :param resource: Any resource data to finish Arn :return: Constructed Sub structure that will resolve to the scoped down Arn """ if service_prefix in (awacs.iam.prefix, awacs.s3.prefix): _region = "" else: _region = f"${{{AWS_REGION}}}" if service_prefix == awacs.s3.prefix: _account_id = "" else: _account_id = f"${{{AWS_ACCOUNT_ID}}}" return Sub( f"arn:${{{AWS_PARTITION}}}:{service_prefix}:{_region}:{_account_id}:{resource}" )
def add_self_ingress(self, sg): """ Method to allow communications internally to the group on set ports :param sg: :return: """ for port in self.config.ports: SecurityGroupIngress( f"AllowingMyselfToMyselfOnPort{port['published']}", template=self.template, FromPort=port["published"], ToPort=port["published"], IpProtocol=port["protocol"], GroupId=GetAtt(sg, "GroupId"), SourceSecurityGroupId=GetAtt(sg, "GroupId"), SourceSecurityGroupOwnerId=Ref(AWS_ACCOUNT_ID), Description=Sub( f"Allowing traffic internally on port {port['published']}" ), )
def get_import_value(title, attribute_name, delimiter=None): """ Wrapper function to define ImportValue for defined resource name :param title: name of the resource exported :param attribute_name: attribute exported :param delimiter: delimiter between stack name, resource name and attribute :return: """ if delimiter is None: delimiter = CFN_EXPORT_DELIMITER elif not isinstance(delimiter, str): LOG.error( f"delimiter must be of type str, got {type(delimiter)}. Setting to default" ) delimiter = CFN_EXPORT_DELIMITER return ImportValue( Sub(f"${{{ROOT_STACK_NAME_T}}}{delimiter}{title}{delimiter}{attribute_name}" ))
def add_resources(self): """Add resources to template.""" template = self.template variables = self.get_variables() sg = template.add_resource( ec2.SecurityGroup('Sg', GroupName=variables['SgName'].ref, GroupDescription=variables['SgDescription'].ref, Tags=Tags( Application=variables['ApplicationName'].ref, Environment=variables['EnvironmentName'].ref, Name=variables['SgName'].ref), VpcId=variables['VpcId'].ref)) template.add_output( Output("{}Id".format(sg.title), Description="Security Group ID", Value=Ref(sg), Export=Export(Sub('${AWS::StackName}-%sId' % sg.title))))
def _add_ecs_service_iam_role(self): role_name = Sub('ecs-svc-${AWS::StackName}-${AWS::Region}') assume_role_policy = { u'Statement': [{ u'Action': [u'sts:AssumeRole'], u'Effect': u'Allow', u'Principal': { u'Service': [u'ecs.amazonaws.com'] } }] } self.ecs_service_role = Role( 'ECSServiceRole', Path='/', ManagedPolicyArns=[ 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole' ], RoleName=role_name, AssumeRolePolicyDocument=assume_role_policy) self.template.add_resource(self.ecs_service_role)
def getCodeBuild( name: str , serviceRole: Role , buildspec: List[str] ) -> Project: env = Environment( ComputeType = "BUILD_GENERAL1_SMALL" , Image = "frolvlad/alpine-python3" , Type = "LINUX_CONTAINER" , PrivilegedMode = False ) source = Source( Type = "CODEPIPELINE" , BuildSpec = Join("\n", buildspec) ) artifacts = Artifacts( Type = "CODEPIPELINE" ) return Project( alphanum(name) , Name = Sub("${AWS::StackName}-" + alphanum(name)) , Environment = env , Source = source , Artifacts = artifacts , ServiceRole = Ref(serviceRole) )
def define_domain_security_group(domain, stack): """ Create a new Security Group for the Domain :param ecs_composex.opensearch.opensearch_stack.OpenSearchDomain domain: :param ecs_composex.common.stacks.ComposeXStack stack: :return: The security Group """ add_parameters(stack.stack_template, [VPC_ID]) sg = SecurityGroup( f"{domain.logical_name}VPCSecurityGroup", GroupDescription=Sub( f"{domain.logical_name} OpenSearch SG in ${{STACK_NAME}}", STACK_NAME=define_stack_name(stack.stack_template), ), VpcId=Ref(VPC_ID), Tags=Tags(OsDomainName=domain.name), ) stack.stack_template.add_resource(sg) return sg
def set_log_group(self, cluster_name, root_stack, log_configuration): self.log_group = LogGroup( "EcsExecLogGroup", LogGroupName=Sub( "/ecs/execute-logs/${CLUSTER_NAME}", CLUSTER_NAME=cluster_name, ), RetentionInDays=120 if not keyisset("LogGroupRetentionInDays", self.parameters) else get_closest_valid_log_retention_period( self.parameters["LogGroupRetentionInDays"]), KmsKeyId=GetAtt(self.log_key.cfn_resource, "Arn") if isinstance( self.log_key, KmsKey) else Ref(AWS_NO_VALUE), DependsOn=[self.log_key.cfn_resource.title] if isinstance( self.log_key, KmsKey) else [], ) root_stack.stack_template.add_resource(self.log_group) log_configuration["CloudWatchLogGroupName"] = Ref(self.log_group) if isinstance(self.log_key, KmsKey): log_configuration["CloudWatchEncryptionEnabled"] = True
def handle_tcp_route(self, routes, router, nodes): """ Function to create the TCP routes for the router :param list routes: routes of TCP protocol :param troposphere.appmesh.VirtualRouter router: The virtual router to attach the route to. :param dict nodes: Nodes in the mesh """ for route in routes: if not all(key in [NODES_KEY] for key in route.keys()): raise AttributeError("Each route must have nodes. Got", route.keys()) route_nodes = [] for node in route[NODES_KEY]: if node[NAME_KEY] in nodes.keys(): route_nodes.append(nodes[node[NAME_KEY]]) else: raise ValueError( f"node {node[NAME_KEY]} is not defined as a virtual node." ) route = appmesh.TcpRoute( Timeout=appmesh.TcpTimeout( Idle=appmesh.Duration(Unit="ms", Value=1)) if keyisset( "Timeout", route) else Ref(AWS_NO_VALUE), Action=appmesh.TcpRouteAction(WeightedTargets=[ appmesh.WeightedTarget( VirtualNode=node.get_node_param, Weight=node.weight, ) for node in route_nodes ]), ) protocol = "TcpRoute" self.routes.append( appmesh.Route( f"{router.title}{protocol}", MeshName=GetAtt(router, "MeshName"), MeshOwner=GetAtt(router, "MeshOwner"), VirtualRouterName=GetAtt(router, "VirtualRouterName"), RouteName=Sub(f"${{AWS::StackName}}{protocol}"), Spec=appmesh.RouteSpec(**{protocol: route}), ))
def add_resources_and_outputs(self): """Add resources and outputs to template.""" template = self.template variables = self.get_variables() logsloggroup = template.add_resource( logs.LogGroup('CloudWatchLogGroup', LogGroupName=variables['LogGroupName'].ref, RetentionInDays=variables['LogRetentionDays'].ref)) template.add_output( Output('{}Arn'.format(logsloggroup.title), Description='CloudWatch Logs log group ARN', Value=GetAtt(logsloggroup, 'Arn'), Export=Export( Sub('${AWS::StackName}-%sArn' % logsloggroup.title)))) template.add_output( Output('{}Name'.format(logsloggroup.title), Description='CloudWatch Logs log group name', Value=Ref(logsloggroup)))
def test_api_with_domain(self): certificate = Parameter('certificate', Type='String') serverless_api = Api( 'SomeApi', StageName='test', Domain=Domain( BasePath=['/'], CertificateArn=Ref(certificate), DomainName=Sub('subdomain.${Zone}', Zone=ImportValue('MyZone')), EndpointConfiguration='REGIONAL', Route53=Route53( HostedZoneId=ImportValue('MyZone'), IpV6=True, ), ), ) t = Template() t.add_parameter(certificate) t.add_resource(serverless_api) t.to_json()
def add_iam_resources(self): self.pod_role = self.template.add_resource(Role( 'FrontendPaymentsRegistrationPodRole', RoleName=Join('-', [ Join('', [Ref(self.service_name), 'PodRole']), Ref(self.environment)] ), Path=Sub('/${Environment}/pod_roles/'), AssumeRolePolicyDocument={ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } ))
def test_http_api_with_domain(self): certificate = Parameter("certificate", Type="String") serverless_http_api = HttpApi( "SomeHttpApi", StageName="testHttp", Domain=HttpApiDomainConfiguration( BasePath=["/"], CertificateArn=Ref(certificate), DomainName=Sub("subdomain.${Zone}", Zone=ImportValue("MyZone")), EndpointConfiguration="REGIONAL", Route53=Route53( HostedZoneId=ImportValue("MyZone"), IpV6=True, ), ), ) t = Template() t.add_parameter(certificate) t.add_resource(serverless_http_api) t.to_json()
def __init__(self, resource, linked_service_name): self._resource = resource self.iam_modules_policies = OrderedDict() self.permissions_boundary = NoValue if ( resource.parameters and keyisset("x-iam", resource.parameters) and keyisset("PermissionsBoundary", resource.parameters["x-iam"]) ): self.permissions_boundarypermissions_boundary = define_iam_policy( resource.parameters["x-iam"]["PermissionsBoundary"] ) self.service_linked_role = IamRole( f"{resource.logical_name}IamRole", AssumeRolePolicyDocument=service_role_trust_policy(linked_service_name), Description=Sub( f"Firehose IAM Service Role for {resource.logical_name} - ${{STACK_NAME}}", STACK_NAME=define_stack_name(), ), PermissionsBoundary=self.permissions_boundary, )
def generate_export_strings(res_name, attribute): """ Function to generate the SSM and CFN import/export strings Returns the import in a tuple :param str res_name: name of the queue as defined in ComposeX File :param str|Parameter attribute: The attribute to use in Import Name. :returns: ImportValue for CFN :rtype: ImportValue """ if isinstance(attribute, str): cfn_string = f"${{{ROOT_STACK_NAME_T}}}{DELIM}{res_name}{DELIM}{attribute}" elif isinstance(attribute, Parameter): cfn_string = ( f"${{{ROOT_STACK_NAME_T}}}{DELIM}{res_name}{DELIM}{attribute.title}" ) else: raise TypeError("Attribute can only be a string or Parameter") return ImportValue(Sub(cfn_string))
def add_independent_rules(dst_family: ComposeFamily, service_name: str, root_stack: ComposeXStack) -> None: """ Adds security groups rules in the root stack as both services need to be created (with their SG) before the ingress rule can be defined. :param dst_family: :param service_name: :param root_stack: :return: """ src_service_stack = root_stack.stack_template.resources[service_name] for port in dst_family.service_networking.ports: target_port = set_else_none("published", port, alt_value=set_else_none( "target", port, None)) if target_port is None: raise ValueError( "Wrong port definition value for security group ingress", port) ingress_rule = SecurityGroupIngress( f"From{src_service_stack.title}To{dst_family.logical_name}On{target_port}", FromPort=target_port, ToPort=target_port, IpProtocol=port["protocol"], Description=Sub( f"From {src_service_stack.title} to {dst_family.logical_name}" f" on port {target_port}/{port['protocol']}"), GroupId=GetAtt( dst_family.stack.title, f"Outputs.{dst_family.logical_name}GroupId", ), SourceSecurityGroupId=GetAtt( src_service_stack.title, f"Outputs.{src_service_stack.title}GroupId", ), SourceSecurityGroupOwnerId=Ref(AWS_ACCOUNT_ID), ) if ingress_rule.title not in root_stack.stack_template.resources: root_stack.stack_template.add_resource(ingress_rule)
def add_memcahed_config(self, template): self.port_attr = CLUSTER_MEMCACHED_PORT if not self.lookup: self.config_parameter = SSMParameter( f"{self.logical_name}Config", template=template, Type="String", Value=Sub( json.dumps( { "endpoint": f"${{{self.logical_name}.{CLUSTER_MEMCACHED_ADDRESS.return_value}}}", "port": f"${{{self.logical_name}.{CLUSTER_MEMCACHED_PORT.return_value}}}", } ), ), ) self.output_properties[CLUSTER_CONFIG] = ( self.config_parameter.title, self.config_parameter, Ref, None, )
def getDockerBuildAction(buildRef, inputs: List[str], outputs: List[str], number=1) -> Actions: ''' Takes a build reference which points to the build configuration, input/output map with the names of the artifacts and (optimal) a number, if multiple build actions must be added to the same pipeline ''' number = str(number) inputArts = map(lambda x: InputArtifacts(Name=x), inputs) outputArts = map(lambda x: OutputArtifacts(Name=x), outputs) actionId = ActionTypeId(Category="Build", Owner="AWS", Version="1", Provider="CodeBuild") return Actions(Name=Sub("${AWS::StackName}-BuildAction" + number), ActionTypeId=actionId, InputArtifacts=list(inputArts), OutputArtifacts=list(outputArts), RunOrder=number, Configuration={"ProjectName": Ref(buildRef)})
def getCodeCommit(t: Template, outputfiles: str) -> Stages: repo = t.add_parameter( Parameter("CodeCommitRepo", Description="Name of the CodeCommit Repository", Type="String")) branch = t.add_parameter( Parameter("Branch", Description="Branch triggering the deployment", Type="String")) actionId = ActionTypeID(Category="Source", Owner="AWS", Version="1", Provider="CodeCommit") action = Actions(Name=Sub("${AWS::StackName}-LambdaSource"), ActionTypeId=actionId, Configuration={ "BranchName": Ref(branch), "RepositoryName": Ref(repo) }, OutputArtifacts=[OutputArtifacts(Name=outputfiles)], RunOrder="1") return Stages(Name="Source", Actions=[action])
def role_trust_policy(service_name, require_mfa=False, external_id=None): """ """ statement = { "Effect": "Allow", "Principal": { "Service": [Sub(f'{service_name}.${{AWS::URLSuffix}}')] }, "Action": ["sts:AssumeRole"] } if require_mfa or external_id: statement['Condition'] = {} if require_mfa: statement['Condition']['Bool'] = { "aws:MultiFactorAuthPresent": "true" } if external_id is not None: statement['Condition']['StringEquals'] = { "sts:ExternalId": external_id } policy_doc = {"Version": "2012-10-17", "Statement": [statement]} return policy_doc
def getDeploy( t: Template, inName: str, stage: str, interimArt: str #artifact containing func code incl. libs , sourceartifact: str = None, getTest: Callable[[Template, str, str], Action] = None) -> Stages: [actionId, role] = getDeployResources(t) params = { "S3Key": { "Fn::GetArtifactAtt": [interimArt, "ObjectKey"] }, "S3Storage": { "Fn::GetArtifactAtt": [interimArt, "BucketName"] } } params = json.dumps(params) params = params.replace('"', '\"').replace('\n', '\\n') config = { "ActionMode": "CREATE_UPDATE", "RoleArn": GetAtt(role, "Arn"), "StackName": Sub("".join(["${AWS::StackName}Functions", stage])), "Capabilities": "CAPABILITY_NAMED_IAM", "TemplatePath": inName + "::stack" + stage + ".json", "ParameterOverrides": params } arts = map(lambda x: InputArtifacts(Name=x), [inName, interimArt]) actions = [ Actions(Name="Deploy" + stage, ActionTypeId=actionId, InputArtifacts=list(arts), RunOrder="1", Configuration=config) ] if sourceartifact is not None and getTest is not None: actions.append(getTest(t, sourceartifact, stage)) return Stages(stage + "Deploy", Name=stage, Actions=actions)
def add_security_group_ingress(service_stack, db_name): """ Function to add a SecurityGroupIngress rule into the ECS Service template :param ecs_composex.ecs.ServicesStack service_stack: The root stack for the services :param str db_name: the name of the database to use for imports """ service_template = service_stack.stack_template sg_id = get_import_value(db_name, DB_EXPORT_SG_ID_T) port = get_import_value(db_name, DB_EXPORT_PORT_T) SecurityGroupIngress( f"AllowRdsFrom{db_name}to{service_stack.title}", template=service_template, GroupId=sg_id, FromPort=port, ToPort=port, Description=Sub(f"Allow FROM {db_name} TO {service_stack.title}"), SourceSecurityGroupId=GetAtt(service_template.resources[SG_T], "GroupId"), SourceSecurityGroupOwnerId=Ref("AWS::AccountId"), IpProtocol="6", )