def _split_resource(context: Context) -> Tuple[str, str, str]: """Splits a background event's resource into a CloudEvent service, resource, and subject.""" service = "" resource = "" if isinstance(context.resource, dict): service = context.resource.get("service", "") resource = context.resource["name"] else: resource = context.resource # If there's no service we'll choose an appropriate one based on the event type. if not service: for b_service, ce_service in _SERVICE_BACKGROUND_TO_CE.items(): if context.event_type.startswith(b_service): service = ce_service break if not service: raise EventConversionException( "Unable to find CloudEvent equivalent service " f"for {context.event_type}") # If we don't need to split the resource string then we're done. if service not in _CE_SERVICE_TO_RESOURCE_RE: return service, resource, "" # Split resource into resource and subject. match = _CE_SERVICE_TO_RESOURCE_RE[service].fullmatch(resource) if not match: raise EventConversionException("Resource regex did not match") return service, match.group(1), match.group(2)
def cloud_event_to_background_event(request) -> Tuple[Any, Context]: """Converts a background event represented by the given HTTP request into a CloudEvent.""" try: event = from_http(request.headers, request.get_data()) data = event.data service, name = _split_ce_source(event["source"]) if event["type"] not in _CE_TO_BACKGROUND_TYPE: raise EventConversionException( f'Unable to find background event equivalent type for "{event["type"]}"' ) if service == _PUBSUB_CE_SERVICE: resource = { "service": service, "name": name, "type": _PUBSUB_MESSAGE_TYPE } if "message" in data: data = data["message"] if "messageId" in data: del data["messageId"] if "publishTime" in data: del data["publishTime"] elif service == _FIREBASE_AUTH_CE_SERVICE: resource = name if "metadata" in data: for old, new in _FIREBASE_AUTH_METADATA_FIELDS_CE_TO_BACKGROUND.items( ): if old in data["metadata"]: data["metadata"][new] = data["metadata"][old] del data["metadata"][old] elif service == _STORAGE_CE_SERVICE: resource = { "name": f"{name}/{event['subject']}", "service": service, "type": data["kind"], } elif service == _FIREBASE_DB_CE_SERVICE: name = re.sub("/locations/[^/]+", "", name) resource = f"{name}/{event['subject']}" else: resource = f"{name}/{event['subject']}" context = Context( eventId=event["id"], timestamp=event["time"], eventType=_CE_TO_BACKGROUND_TYPE[event["type"]], resource=resource, ) return (data, context) except (AttributeError, KeyError, TypeError, MissingRequiredFields): raise EventConversionException( "Failed to convert CloudEvent to BackgroundEvent.")
def marshal_background_event_data(request): """Marshal the request body of a raw Pub/Sub HTTP request into the schema that is expected of a background event""" try: request_data = request.get_json() if not _is_raw_pubsub_payload(request_data): # If this in not a raw Pub/Sub request, return the unaltered request data. return request_data return { "context": { "eventId": request_data["message"]["messageId"], "timestamp": request_data["message"].get( "publishTime", datetime.utcnow().isoformat() + "Z"), "eventType": _PUBSUB_EVENT_TYPE, "resource": { "service": _PUBSUB_CE_SERVICE, "type": _PUBSUB_MESSAGE_TYPE, "name": _parse_pubsub_topic(request.path), }, }, "data": { "@type": _PUBSUB_MESSAGE_TYPE, "data": request_data["message"]["data"], "attributes": request_data["message"]["attributes"], }, } except (AttributeError, KeyError, TypeError): raise EventConversionException( "Failed to convert Pub/Sub payload to event")
def background_event_to_cloudevent(request) -> CloudEvent: """Converts a background event represented by the given HTTP request into a CloudEvent.""" event_data = marshal_background_event_data(request) if not event_data: raise EventConversionException("Failed to parse JSON") event_object = BackgroundEvent(**event_data) data = event_object.data context = Context(**event_object.context) if context.event_type not in _BACKGROUND_TO_CE_TYPE: raise EventConversionException( f'Unable to find CloudEvent equivalent type for "{context.event_type}"' ) new_type = _BACKGROUND_TO_CE_TYPE[context.event_type] service, resource, subject = _split_resource(context) # Handle Pub/Sub events. if service == _PUBSUB_CE_SERVICE: data = {"message": data} # Handle Firebase Auth events. if service == _FIREBASE_AUTH_CE_SERVICE: if "metadata" in data: for old, new in _FIREBASE_AUTH_METADATA_FIELDS_BACKGROUND_TO_CE.items( ): if old in data["metadata"]: data["metadata"][new] = data["metadata"][old] del data["metadata"][old] if "uid" in data: uid = data["uid"] subject = f"users/{uid}" metadata = { "id": context.event_id, "time": context.timestamp, "specversion": _CLOUDEVENT_SPEC_VERSION, "datacontenttype": "application/json", "type": new_type, "source": f"//{service}/{resource}", } if subject: metadata["subject"] = subject return CloudEvent(metadata, data)
def _split_ce_source(source) -> Tuple[str, str]: """Splits a CloudEvent source string into resource and subject components.""" regex = re.compile(r"\/\/([^/]+)\/(.+)") match = regex.fullmatch(source) if not match: raise EventConversionException("Unexpected CloudEvent source.") return match.group(1), match.group(2)
def background_event_to_cloud_event(request) -> CloudEvent: """Converts a background event represented by the given HTTP request into a CloudEvent.""" event_data = marshal_background_event_data(request) if not event_data: raise EventConversionException("Failed to parse JSON") event_object = BackgroundEvent(**event_data) data = event_object.data context = Context(**event_object.context) if context.event_type not in _BACKGROUND_TO_CE_TYPE: raise EventConversionException( f'Unable to find CloudEvent equivalent type for "{context.event_type}"' ) new_type = _BACKGROUND_TO_CE_TYPE[context.event_type] service, resource, subject = _split_resource(context) source = f"//{service}/{resource}" # Handle Pub/Sub events. if service == _PUBSUB_CE_SERVICE: if "messageId" not in data: data["messageId"] = context.event_id if "publishTime" not in data: data["publishTime"] = context.timestamp data = {"message": data} # Handle Firebase Auth events. if service == _FIREBASE_AUTH_CE_SERVICE: if "metadata" in data: for old, new in _FIREBASE_AUTH_METADATA_FIELDS_BACKGROUND_TO_CE.items( ): if old in data["metadata"]: data["metadata"][new] = data["metadata"][old] del data["metadata"][old] if "uid" in data: uid = data["uid"] subject = f"users/{uid}" # Handle Firebase DB events. if service == _FIREBASE_DB_CE_SERVICE: # The CE source of firebasedatabase CloudEvents includes location information # that is inferred from the 'domain' field of legacy events. if "domain" not in event_data: raise EventConversionException( "Invalid FirebaseDB event payload: missing 'domain'") domain = event_data["domain"] location = "us-central1" if domain != "firebaseio.com": location = domain.split(".")[0] resource = f"projects/_/locations/{location}/{resource}" source = f"//{service}/{resource}" metadata = { "id": context.event_id, "time": context.timestamp, "specversion": _CLOUD_EVENT_SPEC_VERSION, "datacontenttype": "application/json", "type": new_type, "source": source, } if subject: metadata["subject"] = subject return CloudEvent(metadata, data)