def outputs(self): result = [] # first, fetch the outputs of nested child stacks for stack in self.nested_stacks: result.extend(stack.outputs) # now, fetch the outputs of this stack for k, details in self.template.get('Outputs', {}).items(): value = None try: template_deployer.resolve_refs_recursively( self.stack_name, details, self.resources) value = details['Value'] except Exception as e: LOG.debug( 'Unable to resolve references in stack outputs: %s - %s' % (details, e)) exports = details.get('Export') or {} export = exports.get('Name') description = details.get('Description') entry = { 'OutputKey': k, 'OutputValue': value, 'Description': description, 'ExportName': export } result.append(entry) return result
def outputs(self): result = [] # first, fetch the outputs of nested child stacks for stack in self.nested_stacks: result.extend(stack.outputs) # now, fetch the outputs of this stack for k, details in self.template.get("Outputs", {}).items(): value = None try: template_deployer.resolve_refs_recursively( self.stack_name, details, self.resources) value = details["Value"] except Exception as e: LOG.debug( "Unable to resolve references in stack outputs: %s - %s" % (details, e)) exports = details.get("Export") or {} export = exports.get("Name") export = template_deployer.resolve_refs_recursively( self.stack_name, export, self.resources) description = details.get("Description") entry = { "OutputKey": k, "OutputValue": value, "Description": description, "ExportName": export, } result.append(entry) return result
def outputs_list(self) -> List[Dict]: """Returns a copy of the outputs of this stack.""" result = [] # first, fetch the outputs of nested child stacks for stack in self.nested_stacks: result.extend(stack.outputs_list()) # now, fetch the outputs of this stack for k, details in self.outputs.items(): value = None try: template_deployer.resolve_refs_recursively(self, details) value = details["Value"] except Exception as e: LOG.debug("Unable to resolve references in stack outputs: %s - %s", details, e) exports = details.get("Export") or {} export = exports.get("Name") export = template_deployer.resolve_refs_recursively(self, export) description = details.get("Description") entry = { "OutputKey": k, "OutputValue": value, "Description": description, "ExportName": export, } result.append(entry) return result
def outputs(self): result = [] for k, details in self.template.get('Outputs', {}).items(): template_deployer.resolve_refs_recursively(self.stack_name, details, self.resources) export = details.get('Export', {}).get('Name') description = details.get('Description') entry = {'OutputKey': k, 'OutputValue': details['Value'], 'Description': description, 'ExportName': export} result.append(entry) return result
def update_resource_id(resource, new_id, props, region_name, stack_name, resource_map): """ Update and fix the ID(s) of the given resource. """ # NOTE: this is a bit of a hack, which is required because # of the order of events when CloudFormation resources are created. # When we process a request to create a CF resource that's part of a # stack, say, an API Gateway Resource, then we (1) create the object # in memory in moto, which generates a random ID for the resource, and # (2) create the actual resource in the backend service using # template_deployer.deploy_resource(..) (see above). # The resource created in (2) now has a different ID than the resource # created in (1), which leads to downstream problems. Hence, we need # the logic below to reconcile the ids, i.e., apply IDs from (2) to (1). backend = apigw_models.apigateway_backends[region_name] if isinstance(resource, apigw_models.RestAPI): backend.apis.pop(resource.id, None) backend.apis[new_id] = resource # We also need to fetch the resources to replace the root resource # that moto automatically adds to newly created RestAPI objects client = aws_stack.connect_to_service('apigateway') resources = client.get_resources(restApiId=new_id, limit=500)['items'] # make sure no resources have been added in addition to the root / assert len(resource.resources) == 1 resource.resources = {} for res in resources: res_path_part = res.get('pathPart') or res.get('path') child = resource.add_child(res_path_part, res.get('parentId')) resource.resources.pop(child.id) child.id = res['id'] child.api_id = new_id resource.resources[child.id] = child resource.id = new_id elif isinstance(resource, apigw_models.Resource): api_id = props['RestApiId'] api_id = template_deployer.resolve_refs_recursively( stack_name, api_id, resource_map) backend.apis[api_id].resources.pop(resource.id, None) backend.apis[api_id].resources[new_id] = resource resource.id = new_id elif isinstance(resource, apigw_models.Deployment): api_id = props['RestApiId'] api_id = template_deployer.resolve_refs_recursively( stack_name, api_id, resource_map) backend.apis[api_id].deployments.pop(resource['id'], None) backend.apis[api_id].deployments[new_id] = resource resource['id'] = new_id else: LOG.warning('Unexpected resource type when updating ID: %s' % type(resource))
def resolve_refs_recursively(cls, stack_name, value, resources): # TODO: restructure code to avoid circular import here from localstack.services.cloudformation.provider import find_stack from localstack.utils.cloudformation.template_deployer import resolve_refs_recursively stack = find_stack(stack_name) return resolve_refs_recursively(stack, value)
def test_resolve_references(self): ref = { "Fn::Join": [ "", [ "arn:", { "Ref": "AWS::Partition" }, ":apigateway:", { "Ref": "AWS::Region" }, ":lambda:path/2015-03-31/functions/", "test:lambda:arn", "/invocations", ], ] } stack_name = "test" resources = {} result = template_deployer.resolve_refs_recursively( stack_name, ref, resources) pattern = ( r"arn:aws:apigateway:.*:lambda:path/2015-03-31/functions/test:lambda:arn/invocations" ) self.assertTrue(re.match(pattern, result))
def _resolve_refs_in_template(template, stack_params: Dict = None): stack = Stack({"StackName": "test"}) stack.stack_parameters() stack_params = stack_params or {} stack_params = [{ "ParameterKey": k, "ParameterValue": v } for k, v in stack_params.items()] stack.metadata["Parameters"].extend(stack_params) return template_deployer.resolve_refs_recursively(stack, template)
def _post_create(resource_id, resources, resource_type, func, stack_name): """attaches managed policies from the template to the role""" from localstack.utils.cloudformation.template_deployer import ( find_stack, resolve_refs_recursively, ) iam = aws_stack.connect_to_service("iam") resource = resources[resource_id] props = resource["Properties"] role_name = props["RoleName"] # attach managed policies policy_arns = props.get("ManagedPolicyArns", []) for arn in policy_arns: iam.attach_role_policy(RoleName=role_name, PolicyArn=arn) # TODO: to be removed once we change the method signature to pass in the stack object directly! stack = find_stack(stack_name) # add inline policies inline_policies = props.get("Policies", []) for policy in inline_policies: assert not isinstance( policy, list) # remove if this doesn't make any problems for a while if policy == PLACEHOLDER_AWS_NO_VALUE: continue if not isinstance(policy, dict): LOG.info('Invalid format of policy for IAM role "%s": %s', props.get("RoleName"), policy) continue pol_name = policy.get("PolicyName") # get policy document - make sure we're resolving references in the policy doc doc = dict(policy["PolicyDocument"]) doc = resolve_refs_recursively(stack, doc) doc["Version"] = doc.get("Version") or IAM_POLICY_VERSION statements = ensure_list(doc["Statement"]) for statement in statements: if isinstance(statement.get("Resource"), list): # filter out empty resource strings statement["Resource"] = [ r for r in statement["Resource"] if r ] doc = json.dumps(doc) iam.put_role_policy( RoleName=props["RoleName"], PolicyName=pol_name, PolicyDocument=doc, )
def outputs(self): result = [] for k, details in self.template.get('Outputs', {}).items(): value = None try: template_deployer.resolve_refs_recursively( self.stack_name, details, self.resources) value = details['Value'] except Exception as e: LOG.debug( 'Unable to resolve references in stack outputs: %s - %s' % (details, e)) export = details.get('Export', {}).get('Name') description = details.get('Description') entry = { 'OutputKey': k, 'OutputValue': value, 'Description': description, 'ExportName': export } result.append(entry) return result
def test_resolve_references(self): ref = { 'Fn::Join': [ '', [ 'arn:', { 'Ref': 'AWS::Partition' }, ':apigateway:', { 'Ref': 'AWS::Region' }, ':lambda:path/2015-03-31/functions/', 'test:lambda:arn', '/invocations' ] ] } stack_name = 'test' resources = {} result = template_deployer.resolve_refs_recursively( stack_name, ref, resources) pattern = r'arn:aws:apigateway:.*:lambda:path/2015-03-31/functions/test:lambda:arn/invocations' self.assertTrue(re.match(pattern, result))
def test_resolve_references(): ref = { "Fn::Join": [ "", [ "arn:", { "Ref": "AWS::Partition" }, ":apigateway:", { "Ref": "AWS::Region" }, ":lambda:path/2015-03-31/functions/", "test:lambda:arn", "/invocations", ], ] } stack_name = "test" stack = Stack({"StackName": stack_name}) result = template_deployer.resolve_refs_recursively(stack, ref) pattern = r"arn:aws:apigateway:.*:lambda:path/2015-03-31/functions/test:lambda:arn/invocations" assert re.match(pattern, result)
def resolve_refs_recursively(self, stack_name, value, resources): # TODO: restructure code to avoid circular import here from localstack.utils.cloudformation.template_deployer import resolve_refs_recursively return resolve_refs_recursively(stack_name, value, resources)