Source code for operators.redshift.get_tenants
from airflow.exceptions import AirflowException
from typing import Any, Sequence
from operators.redshift.base_redshift import BaseRedshiftOperator
[docs]
class RedshiftGetTenantsOperator(BaseRedshiftOperator):
"""
Operator to retrieve a list of tenant schema names from a Redshift database.
Queries svv_all_schemas via the Redshift Data API, filtered to a single
source database (default 'grapi_sync'). Mirrors the API of
GetTenantsOperator (Postgres version) so DAGs can swap discovery backends
with minimal changes.
:param redshift_workgroup: Redshift workgroup name (e.g. Config.REDSHIFT_WORKGROUP).
:type redshift_workgroup: str
:param database: Redshift database to issue the discovery query against
(does not have to match tenant_source_database; this is just where
the SELECT runs).
:type database: str
:param tenant_source_database: Value used in the
``svv_all_schemas.database_name = ...`` filter. Defaults to
``'grapi_sync'``.
:type tenant_source_database: str
:param region: AWS region for the Redshift Data API client.
:type region: str
:param additional_exclude_schemas: Optional collection or string of schema
names to exclude in addition to defaults. Accepts list/tuple/set, a
JSON string (e.g. '["casf","test_base"]'), a Python-literal string
(e.g. "['casf']"), or a single plain string (e.g. "casf"). This is
CaSe SeNsItIvE.
:type additional_exclude_schemas: Any
"""
template_fields: Sequence[str] = (
*BaseRedshiftOperator.template_fields,
"tenant_source_database",
"additional_exclude_schemas",
)
def __init__(
self,
redshift_workgroup: str,
database: str,
tenant_source_database: str = "grapi_sync",
region: str = "us-east-1",
additional_exclude_schemas: Any = None,
*args,
**kwargs,
):
super().__init__(
redshift_workgroup=redshift_workgroup,
database=database,
region=region,
*args,
**kwargs,
)
self.tenant_source_database = tenant_source_database
# Store raw; coerce at execution time after templating has rendered.
self.additional_exclude_schemas = additional_exclude_schemas
[docs]
def execute(self, context):
"""Retrieve the list of tenant schema names from Redshift."""
user_excludes = self._coerce_excludes(self.additional_exclude_schemas)
# Mirror the Postgres GetTenantsOperator default-exclusion set so the
# two operators behave consistently.
base_excludes = {"information_schema", "public", "aws_commons", "aws_s3"}
exclude_schemas = sorted(base_excludes | set(user_excludes))
self.log.info(
"RedshiftGetTenantsOperator using excludes=%s (raw=%r) tenant_source_database=%s",
exclude_schemas,
self.additional_exclude_schemas,
self.tenant_source_database,
)
exclude_clause = ", ".join(self.sql_literal(s) for s in exclude_schemas)
sql = (
"SELECT schema_name\n"
"FROM svv_all_schemas\n"
f"WHERE database_name = {self.sql_literal(self.tenant_source_database)}\n"
f" AND schema_name NOT IN ({exclude_clause})\n"
" AND schema_name NOT LIKE 'pg_%'\n"
"ORDER BY schema_name ASC;"
)
try:
rows = self._rs_fetch(sql)
tenants = [r["schema_name"] for r in rows]
except Exception as e:
self.log.exception(
"Error retrieving tenant schemas from Redshift "
"(workgroup=%s database=%s tenant_source_database=%s): %s",
self.redshift_workgroup,
self.database,
self.tenant_source_database,
e,
)
raise AirflowException(
"Failed to retrieve tenant schemas from Redshift "
f"(workgroup='{self.redshift_workgroup}', "
f"database='{self.database}', "
f"tenant_source_database='{self.tenant_source_database}')."
) from e
if not tenants:
raise AirflowException(
"No tenant schemas found in Redshift "
f"(workgroup='{self.redshift_workgroup}', "
f"database='{self.database}', "
f"tenant_source_database='{self.tenant_source_database}')."
)
self.log.info("Found tenant schemas: %s", tenants)
return tenants