"""Provides a generic connector for upstream GraphQL services.""" import time from ddtrace import tracer from ddtrace.propagation.http import HTTPPropagator import jwt from sgqlc.endpoint.http import HTTPEndpoint from switchboard_consumer.connectors.log_central import get_current_logger from switchboard_consumer.connectors.secretsmanager import \ get_secret_value class GraphQLConnector: """Connector to an upstream GraphQL service.""" def __init__( self, url, secret, shared_secret_name, credential_fetcher=lambda: {}): """Create an upstream server instance.""" self.secret = secret self.shared_secret_name = shared_secret_name self.credential_fetcher = credential_fetcher self.token = None self.tokenExpiry = time.time() self.endpoint = HTTPEndpoint( url, base_headers={ 'x-token': self._get_token(), 'apollographql-client-name': 'daemon-switchboard-consumer' } ) def _get_token(self): if self.token is None or self.tokenExpiry < time.time(): if self.secret is not None: secret = { 'SecretString': self.secret, 'VersionId': 'unused' } else: credentials = self.credential_fetcher() secret = get_secret_value(self.shared_secret_name, credentials) token_payload = { 'systemName': 'ORCHARD', 'exp': time.time() + 7 * 24 * 60 * 60, 'iat': time.time(), 'secretId': secret['VersionId'], } self.token = jwt.encode(token_payload, secret['SecretString']) self.tokenExpiry = time.time() + 24 * 60 * 60 return self.token def _execute(self, query, data, correlation_id): """Execute a call to a GraphQL endpoint.""" with tracer.trace('graphql') as span: logger = get_current_logger(correlation_id) extra_headers = { 'Correlation-Id': correlation_id, 'x-token': self._get_token() } propagator = HTTPPropagator() propagator.inject(span.context, extra_headers) response = self.endpoint(query, data, extra_headers=extra_headers) logger.info('Executed GraphQL query', custom_fields={ 'endpoint': self.endpoint.url, 'query': query, 'data': data, 'response': response }) return response