from six import string_types from six.moves.urllib.parse import urlparse, parse_qs from samtranslator.model.exceptions import InvalidResourceException def parse_s3_uri(uri): """Parses a S3 Uri into a dictionary of the Bucket, Key, and VersionId :return: a BodyS3Location dict or None if not an S3 Uri :rtype: dict """ if not isinstance(uri, string_types): return None url = urlparse(uri) query = parse_qs(url.query) if url.scheme == 's3' and url.netloc and url.path: s3_pointer = { 'Bucket': url.netloc, 'Key': url.path.lstrip('/') } if 'versionId' in query and len(query['versionId']) == 1: s3_pointer['Version'] = query['versionId'][0] return s3_pointer else: return None def to_s3_uri(code_dict): """Constructs a S3 URI string from given code dictionary :param dict code_dict: Dictionary containing Lambda function Code S3 location of the form {S3Bucket, S3Key, S3ObjectVersion} :return: S3 URI of form s3://bucket/key?versionId=version :rtype string """ try: uri = "s3://{bucket}/{key}".format(bucket=code_dict["S3Bucket"], key=code_dict["S3Key"]) version = code_dict.get("S3ObjectVersion", None) except (TypeError, AttributeError): raise TypeError("Code location should be a dictionary") if version: uri += "?versionId=" + version return uri def construct_s3_location_object(location_uri, logical_id, property_name): """Constructs a Lambda `Code` or `Content` property, from the SAM `CodeUri` or `ContentUri` property. This follows the current scheme for Lambda Functions and LayerVersions. :param dict or string location_uri: s3 location dict or string :param string logical_id: logical_id of the resource calling this function :param string property_name: name of the property which is used as an input to this function. :returns: a Code dict, containing the S3 Bucket, Key, and Version of the Lambda layer code :rtype: dict """ if isinstance(location_uri, dict): if not location_uri.get("Bucket") or not location_uri.get("Key"): # location_uri is a dictionary but does not contain Bucket or Key property raise InvalidResourceException(logical_id, "'{}' requires Bucket and Key properties to be " "specified".format(property_name)) s3_pointer = location_uri else: # location_uri is NOT a dictionary. Parse it as a string s3_pointer = parse_s3_uri(location_uri) if s3_pointer is None: raise InvalidResourceException(logical_id, '\'{}\' is not a valid S3 Uri of the form ' '"s3://bucket/key" with optional versionId query ' 'parameter.'.format(property_name)) code = { 'S3Bucket': s3_pointer['Bucket'], 'S3Key': s3_pointer['Key'] } if 'Version' in s3_pointer: code['S3ObjectVersion'] = s3_pointer['Version'] return code