From d170389b6d8729c423a2e8971177885f715e85fa Mon Sep 17 00:00:00 2001 From: pushtisonawala Date: Fri, 11 Sep 2026 00:32:16 +0530 Subject: [PATCH 1/2] feat: add Redis connector Adds a Redis source to Playbooks with three tasks: - Fetch Redis INFO stats (optional section) - Fetch Redis slow query log (SLOWLOG GET) - Run a read-only Redis command (allowlist-guarded; write/admin commands blocked) Covers issue #480. Follows the existing Postgres/Bash connector pattern: proto definitions + regenerated stubs, RedisProcessor (redis-py), RedisSourceManager, facade registration, credential mapping, connector-key maps (only host required), frontend constants, two playbook templates (latency spike, memory & eviction pressure) and unit tests. Fixes #480 Co-Authored-By: Claude Sonnet 5 --- connectors/models.py | 22 ++- connectors/utils.py | 12 ++ executor/playbook_source_facade.py | 2 + .../source_managers/redis_source_manager.py | 147 ++++++++++++++++++ executor/source_processors/redis_processor.py | 140 +++++++++++++++++ .../test_redis_source_manager.py | 91 +++++++++++ .../source_processors/test_redis_processor.py | 110 +++++++++++++ playbooks-templates/redis-latency-spike.json | 30 ++++ .../redis-memory-and-eviction-pressure.json | 30 ++++ protos/base.proto | 10 ++ protos/base_pb2.py | 12 +- protos/base_pb2.pyi | 16 ++ protos/playbooks/playbook.proto | 2 + protos/playbooks/playbook_pb2.py | 75 ++++----- protos/playbooks/playbook_pb2.pyi | 11 +- .../source_task_definitions/redis_task.proto | 41 +++++ .../source_task_definitions/redis_task_pb2.py | 34 ++++ .../redis_task_pb2.pyi | 122 +++++++++++++++ requirements.txt | 1 + web/public/integrations/redis_logo.svg | 8 + web/src/constants/connectors.ts | 1 + web/src/constants/models.ts | 1 + web/src/constants/sources.ts | 1 + web/src/constants/taskTypes.ts | 3 + web/src/utils/common/cardsData.ts | 5 + .../integrations/connectorsWithoutAssets.ts | 1 + 26 files changed, 881 insertions(+), 47 deletions(-) create mode 100644 executor/source_managers/redis_source_manager.py create mode 100644 executor/source_processors/redis_processor.py create mode 100644 executor/tests/source_managers/test_redis_source_manager.py create mode 100644 executor/tests/source_processors/test_redis_processor.py create mode 100644 playbooks-templates/redis-latency-spike.json create mode 100644 playbooks-templates/redis-memory-and-eviction-pressure.json create mode 100644 protos/playbooks/source_task_definitions/redis_task.proto create mode 100644 protos/playbooks/source_task_definitions/redis_task_pb2.py create mode 100644 protos/playbooks/source_task_definitions/redis_task_pb2.pyi create mode 100644 web/public/integrations/redis_logo.svg diff --git a/connectors/models.py b/connectors/models.py index 259f09cd8..068da8b43 100644 --- a/connectors/models.py +++ b/connectors/models.py @@ -50,6 +50,7 @@ Source.BIG_QUERY: 'BIG QUERY', Source.JIRA_CLOUD: 'JIRA', Source.ARGOCD: 'ARGOCD', + Source.REDIS: 'REDIS', } integrations_connector_type_category_map = { @@ -83,6 +84,7 @@ Source.POSTGRES: 'Database', Source.SQL_DATABASE_CONNECTION: 'Database', Source.BIG_QUERY: 'Database', + Source.REDIS: 'Database', Source.OPEN_AI: 'LLM Tools', Source.BASH: 'Remote Server', Source.KUBERNETES: 'Cloud', @@ -343,6 +345,18 @@ SourceKeyType.ARGOCD_TOKEN, ] ], + Source.REDIS: [ + # Only host is required; port/password/db/ssl are optional. Each accepted + # combination is listed because connector validation is an exact set match. + [SourceKeyType.REDIS_HOST], + [SourceKeyType.REDIS_HOST, SourceKeyType.REDIS_PORT], + [SourceKeyType.REDIS_HOST, SourceKeyType.REDIS_PORT, SourceKeyType.REDIS_DB], + [SourceKeyType.REDIS_HOST, SourceKeyType.REDIS_PORT, SourceKeyType.REDIS_PASSWORD], + [SourceKeyType.REDIS_HOST, SourceKeyType.REDIS_PORT, SourceKeyType.REDIS_PASSWORD, + SourceKeyType.REDIS_DB], + [SourceKeyType.REDIS_HOST, SourceKeyType.REDIS_PORT, SourceKeyType.REDIS_PASSWORD, + SourceKeyType.REDIS_DB, SourceKeyType.REDIS_SSL_ENABLED], + ], } integrations_connector_key_display_name_map = { @@ -426,6 +440,11 @@ SourceKeyType.JIRA_DOMAIN: 'Domain', SourceKeyType.ARGOCD_SERVER: 'ArgoCD Server', SourceKeyType.ARGOCD_TOKEN: 'Token', + SourceKeyType.REDIS_HOST: 'Host', + SourceKeyType.REDIS_PORT: 'Port', + SourceKeyType.REDIS_PASSWORD: 'Password', + SourceKeyType.REDIS_DB: 'DB Number', + SourceKeyType.REDIS_SSL_ENABLED: 'SSL Enabled', } @@ -574,7 +593,8 @@ def proto(self): SourceKeyType.JIRA_EMAIL, SourceKeyType.JIRA_DOMAIN, SourceKeyType.ARGOCD_SERVER, - SourceKeyType.ARGOCD_TOKEN]: + SourceKeyType.ARGOCD_TOKEN, + SourceKeyType.REDIS_PASSWORD]: key_value = '*********' + self.key[-4:] return ConnectorKeyProto(key_type=self.key_type, key=StringValue(value=key_value), diff --git a/connectors/utils.py b/connectors/utils.py index dc0e2a172..a71cdf722 100644 --- a/connectors/utils.py +++ b/connectors/utils.py @@ -139,6 +139,18 @@ def generate_credentials_dict(connector_type, connector_keys): credentials_dict['database'] = conn_key.key.value elif conn_key.key_type == SourceKeyType.POSTGRES_PORT: credentials_dict['port'] = conn_key.key.value + elif connector_type == Source.REDIS: + for conn_key in connector_keys: + if conn_key.key_type == SourceKeyType.REDIS_HOST: + credentials_dict['host'] = conn_key.key.value + elif conn_key.key_type == SourceKeyType.REDIS_PORT: + credentials_dict['port'] = conn_key.key.value + elif conn_key.key_type == SourceKeyType.REDIS_PASSWORD: + credentials_dict['password'] = conn_key.key.value + elif conn_key.key_type == SourceKeyType.REDIS_DB: + credentials_dict['db'] = conn_key.key.value + elif conn_key.key_type == SourceKeyType.REDIS_SSL_ENABLED: + credentials_dict['ssl_enabled'] = conn_key.key.value elif connector_type == Source.SQL_DATABASE_CONNECTION: for conn_key in connector_keys: if conn_key.key_type == SourceKeyType.SQL_DATABASE_CONNECTION_STRING_URI: diff --git a/executor/playbook_source_facade.py b/executor/playbook_source_facade.py index 5c2b33be3..7734303ed 100644 --- a/executor/playbook_source_facade.py +++ b/executor/playbook_source_facade.py @@ -28,6 +28,7 @@ from executor.source_managers.datadog_source_manager import DatadogSourceManager from executor.source_managers.eks_source_manager import EksSourceManager from executor.source_managers.postgres_source_manager import PostgresSourceManager +from executor.source_managers.redis_source_manager import RedisSourceManager from executor.source_managers.zenduty_source_manager import ZendutySourceManager from executor.source_managers.sql_database_connection_source_manager import SqlDatabaseConnectionSourceManager from executor.source_managers.rootly_source_manager import RootlySourceManager @@ -134,6 +135,7 @@ def test_source_connection(self, source_connection: ConnectorProto): playbook_source_facade.register(Source.GRAFANA_LOKI, GrafanaLokiSourceManager()) playbook_source_facade.register(Source.POSTGRES, PostgresSourceManager()) +playbook_source_facade.register(Source.REDIS, RedisSourceManager()) playbook_source_facade.register(Source.CLICKHOUSE, ClickhouseSourceManager()) playbook_source_facade.register(Source.SQL_DATABASE_CONNECTION, SqlDatabaseConnectionSourceManager()) playbook_source_facade.register(Source.ELASTIC_SEARCH, ElasticSearchSourceManager()) diff --git a/executor/source_managers/redis_source_manager.py b/executor/source_managers/redis_source_manager.py new file mode 100644 index 000000000..a92079491 --- /dev/null +++ b/executor/source_managers/redis_source_manager.py @@ -0,0 +1,147 @@ +from google.protobuf.wrappers_pb2 import StringValue, UInt64Value, Int64Value + +from connectors.utils import generate_credentials_dict +from executor.playbook_source_manager import PlaybookSourceManager +from executor.source_processors.redis_processor import RedisProcessor +from protos.base_pb2 import Source, TimeRange, SourceModelType +from protos.connectors.connector_pb2 import Connector as ConnectorProto +from protos.literal_pb2 import LiteralType, Literal +from protos.playbooks.playbook_commons_pb2 import PlaybookTaskResult, TableResult, PlaybookTaskResultType +from protos.playbooks.source_task_definitions.redis_task_pb2 import Redis +from protos.ui_definition_pb2 import FormField, FormFieldType + + +class RedisSourceManager(PlaybookSourceManager): + + def __init__(self): + self.source = Source.REDIS + self.task_proto = Redis + self.task_type_callable_map = { + Redis.TaskType.GET_INFO: { + 'executor': self.execute_get_info, + 'model_types': [SourceModelType.REDIS_CONNECTION], + 'result_type': PlaybookTaskResultType.TABLE, + 'display_name': 'Fetch Redis INFO stats', + 'category': 'Database', + 'form_fields': [ + FormField(key_name=StringValue(value="section"), + display_name=StringValue(value="INFO section"), + description=StringValue(value='e.g. memory, clients, stats, replication. Leave blank for all.'), + data_type=LiteralType.STRING, + form_field_type=FormFieldType.TEXT_FT, + is_optional=True), + ] + }, + Redis.TaskType.GET_SLOWLOG: { + 'executor': self.execute_get_slowlog, + 'model_types': [SourceModelType.REDIS_CONNECTION], + 'result_type': PlaybookTaskResultType.TABLE, + 'display_name': 'Fetch Redis slow query log', + 'category': 'Database', + 'form_fields': [ + FormField(key_name=StringValue(value="limit"), + display_name=StringValue(value="Number of entries"), + description=StringValue(value='How many slowlog entries to fetch'), + data_type=LiteralType.LONG, + default_value=Literal(type=LiteralType.LONG, long=Int64Value(value=10)), + form_field_type=FormFieldType.TEXT_FT, + is_optional=True), + ] + }, + Redis.TaskType.RUN_COMMAND: { + 'executor': self.execute_run_command, + 'model_types': [SourceModelType.REDIS_CONNECTION], + 'result_type': PlaybookTaskResultType.TABLE, + 'display_name': 'Run a read-only Redis command', + 'category': 'Database', + 'form_fields': [ + FormField(key_name=StringValue(value="command"), + display_name=StringValue(value="Command"), + description=StringValue(value='Read-only command only, e.g. "DBSIZE", "GET key", "MEMORY USAGE key"'), + data_type=LiteralType.STRING, + form_field_type=FormFieldType.MULTILINE_FT), + ] + }, + } + + def get_connector_processor(self, redis_connector, **kwargs): + generated_credentials = generate_credentials_dict(redis_connector.type, redis_connector.keys) + return RedisProcessor(**generated_credentials) + + @staticmethod + def _table_from_rows(raw_query, rows): + table_rows = [] + for row in rows: + columns = [ + TableResult.TableColumn(name=StringValue(value=str(key)), value=StringValue(value=str(value))) + for key, value in row.items() + ] + table_rows.append(TableResult.TableRow(columns=columns)) + return TableResult( + raw_query=StringValue(value=raw_query), + total_count=UInt64Value(value=len(table_rows)), + rows=table_rows, + ) + + def execute_get_info(self, time_range: TimeRange, redis_task: Redis, + redis_connector: ConnectorProto) -> PlaybookTaskResult: + try: + if not redis_connector: + raise Exception("Task execution Failed:: No Redis source found") + section = redis_task.get_info.section.value.strip() if redis_task.get_info.section.value else None + processor = self.get_connector_processor(redis_connector) + + print("Playbook Task Downstream Request: Type -> {}, Account -> {}, Section -> {}".format( + "Redis", redis_connector.account_id.value, section or 'all'), flush=True) + + info = processor.get_info(section) + rows = [{'metric': k, 'value': v} for k, v in info.items()] + table = self._table_from_rows(f'INFO {section}' if section else 'INFO', rows) + return PlaybookTaskResult(type=PlaybookTaskResultType.TABLE, table=table, source=self.source) + except Exception as e: + raise Exception(f"Error while executing Redis task: {e}") + + def execute_get_slowlog(self, time_range: TimeRange, redis_task: Redis, + redis_connector: ConnectorProto) -> PlaybookTaskResult: + try: + if not redis_connector: + raise Exception("Task execution Failed:: No Redis source found") + limit = redis_task.get_slowlog.limit.value if redis_task.get_slowlog.limit.value else 10 + processor = self.get_connector_processor(redis_connector) + + print("Playbook Task Downstream Request: Type -> {}, Account -> {}, Limit -> {}".format( + "Redis", redis_connector.account_id.value, limit), flush=True) + + entries = processor.get_slowlog(limit) + table = self._table_from_rows(f'SLOWLOG GET {limit}', entries) + return PlaybookTaskResult(type=PlaybookTaskResultType.TABLE, table=table, source=self.source) + except Exception as e: + raise Exception(f"Error while executing Redis task: {e}") + + def execute_run_command(self, time_range: TimeRange, redis_task: Redis, + redis_connector: ConnectorProto) -> PlaybookTaskResult: + try: + if not redis_connector: + raise Exception("Task execution Failed:: No Redis source found") + command = redis_task.run_command.command.value.strip() + if not command: + raise Exception("Task execution Failed:: No command provided") + processor = self.get_connector_processor(redis_connector) + + print("Playbook Task Downstream Request: Type -> {}, Account -> {}, Command -> {}".format( + "Redis", redis_connector.account_id.value, command), flush=True) + + result = processor.run_command(command) + rows = self._normalise_command_result(result) + table = self._table_from_rows(command, rows) + return PlaybookTaskResult(type=PlaybookTaskResultType.TABLE, table=table, source=self.source) + except Exception as e: + raise Exception(f"Error while executing Redis task: {e}") + + @staticmethod + def _normalise_command_result(result): + if isinstance(result, dict): + return [{'key': k, 'value': v} for k, v in result.items()] + if isinstance(result, (list, tuple, set)): + return [{'index': i, 'value': v} for i, v in enumerate(result)] + return [{'result': result}] diff --git a/executor/source_processors/redis_processor.py b/executor/source_processors/redis_processor.py new file mode 100644 index 000000000..77d93503f --- /dev/null +++ b/executor/source_processors/redis_processor.py @@ -0,0 +1,140 @@ +import logging + +import redis + +from executor.source_processors.processor import Processor + +logger = logging.getLogger(__name__) + +# Commands the connector is allowed to run through the "Run Command" task. +# Restricted to read-only / introspection commands so a playbook can never +# mutate or administer the target Redis instance. +REDIS_READ_ONLY_COMMANDS = { + 'GET', 'MGET', 'STRLEN', 'GETRANGE', 'SUBSTR', + 'EXISTS', 'TYPE', 'TTL', 'PTTL', 'EXPIRETIME', 'PEXPIRETIME', + 'KEYS', 'SCAN', 'RANDOMKEY', 'DBSIZE', 'OBJECT', + 'HGET', 'HMGET', 'HGETALL', 'HKEYS', 'HVALS', 'HLEN', 'HEXISTS', 'HSTRLEN', 'HSCAN', + 'LLEN', 'LRANGE', 'LINDEX', 'LPOS', + 'SCARD', 'SISMEMBER', 'SMISMEMBER', 'SMEMBERS', 'SRANDMEMBER', 'SSCAN', 'SINTER', 'SUNION', 'SDIFF', + 'ZCARD', 'ZCOUNT', 'ZRANGE', 'ZRANGEBYSCORE', 'ZRANGEBYLEX', 'ZRANK', 'ZREVRANK', 'ZSCORE', 'ZMSCORE', 'ZSCAN', + 'XLEN', 'XRANGE', 'XREVRANGE', 'XINFO', 'XPENDING', + 'BITCOUNT', 'BITPOS', 'GETBIT', 'PFCOUNT', 'GEOPOS', 'GEODIST', 'GEOSEARCH', + 'INFO', 'MEMORY', 'CLIENT', 'CONFIG', 'SLOWLOG', 'LATENCY', 'COMMAND', + 'PING', 'ECHO', 'TIME', 'LOLWUT', 'DEBUG', 'LASTSAVE', 'WAIT', +} + +# Sub-commands that must be blocked even though their parent command is +# otherwise read-only (e.g. CONFIG SET, MEMORY PURGE, CLIENT KILL). +REDIS_BLOCKED_SUBCOMMANDS = { + ('CONFIG', 'SET'), ('CONFIG', 'RESETSTAT'), ('CONFIG', 'REWRITE'), + ('MEMORY', 'PURGE'), ('MEMORY', 'MALLOC-STATS'), + ('CLIENT', 'KILL'), ('CLIENT', 'SETNAME'), ('CLIENT', 'PAUSE'), ('CLIENT', 'UNPAUSE'), + ('CLIENT', 'NO-EVICT'), ('CLIENT', 'NO-TOUCH'), ('CLIENT', 'SETINFO'), + ('SLOWLOG', 'RESET'), + ('LATENCY', 'RESET'), + ('DEBUG', 'SLEEP'), ('DEBUG', 'SEGFAULT'), ('DEBUG', 'PANIC'), ('DEBUG', 'QUICKLIST-PACKED-THRESHOLD'), + ('DEBUG', 'SET-ACTIVE-EXPIRE'), ('DEBUG', 'JMAP'), ('DEBUG', 'CHANGE-REPL-ID'), +} + + +class RedisCommandNotAllowed(Exception): + pass + + +def _decode(value): + if isinstance(value, (bytes, bytearray)): + return value.decode('utf-8', errors='replace') + if isinstance(value, (list, tuple)): + return ' '.join(_decode(v) for v in value) + return value + + +class RedisProcessor(Processor): + client = None + + def __init__(self, host, port=6379, password=None, db=0, ssl_enabled=False): + self.config = { + 'host': host, + 'port': int(port) if port else 6379, + 'db': int(db) if db else 0, + 'socket_timeout': 15, + 'socket_connect_timeout': 15, + 'decode_responses': True, + } + if password: + self.config['password'] = password + if str(ssl_enabled).lower() == 'true' or ssl_enabled is True: + self.config['ssl'] = True + + def get_connection(self): + try: + if not self.client: + self.client = redis.Redis(**self.config) + return self.client + except Exception as e: + logger.error(f"Exception occurred while creating redis connection with error: {e}") + raise e + + def test_connection(self): + try: + client = self.get_connection() + client.ping() + return True + except Exception as e: + logger.error(f"Exception occurred while testing redis connection with error: {e}") + raise e + + def get_info(self, section=None): + try: + client = self.get_connection() + if section: + return client.info(section) + return client.info() + except Exception as e: + logger.error(f"Exception occurred while fetching redis INFO with error: {e}") + raise e + + def get_slowlog(self, limit=10): + try: + client = self.get_connection() + limit = int(limit) if limit else 10 + entries = client.slowlog_get(limit) + normalised = [] + for entry in entries: + normalised.append({ + 'id': _decode(entry.get('id')), + 'start_time': _decode(entry.get('start_time')), + 'duration_us': _decode(entry.get('duration')), + 'command': _decode(entry.get('command')), + 'client_address': _decode(entry.get('client_address')), + 'client_name': _decode(entry.get('client_name')), + }) + return normalised + except Exception as e: + logger.error(f"Exception occurred while fetching redis SLOWLOG with error: {e}") + raise e + + @staticmethod + def _validate_read_only(tokens): + if not tokens: + raise RedisCommandNotAllowed("Empty command") + command = tokens[0].upper() + if command not in REDIS_READ_ONLY_COMMANDS: + raise RedisCommandNotAllowed( + f"Command '{command}' is not permitted. Only read-only commands can be run through this connector." + ) + if len(tokens) > 1 and (command, tokens[1].upper()) in REDIS_BLOCKED_SUBCOMMANDS: + raise RedisCommandNotAllowed(f"Command '{command} {tokens[1].upper()}' is not permitted.") + return command + + def run_command(self, command_str): + try: + tokens = command_str.split() + self._validate_read_only(tokens) + client = self.get_connection() + return client.execute_command(*tokens) + except RedisCommandNotAllowed: + raise + except Exception as e: + logger.error(f"Exception occurred while running redis command with error: {e}") + raise e diff --git a/executor/tests/source_managers/test_redis_source_manager.py b/executor/tests/source_managers/test_redis_source_manager.py new file mode 100644 index 000000000..d83fe5557 --- /dev/null +++ b/executor/tests/source_managers/test_redis_source_manager.py @@ -0,0 +1,91 @@ +from unittest import mock, TestCase +from unittest.mock import MagicMock + +import django + +django.setup() + +from executor.source_managers.redis_source_manager import RedisSourceManager +from protos.base_pb2 import TimeRange +from protos.playbooks.playbook_commons_pb2 import PlaybookTaskResultType +from protos.playbooks.source_task_definitions.redis_task_pb2 import Redis +from google.protobuf.wrappers_pb2 import StringValue, UInt64Value + + +class TestRedisSourceManager(TestCase): + + @mock.patch('executor.source_managers.redis_source_manager.generate_credentials_dict') + @mock.patch('executor.source_managers.redis_source_manager.RedisProcessor') + def test_get_connector_processor(self, MockRedisProcessor, mock_generate_credentials_dict): + mock_generate_credentials_dict.return_value = {'host': 'localhost', 'port': '6379'} + manager = RedisSourceManager() + connector = MagicMock() + connector.type = 'redis' + connector.keys = {} + + processor = manager.get_connector_processor(connector) + MockRedisProcessor.assert_called_once_with(host='localhost', port='6379') + self.assertEqual(processor, MockRedisProcessor.return_value) + + @mock.patch('executor.source_managers.redis_source_manager.generate_credentials_dict') + @mock.patch('executor.source_managers.redis_source_manager.RedisProcessor') + def test_execute_get_info_returns_table(self, MockRedisProcessor, mock_generate_credentials_dict): + mock_generate_credentials_dict.return_value = {'host': 'localhost'} + MockRedisProcessor.return_value.get_info.return_value = { + 'used_memory_human': '1.2M', 'connected_clients': 3, + } + manager = RedisSourceManager() + connector = MagicMock() + connector.account_id = UInt64Value(value=1) + + task = Redis(type=Redis.TaskType.GET_INFO, get_info=Redis.GetInfo(section=StringValue(value='memory'))) + result = manager.execute_get_info(TimeRange(), task, connector) + + self.assertEqual(result.type, PlaybookTaskResultType.TABLE) + self.assertEqual(len(result.table.rows), 2) + MockRedisProcessor.return_value.get_info.assert_called_once_with('memory') + + @mock.patch('executor.source_managers.redis_source_manager.generate_credentials_dict') + @mock.patch('executor.source_managers.redis_source_manager.RedisProcessor') + def test_execute_get_slowlog_defaults_limit(self, MockRedisProcessor, mock_generate_credentials_dict): + mock_generate_credentials_dict.return_value = {'host': 'localhost'} + MockRedisProcessor.return_value.get_slowlog.return_value = [ + {'id': 1, 'command': 'GET foo', 'duration_us': 12}, + ] + manager = RedisSourceManager() + connector = MagicMock() + connector.account_id = UInt64Value(value=1) + + task = Redis(type=Redis.TaskType.GET_SLOWLOG, get_slowlog=Redis.GetSlowlog()) + result = manager.execute_get_slowlog(TimeRange(), task, connector) + + self.assertEqual(result.type, PlaybookTaskResultType.TABLE) + MockRedisProcessor.return_value.get_slowlog.assert_called_once_with(10) + + @mock.patch('executor.source_managers.redis_source_manager.generate_credentials_dict') + @mock.patch('executor.source_managers.redis_source_manager.RedisProcessor') + def test_execute_run_command(self, MockRedisProcessor, mock_generate_credentials_dict): + mock_generate_credentials_dict.return_value = {'host': 'localhost'} + MockRedisProcessor.return_value.run_command.return_value = 5 + manager = RedisSourceManager() + connector = MagicMock() + connector.account_id = UInt64Value(value=1) + + task = Redis(type=Redis.TaskType.RUN_COMMAND, + run_command=Redis.RunCommand(command=StringValue(value='DBSIZE'))) + result = manager.execute_run_command(TimeRange(), task, connector) + + self.assertEqual(result.type, PlaybookTaskResultType.TABLE) + self.assertEqual(result.table.rows[0].columns[0].value.value, '5') + MockRedisProcessor.return_value.run_command.assert_called_once_with('DBSIZE') + + def test_task_type_callable_map_complete(self): + manager = RedisSourceManager() + self.assertEqual( + set(manager.task_type_callable_map.keys()), + {Redis.TaskType.GET_INFO, Redis.TaskType.GET_SLOWLOG, Redis.TaskType.RUN_COMMAND}, + ) + for info in manager.task_type_callable_map.values(): + self.assertIn('executor', info) + self.assertIn('display_name', info) + self.assertEqual(info['result_type'], PlaybookTaskResultType.TABLE) diff --git a/executor/tests/source_processors/test_redis_processor.py b/executor/tests/source_processors/test_redis_processor.py new file mode 100644 index 000000000..a6f70488d --- /dev/null +++ b/executor/tests/source_processors/test_redis_processor.py @@ -0,0 +1,110 @@ +from unittest import mock + +from django.test import TestCase + +from executor.source_processors.redis_processor import RedisProcessor, RedisCommandNotAllowed + + +class TestRedisProcessor(TestCase): + + @mock.patch('redis.Redis') + def test_successful_connection(self, mock_redis): + mock_client = mock.Mock() + mock_redis.return_value = mock_client + + processor = RedisProcessor(host='localhost', port=6379, password='pass', db=0) + connection = processor.get_connection() + + self.assertEqual(connection, mock_client) + _, kwargs = mock_redis.call_args + self.assertEqual(kwargs['host'], 'localhost') + self.assertEqual(kwargs['port'], 6379) + self.assertEqual(kwargs['password'], 'pass') + self.assertEqual(kwargs['db'], 0) + + @mock.patch('redis.Redis') + def test_password_omitted_when_blank(self, mock_redis): + RedisProcessor(host='localhost', password='').get_connection() + _, kwargs = mock_redis.call_args + self.assertNotIn('password', kwargs) + + @mock.patch('redis.Redis') + def test_ssl_enabled_flag(self, mock_redis): + RedisProcessor(host='localhost', ssl_enabled='true').get_connection() + _, kwargs = mock_redis.call_args + self.assertTrue(kwargs['ssl']) + + @mock.patch('executor.source_processors.redis_processor.logger') + @mock.patch('redis.Redis', side_effect=Exception("connection refused")) + def test_connection_failure(self, mock_redis, mock_logger): + processor = RedisProcessor(host='localhost') + with self.assertRaises(Exception) as ctx: + processor.get_connection() + self.assertEqual(str(ctx.exception), "connection refused") + + @mock.patch('redis.Redis') + def test_test_connection_pings(self, mock_redis): + mock_client = mock.Mock() + mock_redis.return_value = mock_client + self.assertTrue(RedisProcessor(host='localhost').test_connection()) + mock_client.ping.assert_called_once() + + @mock.patch('redis.Redis') + def test_get_info_with_section(self, mock_redis): + mock_client = mock.Mock() + mock_client.info.return_value = {'used_memory': 1024} + mock_redis.return_value = mock_client + + result = RedisProcessor(host='localhost').get_info('memory') + self.assertEqual(result, {'used_memory': 1024}) + mock_client.info.assert_called_once_with('memory') + + @mock.patch('redis.Redis') + def test_get_slowlog_normalises_entries(self, mock_redis): + mock_client = mock.Mock() + mock_client.slowlog_get.return_value = [{ + 'id': 1, + 'start_time': 1700000000, + 'duration': 1500, + 'command': [b'GET', b'foo'], + 'client_address': b'127.0.0.1:5000', + 'client_name': b'', + }] + mock_redis.return_value = mock_client + + result = RedisProcessor(host='localhost').get_slowlog(5) + self.assertEqual(len(result), 1) + self.assertEqual(result[0]['command'], 'GET foo') + self.assertEqual(result[0]['duration_us'], 1500) + self.assertEqual(result[0]['client_address'], '127.0.0.1:5000') + mock_client.slowlog_get.assert_called_once_with(5) + + @mock.patch('redis.Redis') + def test_run_command_allows_read_only(self, mock_redis): + mock_client = mock.Mock() + mock_client.execute_command.return_value = 42 + mock_redis.return_value = mock_client + + result = RedisProcessor(host='localhost').run_command('DBSIZE') + self.assertEqual(result, 42) + mock_client.execute_command.assert_called_once_with('DBSIZE') + + def test_run_command_blocks_write_command(self): + with self.assertRaises(RedisCommandNotAllowed): + RedisProcessor(host='localhost').run_command('SET foo bar') + + def test_run_command_blocks_flushall(self): + with self.assertRaises(RedisCommandNotAllowed): + RedisProcessor(host='localhost').run_command('FLUSHALL') + + def test_run_command_blocks_config_set(self): + with self.assertRaises(RedisCommandNotAllowed): + RedisProcessor(host='localhost').run_command('CONFIG SET maxmemory 100mb') + + def test_run_command_allows_config_get(self): + with mock.patch('redis.Redis') as mock_redis: + mock_client = mock.Mock() + mock_client.execute_command.return_value = ['maxmemory', '0'] + mock_redis.return_value = mock_client + result = RedisProcessor(host='localhost').run_command('CONFIG GET maxmemory') + self.assertEqual(result, ['maxmemory', '0']) diff --git a/playbooks-templates/redis-latency-spike.json b/playbooks-templates/redis-latency-spike.json new file mode 100644 index 000000000..13573e4fd --- /dev/null +++ b/playbooks-templates/redis-latency-spike.json @@ -0,0 +1,30 @@ +{ + "name": "Redis Latency Spike Investigation", + "description": "Playbook to investigate a spike in Redis command latency using the Redis connector (INFO stats, slow query log) alongside client and upstream traffic checks.", + "steps": [ + { + "description": "Pull the slow query log", + "notes": "Run the Redis connector task **Fetch Redis slow query log** with a limit of 50. Look for a recurring command pattern, large key names, or `KEYS` / `SMEMBERS` / `HGETALL` on big collections. Note the slowest commands and their duration in microseconds." + }, + { + "description": "Check server-side latency stats", + "notes": "Run **Run a read-only Redis command** with `LATENCY LATEST` and `LATENCY HISTORY command`. Also run `INFO commandstats` to see per-command call counts and `usec_per_call`. A high `usec_per_call` on a frequent command points to an expensive access pattern." + }, + { + "description": "Check for blocking and fork pauses", + "notes": "Run **Fetch Redis INFO stats** for the `stats` section. Review `latest_fork_usec` (RDB/AOF fork pauses), `blocked_clients`, and `total_reads_processed` vs `instantaneous_ops_per_sec`. A large `latest_fork_usec` means persistence forks are stalling the event loop." + }, + { + "description": "Check memory pressure and eviction", + "notes": "Run **Fetch Redis INFO stats** for the `memory` section. A `mem_fragmentation_ratio` well above 1.5, `used_memory` near `maxmemory`, or rising `evicted_keys` (from the `stats` section) all add latency. Cross-check with the Redis Memory & Eviction Pressure playbook." + }, + { + "description": "Check client and connection load", + "notes": "Run **Fetch Redis INFO stats** for the `clients` section. Compare `connected_clients` against `maxclients` from `INFO`. Run `CLIENT LIST` via **Run a read-only Redis command** to spot a client with a large `omem` (output buffer) or many `cmd=subscribe` connections." + }, + { + "description": "Correlate with upstream traffic", + "notes": "Select the services that call this Redis instance and check their request rate and error rate over the same window. A latency spike that starts with a traffic increase usually means an expensive query pattern is now being run more often." + } + ] +} diff --git a/playbooks-templates/redis-memory-and-eviction-pressure.json b/playbooks-templates/redis-memory-and-eviction-pressure.json new file mode 100644 index 000000000..854349cda --- /dev/null +++ b/playbooks-templates/redis-memory-and-eviction-pressure.json @@ -0,0 +1,30 @@ +{ + "name": "Redis Memory & Eviction Pressure Investigation", + "description": "Playbook to investigate high Redis memory usage, key eviction, or OOM errors by querying the instance directly with the Redis connector.", + "steps": [ + { + "description": "Snapshot current memory", + "notes": "Run the Redis connector task **Fetch Redis INFO stats** for the `memory` section. Record `used_memory_human`, `used_memory_rss_human`, `used_memory_peak_human`, `maxmemory_human`, `maxmemory_policy`, and `mem_fragmentation_ratio`. If `used_memory` is close to `maxmemory`, the instance is at capacity." + }, + { + "description": "Check eviction and keyspace churn", + "notes": "Run **Fetch Redis INFO stats** for the `stats` section. Rising `evicted_keys` and `expired_keys`, and a low `keyspace_hits` / (`keyspace_hits` + `keyspace_misses`) ratio, mean useful data is being pushed out. Note the rate of change against the alert window." + }, + { + "description": "Find what is consuming memory", + "notes": "Run **Run a read-only Redis command** with `MEMORY STATS` for a breakdown, and `INFO keyspace` to see key counts per DB. Use `SCAN` with `COUNT 100` plus `MEMORY USAGE ` on a sample of large keys to identify oversized values or collections." + }, + { + "description": "Check fragmentation and allocator", + "notes": "A `mem_fragmentation_ratio` above ~1.5 wastes RSS; below 1.0 means Redis is swapping. Run **Run a read-only Redis command** with `MEMORY DOCTOR` for Redis's own assessment." + }, + { + "description": "Review the eviction policy fit", + "notes": "If `maxmemory_policy` is `noeviction`, writes will fail with OOM once full — confirm that is intended. For a cache, `allkeys-lru` or `allkeys-lfu` is usually correct. Flag a mismatch to the owning team." + }, + { + "description": "Correlate with upstream writes", + "notes": "Select the services writing to this instance and check for a deploy or traffic change at the start of the memory climb. A steady climb with flat traffic suggests missing TTLs or a key leak rather than load." + } + ] +} diff --git a/protos/base.proto b/protos/base.proto index d2d72d0c2..8b2874ab1 100644 --- a/protos/base.proto +++ b/protos/base.proto @@ -178,6 +178,7 @@ enum Source { JENKINS = 62; GITHUB_ACTIONS = 63; ARGOCD = 65; + REDIS = 66; } enum SourceKeyType { @@ -329,6 +330,12 @@ enum SourceKeyType { ARGOCD_SERVER = 106; ARGOCD_TOKEN = 107; + + REDIS_HOST = 111; + REDIS_PORT = 112; + REDIS_PASSWORD = 113; + REDIS_DB = 114; + REDIS_SSL_ENABLED = 115; } enum SourceModelType { @@ -441,4 +448,7 @@ enum SourceModelType { // ArgoCD Models ARGOCD_APPS = 2801; + + // Redis Models + REDIS_CONNECTION = 2901; } diff --git a/protos/base_pb2.py b/protos/base_pb2.py index fa48c67ba..0e03edf24 100644 --- a/protos/base_pb2.py +++ b/protos/base_pb2.py @@ -14,7 +14,7 @@ from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11protos/base.proto\x12\x06protos\x1a\x1egoogle/protobuf/wrappers.proto\".\n\tTimeRange\x12\x10\n\x08time_geq\x18\x01 \x01(\x04\x12\x0f\n\x07time_lt\x18\x02 \x01(\x04\"a\n\x04Page\x12+\n\x05limit\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12,\n\x06offset\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\"\xaf\x01\n\x04Meta\x12%\n\ntime_range\x18\x01 \x01(\x0b\x32\x11.protos.TimeRange\x12\x1a\n\x04page\x18\x02 \x01(\x0b\x32\x0c.protos.Page\x12\x31\n\x0btotal_count\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12\x31\n\rshow_inactive\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\"@\n\x07Message\x12\r\n\x05title\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x11\n\ttraceback\x18\x03 \x01(\t\"[\n\x0c\x45rrorMessage\x12)\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x12 \n\x07message\x18\x02 \x01(\x0b\x32\x0f.protos.Message\"\\\n\rOpDescription\x12\x16\n\x02op\x18\x01 \x01(\x0e\x32\n.protos.Op\x12\r\n\x05label\x18\x02 \x01(\t\x12\x10\n\x08is_unary\x18\x03 \x01(\x08\x12\x12\n\nis_logical\x18\x04 \x01(\x08\"\xc7\x02\n\x10TaskCronSchedule\x12-\n\x07minutes\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05hours\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x10\x64\x61ys_of_the_week\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x37\n\x11\x64\x61ys_of_the_month\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x10\x64\x61ys_of_the_year\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12.\n\x08timezone\x18\x06 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"I\n\x0cTaskInterval\x12\x39\n\x13interval_in_seconds\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\"j\n\x0cTaskCronRule\x12*\n\x04rule\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12.\n\x08timezone\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue*Q\n\x08\x46unction\x12\r\n\tUNKNOWN_F\x10\x00\x12\t\n\x05\x41VG_F\x10\x01\x12\t\n\x05SUM_F\x10\x02\x12\t\n\x05MIN_F\x10\x03\x12\t\n\x05MAX_F\x10\x04\x12\n\n\x06LAST_F\x10\x05*\xb7\x01\n\x08Operator\x12\r\n\tUNKNOWN_O\x10\x00\x12\x12\n\x0eGREATER_THAN_O\x10\x01\x12\x18\n\x14GREATER_THAN_EQUAL_O\x10\x02\x12\x0f\n\x0bLESS_THAN_O\x10\x03\x12\x15\n\x11LESS_THAN_EQUAL_O\x10\x04\x12\x0b\n\x07\x45QUAL_O\x10\x05\x12\x0f\n\x0bNOT_EQUAL_O\x10\x06\x12\n\n\x06LIKE_O\x10\x07\x12\x0c\n\x08\x45XISTS_O\x10\x08\x12\x0e\n\nCONTAINS_O\x10\t*D\n\x0fLogicalOperator\x12\x0e\n\nUNKNOWN_LO\x10\x00\x12\n\n\x06\x41ND_LO\x10\x01\x12\t\n\x05OR_LO\x10\x02\x12\n\n\x06NOT_LO\x10\x03*\x8e\x01\n\x02Op\x12\x0e\n\nUNKNOWN_OP\x10\x00\x12\x06\n\x02\x45Q\x10\x01\x12\x07\n\x03NEQ\x10\x02\x12\x06\n\x02GT\x10\x03\x12\x06\n\x02LT\x10\x04\x12\x07\n\x03GTE\x10\x05\x12\x07\n\x03LTE\x10\x06\x12\x06\n\x02IN\x10\x07\x12\n\n\x06NOT_IN\x10\x08\x12\x0b\n\x07IS_NULL\x10\t\x12\n\n\x06\x45XISTS\x10\n\x12\x07\n\x03\x41ND\x10\x14\x12\x06\n\x02OR\x10\x15\x12\x07\n\x03NOT\x10\x16*d\n\x07\x43ontext\x12\r\n\tUNKNOWN_C\x10\x00\x12\x0c\n\x08PLAYBOOK\x10\x01\x12\x16\n\x12PLAYBOOK_EXECUTION\x10\x02\x12\x0c\n\x08WORKFLOW\x10\x03\x12\x16\n\x12WORKFLOW_EXECUTION\x10\x04*\xe1\x07\n\x06Source\x12\x0b\n\x07UNKNOWN\x10\x00\x12\n\n\x06SENTRY\x10\x01\x12\x0b\n\x07SEGMENT\x10\x02\x12\x12\n\x0e\x45LASTIC_SEARCH\x10\x03\x12\r\n\tAMPLITUDE\x10\x04\x12\x0f\n\x0b\x41WS_KINESIS\x10\x05\x12\x0e\n\nCLOUDWATCH\x10\x06\x12\r\n\tCLEVERTAP\x10\x07\x12\x0f\n\x0bRUDDERSTACK\x10\x08\x12\x0c\n\x08MOENGAGE\x10\t\x12\t\n\x05\x43RIBL\x10\n\x12\t\n\x05KAFKA\x10\x0b\x12\x0b\n\x07\x44\x41TADOG\x10\x0c\x12\x0c\n\x08\x46ILEBEAT\x10\r\x12\x0c\n\x08LOGSTASH\x10\x0e\x12\x0b\n\x07\x46LUENTD\x10\x0f\x12\r\n\tFLUENTBIT\x10\x10\x12\x0e\n\nPAGER_DUTY\x10\x11\x12\r\n\tNEW_RELIC\x10\x12\x12\t\n\x05SLACK\x10\x13\x12\x0f\n\x0bHONEYBADGER\x10\x14\x12\x0f\n\x0bGOOGLE_CHAT\x10\x15\x12\x11\n\rDATADOG_OAUTH\x10\x16\x12\x07\n\x03GCM\x10\x17\x12\x0e\n\nPROMETHEUS\x10\x18\x12\x0f\n\x0b\x45LASTIC_APM\x10\x19\x12\x14\n\x10VICTORIA_METRICS\x10\x1a\x12\x11\n\rSLACK_CONNECT\x10\x1b\x12\x0b\n\x07GRAFANA\x10\x1c\x12\x0e\n\nCLICKHOUSE\x10\x1d\x12\x11\n\rDOCUMENTATION\x10\x1e\x12\x0c\n\x08POSTGRES\x10\x1f\x12\r\n\tOPS_GENIE\x10 \x12\x07\n\x03\x45KS\x10!\x12\x0f\n\x0b\x41GENT_PROXY\x10\"\x12\x0f\n\x0bGRAFANA_VPC\x10#\x12\n\n\x06GITHUB\x10$\x12\x1b\n\x17SQL_DATABASE_CONNECTION\x10%\x12\x0b\n\x07OPEN_AI\x10&\x12\x11\n\rREMOTE_SERVER\x10\'\x12\x07\n\x03\x41PI\x10(\x12\x08\n\x04\x42\x41SH\x10)\x12\t\n\x05\x41ZURE\x10*\x12\x11\n\rGRAFANA_MIMIR\x10+\x12\x07\n\x03GKE\x10,\x12\x0c\n\x08MS_TEAMS\x10-\x12\x10\n\x0cGRAFANA_LOKI\x10.\x12\x0e\n\nKUBERNETES\x10/\x12\x08\n\x04SMTP\x10\x30\x12\r\n\tBIG_QUERY\x10\x31\x12\x0b\n\x07ZENDUTY\x10\x32\x12\n\n\x06ROOTLY\x10\x33\x12\x0e\n\nJIRA_CLOUD\x10\x34\x12\t\n\x05\x41SANA\x10\x35\x12\x14\n\x10\x43ONFLUENCE_CLOUD\x10\x36\x12\x1a\n\x16\x43ONFLUENCE_SELF_HOSTED\x10\x37\x12\x10\n\x0cGOOGLE_DRIVE\x10\x38\x12\n\n\x06NOTION\x10\x39\x12\x13\n\x0f\x44RD_PROXY_AGENT\x10:\x12\x0b\n\x07MONGODB\x10;\x12\x0f\n\x0bOPEN_SEARCH\x10<\x12\n\n\x06LINEAR\x10=\x12\x0b\n\x07JENKINS\x10>\x12\x12\n\x0eGITHUB_ACTIONS\x10?\x12\n\n\x06\x41RGOCD\x10\x41*\x90\x14\n\rSourceKeyType\x12\x0f\n\x0bUNKNOWN_SKT\x10\x00\x12\x12\n\x0eSENTRY_API_KEY\x10\x01\x12\x13\n\x0fSENTRY_ORG_SLUG\x10\x06\x12\x13\n\x0f\x44\x41TADOG_APP_KEY\x10\x02\x12\x13\n\x0f\x44\x41TADOG_API_KEY\x10\x03\x12\x16\n\x12\x44\x41TADOG_AUTH_TOKEN\x10\x0f\x12\x16\n\x12\x44\x41TADOG_API_DOMAIN\x10\x12\x12\x14\n\x10NEWRELIC_API_KEY\x10\x04\x12\x13\n\x0fNEWRELIC_APP_ID\x10\x05\x12\x16\n\x12NEWRELIC_QUERY_KEY\x10\x07\x12\x17\n\x13NEWRELIC_API_DOMAIN\x10\x13\x12\x18\n\x14SLACK_BOT_AUTH_TOKEN\x10\x08\x12\x14\n\x10SLACK_CHANNEL_ID\x10\t\x12\x10\n\x0cSLACK_APP_ID\x10.\x12\x18\n\x14HONEYBADGER_USERNAME\x10\n\x12\x18\n\x14HONEYBADGER_PASSWORD\x10\x0b\x12\x1a\n\x16HONEYBADGER_PROJECT_ID\x10\x0c\x12\x12\n\x0e\x41WS_ACCESS_KEY\x10\r\x12\x12\n\x0e\x41WS_SECRET_KEY\x10\x0e\x12\x0e\n\nAWS_REGION\x10\x14\x12\x18\n\x14\x41WS_ASSUMED_ROLE_ARN\x10\x17\x12\x10\n\x0c\x45KS_ROLE_ARN\x10(\x12\x1a\n\x16\x41WS_DRD_CLOUD_ROLE_ARN\x10]\x12\x1f\n\x1bGOOGLE_CHAT_BOT_OAUTH_TOKEN\x10\x10\x12\x1a\n\x16GOOGLE_CHAT_BOT_SPACES\x10\x11\x12\x10\n\x0cGRAFANA_HOST\x10\x15\x12\x13\n\x0fGRAFANA_API_KEY\x10\x16\x12\x18\n\x14\x43LICKHOUSE_INTERFACE\x10\x18\x12\x13\n\x0f\x43LICKHOUSE_HOST\x10\x19\x12\x13\n\x0f\x43LICKHOUSE_PORT\x10\x1a\x12\x13\n\x0f\x43LICKHOUSE_USER\x10\x1b\x12\x17\n\x13\x43LICKHOUSE_PASSWORD\x10\x1c\x12\x12\n\x0eGCM_PROJECT_ID\x10\x1d\x12\x13\n\x0fGCM_PRIVATE_KEY\x10\x1e\x12\x14\n\x10GCM_CLIENT_EMAIL\x10\x1f\x12\x11\n\rGCM_TOKEN_URI\x10 \x12\x11\n\rPOSTGRES_HOST\x10!\x12\x11\n\rPOSTGRES_USER\x10\"\x12\x15\n\x11POSTGRES_PASSWORD\x10#\x12\x11\n\rPOSTGRES_PORT\x10$\x12\x15\n\x11POSTGRES_DATABASE\x10%\x12\x14\n\x10POSTGRES_OPTIONS\x10&\x12&\n\"SQL_DATABASE_CONNECTION_STRING_URI\x10\'\x12\x16\n\x12PAGER_DUTY_API_KEY\x10)\x12\x1f\n\x1bPAGER_DUTY_CONFIGURED_EMAIL\x10?\x12\x15\n\x11OPS_GENIE_API_KEY\x10*\x12\x14\n\x10\x41GENT_PROXY_HOST\x10+\x12\x17\n\x13\x41GENT_PROXY_API_KEY\x10,\x12\x10\n\x0cGITHUB_TOKEN\x10-\x12\x13\n\x0fOPEN_AI_API_KEY\x10/\x12\x15\n\x11REMOTE_SERVER_PEM\x10\x31\x12\x16\n\x12REMOTE_SERVER_USER\x10\x32\x12\x16\n\x12REMOTE_SERVER_HOST\x10\x33\x12\x1a\n\x16REMOTE_SERVER_PASSWORD\x10\x34\x12\x0e\n\nMIMIR_HOST\x10\x35\x12\x12\n\x0eX_SCOPE_ORG_ID\x10\x36\x12\x0e\n\nSSL_VERIFY\x10\x37\x12\x19\n\x15\x41ZURE_SUBSCRIPTION_ID\x10\x38\x12\x13\n\x0f\x41ZURE_TENANT_ID\x10\x39\x12\x13\n\x0f\x41ZURE_CLIENT_ID\x10:\x12\x17\n\x13\x41ZURE_CLIENT_SECRET\x10;\x12\x12\n\x0eGKE_PROJECT_ID\x10<\x12\x1c\n\x18GKE_SERVICE_ACCOUNT_JSON\x10=\x12\"\n\x1eMS_TEAMS_CONNECTOR_WEBHOOK_URL\x10>\x12\x1b\n\x17\x45LASTIC_SEARCH_PROTOCOL\x10@\x12\x17\n\x13\x45LASTIC_SEARCH_HOST\x10\x41\x12\x17\n\x13\x45LASTIC_SEARCH_PORT\x10\x42\x12\x1d\n\x19\x45LASTIC_SEARCH_API_KEY_ID\x10\x43\x12\x1a\n\x16\x45LASTIC_SEARCH_API_KEY\x10\x44\x12\x19\n\x15GRAFANA_LOKI_PROTOCOL\x10\x45\x12\x15\n\x11GRAFANA_LOKI_HOST\x10\x46\x12\x15\n\x11GRAFANA_LOKI_PORT\x10G\x12\x1b\n\x17KUBERNETES_CLUSTER_NAME\x10H\x12!\n\x1dKUBERNETES_CLUSTER_API_SERVER\x10I\x12\x1c\n\x18KUBERNETES_CLUSTER_TOKEN\x10J\x12\x31\n-KUBERNETES_CLUSTER_CERTIFICATE_AUTHORITY_DATA\x10K\x12\x31\n-KUBERNETES_CLUSTER_CERTIFICATE_AUTHORITY_PATH\x10L\x12\r\n\tSMTP_HOST\x10M\x12\r\n\tSMTP_PORT\x10N\x12\r\n\tSMTP_USER\x10O\x12\x11\n\rSMTP_PASSWORD\x10P\x12\x18\n\x14\x42IG_QUERY_PROJECT_ID\x10Q\x12\"\n\x1e\x42IG_QUERY_SERVICE_ACCOUNT_JSON\x10R\x12\x13\n\x0fZENDUTY_API_KEY\x10S\x12\x12\n\x0eROOTLY_API_KEY\x10T\x12\x16\n\x12JIRA_CLOUD_API_KEY\x10U\x12\x16\n\x12\x41SANA_ACCESS_TOKEN\x10V\x12\x1c\n\x18\x43ONFLUENCE_CLOUD_API_KEY\x10W\x12\x12\n\x0eNOTION_API_KEY\x10X\x12\x18\n\x14\x44RD_PROXY_AGENT_HOST\x10Y\x12\x1d\n\x19\x44RD_PROXY_AGENT_API_TOKEN\x10Z\x12\x1b\n\x17\x44RD_PROXY_AGENT_API_KEY\x10[\x12\x1d\n\x19MONGODB_CONNECTION_STRING\x10\\\x12\x18\n\x14OPEN_SEARCH_PROTOCOL\x10^\x12\x14\n\x10OPEN_SEARCH_HOST\x10_\x12\x14\n\x10OPEN_SEARCH_PORT\x10`\x12\x18\n\x14OPEN_SEARCH_USERNAME\x10\x61\x12\x18\n\x14OPEN_SEARCH_PASSWORD\x10\x62\x12\x12\n\x0eLINEAR_API_KEY\x10\x63\x12\x0f\n\x0bJENKINS_URL\x10\x64\x12\x14\n\x10JENKINS_USERNAME\x10\x65\x12\x15\n\x11JENKINS_API_TOKEN\x10\x66\x12\x18\n\x14GITHUB_ACTIONS_TOKEN\x10g\x12\x0e\n\nJIRA_EMAIL\x10n\x12\x0f\n\x0bJIRA_DOMAIN\x10m\x12\x11\n\rARGOCD_SERVER\x10j\x12\x10\n\x0c\x41RGOCD_TOKEN\x10k*\x88\x0b\n\x0fSourceModelType\x12\x0e\n\nUNKNOWN_MT\x10\x00\x12\x14\n\x10NEW_RELIC_POLICY\x10\x01\x12\x17\n\x13NEW_RELIC_CONDITION\x10\x02\x12\x14\n\x10NEW_RELIC_ENTITY\x10\x03\x12\x1e\n\x1aNEW_RELIC_ENTITY_DASHBOARD\x10\x04\x12 \n\x1cNEW_RELIC_ENTITY_APPLICATION\x10\x05\x12\x12\n\x0eNEW_RELIC_NRQL\x10\x06\x12\x13\n\x0f\x44\x41TADOG_MONITOR\x10\x65\x12\x15\n\x11\x44\x41TADOG_DASHBOARD\x10\x66\x12 \n\x1c\x44\x41TADOG_LIVE_INTEGRATION_AWS\x10g\x12$\n DATADOG_LIVE_INTEGRATION_AWS_LOG\x10h\x12\"\n\x1e\x44\x41TADOG_LIVE_INTEGRATION_AZURE\x10i\x12\'\n#DATADOG_LIVE_INTEGRATION_CLOUDFLARE\x10j\x12#\n\x1f\x44\x41TADOG_LIVE_INTEGRATION_FASTLY\x10k\x12 \n\x1c\x44\x41TADOG_LIVE_INTEGRATION_GCP\x10l\x12&\n\"DATADOG_LIVE_INTEGRATION_CONFLUENT\x10m\x12\x13\n\x0f\x44\x41TADOG_SERVICE\x10n\x12\x12\n\x0e\x44\x41TADOG_METRIC\x10o\x12\x11\n\rDATADOG_QUERY\x10p\x12\x16\n\x11\x43LOUDWATCH_METRIC\x10\xc9\x01\x12\x19\n\x14\x43LOUDWATCH_LOG_GROUP\x10\xca\x01\x12\x1f\n\x1a\x43LOUDWATCH_LOG_GROUP_QUERY\x10\xcb\x01\x12\x16\n\x11\x43LOUDWATCH_ALARMS\x10\xcc\x01\x12\x12\n\rRDS_INSTANCES\x10\xcd\x01\x12\x17\n\x12GRAFANA_DATASOURCE\x10\xad\x02\x12\x16\n\x11GRAFANA_DASHBOARD\x10\xae\x02\x12!\n\x1cGRAFANA_TARGET_METRIC_PROMQL\x10\xaf\x02\x12\"\n\x1dGRAFANA_PROMETHEUS_DATASOURCE\x10\xb0\x02\x12\x17\n\x12GRAFANA_ALERT_RULE\x10\xb1\x02\x12\x18\n\x13\x43LICKHOUSE_DATABASE\x10\x91\x03\x12\x12\n\rSLACK_CHANNEL\x10\xf5\x03\x12\r\n\x08MARKDOWN\x10\xd9\x04\x12\x0b\n\x06IFRAME\x10\xda\x04\x12\x13\n\x0ePOSTGRES_QUERY\x10\xbd\x05\x12\x10\n\x0b\x45KS_CLUSTER\x10\xa1\x06\x12&\n!SQL_DATABASE_CONNECTION_RAW_QUERY\x10\x85\x07\x12\x14\n\x0f\x41ZURE_WORKSPACE\x10\xe9\x07\x12\x0f\n\nSSH_SERVER\x10\xcc\x08\x12\x19\n\x14GRAFANA_MIMIR_PROMQL\x10\xb1\t\x12\x10\n\x0bGKE_CLUSTER\x10\x95\n\x12\x15\n\x10MS_TEAMS_CHANNEL\x10\xf9\n\x12\x17\n\x12PAGERDUTY_INCIDENT\x10\xdd\x0b\x12\x19\n\x14\x45LASTIC_SEARCH_INDEX\x10\xc1\x0c\x12\x0f\n\nGCM_METRIC\x10\xa5\r\x12\x15\n\x10ZENDUTY_INCIDENT\x10\x89\x0e\x12\x14\n\x0fROOTLY_INCIDENT\x10\xed\x0e\x12\x19\n\x14OPS_GENIE_ESCALATION\x10\xd1\x0f\x12\x13\n\x0eOPS_GENIE_TEAM\x10\xd2\x0f\x12\x16\n\x11GOOGLE_CHAT_SPACE\x10\xb5\x10\x12\x15\n\x10MONGODB_DATABASE\x10\x99\x11\x12\x17\n\x12MONGODB_COLLECTION\x10\x9a\x11\x12\x16\n\x11OPEN_SEARCH_INDEX\x10\xfd\x11\x12\x12\n\rASANA_PROJECT\x10\xe1\x12\x12\x11\n\x0cJIRA_PROJECT\x10\x8d\x15\x12\x0e\n\tJIRA_USER\x10\x8e\x15\x12\x10\n\x0b\x41RGOCD_APPS\x10\xf1\x15\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11protos/base.proto\x12\x06protos\x1a\x1egoogle/protobuf/wrappers.proto\".\n\tTimeRange\x12\x10\n\x08time_geq\x18\x01 \x01(\x04\x12\x0f\n\x07time_lt\x18\x02 \x01(\x04\"a\n\x04Page\x12+\n\x05limit\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12,\n\x06offset\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\"\xaf\x01\n\x04Meta\x12%\n\ntime_range\x18\x01 \x01(\x0b\x32\x11.protos.TimeRange\x12\x1a\n\x04page\x18\x02 \x01(\x0b\x32\x0c.protos.Page\x12\x31\n\x0btotal_count\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.UInt32Value\x12\x31\n\rshow_inactive\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\"@\n\x07Message\x12\r\n\x05title\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x11\n\ttraceback\x18\x03 \x01(\t\"[\n\x0c\x45rrorMessage\x12)\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x12 \n\x07message\x18\x02 \x01(\x0b\x32\x0f.protos.Message\"\\\n\rOpDescription\x12\x16\n\x02op\x18\x01 \x01(\x0e\x32\n.protos.Op\x12\r\n\x05label\x18\x02 \x01(\t\x12\x10\n\x08is_unary\x18\x03 \x01(\x08\x12\x12\n\nis_logical\x18\x04 \x01(\x08\"\xc7\x02\n\x10TaskCronSchedule\x12-\n\x07minutes\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05hours\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x10\x64\x61ys_of_the_week\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x37\n\x11\x64\x61ys_of_the_month\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x10\x64\x61ys_of_the_year\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12.\n\x08timezone\x18\x06 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"I\n\x0cTaskInterval\x12\x39\n\x13interval_in_seconds\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\"j\n\x0cTaskCronRule\x12*\n\x04rule\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12.\n\x08timezone\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue*Q\n\x08\x46unction\x12\r\n\tUNKNOWN_F\x10\x00\x12\t\n\x05\x41VG_F\x10\x01\x12\t\n\x05SUM_F\x10\x02\x12\t\n\x05MIN_F\x10\x03\x12\t\n\x05MAX_F\x10\x04\x12\n\n\x06LAST_F\x10\x05*\xb7\x01\n\x08Operator\x12\r\n\tUNKNOWN_O\x10\x00\x12\x12\n\x0eGREATER_THAN_O\x10\x01\x12\x18\n\x14GREATER_THAN_EQUAL_O\x10\x02\x12\x0f\n\x0bLESS_THAN_O\x10\x03\x12\x15\n\x11LESS_THAN_EQUAL_O\x10\x04\x12\x0b\n\x07\x45QUAL_O\x10\x05\x12\x0f\n\x0bNOT_EQUAL_O\x10\x06\x12\n\n\x06LIKE_O\x10\x07\x12\x0c\n\x08\x45XISTS_O\x10\x08\x12\x0e\n\nCONTAINS_O\x10\t*D\n\x0fLogicalOperator\x12\x0e\n\nUNKNOWN_LO\x10\x00\x12\n\n\x06\x41ND_LO\x10\x01\x12\t\n\x05OR_LO\x10\x02\x12\n\n\x06NOT_LO\x10\x03*\x8e\x01\n\x02Op\x12\x0e\n\nUNKNOWN_OP\x10\x00\x12\x06\n\x02\x45Q\x10\x01\x12\x07\n\x03NEQ\x10\x02\x12\x06\n\x02GT\x10\x03\x12\x06\n\x02LT\x10\x04\x12\x07\n\x03GTE\x10\x05\x12\x07\n\x03LTE\x10\x06\x12\x06\n\x02IN\x10\x07\x12\n\n\x06NOT_IN\x10\x08\x12\x0b\n\x07IS_NULL\x10\t\x12\n\n\x06\x45XISTS\x10\n\x12\x07\n\x03\x41ND\x10\x14\x12\x06\n\x02OR\x10\x15\x12\x07\n\x03NOT\x10\x16*d\n\x07\x43ontext\x12\r\n\tUNKNOWN_C\x10\x00\x12\x0c\n\x08PLAYBOOK\x10\x01\x12\x16\n\x12PLAYBOOK_EXECUTION\x10\x02\x12\x0c\n\x08WORKFLOW\x10\x03\x12\x16\n\x12WORKFLOW_EXECUTION\x10\x04*\xec\x07\n\x06Source\x12\x0b\n\x07UNKNOWN\x10\x00\x12\n\n\x06SENTRY\x10\x01\x12\x0b\n\x07SEGMENT\x10\x02\x12\x12\n\x0e\x45LASTIC_SEARCH\x10\x03\x12\r\n\tAMPLITUDE\x10\x04\x12\x0f\n\x0b\x41WS_KINESIS\x10\x05\x12\x0e\n\nCLOUDWATCH\x10\x06\x12\r\n\tCLEVERTAP\x10\x07\x12\x0f\n\x0bRUDDERSTACK\x10\x08\x12\x0c\n\x08MOENGAGE\x10\t\x12\t\n\x05\x43RIBL\x10\n\x12\t\n\x05KAFKA\x10\x0b\x12\x0b\n\x07\x44\x41TADOG\x10\x0c\x12\x0c\n\x08\x46ILEBEAT\x10\r\x12\x0c\n\x08LOGSTASH\x10\x0e\x12\x0b\n\x07\x46LUENTD\x10\x0f\x12\r\n\tFLUENTBIT\x10\x10\x12\x0e\n\nPAGER_DUTY\x10\x11\x12\r\n\tNEW_RELIC\x10\x12\x12\t\n\x05SLACK\x10\x13\x12\x0f\n\x0bHONEYBADGER\x10\x14\x12\x0f\n\x0bGOOGLE_CHAT\x10\x15\x12\x11\n\rDATADOG_OAUTH\x10\x16\x12\x07\n\x03GCM\x10\x17\x12\x0e\n\nPROMETHEUS\x10\x18\x12\x0f\n\x0b\x45LASTIC_APM\x10\x19\x12\x14\n\x10VICTORIA_METRICS\x10\x1a\x12\x11\n\rSLACK_CONNECT\x10\x1b\x12\x0b\n\x07GRAFANA\x10\x1c\x12\x0e\n\nCLICKHOUSE\x10\x1d\x12\x11\n\rDOCUMENTATION\x10\x1e\x12\x0c\n\x08POSTGRES\x10\x1f\x12\r\n\tOPS_GENIE\x10 \x12\x07\n\x03\x45KS\x10!\x12\x0f\n\x0b\x41GENT_PROXY\x10\"\x12\x0f\n\x0bGRAFANA_VPC\x10#\x12\n\n\x06GITHUB\x10$\x12\x1b\n\x17SQL_DATABASE_CONNECTION\x10%\x12\x0b\n\x07OPEN_AI\x10&\x12\x11\n\rREMOTE_SERVER\x10\'\x12\x07\n\x03\x41PI\x10(\x12\x08\n\x04\x42\x41SH\x10)\x12\t\n\x05\x41ZURE\x10*\x12\x11\n\rGRAFANA_MIMIR\x10+\x12\x07\n\x03GKE\x10,\x12\x0c\n\x08MS_TEAMS\x10-\x12\x10\n\x0cGRAFANA_LOKI\x10.\x12\x0e\n\nKUBERNETES\x10/\x12\x08\n\x04SMTP\x10\x30\x12\r\n\tBIG_QUERY\x10\x31\x12\x0b\n\x07ZENDUTY\x10\x32\x12\n\n\x06ROOTLY\x10\x33\x12\x0e\n\nJIRA_CLOUD\x10\x34\x12\t\n\x05\x41SANA\x10\x35\x12\x14\n\x10\x43ONFLUENCE_CLOUD\x10\x36\x12\x1a\n\x16\x43ONFLUENCE_SELF_HOSTED\x10\x37\x12\x10\n\x0cGOOGLE_DRIVE\x10\x38\x12\n\n\x06NOTION\x10\x39\x12\x13\n\x0f\x44RD_PROXY_AGENT\x10:\x12\x0b\n\x07MONGODB\x10;\x12\x0f\n\x0bOPEN_SEARCH\x10<\x12\n\n\x06LINEAR\x10=\x12\x0b\n\x07JENKINS\x10>\x12\x12\n\x0eGITHUB_ACTIONS\x10?\x12\n\n\x06\x41RGOCD\x10\x41\x12\t\n\x05REDIS\x10\x42*\xe9\x14\n\rSourceKeyType\x12\x0f\n\x0bUNKNOWN_SKT\x10\x00\x12\x12\n\x0eSENTRY_API_KEY\x10\x01\x12\x13\n\x0fSENTRY_ORG_SLUG\x10\x06\x12\x13\n\x0f\x44\x41TADOG_APP_KEY\x10\x02\x12\x13\n\x0f\x44\x41TADOG_API_KEY\x10\x03\x12\x16\n\x12\x44\x41TADOG_AUTH_TOKEN\x10\x0f\x12\x16\n\x12\x44\x41TADOG_API_DOMAIN\x10\x12\x12\x14\n\x10NEWRELIC_API_KEY\x10\x04\x12\x13\n\x0fNEWRELIC_APP_ID\x10\x05\x12\x16\n\x12NEWRELIC_QUERY_KEY\x10\x07\x12\x17\n\x13NEWRELIC_API_DOMAIN\x10\x13\x12\x18\n\x14SLACK_BOT_AUTH_TOKEN\x10\x08\x12\x14\n\x10SLACK_CHANNEL_ID\x10\t\x12\x10\n\x0cSLACK_APP_ID\x10.\x12\x18\n\x14HONEYBADGER_USERNAME\x10\n\x12\x18\n\x14HONEYBADGER_PASSWORD\x10\x0b\x12\x1a\n\x16HONEYBADGER_PROJECT_ID\x10\x0c\x12\x12\n\x0e\x41WS_ACCESS_KEY\x10\r\x12\x12\n\x0e\x41WS_SECRET_KEY\x10\x0e\x12\x0e\n\nAWS_REGION\x10\x14\x12\x18\n\x14\x41WS_ASSUMED_ROLE_ARN\x10\x17\x12\x10\n\x0c\x45KS_ROLE_ARN\x10(\x12\x1a\n\x16\x41WS_DRD_CLOUD_ROLE_ARN\x10]\x12\x1f\n\x1bGOOGLE_CHAT_BOT_OAUTH_TOKEN\x10\x10\x12\x1a\n\x16GOOGLE_CHAT_BOT_SPACES\x10\x11\x12\x10\n\x0cGRAFANA_HOST\x10\x15\x12\x13\n\x0fGRAFANA_API_KEY\x10\x16\x12\x18\n\x14\x43LICKHOUSE_INTERFACE\x10\x18\x12\x13\n\x0f\x43LICKHOUSE_HOST\x10\x19\x12\x13\n\x0f\x43LICKHOUSE_PORT\x10\x1a\x12\x13\n\x0f\x43LICKHOUSE_USER\x10\x1b\x12\x17\n\x13\x43LICKHOUSE_PASSWORD\x10\x1c\x12\x12\n\x0eGCM_PROJECT_ID\x10\x1d\x12\x13\n\x0fGCM_PRIVATE_KEY\x10\x1e\x12\x14\n\x10GCM_CLIENT_EMAIL\x10\x1f\x12\x11\n\rGCM_TOKEN_URI\x10 \x12\x11\n\rPOSTGRES_HOST\x10!\x12\x11\n\rPOSTGRES_USER\x10\"\x12\x15\n\x11POSTGRES_PASSWORD\x10#\x12\x11\n\rPOSTGRES_PORT\x10$\x12\x15\n\x11POSTGRES_DATABASE\x10%\x12\x14\n\x10POSTGRES_OPTIONS\x10&\x12&\n\"SQL_DATABASE_CONNECTION_STRING_URI\x10\'\x12\x16\n\x12PAGER_DUTY_API_KEY\x10)\x12\x1f\n\x1bPAGER_DUTY_CONFIGURED_EMAIL\x10?\x12\x15\n\x11OPS_GENIE_API_KEY\x10*\x12\x14\n\x10\x41GENT_PROXY_HOST\x10+\x12\x17\n\x13\x41GENT_PROXY_API_KEY\x10,\x12\x10\n\x0cGITHUB_TOKEN\x10-\x12\x13\n\x0fOPEN_AI_API_KEY\x10/\x12\x15\n\x11REMOTE_SERVER_PEM\x10\x31\x12\x16\n\x12REMOTE_SERVER_USER\x10\x32\x12\x16\n\x12REMOTE_SERVER_HOST\x10\x33\x12\x1a\n\x16REMOTE_SERVER_PASSWORD\x10\x34\x12\x0e\n\nMIMIR_HOST\x10\x35\x12\x12\n\x0eX_SCOPE_ORG_ID\x10\x36\x12\x0e\n\nSSL_VERIFY\x10\x37\x12\x19\n\x15\x41ZURE_SUBSCRIPTION_ID\x10\x38\x12\x13\n\x0f\x41ZURE_TENANT_ID\x10\x39\x12\x13\n\x0f\x41ZURE_CLIENT_ID\x10:\x12\x17\n\x13\x41ZURE_CLIENT_SECRET\x10;\x12\x12\n\x0eGKE_PROJECT_ID\x10<\x12\x1c\n\x18GKE_SERVICE_ACCOUNT_JSON\x10=\x12\"\n\x1eMS_TEAMS_CONNECTOR_WEBHOOK_URL\x10>\x12\x1b\n\x17\x45LASTIC_SEARCH_PROTOCOL\x10@\x12\x17\n\x13\x45LASTIC_SEARCH_HOST\x10\x41\x12\x17\n\x13\x45LASTIC_SEARCH_PORT\x10\x42\x12\x1d\n\x19\x45LASTIC_SEARCH_API_KEY_ID\x10\x43\x12\x1a\n\x16\x45LASTIC_SEARCH_API_KEY\x10\x44\x12\x19\n\x15GRAFANA_LOKI_PROTOCOL\x10\x45\x12\x15\n\x11GRAFANA_LOKI_HOST\x10\x46\x12\x15\n\x11GRAFANA_LOKI_PORT\x10G\x12\x1b\n\x17KUBERNETES_CLUSTER_NAME\x10H\x12!\n\x1dKUBERNETES_CLUSTER_API_SERVER\x10I\x12\x1c\n\x18KUBERNETES_CLUSTER_TOKEN\x10J\x12\x31\n-KUBERNETES_CLUSTER_CERTIFICATE_AUTHORITY_DATA\x10K\x12\x31\n-KUBERNETES_CLUSTER_CERTIFICATE_AUTHORITY_PATH\x10L\x12\r\n\tSMTP_HOST\x10M\x12\r\n\tSMTP_PORT\x10N\x12\r\n\tSMTP_USER\x10O\x12\x11\n\rSMTP_PASSWORD\x10P\x12\x18\n\x14\x42IG_QUERY_PROJECT_ID\x10Q\x12\"\n\x1e\x42IG_QUERY_SERVICE_ACCOUNT_JSON\x10R\x12\x13\n\x0fZENDUTY_API_KEY\x10S\x12\x12\n\x0eROOTLY_API_KEY\x10T\x12\x16\n\x12JIRA_CLOUD_API_KEY\x10U\x12\x16\n\x12\x41SANA_ACCESS_TOKEN\x10V\x12\x1c\n\x18\x43ONFLUENCE_CLOUD_API_KEY\x10W\x12\x12\n\x0eNOTION_API_KEY\x10X\x12\x18\n\x14\x44RD_PROXY_AGENT_HOST\x10Y\x12\x1d\n\x19\x44RD_PROXY_AGENT_API_TOKEN\x10Z\x12\x1b\n\x17\x44RD_PROXY_AGENT_API_KEY\x10[\x12\x1d\n\x19MONGODB_CONNECTION_STRING\x10\\\x12\x18\n\x14OPEN_SEARCH_PROTOCOL\x10^\x12\x14\n\x10OPEN_SEARCH_HOST\x10_\x12\x14\n\x10OPEN_SEARCH_PORT\x10`\x12\x18\n\x14OPEN_SEARCH_USERNAME\x10\x61\x12\x18\n\x14OPEN_SEARCH_PASSWORD\x10\x62\x12\x12\n\x0eLINEAR_API_KEY\x10\x63\x12\x0f\n\x0bJENKINS_URL\x10\x64\x12\x14\n\x10JENKINS_USERNAME\x10\x65\x12\x15\n\x11JENKINS_API_TOKEN\x10\x66\x12\x18\n\x14GITHUB_ACTIONS_TOKEN\x10g\x12\x0e\n\nJIRA_EMAIL\x10n\x12\x0f\n\x0bJIRA_DOMAIN\x10m\x12\x11\n\rARGOCD_SERVER\x10j\x12\x10\n\x0c\x41RGOCD_TOKEN\x10k\x12\x0e\n\nREDIS_HOST\x10o\x12\x0e\n\nREDIS_PORT\x10p\x12\x12\n\x0eREDIS_PASSWORD\x10q\x12\x0c\n\x08REDIS_DB\x10r\x12\x15\n\x11REDIS_SSL_ENABLED\x10s*\x9f\x0b\n\x0fSourceModelType\x12\x0e\n\nUNKNOWN_MT\x10\x00\x12\x14\n\x10NEW_RELIC_POLICY\x10\x01\x12\x17\n\x13NEW_RELIC_CONDITION\x10\x02\x12\x14\n\x10NEW_RELIC_ENTITY\x10\x03\x12\x1e\n\x1aNEW_RELIC_ENTITY_DASHBOARD\x10\x04\x12 \n\x1cNEW_RELIC_ENTITY_APPLICATION\x10\x05\x12\x12\n\x0eNEW_RELIC_NRQL\x10\x06\x12\x13\n\x0f\x44\x41TADOG_MONITOR\x10\x65\x12\x15\n\x11\x44\x41TADOG_DASHBOARD\x10\x66\x12 \n\x1c\x44\x41TADOG_LIVE_INTEGRATION_AWS\x10g\x12$\n DATADOG_LIVE_INTEGRATION_AWS_LOG\x10h\x12\"\n\x1e\x44\x41TADOG_LIVE_INTEGRATION_AZURE\x10i\x12\'\n#DATADOG_LIVE_INTEGRATION_CLOUDFLARE\x10j\x12#\n\x1f\x44\x41TADOG_LIVE_INTEGRATION_FASTLY\x10k\x12 \n\x1c\x44\x41TADOG_LIVE_INTEGRATION_GCP\x10l\x12&\n\"DATADOG_LIVE_INTEGRATION_CONFLUENT\x10m\x12\x13\n\x0f\x44\x41TADOG_SERVICE\x10n\x12\x12\n\x0e\x44\x41TADOG_METRIC\x10o\x12\x11\n\rDATADOG_QUERY\x10p\x12\x16\n\x11\x43LOUDWATCH_METRIC\x10\xc9\x01\x12\x19\n\x14\x43LOUDWATCH_LOG_GROUP\x10\xca\x01\x12\x1f\n\x1a\x43LOUDWATCH_LOG_GROUP_QUERY\x10\xcb\x01\x12\x16\n\x11\x43LOUDWATCH_ALARMS\x10\xcc\x01\x12\x12\n\rRDS_INSTANCES\x10\xcd\x01\x12\x17\n\x12GRAFANA_DATASOURCE\x10\xad\x02\x12\x16\n\x11GRAFANA_DASHBOARD\x10\xae\x02\x12!\n\x1cGRAFANA_TARGET_METRIC_PROMQL\x10\xaf\x02\x12\"\n\x1dGRAFANA_PROMETHEUS_DATASOURCE\x10\xb0\x02\x12\x17\n\x12GRAFANA_ALERT_RULE\x10\xb1\x02\x12\x18\n\x13\x43LICKHOUSE_DATABASE\x10\x91\x03\x12\x12\n\rSLACK_CHANNEL\x10\xf5\x03\x12\r\n\x08MARKDOWN\x10\xd9\x04\x12\x0b\n\x06IFRAME\x10\xda\x04\x12\x13\n\x0ePOSTGRES_QUERY\x10\xbd\x05\x12\x10\n\x0b\x45KS_CLUSTER\x10\xa1\x06\x12&\n!SQL_DATABASE_CONNECTION_RAW_QUERY\x10\x85\x07\x12\x14\n\x0f\x41ZURE_WORKSPACE\x10\xe9\x07\x12\x0f\n\nSSH_SERVER\x10\xcc\x08\x12\x19\n\x14GRAFANA_MIMIR_PROMQL\x10\xb1\t\x12\x10\n\x0bGKE_CLUSTER\x10\x95\n\x12\x15\n\x10MS_TEAMS_CHANNEL\x10\xf9\n\x12\x17\n\x12PAGERDUTY_INCIDENT\x10\xdd\x0b\x12\x19\n\x14\x45LASTIC_SEARCH_INDEX\x10\xc1\x0c\x12\x0f\n\nGCM_METRIC\x10\xa5\r\x12\x15\n\x10ZENDUTY_INCIDENT\x10\x89\x0e\x12\x14\n\x0fROOTLY_INCIDENT\x10\xed\x0e\x12\x19\n\x14OPS_GENIE_ESCALATION\x10\xd1\x0f\x12\x13\n\x0eOPS_GENIE_TEAM\x10\xd2\x0f\x12\x16\n\x11GOOGLE_CHAT_SPACE\x10\xb5\x10\x12\x15\n\x10MONGODB_DATABASE\x10\x99\x11\x12\x17\n\x12MONGODB_COLLECTION\x10\x9a\x11\x12\x16\n\x11OPEN_SEARCH_INDEX\x10\xfd\x11\x12\x12\n\rASANA_PROJECT\x10\xe1\x12\x12\x11\n\x0cJIRA_PROJECT\x10\x8d\x15\x12\x0e\n\tJIRA_USER\x10\x8e\x15\x12\x10\n\x0b\x41RGOCD_APPS\x10\xf1\x15\x12\x15\n\x10REDIS_CONNECTION\x10\xd5\x16\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'protos.base_pb2', globals()) @@ -32,11 +32,11 @@ _CONTEXT._serialized_start=1636 _CONTEXT._serialized_end=1736 _SOURCE._serialized_start=1739 - _SOURCE._serialized_end=2732 - _SOURCEKEYTYPE._serialized_start=2735 - _SOURCEKEYTYPE._serialized_end=5311 - _SOURCEMODELTYPE._serialized_start=5314 - _SOURCEMODELTYPE._serialized_end=6730 + _SOURCE._serialized_end=2743 + _SOURCEKEYTYPE._serialized_start=2746 + _SOURCEKEYTYPE._serialized_end=5411 + _SOURCEMODELTYPE._serialized_start=5414 + _SOURCEMODELTYPE._serialized_end=6853 _TIMERANGE._serialized_start=61 _TIMERANGE._serialized_end=107 _PAGE._serialized_start=109 diff --git a/protos/base_pb2.pyi b/protos/base_pb2.pyi index a17cb04c6..854692c54 100644 --- a/protos/base_pb2.pyi +++ b/protos/base_pb2.pyi @@ -221,6 +221,7 @@ class _SourceEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTyp JENKINS: _Source.ValueType # 62 GITHUB_ACTIONS: _Source.ValueType # 63 ARGOCD: _Source.ValueType # 65 + REDIS: _Source.ValueType # 66 class Source(_Source, metaclass=_SourceEnumTypeWrapper): ... @@ -289,6 +290,7 @@ LINEAR: Source.ValueType # 61 JENKINS: Source.ValueType # 62 GITHUB_ACTIONS: Source.ValueType # 63 ARGOCD: Source.ValueType # 65 +REDIS: Source.ValueType # 66 global___Source = Source class _SourceKeyType: @@ -404,6 +406,11 @@ class _SourceKeyTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._ JIRA_DOMAIN: _SourceKeyType.ValueType # 109 ARGOCD_SERVER: _SourceKeyType.ValueType # 106 ARGOCD_TOKEN: _SourceKeyType.ValueType # 107 + REDIS_HOST: _SourceKeyType.ValueType # 111 + REDIS_PORT: _SourceKeyType.ValueType # 112 + REDIS_PASSWORD: _SourceKeyType.ValueType # 113 + REDIS_DB: _SourceKeyType.ValueType # 114 + REDIS_SSL_ENABLED: _SourceKeyType.ValueType # 115 class SourceKeyType(_SourceKeyType, metaclass=_SourceKeyTypeEnumTypeWrapper): ... @@ -514,6 +521,11 @@ JIRA_EMAIL: SourceKeyType.ValueType # 110 JIRA_DOMAIN: SourceKeyType.ValueType # 109 ARGOCD_SERVER: SourceKeyType.ValueType # 106 ARGOCD_TOKEN: SourceKeyType.ValueType # 107 +REDIS_HOST: SourceKeyType.ValueType # 111 +REDIS_PORT: SourceKeyType.ValueType # 112 +REDIS_PASSWORD: SourceKeyType.ValueType # 113 +REDIS_DB: SourceKeyType.ValueType # 114 +REDIS_SSL_ENABLED: SourceKeyType.ValueType # 115 global___SourceKeyType = SourceKeyType class _SourceModelType: @@ -605,6 +617,8 @@ class _SourceModelTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper JIRA_USER: _SourceModelType.ValueType # 2702 ARGOCD_APPS: _SourceModelType.ValueType # 2801 """ArgoCD Models""" + REDIS_CONNECTION: _SourceModelType.ValueType # 2901 + """Redis Models""" class SourceModelType(_SourceModelType, metaclass=_SourceModelTypeEnumTypeWrapper): ... @@ -691,6 +705,8 @@ JIRA_PROJECT: SourceModelType.ValueType # 2701 JIRA_USER: SourceModelType.ValueType # 2702 ARGOCD_APPS: SourceModelType.ValueType # 2801 """ArgoCD Models""" +REDIS_CONNECTION: SourceModelType.ValueType # 2901 +"""Redis Models""" global___SourceModelType = SourceModelType @typing_extensions.final diff --git a/protos/playbooks/playbook.proto b/protos/playbooks/playbook.proto index c3ac1408c..444546d25 100644 --- a/protos/playbooks/playbook.proto +++ b/protos/playbooks/playbook.proto @@ -19,6 +19,7 @@ import "protos/playbooks/source_task_definitions/eks_task.proto"; import "protos/playbooks/source_task_definitions/sql_data_fetch_task.proto"; import "protos/playbooks/source_task_definitions/api_task.proto"; import "protos/playbooks/source_task_definitions/bash_task.proto"; +import "protos/playbooks/source_task_definitions/redis_task.proto"; import "protos/playbooks/source_task_definitions/documentation_task.proto"; import "protos/playbooks/source_task_definitions/promql_task.proto"; import "protos/playbooks/source_task_definitions/azure_task.proto"; @@ -88,6 +89,7 @@ message PlaybookTask { BigQuery big_query = 121; Jira jira_cloud = 122; ArgoCD argocd = 123; + Redis redis = 124; } } diff --git a/protos/playbooks/playbook_pb2.py b/protos/playbooks/playbook_pb2.py index 621cc9931..99765be9c 100644 --- a/protos/playbooks/playbook_pb2.py +++ b/protos/playbooks/playbook_pb2.py @@ -27,6 +27,7 @@ from protos.playbooks.source_task_definitions import sql_data_fetch_task_pb2 as protos_dot_playbooks_dot_source__task__definitions_dot_sql__data__fetch__task__pb2 from protos.playbooks.source_task_definitions import api_task_pb2 as protos_dot_playbooks_dot_source__task__definitions_dot_api__task__pb2 from protos.playbooks.source_task_definitions import bash_task_pb2 as protos_dot_playbooks_dot_source__task__definitions_dot_bash__task__pb2 +from protos.playbooks.source_task_definitions import redis_task_pb2 as protos_dot_playbooks_dot_source__task__definitions_dot_redis__task__pb2 from protos.playbooks.source_task_definitions import documentation_task_pb2 as protos_dot_playbooks_dot_source__task__definitions_dot_documentation__task__pb2 from protos.playbooks.source_task_definitions import promql_task_pb2 as protos_dot_playbooks_dot_source__task__definitions_dot_promql__task__pb2 from protos.playbooks.source_task_definitions import azure_task_pb2 as protos_dot_playbooks_dot_source__task__definitions_dot_azure__task__pb2 @@ -43,47 +44,47 @@ from protos.playbooks.source_task_definitions import argocd_task_pb2 as protos_dot_playbooks_dot_source__task__definitions_dot_argocd__task__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fprotos/playbooks/playbook.proto\x12\x10protos.playbooks\x1a\x11protos/base.proto\x1a\x1egoogle/protobuf/wrappers.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\'protos/playbooks/playbook_commons.proto\x1a\x35protos/playbooks/intelligence_layer/interpreter.proto\x1a\x35protos/playbooks/playbook_task_result_evaluator.proto\x1a\x35protos/playbooks/playbook_step_result_evaluator.proto\x1a\x39protos/playbooks/playbook_global_variable_evaluator.proto\x1a>protos/playbooks/source_task_definitions/cloudwatch_task.proto\x1a;protos/playbooks/source_task_definitions/grafana_task.proto\x1a=protos/playbooks/source_task_definitions/new_relic_task.proto\x1a;protos/playbooks/source_task_definitions/datadog_task.proto\x1a\x37protos/playbooks/source_task_definitions/eks_task.proto\x1a\x42protos/playbooks/source_task_definitions/sql_data_fetch_task.proto\x1a\x37protos/playbooks/source_task_definitions/api_task.proto\x1a\x38protos/playbooks/source_task_definitions/bash_task.proto\x1a\x41protos/playbooks/source_task_definitions/documentation_task.proto\x1a:protos/playbooks/source_task_definitions/promql_task.proto\x1a\x39protos/playbooks/source_task_definitions/azure_task.proto\x1a\x37protos/playbooks/source_task_definitions/gke_task.proto\x1a\x42protos/playbooks/source_task_definitions/elastic_search_task.proto\x1a@protos/playbooks/source_task_definitions/grafana_loki_task.proto\x1a;protos/playbooks/source_task_definitions/kubectl_task.proto\x1a\x37protos/playbooks/source_task_definitions/gcm_task.proto\x1a\x39protos/playbooks/source_task_definitions/email_task.proto\x1a\x43protos/playbooks/source_task_definitions/lambda_function_task.proto\x1a\x39protos/playbooks/source_task_definitions/slack_task.proto\x1a=protos/playbooks/source_task_definitions/big_query_task.proto\x1a\x38protos/playbooks/source_task_definitions/jira_task.proto\x1a:protos/playbooks/source_task_definitions/argocd_task.proto\"\x94\x11\n\x0cPlaybookTask\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x1e\n\x06source\x18\x02 \x01(\x0e\x32\x0e.protos.Source\x12\x32\n\x0creference_id\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12*\n\x04name\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x31\n\x0b\x64\x65scription\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05notes\x18\x06 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x30\n\ncreated_by\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x34\n\x13global_variable_set\x18\x08 \x01(\x0b\x32\x17.google.protobuf.Struct\x12;\n\x10interpreter_type\x18\t \x01(\x0e\x32!.protos.playbooks.InterpreterType\x12Z\n\x16task_connector_sources\x18\n \x03(\x0b\x32:.protos.playbooks.PlaybookTask.PlaybookTaskConnectorSource\x12V\n\x17\x65xecution_configuration\x18\x0b \x01(\x0b\x32\x35.protos.playbooks.PlaybookTask.ExecutionConfiguration\x12\x38\n\rdocumentation\x18\x65 \x01(\x0b\x32\x1f.protos.playbooks.DocumentationH\x00\x12\x32\n\ncloudwatch\x18\x66 \x01(\x0b\x32\x1c.protos.playbooks.CloudwatchH\x00\x12,\n\x07grafana\x18g \x01(\x0b\x32\x19.protos.playbooks.GrafanaH\x00\x12/\n\tnew_relic\x18h \x01(\x0b\x32\x1a.protos.playbooks.NewRelicH\x00\x12,\n\x07\x64\x61tadog\x18i \x01(\x0b\x32\x19.protos.playbooks.DatadogH\x00\x12\x34\n\nclickhouse\x18j \x01(\x0b\x32\x1e.protos.playbooks.SqlDataFetchH\x00\x12\x32\n\x08postgres\x18k \x01(\x0b\x32\x1e.protos.playbooks.SqlDataFetchH\x00\x12$\n\x03\x65ks\x18l \x01(\x0b\x32\x15.protos.playbooks.EksH\x00\x12\x41\n\x17sql_database_connection\x18m \x01(\x0b\x32\x1e.protos.playbooks.SqlDataFetchH\x00\x12$\n\x03\x61pi\x18n \x01(\x0b\x32\x15.protos.playbooks.ApiH\x00\x12&\n\x04\x62\x61sh\x18o \x01(\x0b\x32\x16.protos.playbooks.BashH\x00\x12\x31\n\rgrafana_mimir\x18p \x01(\x0b\x32\x18.protos.playbooks.PromQlH\x00\x12(\n\x05\x61zure\x18q \x01(\x0b\x32\x17.protos.playbooks.AzureH\x00\x12$\n\x03gke\x18r \x01(\x0b\x32\x15.protos.playbooks.GkeH\x00\x12\x39\n\x0e\x65lastic_search\x18s \x01(\x0b\x32\x1f.protos.playbooks.ElasticSearchH\x00\x12\x35\n\x0cgrafana_loki\x18t \x01(\x0b\x32\x1d.protos.playbooks.GrafanaLokiH\x00\x12/\n\nkubernetes\x18u \x01(\x0b\x32\x19.protos.playbooks.KubectlH\x00\x12$\n\x03gcm\x18v \x01(\x0b\x32\x15.protos.playbooks.GcmH\x00\x12&\n\x04smtp\x18w \x01(\x0b\x32\x16.protos.playbooks.SMTPH\x00\x12(\n\x05slack\x18x \x01(\x0b\x32\x17.protos.playbooks.SlackH\x00\x12/\n\tbig_query\x18y \x01(\x0b\x32\x1a.protos.playbooks.BigQueryH\x00\x12,\n\njira_cloud\x18z \x01(\x0b\x32\x16.protos.playbooks.JiraH\x00\x12*\n\x06\x61rgocd\x18{ \x01(\x0b\x32\x18.protos.playbooks.ArgoCDH\x00\x1a\x93\x01\n\x1bPlaybookTaskConnectorSource\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x1e\n\x06source\x18\x02 \x01(\x0e\x32\x0e.protos.Source\x12*\n\x04name\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x1a\xbd\x02\n\x16\x45xecutionConfiguration\x12\x35\n\x11is_bulk_execution\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x12>\n\x18\x62ulk_execution_var_field\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x1a\n\x12timeseries_offsets\x18\x03 \x03(\r\x12\x41\n\x1dis_result_transformer_enabled\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x12M\n\"result_transformer_lambda_function\x18\x05 \x01(\x0b\x32!.protos.playbooks.Lambda.FunctionB\x06\n\x04task\"\xf9\x02\n\x16PlaybookTaskResultRule\x12\x36\n\x04type\x18\x01 \x01(\x0e\x32(.protos.playbooks.PlaybookTaskResultType\x12,\n\x04task\x18\x02 \x01(\x0b\x32\x1e.protos.playbooks.PlaybookTask\x12<\n\ntimeseries\x18\x65 \x01(\x0b\x32&.protos.playbooks.TimeseriesResultRuleH\x00\x12\x32\n\x05table\x18\x66 \x01(\x0b\x32!.protos.playbooks.TableResultRuleH\x00\x12\x31\n\x04logs\x18g \x01(\x0b\x32!.protos.playbooks.TableResultRuleH\x00\x12L\n\x13\x62\x61sh_command_output\x18h \x01(\x0b\x32-.protos.playbooks.BashCommandOutputResultRuleH\x00\x42\x06\n\x04rule\"\x8e\x03\n\x18PlaybookTaskExecutionLog\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x11\n\ttimestamp\x18\x02 \x01(\x10\x12,\n\x04task\x18\x03 \x01(\x0b\x32\x1e.protos.playbooks.PlaybookTask\x12\x34\n\x06result\x18\x04 \x01(\x0b\x32$.protos.playbooks.PlaybookTaskResult\x12\x38\n\x0einterpretation\x18\x05 \x01(\x0b\x32 .protos.playbooks.Interpretation\x12%\n\ntime_range\x18\x06 \x01(\x0b\x32\x11.protos.TimeRange\x12\x30\n\ncreated_by\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12>\n\x1d\x65xecution_global_variable_set\x18\x08 \x01(\x0b\x32\x17.google.protobuf.Struct\"\x94\x03\n\x1bPlaybookStepResultCondition\x12\x31\n\x10logical_operator\x18\x01 \x01(\x0e\x32\x17.protos.LogicalOperator\x12H\n\trule_sets\x18\x02 \x03(\x0b\x32\x35.protos.playbooks.PlaybookStepResultCondition.RuleSet\x1a\xf7\x01\n\x07RuleSet\x12\x31\n\x10logical_operator\x18\x01 \x01(\x0e\x32\x17.protos.LogicalOperator\x12\x37\n\x05rules\x18\x02 \x03(\x0b\x32(.protos.playbooks.PlaybookTaskResultRule\x12<\n\nstep_rules\x18\x03 \x03(\x0b\x32(.protos.playbooks.PlaybookStepResultRule\x12\x42\n\x0evariable_rules\x18\x04 \x03(\x0b\x32*.protos.playbooks.GlobalVariableResultRule\"\xd6\x03\n\x0cPlaybookStep\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x32\n\x0creference_id\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12*\n\x04name\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x31\n\x0b\x64\x65scription\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05notes\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x0e\x65xternal_links\x18\x06 \x03(\x0b\x32\x1e.protos.playbooks.ExternalLink\x12;\n\x10interpreter_type\x18\x07 \x01(\x0e\x32!.protos.playbooks.InterpreterType\x12-\n\x05tasks\x18\x08 \x03(\x0b\x32\x1e.protos.playbooks.PlaybookTask\x12\x38\n\x08\x63hildren\x18\t \x03(\x0b\x32&.protos.playbooks.PlaybookStepRelation\"\x90\x02\n\x14PlaybookStepRelation\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12.\n\x06parent\x18\x02 \x01(\x0b\x32\x1e.protos.playbooks.PlaybookStep\x12-\n\x05\x63hild\x18\x03 \x01(\x0b\x32\x1e.protos.playbooks.PlaybookStep\x12@\n\tcondition\x18\x04 \x01(\x0b\x32-.protos.playbooks.PlaybookStepResultCondition\x12-\n\tis_active\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\"\xb9\x02\n PlaybookStepRelationExecutionLog\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x38\n\x08relation\x18\x02 \x01(\x0b\x32&.protos.playbooks.PlaybookStepRelation\x12\x35\n\x11\x65valuation_result\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x12\x32\n\x11\x65valuation_output\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x46\n\x1cstep_relation_interpretation\x18\x05 \x01(\x0b\x32 .protos.playbooks.Interpretation\"\xf2\x03\n\x18PlaybookStepExecutionLog\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x11\n\ttimestamp\x18\x02 \x01(\x10\x12\x35\n\x0fplaybook_run_id\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12,\n\x04step\x18\x04 \x01(\x0b\x32\x1e.protos.playbooks.PlaybookStep\x12G\n\x13task_execution_logs\x18\x05 \x03(\x0b\x32*.protos.playbooks.PlaybookTaskExecutionLog\x12S\n\x17relation_execution_logs\x18\x06 \x03(\x0b\x32\x32.protos.playbooks.PlaybookStepRelationExecutionLog\x12=\n\x13step_interpretation\x18\x07 \x01(\x0b\x32 .protos.playbooks.Interpretation\x12%\n\ntime_range\x18\x08 \x01(\x0b\x32\x11.protos.TimeRange\x12\x30\n\ncreated_by\x18\t \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x81\x04\n\x08Playbook\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12*\n\x04name\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x31\n\x0b\x64\x65scription\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x34\n\x13global_variable_set\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x30\n\ncreated_by\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12-\n\tis_active\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x12\x12\n\ncreated_at\x18\x07 \x01(\x10\x12\x13\n\x0blast_run_at\x18\x08 \x01(\x10\x12=\n\x06status\x18\t \x01(\x0e\x32-.protos.playbooks.PlaybookExecutionStatusType\x12-\n\x05steps\x18\n \x03(\x0b\x32\x1e.protos.playbooks.PlaybookStep\x12>\n\x0estep_relations\x18\x0b \x03(\x0b\x32&.protos.playbooks.PlaybookStepRelation\"\x80\x04\n\x11PlaybookExecution\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x35\n\x0fplaybook_run_id\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12,\n\x08playbook\x18\x03 \x01(\x0b\x32\x1a.protos.playbooks.Playbook\x12=\n\x06status\x18\x04 \x01(\x0e\x32-.protos.playbooks.PlaybookExecutionStatusType\x12\x12\n\ncreated_at\x18\x05 \x01(\x10\x12\x12\n\nstarted_at\x18\x06 \x01(\x10\x12\x13\n\x0b\x66inished_at\x18\x07 \x01(\x10\x12%\n\ntime_range\x18\x08 \x01(\x0b\x32\x11.protos.TimeRange\x12\x30\n\ncreated_by\x18\t \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12G\n\x13step_execution_logs\x18\x0b \x03(\x0b\x32*.protos.playbooks.PlaybookStepExecutionLog\x12>\n\x1d\x65xecution_global_variable_set\x18\x0c \x01(\x0b\x32\x17.google.protobuf.Struct\"\xf6\x04\n\x10UpdatePlaybookOp\x12\x31\n\x02op\x18\x01 \x01(\x0e\x32%.protos.playbooks.UpdatePlaybookOp.Op\x12U\n\x14update_playbook_name\x18\x02 \x01(\x0b\x32\x35.protos.playbooks.UpdatePlaybookOp.UpdatePlaybookNameH\x00\x12Y\n\x16update_playbook_status\x18\x03 \x01(\x0b\x32\x37.protos.playbooks.UpdatePlaybookOp.UpdatePlaybookStatusH\x00\x12L\n\x0fupdate_playbook\x18\x04 \x01(\x0b\x32\x31.protos.playbooks.UpdatePlaybookOp.UpdatePlaybookH\x00\x1a@\n\x12UpdatePlaybookName\x12*\n\x04name\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x1a\x45\n\x14UpdatePlaybookStatus\x12-\n\tis_active\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x1a>\n\x0eUpdatePlaybook\x12,\n\x08playbook\x18\x01 \x01(\x0b\x32\x1a.protos.playbooks.Playbook\"\\\n\x02Op\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x18\n\x14UPDATE_PLAYBOOK_NAME\x10\x01\x12\x1a\n\x16UPDATE_PLAYBOOK_STATUS\x10\x02\x12\x13\n\x0fUPDATE_PLAYBOOK\x10\x03\x42\x08\n\x06updateb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fprotos/playbooks/playbook.proto\x12\x10protos.playbooks\x1a\x11protos/base.proto\x1a\x1egoogle/protobuf/wrappers.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\'protos/playbooks/playbook_commons.proto\x1a\x35protos/playbooks/intelligence_layer/interpreter.proto\x1a\x35protos/playbooks/playbook_task_result_evaluator.proto\x1a\x35protos/playbooks/playbook_step_result_evaluator.proto\x1a\x39protos/playbooks/playbook_global_variable_evaluator.proto\x1a>protos/playbooks/source_task_definitions/cloudwatch_task.proto\x1a;protos/playbooks/source_task_definitions/grafana_task.proto\x1a=protos/playbooks/source_task_definitions/new_relic_task.proto\x1a;protos/playbooks/source_task_definitions/datadog_task.proto\x1a\x37protos/playbooks/source_task_definitions/eks_task.proto\x1a\x42protos/playbooks/source_task_definitions/sql_data_fetch_task.proto\x1a\x37protos/playbooks/source_task_definitions/api_task.proto\x1a\x38protos/playbooks/source_task_definitions/bash_task.proto\x1a\x39protos/playbooks/source_task_definitions/redis_task.proto\x1a\x41protos/playbooks/source_task_definitions/documentation_task.proto\x1a:protos/playbooks/source_task_definitions/promql_task.proto\x1a\x39protos/playbooks/source_task_definitions/azure_task.proto\x1a\x37protos/playbooks/source_task_definitions/gke_task.proto\x1a\x42protos/playbooks/source_task_definitions/elastic_search_task.proto\x1a@protos/playbooks/source_task_definitions/grafana_loki_task.proto\x1a;protos/playbooks/source_task_definitions/kubectl_task.proto\x1a\x37protos/playbooks/source_task_definitions/gcm_task.proto\x1a\x39protos/playbooks/source_task_definitions/email_task.proto\x1a\x43protos/playbooks/source_task_definitions/lambda_function_task.proto\x1a\x39protos/playbooks/source_task_definitions/slack_task.proto\x1a=protos/playbooks/source_task_definitions/big_query_task.proto\x1a\x38protos/playbooks/source_task_definitions/jira_task.proto\x1a:protos/playbooks/source_task_definitions/argocd_task.proto\"\xbe\x11\n\x0cPlaybookTask\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x1e\n\x06source\x18\x02 \x01(\x0e\x32\x0e.protos.Source\x12\x32\n\x0creference_id\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12*\n\x04name\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x31\n\x0b\x64\x65scription\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05notes\x18\x06 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x30\n\ncreated_by\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x34\n\x13global_variable_set\x18\x08 \x01(\x0b\x32\x17.google.protobuf.Struct\x12;\n\x10interpreter_type\x18\t \x01(\x0e\x32!.protos.playbooks.InterpreterType\x12Z\n\x16task_connector_sources\x18\n \x03(\x0b\x32:.protos.playbooks.PlaybookTask.PlaybookTaskConnectorSource\x12V\n\x17\x65xecution_configuration\x18\x0b \x01(\x0b\x32\x35.protos.playbooks.PlaybookTask.ExecutionConfiguration\x12\x38\n\rdocumentation\x18\x65 \x01(\x0b\x32\x1f.protos.playbooks.DocumentationH\x00\x12\x32\n\ncloudwatch\x18\x66 \x01(\x0b\x32\x1c.protos.playbooks.CloudwatchH\x00\x12,\n\x07grafana\x18g \x01(\x0b\x32\x19.protos.playbooks.GrafanaH\x00\x12/\n\tnew_relic\x18h \x01(\x0b\x32\x1a.protos.playbooks.NewRelicH\x00\x12,\n\x07\x64\x61tadog\x18i \x01(\x0b\x32\x19.protos.playbooks.DatadogH\x00\x12\x34\n\nclickhouse\x18j \x01(\x0b\x32\x1e.protos.playbooks.SqlDataFetchH\x00\x12\x32\n\x08postgres\x18k \x01(\x0b\x32\x1e.protos.playbooks.SqlDataFetchH\x00\x12$\n\x03\x65ks\x18l \x01(\x0b\x32\x15.protos.playbooks.EksH\x00\x12\x41\n\x17sql_database_connection\x18m \x01(\x0b\x32\x1e.protos.playbooks.SqlDataFetchH\x00\x12$\n\x03\x61pi\x18n \x01(\x0b\x32\x15.protos.playbooks.ApiH\x00\x12&\n\x04\x62\x61sh\x18o \x01(\x0b\x32\x16.protos.playbooks.BashH\x00\x12\x31\n\rgrafana_mimir\x18p \x01(\x0b\x32\x18.protos.playbooks.PromQlH\x00\x12(\n\x05\x61zure\x18q \x01(\x0b\x32\x17.protos.playbooks.AzureH\x00\x12$\n\x03gke\x18r \x01(\x0b\x32\x15.protos.playbooks.GkeH\x00\x12\x39\n\x0e\x65lastic_search\x18s \x01(\x0b\x32\x1f.protos.playbooks.ElasticSearchH\x00\x12\x35\n\x0cgrafana_loki\x18t \x01(\x0b\x32\x1d.protos.playbooks.GrafanaLokiH\x00\x12/\n\nkubernetes\x18u \x01(\x0b\x32\x19.protos.playbooks.KubectlH\x00\x12$\n\x03gcm\x18v \x01(\x0b\x32\x15.protos.playbooks.GcmH\x00\x12&\n\x04smtp\x18w \x01(\x0b\x32\x16.protos.playbooks.SMTPH\x00\x12(\n\x05slack\x18x \x01(\x0b\x32\x17.protos.playbooks.SlackH\x00\x12/\n\tbig_query\x18y \x01(\x0b\x32\x1a.protos.playbooks.BigQueryH\x00\x12,\n\njira_cloud\x18z \x01(\x0b\x32\x16.protos.playbooks.JiraH\x00\x12*\n\x06\x61rgocd\x18{ \x01(\x0b\x32\x18.protos.playbooks.ArgoCDH\x00\x12(\n\x05redis\x18| \x01(\x0b\x32\x17.protos.playbooks.RedisH\x00\x1a\x93\x01\n\x1bPlaybookTaskConnectorSource\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x1e\n\x06source\x18\x02 \x01(\x0e\x32\x0e.protos.Source\x12*\n\x04name\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x1a\xbd\x02\n\x16\x45xecutionConfiguration\x12\x35\n\x11is_bulk_execution\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x12>\n\x18\x62ulk_execution_var_field\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x1a\n\x12timeseries_offsets\x18\x03 \x03(\r\x12\x41\n\x1dis_result_transformer_enabled\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x12M\n\"result_transformer_lambda_function\x18\x05 \x01(\x0b\x32!.protos.playbooks.Lambda.FunctionB\x06\n\x04task\"\xf9\x02\n\x16PlaybookTaskResultRule\x12\x36\n\x04type\x18\x01 \x01(\x0e\x32(.protos.playbooks.PlaybookTaskResultType\x12,\n\x04task\x18\x02 \x01(\x0b\x32\x1e.protos.playbooks.PlaybookTask\x12<\n\ntimeseries\x18\x65 \x01(\x0b\x32&.protos.playbooks.TimeseriesResultRuleH\x00\x12\x32\n\x05table\x18\x66 \x01(\x0b\x32!.protos.playbooks.TableResultRuleH\x00\x12\x31\n\x04logs\x18g \x01(\x0b\x32!.protos.playbooks.TableResultRuleH\x00\x12L\n\x13\x62\x61sh_command_output\x18h \x01(\x0b\x32-.protos.playbooks.BashCommandOutputResultRuleH\x00\x42\x06\n\x04rule\"\x8e\x03\n\x18PlaybookTaskExecutionLog\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x11\n\ttimestamp\x18\x02 \x01(\x10\x12,\n\x04task\x18\x03 \x01(\x0b\x32\x1e.protos.playbooks.PlaybookTask\x12\x34\n\x06result\x18\x04 \x01(\x0b\x32$.protos.playbooks.PlaybookTaskResult\x12\x38\n\x0einterpretation\x18\x05 \x01(\x0b\x32 .protos.playbooks.Interpretation\x12%\n\ntime_range\x18\x06 \x01(\x0b\x32\x11.protos.TimeRange\x12\x30\n\ncreated_by\x18\x07 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12>\n\x1d\x65xecution_global_variable_set\x18\x08 \x01(\x0b\x32\x17.google.protobuf.Struct\"\x94\x03\n\x1bPlaybookStepResultCondition\x12\x31\n\x10logical_operator\x18\x01 \x01(\x0e\x32\x17.protos.LogicalOperator\x12H\n\trule_sets\x18\x02 \x03(\x0b\x32\x35.protos.playbooks.PlaybookStepResultCondition.RuleSet\x1a\xf7\x01\n\x07RuleSet\x12\x31\n\x10logical_operator\x18\x01 \x01(\x0e\x32\x17.protos.LogicalOperator\x12\x37\n\x05rules\x18\x02 \x03(\x0b\x32(.protos.playbooks.PlaybookTaskResultRule\x12<\n\nstep_rules\x18\x03 \x03(\x0b\x32(.protos.playbooks.PlaybookStepResultRule\x12\x42\n\x0evariable_rules\x18\x04 \x03(\x0b\x32*.protos.playbooks.GlobalVariableResultRule\"\xd6\x03\n\x0cPlaybookStep\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x32\n\x0creference_id\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12*\n\x04name\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x31\n\x0b\x64\x65scription\x18\x04 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12+\n\x05notes\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x36\n\x0e\x65xternal_links\x18\x06 \x03(\x0b\x32\x1e.protos.playbooks.ExternalLink\x12;\n\x10interpreter_type\x18\x07 \x01(\x0e\x32!.protos.playbooks.InterpreterType\x12-\n\x05tasks\x18\x08 \x03(\x0b\x32\x1e.protos.playbooks.PlaybookTask\x12\x38\n\x08\x63hildren\x18\t \x03(\x0b\x32&.protos.playbooks.PlaybookStepRelation\"\x90\x02\n\x14PlaybookStepRelation\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12.\n\x06parent\x18\x02 \x01(\x0b\x32\x1e.protos.playbooks.PlaybookStep\x12-\n\x05\x63hild\x18\x03 \x01(\x0b\x32\x1e.protos.playbooks.PlaybookStep\x12@\n\tcondition\x18\x04 \x01(\x0b\x32-.protos.playbooks.PlaybookStepResultCondition\x12-\n\tis_active\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\"\xb9\x02\n PlaybookStepRelationExecutionLog\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x38\n\x08relation\x18\x02 \x01(\x0b\x32&.protos.playbooks.PlaybookStepRelation\x12\x35\n\x11\x65valuation_result\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x12\x32\n\x11\x65valuation_output\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x46\n\x1cstep_relation_interpretation\x18\x05 \x01(\x0b\x32 .protos.playbooks.Interpretation\"\xf2\x03\n\x18PlaybookStepExecutionLog\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x11\n\ttimestamp\x18\x02 \x01(\x10\x12\x35\n\x0fplaybook_run_id\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12,\n\x04step\x18\x04 \x01(\x0b\x32\x1e.protos.playbooks.PlaybookStep\x12G\n\x13task_execution_logs\x18\x05 \x03(\x0b\x32*.protos.playbooks.PlaybookTaskExecutionLog\x12S\n\x17relation_execution_logs\x18\x06 \x03(\x0b\x32\x32.protos.playbooks.PlaybookStepRelationExecutionLog\x12=\n\x13step_interpretation\x18\x07 \x01(\x0b\x32 .protos.playbooks.Interpretation\x12%\n\ntime_range\x18\x08 \x01(\x0b\x32\x11.protos.TimeRange\x12\x30\n\ncreated_by\x18\t \x01(\x0b\x32\x1c.google.protobuf.StringValue\"\x81\x04\n\x08Playbook\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12*\n\x04name\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x31\n\x0b\x64\x65scription\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12\x34\n\x13global_variable_set\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x30\n\ncreated_by\x18\x05 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12-\n\tis_active\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x12\x12\n\ncreated_at\x18\x07 \x01(\x10\x12\x13\n\x0blast_run_at\x18\x08 \x01(\x10\x12=\n\x06status\x18\t \x01(\x0e\x32-.protos.playbooks.PlaybookExecutionStatusType\x12-\n\x05steps\x18\n \x03(\x0b\x32\x1e.protos.playbooks.PlaybookStep\x12>\n\x0estep_relations\x18\x0b \x03(\x0b\x32&.protos.playbooks.PlaybookStepRelation\"\x80\x04\n\x11PlaybookExecution\x12(\n\x02id\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x12\x35\n\x0fplaybook_run_id\x18\x02 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12,\n\x08playbook\x18\x03 \x01(\x0b\x32\x1a.protos.playbooks.Playbook\x12=\n\x06status\x18\x04 \x01(\x0e\x32-.protos.playbooks.PlaybookExecutionStatusType\x12\x12\n\ncreated_at\x18\x05 \x01(\x10\x12\x12\n\nstarted_at\x18\x06 \x01(\x10\x12\x13\n\x0b\x66inished_at\x18\x07 \x01(\x10\x12%\n\ntime_range\x18\x08 \x01(\x0b\x32\x11.protos.TimeRange\x12\x30\n\ncreated_by\x18\t \x01(\x0b\x32\x1c.google.protobuf.StringValue\x12G\n\x13step_execution_logs\x18\x0b \x03(\x0b\x32*.protos.playbooks.PlaybookStepExecutionLog\x12>\n\x1d\x65xecution_global_variable_set\x18\x0c \x01(\x0b\x32\x17.google.protobuf.Struct\"\xf6\x04\n\x10UpdatePlaybookOp\x12\x31\n\x02op\x18\x01 \x01(\x0e\x32%.protos.playbooks.UpdatePlaybookOp.Op\x12U\n\x14update_playbook_name\x18\x02 \x01(\x0b\x32\x35.protos.playbooks.UpdatePlaybookOp.UpdatePlaybookNameH\x00\x12Y\n\x16update_playbook_status\x18\x03 \x01(\x0b\x32\x37.protos.playbooks.UpdatePlaybookOp.UpdatePlaybookStatusH\x00\x12L\n\x0fupdate_playbook\x18\x04 \x01(\x0b\x32\x31.protos.playbooks.UpdatePlaybookOp.UpdatePlaybookH\x00\x1a@\n\x12UpdatePlaybookName\x12*\n\x04name\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x1a\x45\n\x14UpdatePlaybookStatus\x12-\n\tis_active\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.BoolValue\x1a>\n\x0eUpdatePlaybook\x12,\n\x08playbook\x18\x01 \x01(\x0b\x32\x1a.protos.playbooks.Playbook\"\\\n\x02Op\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x18\n\x14UPDATE_PLAYBOOK_NAME\x10\x01\x12\x1a\n\x16UPDATE_PLAYBOOK_STATUS\x10\x02\x12\x13\n\x0fUPDATE_PLAYBOOK\x10\x03\x42\x08\n\x06updateb\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'protos.playbooks.playbook_pb2', globals()) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _PLAYBOOKTASK._serialized_start=1752 - _PLAYBOOKTASK._serialized_end=3948 - _PLAYBOOKTASK_PLAYBOOKTASKCONNECTORSOURCE._serialized_start=3473 - _PLAYBOOKTASK_PLAYBOOKTASKCONNECTORSOURCE._serialized_end=3620 - _PLAYBOOKTASK_EXECUTIONCONFIGURATION._serialized_start=3623 - _PLAYBOOKTASK_EXECUTIONCONFIGURATION._serialized_end=3940 - _PLAYBOOKTASKRESULTRULE._serialized_start=3951 - _PLAYBOOKTASKRESULTRULE._serialized_end=4328 - _PLAYBOOKTASKEXECUTIONLOG._serialized_start=4331 - _PLAYBOOKTASKEXECUTIONLOG._serialized_end=4729 - _PLAYBOOKSTEPRESULTCONDITION._serialized_start=4732 - _PLAYBOOKSTEPRESULTCONDITION._serialized_end=5136 - _PLAYBOOKSTEPRESULTCONDITION_RULESET._serialized_start=4889 - _PLAYBOOKSTEPRESULTCONDITION_RULESET._serialized_end=5136 - _PLAYBOOKSTEP._serialized_start=5139 - _PLAYBOOKSTEP._serialized_end=5609 - _PLAYBOOKSTEPRELATION._serialized_start=5612 - _PLAYBOOKSTEPRELATION._serialized_end=5884 - _PLAYBOOKSTEPRELATIONEXECUTIONLOG._serialized_start=5887 - _PLAYBOOKSTEPRELATIONEXECUTIONLOG._serialized_end=6200 - _PLAYBOOKSTEPEXECUTIONLOG._serialized_start=6203 - _PLAYBOOKSTEPEXECUTIONLOG._serialized_end=6701 - _PLAYBOOK._serialized_start=6704 - _PLAYBOOK._serialized_end=7217 - _PLAYBOOKEXECUTION._serialized_start=7220 - _PLAYBOOKEXECUTION._serialized_end=7732 - _UPDATEPLAYBOOKOP._serialized_start=7735 - _UPDATEPLAYBOOKOP._serialized_end=8365 - _UPDATEPLAYBOOKOP_UPDATEPLAYBOOKNAME._serialized_start=8062 - _UPDATEPLAYBOOKOP_UPDATEPLAYBOOKNAME._serialized_end=8126 - _UPDATEPLAYBOOKOP_UPDATEPLAYBOOKSTATUS._serialized_start=8128 - _UPDATEPLAYBOOKOP_UPDATEPLAYBOOKSTATUS._serialized_end=8197 - _UPDATEPLAYBOOKOP_UPDATEPLAYBOOK._serialized_start=8199 - _UPDATEPLAYBOOKOP_UPDATEPLAYBOOK._serialized_end=8261 - _UPDATEPLAYBOOKOP_OP._serialized_start=8263 - _UPDATEPLAYBOOKOP_OP._serialized_end=8355 + _PLAYBOOKTASK._serialized_start=1811 + _PLAYBOOKTASK._serialized_end=4049 + _PLAYBOOKTASK_PLAYBOOKTASKCONNECTORSOURCE._serialized_start=3574 + _PLAYBOOKTASK_PLAYBOOKTASKCONNECTORSOURCE._serialized_end=3721 + _PLAYBOOKTASK_EXECUTIONCONFIGURATION._serialized_start=3724 + _PLAYBOOKTASK_EXECUTIONCONFIGURATION._serialized_end=4041 + _PLAYBOOKTASKRESULTRULE._serialized_start=4052 + _PLAYBOOKTASKRESULTRULE._serialized_end=4429 + _PLAYBOOKTASKEXECUTIONLOG._serialized_start=4432 + _PLAYBOOKTASKEXECUTIONLOG._serialized_end=4830 + _PLAYBOOKSTEPRESULTCONDITION._serialized_start=4833 + _PLAYBOOKSTEPRESULTCONDITION._serialized_end=5237 + _PLAYBOOKSTEPRESULTCONDITION_RULESET._serialized_start=4990 + _PLAYBOOKSTEPRESULTCONDITION_RULESET._serialized_end=5237 + _PLAYBOOKSTEP._serialized_start=5240 + _PLAYBOOKSTEP._serialized_end=5710 + _PLAYBOOKSTEPRELATION._serialized_start=5713 + _PLAYBOOKSTEPRELATION._serialized_end=5985 + _PLAYBOOKSTEPRELATIONEXECUTIONLOG._serialized_start=5988 + _PLAYBOOKSTEPRELATIONEXECUTIONLOG._serialized_end=6301 + _PLAYBOOKSTEPEXECUTIONLOG._serialized_start=6304 + _PLAYBOOKSTEPEXECUTIONLOG._serialized_end=6802 + _PLAYBOOK._serialized_start=6805 + _PLAYBOOK._serialized_end=7318 + _PLAYBOOKEXECUTION._serialized_start=7321 + _PLAYBOOKEXECUTION._serialized_end=7833 + _UPDATEPLAYBOOKOP._serialized_start=7836 + _UPDATEPLAYBOOKOP._serialized_end=8466 + _UPDATEPLAYBOOKOP_UPDATEPLAYBOOKNAME._serialized_start=8163 + _UPDATEPLAYBOOKOP_UPDATEPLAYBOOKNAME._serialized_end=8227 + _UPDATEPLAYBOOKOP_UPDATEPLAYBOOKSTATUS._serialized_start=8229 + _UPDATEPLAYBOOKOP_UPDATEPLAYBOOKSTATUS._serialized_end=8298 + _UPDATEPLAYBOOKOP_UPDATEPLAYBOOK._serialized_start=8300 + _UPDATEPLAYBOOKOP_UPDATEPLAYBOOK._serialized_end=8362 + _UPDATEPLAYBOOKOP_OP._serialized_start=8364 + _UPDATEPLAYBOOKOP_OP._serialized_end=8456 # @@protoc_insertion_point(module_scope) diff --git a/protos/playbooks/playbook_pb2.pyi b/protos/playbooks/playbook_pb2.pyi index 7067943f9..ed69baa51 100644 --- a/protos/playbooks/playbook_pb2.pyi +++ b/protos/playbooks/playbook_pb2.pyi @@ -36,6 +36,7 @@ import protos.playbooks.source_task_definitions.kubectl_task_pb2 import protos.playbooks.source_task_definitions.lambda_function_task_pb2 import protos.playbooks.source_task_definitions.new_relic_task_pb2 import protos.playbooks.source_task_definitions.promql_task_pb2 +import protos.playbooks.source_task_definitions.redis_task_pb2 import protos.playbooks.source_task_definitions.slack_task_pb2 import protos.playbooks.source_task_definitions.sql_data_fetch_task_pb2 import sys @@ -141,6 +142,7 @@ class PlaybookTask(google.protobuf.message.Message): BIG_QUERY_FIELD_NUMBER: builtins.int JIRA_CLOUD_FIELD_NUMBER: builtins.int ARGOCD_FIELD_NUMBER: builtins.int + REDIS_FIELD_NUMBER: builtins.int @property def id(self) -> google.protobuf.wrappers_pb2.UInt64Value: ... source: protos.base_pb2.Source.ValueType @@ -207,6 +209,8 @@ class PlaybookTask(google.protobuf.message.Message): def jira_cloud(self) -> protos.playbooks.source_task_definitions.jira_task_pb2.Jira: ... @property def argocd(self) -> protos.playbooks.source_task_definitions.argocd_task_pb2.ArgoCD: ... + @property + def redis(self) -> protos.playbooks.source_task_definitions.redis_task_pb2.Redis: ... def __init__( self, *, @@ -244,10 +248,11 @@ class PlaybookTask(google.protobuf.message.Message): big_query: protos.playbooks.source_task_definitions.big_query_task_pb2.BigQuery | None = ..., jira_cloud: protos.playbooks.source_task_definitions.jira_task_pb2.Jira | None = ..., argocd: protos.playbooks.source_task_definitions.argocd_task_pb2.ArgoCD | None = ..., + redis: protos.playbooks.source_task_definitions.redis_task_pb2.Redis | None = ..., ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["api", b"api", "argocd", b"argocd", "azure", b"azure", "bash", b"bash", "big_query", b"big_query", "clickhouse", b"clickhouse", "cloudwatch", b"cloudwatch", "created_by", b"created_by", "datadog", b"datadog", "description", b"description", "documentation", b"documentation", "eks", b"eks", "elastic_search", b"elastic_search", "execution_configuration", b"execution_configuration", "gcm", b"gcm", "gke", b"gke", "global_variable_set", b"global_variable_set", "grafana", b"grafana", "grafana_loki", b"grafana_loki", "grafana_mimir", b"grafana_mimir", "id", b"id", "jira_cloud", b"jira_cloud", "kubernetes", b"kubernetes", "name", b"name", "new_relic", b"new_relic", "notes", b"notes", "postgres", b"postgres", "reference_id", b"reference_id", "slack", b"slack", "smtp", b"smtp", "sql_database_connection", b"sql_database_connection", "task", b"task"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["api", b"api", "argocd", b"argocd", "azure", b"azure", "bash", b"bash", "big_query", b"big_query", "clickhouse", b"clickhouse", "cloudwatch", b"cloudwatch", "created_by", b"created_by", "datadog", b"datadog", "description", b"description", "documentation", b"documentation", "eks", b"eks", "elastic_search", b"elastic_search", "execution_configuration", b"execution_configuration", "gcm", b"gcm", "gke", b"gke", "global_variable_set", b"global_variable_set", "grafana", b"grafana", "grafana_loki", b"grafana_loki", "grafana_mimir", b"grafana_mimir", "id", b"id", "interpreter_type", b"interpreter_type", "jira_cloud", b"jira_cloud", "kubernetes", b"kubernetes", "name", b"name", "new_relic", b"new_relic", "notes", b"notes", "postgres", b"postgres", "reference_id", b"reference_id", "slack", b"slack", "smtp", b"smtp", "source", b"source", "sql_database_connection", b"sql_database_connection", "task", b"task", "task_connector_sources", b"task_connector_sources"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["task", b"task"]) -> typing_extensions.Literal["documentation", "cloudwatch", "grafana", "new_relic", "datadog", "clickhouse", "postgres", "eks", "sql_database_connection", "api", "bash", "grafana_mimir", "azure", "gke", "elastic_search", "grafana_loki", "kubernetes", "gcm", "smtp", "slack", "big_query", "jira_cloud", "argocd"] | None: ... + def HasField(self, field_name: typing_extensions.Literal["api", b"api", "argocd", b"argocd", "azure", b"azure", "bash", b"bash", "big_query", b"big_query", "clickhouse", b"clickhouse", "cloudwatch", b"cloudwatch", "created_by", b"created_by", "datadog", b"datadog", "description", b"description", "documentation", b"documentation", "eks", b"eks", "elastic_search", b"elastic_search", "execution_configuration", b"execution_configuration", "gcm", b"gcm", "gke", b"gke", "global_variable_set", b"global_variable_set", "grafana", b"grafana", "grafana_loki", b"grafana_loki", "grafana_mimir", b"grafana_mimir", "id", b"id", "jira_cloud", b"jira_cloud", "kubernetes", b"kubernetes", "name", b"name", "new_relic", b"new_relic", "notes", b"notes", "postgres", b"postgres", "redis", b"redis", "reference_id", b"reference_id", "slack", b"slack", "smtp", b"smtp", "sql_database_connection", b"sql_database_connection", "task", b"task"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["api", b"api", "argocd", b"argocd", "azure", b"azure", "bash", b"bash", "big_query", b"big_query", "clickhouse", b"clickhouse", "cloudwatch", b"cloudwatch", "created_by", b"created_by", "datadog", b"datadog", "description", b"description", "documentation", b"documentation", "eks", b"eks", "elastic_search", b"elastic_search", "execution_configuration", b"execution_configuration", "gcm", b"gcm", "gke", b"gke", "global_variable_set", b"global_variable_set", "grafana", b"grafana", "grafana_loki", b"grafana_loki", "grafana_mimir", b"grafana_mimir", "id", b"id", "interpreter_type", b"interpreter_type", "jira_cloud", b"jira_cloud", "kubernetes", b"kubernetes", "name", b"name", "new_relic", b"new_relic", "notes", b"notes", "postgres", b"postgres", "redis", b"redis", "reference_id", b"reference_id", "slack", b"slack", "smtp", b"smtp", "source", b"source", "sql_database_connection", b"sql_database_connection", "task", b"task", "task_connector_sources", b"task_connector_sources"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["task", b"task"]) -> typing_extensions.Literal["documentation", "cloudwatch", "grafana", "new_relic", "datadog", "clickhouse", "postgres", "eks", "sql_database_connection", "api", "bash", "grafana_mimir", "azure", "gke", "elastic_search", "grafana_loki", "kubernetes", "gcm", "smtp", "slack", "big_query", "jira_cloud", "argocd", "redis"] | None: ... global___PlaybookTask = PlaybookTask diff --git a/protos/playbooks/source_task_definitions/redis_task.proto b/protos/playbooks/source_task_definitions/redis_task.proto new file mode 100644 index 000000000..6d72bfbe7 --- /dev/null +++ b/protos/playbooks/source_task_definitions/redis_task.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; +package protos.playbooks; + +import "google/protobuf/wrappers.proto"; + + +message Redis { + // Fetch server statistics via the Redis INFO command. + message GetInfo { + // Optional INFO section (e.g. "memory", "clients", "stats", "replication"). + // When empty, the default INFO output is returned. + google.protobuf.StringValue section = 1; + } + + // Fetch the Redis slow query log via SLOWLOG GET. + message GetSlowlog { + // Number of entries to return. Defaults to 10 when unset. + google.protobuf.UInt64Value limit = 1; + } + + // Run a single read-only Redis command (e.g. "DBSIZE", "GET foo", + // "MEMORY USAGE key", "CLIENT LIST"). Write/administrative commands are + // rejected by the connector. + message RunCommand { + google.protobuf.StringValue command = 1; + } + + enum TaskType { + UNKNOWN = 0; + GET_INFO = 1; + GET_SLOWLOG = 2; + RUN_COMMAND = 3; + } + + TaskType type = 1; + oneof task { + GetInfo get_info = 2; + GetSlowlog get_slowlog = 3; + RunCommand run_command = 4; + } +} diff --git a/protos/playbooks/source_task_definitions/redis_task_pb2.py b/protos/playbooks/source_task_definitions/redis_task_pb2.py new file mode 100644 index 000000000..6937fbb96 --- /dev/null +++ b/protos/playbooks/source_task_definitions/redis_task_pb2.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: protos/playbooks/source_task_definitions/redis_task.proto +"""Generated protocol buffer code.""" +from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n9protos/playbooks/source_task_definitions/redis_task.proto\x12\x10protos.playbooks\x1a\x1egoogle/protobuf/wrappers.proto\"\xe5\x03\n\x05Redis\x12.\n\x04type\x18\x01 \x01(\x0e\x32 .protos.playbooks.Redis.TaskType\x12\x33\n\x08get_info\x18\x02 \x01(\x0b\x32\x1f.protos.playbooks.Redis.GetInfoH\x00\x12\x39\n\x0bget_slowlog\x18\x03 \x01(\x0b\x32\".protos.playbooks.Redis.GetSlowlogH\x00\x12\x39\n\x0brun_command\x18\x04 \x01(\x0b\x32\".protos.playbooks.Redis.RunCommandH\x00\x1a\x38\n\x07GetInfo\x12-\n\x07section\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\x1a\x39\n\nGetSlowlog\x12+\n\x05limit\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.UInt64Value\x1a;\n\nRunCommand\x12-\n\x07\x63ommand\x18\x01 \x01(\x0b\x32\x1c.google.protobuf.StringValue\"G\n\x08TaskType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0c\n\x08GET_INFO\x10\x01\x12\x0f\n\x0bGET_SLOWLOG\x10\x02\x12\x0f\n\x0bRUN_COMMAND\x10\x03\x42\x06\n\x04taskb\x06proto3') + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'protos.playbooks.source_task_definitions.redis_task_pb2', globals()) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _REDIS._serialized_start=112 + _REDIS._serialized_end=597 + _REDIS_GETINFO._serialized_start=340 + _REDIS_GETINFO._serialized_end=396 + _REDIS_GETSLOWLOG._serialized_start=398 + _REDIS_GETSLOWLOG._serialized_end=455 + _REDIS_RUNCOMMAND._serialized_start=457 + _REDIS_RUNCOMMAND._serialized_end=516 + _REDIS_TASKTYPE._serialized_start=518 + _REDIS_TASKTYPE._serialized_end=589 +# @@protoc_insertion_point(module_scope) diff --git a/protos/playbooks/source_task_definitions/redis_task_pb2.pyi b/protos/playbooks/source_task_definitions/redis_task_pb2.pyi new file mode 100644 index 000000000..a47344b0f --- /dev/null +++ b/protos/playbooks/source_task_definitions/redis_task_pb2.pyi @@ -0,0 +1,122 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" +import builtins +import google.protobuf.descriptor +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import google.protobuf.wrappers_pb2 +import sys +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing_extensions.final +class Redis(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _TaskType: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _TaskTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Redis._TaskType.ValueType], builtins.type): # noqa: F821 + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + UNKNOWN: Redis._TaskType.ValueType # 0 + GET_INFO: Redis._TaskType.ValueType # 1 + GET_SLOWLOG: Redis._TaskType.ValueType # 2 + RUN_COMMAND: Redis._TaskType.ValueType # 3 + + class TaskType(_TaskType, metaclass=_TaskTypeEnumTypeWrapper): ... + UNKNOWN: Redis.TaskType.ValueType # 0 + GET_INFO: Redis.TaskType.ValueType # 1 + GET_SLOWLOG: Redis.TaskType.ValueType # 2 + RUN_COMMAND: Redis.TaskType.ValueType # 3 + + @typing_extensions.final + class GetInfo(google.protobuf.message.Message): + """Fetch server statistics via the Redis INFO command.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + SECTION_FIELD_NUMBER: builtins.int + @property + def section(self) -> google.protobuf.wrappers_pb2.StringValue: + """Optional INFO section (e.g. "memory", "clients", "stats", "replication"). + When empty, the default INFO output is returned. + """ + def __init__( + self, + *, + section: google.protobuf.wrappers_pb2.StringValue | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["section", b"section"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["section", b"section"]) -> None: ... + + @typing_extensions.final + class GetSlowlog(google.protobuf.message.Message): + """Fetch the Redis slow query log via SLOWLOG GET.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + LIMIT_FIELD_NUMBER: builtins.int + @property + def limit(self) -> google.protobuf.wrappers_pb2.UInt64Value: + """Number of entries to return. Defaults to 10 when unset.""" + def __init__( + self, + *, + limit: google.protobuf.wrappers_pb2.UInt64Value | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["limit", b"limit"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["limit", b"limit"]) -> None: ... + + @typing_extensions.final + class RunCommand(google.protobuf.message.Message): + """Run a single read-only Redis command (e.g. "DBSIZE", "GET foo", + "MEMORY USAGE key", "CLIENT LIST"). Write/administrative commands are + rejected by the connector. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + COMMAND_FIELD_NUMBER: builtins.int + @property + def command(self) -> google.protobuf.wrappers_pb2.StringValue: ... + def __init__( + self, + *, + command: google.protobuf.wrappers_pb2.StringValue | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["command", b"command"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["command", b"command"]) -> None: ... + + TYPE_FIELD_NUMBER: builtins.int + GET_INFO_FIELD_NUMBER: builtins.int + GET_SLOWLOG_FIELD_NUMBER: builtins.int + RUN_COMMAND_FIELD_NUMBER: builtins.int + type: global___Redis.TaskType.ValueType + @property + def get_info(self) -> global___Redis.GetInfo: ... + @property + def get_slowlog(self) -> global___Redis.GetSlowlog: ... + @property + def run_command(self) -> global___Redis.RunCommand: ... + def __init__( + self, + *, + type: global___Redis.TaskType.ValueType = ..., + get_info: global___Redis.GetInfo | None = ..., + get_slowlog: global___Redis.GetSlowlog | None = ..., + run_command: global___Redis.RunCommand | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["get_info", b"get_info", "get_slowlog", b"get_slowlog", "run_command", b"run_command", "task", b"task"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["get_info", b"get_info", "get_slowlog", b"get_slowlog", "run_command", b"run_command", "task", b"task", "type", b"type"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["task", b"task"]) -> typing_extensions.Literal["get_info", "get_slowlog", "run_command"] | None: ... + +global___Redis = Redis diff --git a/requirements.txt b/requirements.txt index 393550702..2b31f51a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -59,6 +59,7 @@ pytest-cov==5.0.0 pytest-django==4.8.0 pytest-html==4.1.1 pytest-mock==3.14.0 +redis==4.6.0 requests-toolbelt==1.0.0 simplejson==3.19.2 slack_sdk==3.27.1 diff --git a/web/public/integrations/redis_logo.svg b/web/public/integrations/redis_logo.svg new file mode 100644 index 000000000..72fca71ac --- /dev/null +++ b/web/public/integrations/redis_logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/web/src/constants/connectors.ts b/web/src/constants/connectors.ts index 7d9d4e485..54d86e0f6 100644 --- a/web/src/constants/connectors.ts +++ b/web/src/constants/connectors.ts @@ -25,4 +25,5 @@ export const connectors = { SMTP: "SMTP", ROOTLY: "ROOTLY", ZENDUTY: "ZENDUTY", + REDIS: "REDIS", }; diff --git a/web/src/constants/models.ts b/web/src/constants/models.ts index 95acf6193..837b787f8 100644 --- a/web/src/constants/models.ts +++ b/web/src/constants/models.ts @@ -18,4 +18,5 @@ export const models = { GRAFANA_MIMIR_PROMQL: "GRAFANA_MIMIR_PROMQL", BASH: "BASH", AZURE_WORKSPACE: "AZURE_WORKSPACE", + REDIS_CONNECTION: "REDIS_CONNECTION", }; diff --git a/web/src/constants/sources.ts b/web/src/constants/sources.ts index df38deefc..5abda8121 100644 --- a/web/src/constants/sources.ts +++ b/web/src/constants/sources.ts @@ -20,4 +20,5 @@ export const SOURCES = { GRAFANA_LOKI: "GRAFANA_LOKI", ELASTIC_SEARCH: "ELASTIC_SEARCH", SMTP: "SMTP", + REDIS: "REDIS", }; diff --git a/web/src/constants/taskTypes.ts b/web/src/constants/taskTypes.ts index c72a8be71..81343ffa3 100644 --- a/web/src/constants/taskTypes.ts +++ b/web/src/constants/taskTypes.ts @@ -28,6 +28,9 @@ export const taskTypes = { "GRAFANA_MIMIR PROMQL_METRIC_EXECUTION", AZURE_FILTER_LOG_EVENTS: "AZURE FILTER_LOG_EVENTS", POSTGRES_SQL_QUERY: "POSTGRES SQL_QUERY", + REDIS_GET_INFO: "REDIS GET_INFO", + REDIS_GET_SLOWLOG: "REDIS GET_SLOWLOG", + REDIS_RUN_COMMAND: "REDIS RUN_COMMAND", CLICKHOUSE_SQL_QUERY: "CLICKHOUSE SQL_QUERY", SQL_DATABASE_CONNECTION_SQL_QUERY: "SQL_DATABASE_CONNECTION SQL_QUERY", API_HTTP_REQUEST: "API HTTP_REQUEST", diff --git a/web/src/utils/common/cardsData.ts b/web/src/utils/common/cardsData.ts index 3424429b1..73ca85981 100644 --- a/web/src/utils/common/cardsData.ts +++ b/web/src/utils/common/cardsData.ts @@ -86,6 +86,11 @@ export const cardsData = [ desc: "Connect Postgres tables", docs: "https://docs.drdroid.io/docs/postgresql-database", }, + { + url: "/integrations/redis_logo.svg", + enum: "REDIS", + desc: "Fetch Redis INFO stats, slow query log and run read-only commands", + }, { url: "/integrations/opsgenie-logo.png", enum: "OPS_GENIE", diff --git a/web/src/utils/integrations/connectorsWithoutAssets.ts b/web/src/utils/integrations/connectorsWithoutAssets.ts index adfd5797d..9ad137295 100644 --- a/web/src/utils/integrations/connectorsWithoutAssets.ts +++ b/web/src/utils/integrations/connectorsWithoutAssets.ts @@ -11,4 +11,5 @@ export const connectorsWithoutAssets = [ connectors.SMTP, connectors.ROOTLY, connectors.ZENDUTY, + connectors.REDIS, ]; From 365ece831de1534deca2d3dd040220fb3d160b79 Mon Sep 17 00:00:00 2001 From: pushtisonawala Date: Fri, 11 Sep 2026 10:19:49 +0530 Subject: [PATCH 2/2] fix: tighten Redis run-command allow-list per review - CONFIG GET could leak the Redis password (requirepass, masterauth, ...). CONFIG is no longer allow-listed wholesale; only CONFIG GET is permitted, and its result is filtered to strip sensitive keys regardless of the request pattern (so `CONFIG GET *` can't exfiltrate them either). - DEBUG and WAIT removed entirely: DEBUG POPULATE writes keys, DEBUG RELOAD/ RESTART/CRASH-AND-RECOVER disrupt the server, and WAIT blocks the connection - none of that belongs on a "read-only" allow-list. - CLIENT, MEMORY, SLOWLOG, LATENCY, OBJECT, XINFO, COMMAND are no longer allow-listed wholesale either. Only their genuinely read-only sub-commands are permitted (CLIENT LIST/INFO/ID/GETNAME, MEMORY USAGE/STATS/DOCTOR, SLOWLOG GET/LEN, LATENCY LATEST/HISTORY/DOCTOR, OBJECT ENCODING/FREQ/ IDLETIME/REFCOUNT, XINFO STREAM/GROUPS/CONSUMERS, COMMAND COUNT/DOCS/INFO/ LIST/GETKEYS). CLIENT REPLY OFF in particular would also have wedged the connector's cached connection for every later call. - get_connection()'s cached client is now reset on any failure instead of being reused, so a blocked/broken connection doesn't poison later tasks. - Command parsing uses shlex.split() instead of str.split(), so a quoted argument (`GET "my key"`) is tokenised correctly. - Reverted the Redis connector-key map to a single full 5-key set (matching Postgres/Clickhouse) instead of enumerating subsets - RedisProcessor already tolerates blank port/password/db/ssl, and the UI always submits all five keys anyway. - Added the migration for the new Source/SourceKeyType/SourceModelType choices (0034_alter_connector_connector_type_and_more.py). - Added negative test coverage for all of the above; verified live against a real Redis 7 (with requirepass set) that CONFIG GET can no longer surface the password and that DEBUG/WAIT/CLIENT REPLY/CLIENT KILL/MEMORY PURGE/SLOWLOG RESET/LATENCY RESET are all rejected. Co-Authored-By: Claude Sonnet 5 --- ...alter_connector_connector_type_and_more.py | 33 +++++ connectors/models.py | 22 +-- executor/source_processors/redis_processor.py | 132 ++++++++++++++---- .../source_processors/test_redis_processor.py | 107 ++++++++++++++ 4 files changed, 254 insertions(+), 40 deletions(-) create mode 100644 connectors/migrations/0034_alter_connector_connector_type_and_more.py diff --git a/connectors/migrations/0034_alter_connector_connector_type_and_more.py b/connectors/migrations/0034_alter_connector_connector_type_and_more.py new file mode 100644 index 000000000..23158be07 --- /dev/null +++ b/connectors/migrations/0034_alter_connector_connector_type_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.1.13 on 2026-09-11 04:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('connectors', '0033_alter_connector_connector_type_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='connector', + name='connector_type', + field=models.IntegerField(blank=True, choices=[(0, 'UNKNOWN'), (1, 'SENTRY'), (2, 'SEGMENT'), (3, 'ELASTIC_SEARCH'), (4, 'AMPLITUDE'), (5, 'AWS_KINESIS'), (6, 'CLOUDWATCH'), (7, 'CLEVERTAP'), (8, 'RUDDERSTACK'), (9, 'MOENGAGE'), (10, 'CRIBL'), (11, 'KAFKA'), (12, 'DATADOG'), (13, 'FILEBEAT'), (14, 'LOGSTASH'), (15, 'FLUENTD'), (16, 'FLUENTBIT'), (17, 'PAGER_DUTY'), (18, 'NEW_RELIC'), (19, 'SLACK'), (20, 'HONEYBADGER'), (21, 'GOOGLE_CHAT'), (22, 'DATADOG_OAUTH'), (23, 'GCM'), (24, 'PROMETHEUS'), (25, 'ELASTIC_APM'), (26, 'VICTORIA_METRICS'), (27, 'SLACK_CONNECT'), (28, 'GRAFANA'), (29, 'CLICKHOUSE'), (30, 'DOCUMENTATION'), (31, 'POSTGRES'), (32, 'OPS_GENIE'), (33, 'EKS'), (34, 'AGENT_PROXY'), (35, 'GRAFANA_VPC'), (36, 'GITHUB'), (37, 'SQL_DATABASE_CONNECTION'), (38, 'OPEN_AI'), (39, 'REMOTE_SERVER'), (40, 'API'), (41, 'BASH'), (42, 'AZURE'), (43, 'GRAFANA_MIMIR'), (44, 'GKE'), (45, 'MS_TEAMS'), (46, 'GRAFANA_LOKI'), (47, 'KUBERNETES'), (48, 'SMTP'), (49, 'BIG_QUERY'), (50, 'ZENDUTY'), (51, 'ROOTLY'), (52, 'JIRA_CLOUD'), (53, 'ASANA'), (54, 'CONFLUENCE_CLOUD'), (55, 'CONFLUENCE_SELF_HOSTED'), (56, 'GOOGLE_DRIVE'), (57, 'NOTION'), (58, 'DRD_PROXY_AGENT'), (59, 'MONGODB'), (60, 'OPEN_SEARCH'), (61, 'LINEAR'), (62, 'JENKINS'), (63, 'GITHUB_ACTIONS'), (65, 'ARGOCD'), (66, 'REDIS')], default=0, null=True), + ), + migrations.AlterField( + model_name='connectorkey', + name='key_type', + field=models.IntegerField(blank=True, choices=[(0, 'UNKNOWN_SKT'), (1, 'SENTRY_API_KEY'), (6, 'SENTRY_ORG_SLUG'), (2, 'DATADOG_APP_KEY'), (3, 'DATADOG_API_KEY'), (15, 'DATADOG_AUTH_TOKEN'), (18, 'DATADOG_API_DOMAIN'), (4, 'NEWRELIC_API_KEY'), (5, 'NEWRELIC_APP_ID'), (7, 'NEWRELIC_QUERY_KEY'), (19, 'NEWRELIC_API_DOMAIN'), (8, 'SLACK_BOT_AUTH_TOKEN'), (9, 'SLACK_CHANNEL_ID'), (46, 'SLACK_APP_ID'), (10, 'HONEYBADGER_USERNAME'), (11, 'HONEYBADGER_PASSWORD'), (12, 'HONEYBADGER_PROJECT_ID'), (13, 'AWS_ACCESS_KEY'), (14, 'AWS_SECRET_KEY'), (20, 'AWS_REGION'), (23, 'AWS_ASSUMED_ROLE_ARN'), (40, 'EKS_ROLE_ARN'), (93, 'AWS_DRD_CLOUD_ROLE_ARN'), (16, 'GOOGLE_CHAT_BOT_OAUTH_TOKEN'), (17, 'GOOGLE_CHAT_BOT_SPACES'), (21, 'GRAFANA_HOST'), (22, 'GRAFANA_API_KEY'), (24, 'CLICKHOUSE_INTERFACE'), (25, 'CLICKHOUSE_HOST'), (26, 'CLICKHOUSE_PORT'), (27, 'CLICKHOUSE_USER'), (28, 'CLICKHOUSE_PASSWORD'), (29, 'GCM_PROJECT_ID'), (30, 'GCM_PRIVATE_KEY'), (31, 'GCM_CLIENT_EMAIL'), (32, 'GCM_TOKEN_URI'), (33, 'POSTGRES_HOST'), (34, 'POSTGRES_USER'), (35, 'POSTGRES_PASSWORD'), (36, 'POSTGRES_PORT'), (37, 'POSTGRES_DATABASE'), (38, 'POSTGRES_OPTIONS'), (39, 'SQL_DATABASE_CONNECTION_STRING_URI'), (41, 'PAGER_DUTY_API_KEY'), (63, 'PAGER_DUTY_CONFIGURED_EMAIL'), (42, 'OPS_GENIE_API_KEY'), (43, 'AGENT_PROXY_HOST'), (44, 'AGENT_PROXY_API_KEY'), (45, 'GITHUB_TOKEN'), (47, 'OPEN_AI_API_KEY'), (49, 'REMOTE_SERVER_PEM'), (50, 'REMOTE_SERVER_USER'), (51, 'REMOTE_SERVER_HOST'), (52, 'REMOTE_SERVER_PASSWORD'), (53, 'MIMIR_HOST'), (54, 'X_SCOPE_ORG_ID'), (55, 'SSL_VERIFY'), (56, 'AZURE_SUBSCRIPTION_ID'), (57, 'AZURE_TENANT_ID'), (58, 'AZURE_CLIENT_ID'), (59, 'AZURE_CLIENT_SECRET'), (60, 'GKE_PROJECT_ID'), (61, 'GKE_SERVICE_ACCOUNT_JSON'), (62, 'MS_TEAMS_CONNECTOR_WEBHOOK_URL'), (64, 'ELASTIC_SEARCH_PROTOCOL'), (65, 'ELASTIC_SEARCH_HOST'), (66, 'ELASTIC_SEARCH_PORT'), (67, 'ELASTIC_SEARCH_API_KEY_ID'), (68, 'ELASTIC_SEARCH_API_KEY'), (69, 'GRAFANA_LOKI_PROTOCOL'), (70, 'GRAFANA_LOKI_HOST'), (71, 'GRAFANA_LOKI_PORT'), (72, 'KUBERNETES_CLUSTER_NAME'), (73, 'KUBERNETES_CLUSTER_API_SERVER'), (74, 'KUBERNETES_CLUSTER_TOKEN'), (75, 'KUBERNETES_CLUSTER_CERTIFICATE_AUTHORITY_DATA'), (76, 'KUBERNETES_CLUSTER_CERTIFICATE_AUTHORITY_PATH'), (77, 'SMTP_HOST'), (78, 'SMTP_PORT'), (79, 'SMTP_USER'), (80, 'SMTP_PASSWORD'), (81, 'BIG_QUERY_PROJECT_ID'), (82, 'BIG_QUERY_SERVICE_ACCOUNT_JSON'), (83, 'ZENDUTY_API_KEY'), (84, 'ROOTLY_API_KEY'), (85, 'JIRA_CLOUD_API_KEY'), (86, 'ASANA_ACCESS_TOKEN'), (87, 'CONFLUENCE_CLOUD_API_KEY'), (88, 'NOTION_API_KEY'), (89, 'DRD_PROXY_AGENT_HOST'), (90, 'DRD_PROXY_AGENT_API_TOKEN'), (91, 'DRD_PROXY_AGENT_API_KEY'), (92, 'MONGODB_CONNECTION_STRING'), (94, 'OPEN_SEARCH_PROTOCOL'), (95, 'OPEN_SEARCH_HOST'), (96, 'OPEN_SEARCH_PORT'), (97, 'OPEN_SEARCH_USERNAME'), (98, 'OPEN_SEARCH_PASSWORD'), (99, 'LINEAR_API_KEY'), (100, 'JENKINS_URL'), (101, 'JENKINS_USERNAME'), (102, 'JENKINS_API_TOKEN'), (103, 'GITHUB_ACTIONS_TOKEN'), (110, 'JIRA_EMAIL'), (109, 'JIRA_DOMAIN'), (106, 'ARGOCD_SERVER'), (107, 'ARGOCD_TOKEN'), (111, 'REDIS_HOST'), (112, 'REDIS_PORT'), (113, 'REDIS_PASSWORD'), (114, 'REDIS_DB'), (115, 'REDIS_SSL_ENABLED')], default=0, null=True), + ), + migrations.AlterField( + model_name='connectormetadatamodelstore', + name='connector_type', + field=models.IntegerField(choices=[(0, 'UNKNOWN'), (1, 'SENTRY'), (2, 'SEGMENT'), (3, 'ELASTIC_SEARCH'), (4, 'AMPLITUDE'), (5, 'AWS_KINESIS'), (6, 'CLOUDWATCH'), (7, 'CLEVERTAP'), (8, 'RUDDERSTACK'), (9, 'MOENGAGE'), (10, 'CRIBL'), (11, 'KAFKA'), (12, 'DATADOG'), (13, 'FILEBEAT'), (14, 'LOGSTASH'), (15, 'FLUENTD'), (16, 'FLUENTBIT'), (17, 'PAGER_DUTY'), (18, 'NEW_RELIC'), (19, 'SLACK'), (20, 'HONEYBADGER'), (21, 'GOOGLE_CHAT'), (22, 'DATADOG_OAUTH'), (23, 'GCM'), (24, 'PROMETHEUS'), (25, 'ELASTIC_APM'), (26, 'VICTORIA_METRICS'), (27, 'SLACK_CONNECT'), (28, 'GRAFANA'), (29, 'CLICKHOUSE'), (30, 'DOCUMENTATION'), (31, 'POSTGRES'), (32, 'OPS_GENIE'), (33, 'EKS'), (34, 'AGENT_PROXY'), (35, 'GRAFANA_VPC'), (36, 'GITHUB'), (37, 'SQL_DATABASE_CONNECTION'), (38, 'OPEN_AI'), (39, 'REMOTE_SERVER'), (40, 'API'), (41, 'BASH'), (42, 'AZURE'), (43, 'GRAFANA_MIMIR'), (44, 'GKE'), (45, 'MS_TEAMS'), (46, 'GRAFANA_LOKI'), (47, 'KUBERNETES'), (48, 'SMTP'), (49, 'BIG_QUERY'), (50, 'ZENDUTY'), (51, 'ROOTLY'), (52, 'JIRA_CLOUD'), (53, 'ASANA'), (54, 'CONFLUENCE_CLOUD'), (55, 'CONFLUENCE_SELF_HOSTED'), (56, 'GOOGLE_DRIVE'), (57, 'NOTION'), (58, 'DRD_PROXY_AGENT'), (59, 'MONGODB'), (60, 'OPEN_SEARCH'), (61, 'LINEAR'), (62, 'JENKINS'), (63, 'GITHUB_ACTIONS'), (65, 'ARGOCD'), (66, 'REDIS')], db_index=True, default=0), + ), + migrations.AlterField( + model_name='connectormetadatamodelstore', + name='model_type', + field=models.IntegerField(choices=[(0, 'UNKNOWN_MT'), (1, 'NEW_RELIC_POLICY'), (2, 'NEW_RELIC_CONDITION'), (3, 'NEW_RELIC_ENTITY'), (4, 'NEW_RELIC_ENTITY_DASHBOARD'), (5, 'NEW_RELIC_ENTITY_APPLICATION'), (6, 'NEW_RELIC_NRQL'), (101, 'DATADOG_MONITOR'), (102, 'DATADOG_DASHBOARD'), (103, 'DATADOG_LIVE_INTEGRATION_AWS'), (104, 'DATADOG_LIVE_INTEGRATION_AWS_LOG'), (105, 'DATADOG_LIVE_INTEGRATION_AZURE'), (106, 'DATADOG_LIVE_INTEGRATION_CLOUDFLARE'), (107, 'DATADOG_LIVE_INTEGRATION_FASTLY'), (108, 'DATADOG_LIVE_INTEGRATION_GCP'), (109, 'DATADOG_LIVE_INTEGRATION_CONFLUENT'), (110, 'DATADOG_SERVICE'), (111, 'DATADOG_METRIC'), (112, 'DATADOG_QUERY'), (201, 'CLOUDWATCH_METRIC'), (202, 'CLOUDWATCH_LOG_GROUP'), (203, 'CLOUDWATCH_LOG_GROUP_QUERY'), (204, 'CLOUDWATCH_ALARMS'), (205, 'RDS_INSTANCES'), (301, 'GRAFANA_DATASOURCE'), (302, 'GRAFANA_DASHBOARD'), (303, 'GRAFANA_TARGET_METRIC_PROMQL'), (304, 'GRAFANA_PROMETHEUS_DATASOURCE'), (305, 'GRAFANA_ALERT_RULE'), (401, 'CLICKHOUSE_DATABASE'), (501, 'SLACK_CHANNEL'), (601, 'MARKDOWN'), (602, 'IFRAME'), (701, 'POSTGRES_QUERY'), (801, 'EKS_CLUSTER'), (901, 'SQL_DATABASE_CONNECTION_RAW_QUERY'), (1001, 'AZURE_WORKSPACE'), (1100, 'SSH_SERVER'), (1201, 'GRAFANA_MIMIR_PROMQL'), (1301, 'GKE_CLUSTER'), (1401, 'MS_TEAMS_CHANNEL'), (1501, 'PAGERDUTY_INCIDENT'), (1601, 'ELASTIC_SEARCH_INDEX'), (1701, 'GCM_METRIC'), (1801, 'ZENDUTY_INCIDENT'), (1901, 'ROOTLY_INCIDENT'), (2001, 'OPS_GENIE_ESCALATION'), (2002, 'OPS_GENIE_TEAM'), (2101, 'GOOGLE_CHAT_SPACE'), (2201, 'MONGODB_DATABASE'), (2202, 'MONGODB_COLLECTION'), (2301, 'OPEN_SEARCH_INDEX'), (2401, 'ASANA_PROJECT'), (2701, 'JIRA_PROJECT'), (2702, 'JIRA_USER'), (2801, 'ARGOCD_APPS'), (2901, 'REDIS_CONNECTION')], db_index=True, default=0), + ), + ] diff --git a/connectors/models.py b/connectors/models.py index 068da8b43..2ccd6d460 100644 --- a/connectors/models.py +++ b/connectors/models.py @@ -346,16 +346,18 @@ ] ], Source.REDIS: [ - # Only host is required; port/password/db/ssl are optional. Each accepted - # combination is listed because connector validation is an exact set match. - [SourceKeyType.REDIS_HOST], - [SourceKeyType.REDIS_HOST, SourceKeyType.REDIS_PORT], - [SourceKeyType.REDIS_HOST, SourceKeyType.REDIS_PORT, SourceKeyType.REDIS_DB], - [SourceKeyType.REDIS_HOST, SourceKeyType.REDIS_PORT, SourceKeyType.REDIS_PASSWORD], - [SourceKeyType.REDIS_HOST, SourceKeyType.REDIS_PORT, SourceKeyType.REDIS_PASSWORD, - SourceKeyType.REDIS_DB], - [SourceKeyType.REDIS_HOST, SourceKeyType.REDIS_PORT, SourceKeyType.REDIS_PASSWORD, - SourceKeyType.REDIS_DB, SourceKeyType.REDIS_SSL_ENABLED], + # A single full-key set, same as Postgres/Clickhouse. The UI always + # submits all five keys (empty string for the ones left blank), and + # RedisProcessor.__init__ already treats blank port/password/db/ssl + # as "use the default" - so this covers the common case without the + # combinatorial subset list validation would otherwise need. + [ + SourceKeyType.REDIS_HOST, + SourceKeyType.REDIS_PORT, + SourceKeyType.REDIS_PASSWORD, + SourceKeyType.REDIS_DB, + SourceKeyType.REDIS_SSL_ENABLED, + ] ], } diff --git a/executor/source_processors/redis_processor.py b/executor/source_processors/redis_processor.py index 77d93503f..6913f7d40 100644 --- a/executor/source_processors/redis_processor.py +++ b/executor/source_processors/redis_processor.py @@ -1,4 +1,5 @@ import logging +import shlex import redis @@ -6,36 +7,61 @@ logger = logging.getLogger(__name__) -# Commands the connector is allowed to run through the "Run Command" task. -# Restricted to read-only / introspection commands so a playbook can never -# mutate or administer the target Redis instance. +# Commands the connector is allowed to run through the "Run Command" task, +# with no sub-command to worry about. Restricted to read-only / introspection +# commands so a playbook can never mutate or administer the target instance. +# +# Not included on purpose (and not allow-listed below either): +# DEBUG - DEBUG POPULATE writes keys, DEBUG RELOAD/RESTART/CRASH-AND-RECOVER +# disrupt the server; DEBUG is enabled by default on Redis < 7. +# WAIT - blocks the connection until enough replicas ack or the timeout +# hits; not read-only in any useful sense here. REDIS_READ_ONLY_COMMANDS = { 'GET', 'MGET', 'STRLEN', 'GETRANGE', 'SUBSTR', 'EXISTS', 'TYPE', 'TTL', 'PTTL', 'EXPIRETIME', 'PEXPIRETIME', - 'KEYS', 'SCAN', 'RANDOMKEY', 'DBSIZE', 'OBJECT', + 'KEYS', 'SCAN', 'RANDOMKEY', 'DBSIZE', 'HGET', 'HMGET', 'HGETALL', 'HKEYS', 'HVALS', 'HLEN', 'HEXISTS', 'HSTRLEN', 'HSCAN', 'LLEN', 'LRANGE', 'LINDEX', 'LPOS', 'SCARD', 'SISMEMBER', 'SMISMEMBER', 'SMEMBERS', 'SRANDMEMBER', 'SSCAN', 'SINTER', 'SUNION', 'SDIFF', 'ZCARD', 'ZCOUNT', 'ZRANGE', 'ZRANGEBYSCORE', 'ZRANGEBYLEX', 'ZRANK', 'ZREVRANK', 'ZSCORE', 'ZMSCORE', 'ZSCAN', - 'XLEN', 'XRANGE', 'XREVRANGE', 'XINFO', 'XPENDING', + 'XLEN', 'XRANGE', 'XREVRANGE', 'XPENDING', 'BITCOUNT', 'BITPOS', 'GETBIT', 'PFCOUNT', 'GEOPOS', 'GEODIST', 'GEOSEARCH', - 'INFO', 'MEMORY', 'CLIENT', 'CONFIG', 'SLOWLOG', 'LATENCY', 'COMMAND', - 'PING', 'ECHO', 'TIME', 'LOLWUT', 'DEBUG', 'LASTSAVE', 'WAIT', + 'PING', 'ECHO', 'TIME', 'LOLWUT', 'LASTSAVE', + 'INFO', } -# Sub-commands that must be blocked even though their parent command is -# otherwise read-only (e.g. CONFIG SET, MEMORY PURGE, CLIENT KILL). -REDIS_BLOCKED_SUBCOMMANDS = { - ('CONFIG', 'SET'), ('CONFIG', 'RESETSTAT'), ('CONFIG', 'REWRITE'), - ('MEMORY', 'PURGE'), ('MEMORY', 'MALLOC-STATS'), - ('CLIENT', 'KILL'), ('CLIENT', 'SETNAME'), ('CLIENT', 'PAUSE'), ('CLIENT', 'UNPAUSE'), - ('CLIENT', 'NO-EVICT'), ('CLIENT', 'NO-TOUCH'), ('CLIENT', 'SETINFO'), - ('SLOWLOG', 'RESET'), - ('LATENCY', 'RESET'), - ('DEBUG', 'SLEEP'), ('DEBUG', 'SEGFAULT'), ('DEBUG', 'PANIC'), ('DEBUG', 'QUICKLIST-PACKED-THRESHOLD'), - ('DEBUG', 'SET-ACTIVE-EXPIRE'), ('DEBUG', 'JMAP'), ('DEBUG', 'CHANGE-REPL-ID'), +# Commands that also carry dangerous sub-commands (mutation, admin, or +# connection-disrupting actions) are NOT allow-listed wholesale. Only the +# sub-commands below - which are read-only introspection - are permitted. +# Anything not listed here for these commands (CLIENT KILL/PAUSE/REPLY/ +# TRACKING/SETNAME, MEMORY PURGE, CONFIG SET/RESETSTAT/REWRITE, SLOWLOG RESET, +# LATENCY RESET, ...) is rejected. +REDIS_ALLOWED_SUBCOMMANDS = { + 'CLIENT': {'LIST', 'INFO', 'ID', 'GETNAME'}, + 'MEMORY': {'USAGE', 'STATS', 'DOCTOR'}, + 'SLOWLOG': {'GET', 'LEN'}, + 'LATENCY': {'LATEST', 'HISTORY', 'DOCTOR'}, + 'OBJECT': {'ENCODING', 'FREQ', 'IDLETIME', 'REFCOUNT'}, + 'XINFO': {'STREAM', 'GROUPS', 'CONSUMERS'}, + 'COMMAND': {'COUNT', 'DOCS', 'INFO', 'LIST', 'GETKEYS'}, + # CONFIG GET can return secrets (requirepass, masterauth, ...); the + # result is filtered in run_command() below rather than trusting the + # request pattern, since a glob like `CONFIG GET *` would otherwise + # still return them. + 'CONFIG': {'GET'}, } +# Config keys CONFIG GET must never return, plus a substring fallback so a +# future/unknown Redis config option that looks like a credential is caught +# even if it isn't in the explicit set. +_SENSITIVE_CONFIG_KEYS = { + 'requirepass', 'masterauth', 'masteruser', 'aclfile', + 'tls-key-file', 'tls-key-file-pass', 'tls-client-key-file', 'tls-client-key-file-pass', + 'tls-cert-file', 'tls-ca-cert-file', 'tls-ca-cert-dir', + 'dir', 'dbfilename', +} +_SENSITIVE_CONFIG_KEY_MARKERS = ('pass', 'auth', 'secret') + class RedisCommandNotAllowed(Exception): pass @@ -49,6 +75,29 @@ def _decode(value): return value +def _is_sensitive_config_key(key) -> bool: + key_lower = str(key).lower() + if key_lower in _SENSITIVE_CONFIG_KEYS: + return True + return any(marker in key_lower for marker in _SENSITIVE_CONFIG_KEY_MARKERS) + + +def _filter_config_result(result): + """Strip sensitive keys from a CONFIG GET result, whatever shape the + redis-py version in use returns it as (dict, or the flat + [key, value, key, value, ...] list execute_command() gives back).""" + if isinstance(result, dict): + return {k: v for k, v in result.items() if not _is_sensitive_config_key(k)} + if isinstance(result, (list, tuple)): + filtered = [] + for i in range(0, len(result) - 1, 2): + key, value = result[i], result[i + 1] + if not _is_sensitive_config_key(key): + filtered.extend([key, value]) + return filtered + return result + + class RedisProcessor(Processor): client = None @@ -63,7 +112,7 @@ def __init__(self, host, port=6379, password=None, db=0, ssl_enabled=False): } if password: self.config['password'] = password - if str(ssl_enabled).lower() == 'true' or ssl_enabled is True: + if str(ssl_enabled).lower() == 'true': self.config['ssl'] = True def get_connection(self): @@ -73,6 +122,7 @@ def get_connection(self): return self.client except Exception as e: logger.error(f"Exception occurred while creating redis connection with error: {e}") + self.client = None raise e def test_connection(self): @@ -82,6 +132,7 @@ def test_connection(self): return True except Exception as e: logger.error(f"Exception occurred while testing redis connection with error: {e}") + self.client = None raise e def get_info(self, section=None): @@ -92,15 +143,18 @@ def get_info(self, section=None): return client.info() except Exception as e: logger.error(f"Exception occurred while fetching redis INFO with error: {e}") + self.client = None raise e def get_slowlog(self, limit=10): try: client = self.get_connection() - limit = int(limit) if limit else 10 entries = client.slowlog_get(limit) normalised = [] for entry in entries: + # Even with decode_responses=True, slowlog_get() can still + # hand back bytes for these fields (verified against the + # redis==4.6.0 pinned in requirements.txt) - decode explicitly. normalised.append({ 'id': _decode(entry.get('id')), 'start_time': _decode(entry.get('start_time')), @@ -112,6 +166,7 @@ def get_slowlog(self, limit=10): return normalised except Exception as e: logger.error(f"Exception occurred while fetching redis SLOWLOG with error: {e}") + self.client = None raise e @staticmethod @@ -119,22 +174,39 @@ def _validate_read_only(tokens): if not tokens: raise RedisCommandNotAllowed("Empty command") command = tokens[0].upper() - if command not in REDIS_READ_ONLY_COMMANDS: - raise RedisCommandNotAllowed( - f"Command '{command}' is not permitted. Only read-only commands can be run through this connector." - ) - if len(tokens) > 1 and (command, tokens[1].upper()) in REDIS_BLOCKED_SUBCOMMANDS: - raise RedisCommandNotAllowed(f"Command '{command} {tokens[1].upper()}' is not permitted.") - return command + subcommand = tokens[1].upper() if len(tokens) > 1 else None + + if command in REDIS_ALLOWED_SUBCOMMANDS: + if command == 'COMMAND' and subcommand is None: + return command # bare COMMAND lists the command table; read-only + if subcommand not in REDIS_ALLOWED_SUBCOMMANDS[command]: + allowed = ', '.join(sorted(REDIS_ALLOWED_SUBCOMMANDS[command])) + attempted = f"{command} {subcommand}" if subcommand else command + raise RedisCommandNotAllowed( + f"'{attempted}' is not permitted. " + f"Allowed {command} sub-commands: {allowed}." + ) + return command + + if command in REDIS_READ_ONLY_COMMANDS: + return command + + raise RedisCommandNotAllowed( + f"Command '{command}' is not permitted. Only read-only commands can be run through this connector." + ) def run_command(self, command_str): try: - tokens = command_str.split() - self._validate_read_only(tokens) + tokens = shlex.split(command_str) + command = self._validate_read_only(tokens) client = self.get_connection() - return client.execute_command(*tokens) + result = client.execute_command(*tokens) + if command == 'CONFIG': + result = _filter_config_result(result) + return result except RedisCommandNotAllowed: raise except Exception as e: logger.error(f"Exception occurred while running redis command with error: {e}") + self.client = None raise e diff --git a/executor/tests/source_processors/test_redis_processor.py b/executor/tests/source_processors/test_redis_processor.py index a6f70488d..9e00da794 100644 --- a/executor/tests/source_processors/test_redis_processor.py +++ b/executor/tests/source_processors/test_redis_processor.py @@ -108,3 +108,110 @@ def test_run_command_allows_config_get(self): mock_redis.return_value = mock_client result = RedisProcessor(host='localhost').run_command('CONFIG GET maxmemory') self.assertEqual(result, ['maxmemory', '0']) + + def test_run_command_config_get_filters_password(self): + # CONFIG GET requirepass must never surface the password, even though + # GET is the allowed sub-command - the result is filtered, not the request. + with mock.patch('redis.Redis') as mock_redis: + mock_client = mock.Mock() + mock_client.execute_command.return_value = ['requirepass', 'super-secret'] + mock_redis.return_value = mock_client + result = RedisProcessor(host='localhost').run_command('CONFIG GET requirepass') + self.assertEqual(result, []) + + def test_run_command_config_get_wildcard_filters_password_only(self): + with mock.patch('redis.Redis') as mock_redis: + mock_client = mock.Mock() + mock_client.execute_command.return_value = [ + 'maxmemory', '0', 'requirepass', 'super-secret', 'masterauth', 'also-secret', + ] + mock_redis.return_value = mock_client + result = RedisProcessor(host='localhost').run_command('CONFIG GET *') + self.assertEqual(result, ['maxmemory', '0']) + + def test_run_command_config_get_filters_dict_shape(self): + # Some redis-py versions/paths return CONFIG GET as a dict rather than + # a flat list - filtering must handle both. + with mock.patch('redis.Redis') as mock_redis: + mock_client = mock.Mock() + mock_client.execute_command.return_value = {'requirepass': 'secret', 'maxmemory': '0'} + mock_redis.return_value = mock_client + result = RedisProcessor(host='localhost').run_command('CONFIG GET *') + self.assertEqual(result, {'maxmemory': '0'}) + + def test_run_command_blocks_debug(self): + # DEBUG POPULATE/RELOAD/RESTART are destructive/disruptive; DEBUG is + # not allow-listed at all rather than trying to enumerate every + # dangerous sub-command. + with self.assertRaises(RedisCommandNotAllowed): + RedisProcessor(host='localhost').run_command('DEBUG POPULATE 1000') + + def test_run_command_blocks_wait(self): + with self.assertRaises(RedisCommandNotAllowed): + RedisProcessor(host='localhost').run_command('WAIT 1 0') + + def test_run_command_blocks_client_reply(self): + # CLIENT REPLY OFF would also wedge the cached connection for every + # later command on this processor instance. + with self.assertRaises(RedisCommandNotAllowed): + RedisProcessor(host='localhost').run_command('CLIENT REPLY OFF') + + def test_run_command_blocks_client_kill(self): + with self.assertRaises(RedisCommandNotAllowed): + RedisProcessor(host='localhost').run_command('CLIENT KILL ID 1') + + def test_run_command_allows_client_list(self): + with mock.patch('redis.Redis') as mock_redis: + mock_client = mock.Mock() + mock_client.execute_command.return_value = 'id=1 addr=127.0.0.1:0' + mock_redis.return_value = mock_client + result = RedisProcessor(host='localhost').run_command('CLIENT LIST') + self.assertEqual(result, 'id=1 addr=127.0.0.1:0') + + def test_run_command_blocks_memory_purge(self): + with self.assertRaises(RedisCommandNotAllowed): + RedisProcessor(host='localhost').run_command('MEMORY PURGE') + + def test_run_command_allows_memory_usage(self): + with mock.patch('redis.Redis') as mock_redis: + mock_client = mock.Mock() + mock_client.execute_command.return_value = 56 + mock_redis.return_value = mock_client + result = RedisProcessor(host='localhost').run_command('MEMORY USAGE mykey') + self.assertEqual(result, 56) + + def test_run_command_blocks_slowlog_reset(self): + with self.assertRaises(RedisCommandNotAllowed): + RedisProcessor(host='localhost').run_command('SLOWLOG RESET') + + def test_run_command_allows_bare_command(self): + # Bare COMMAND (no sub-command) lists the command table - read-only. + with mock.patch('redis.Redis') as mock_redis: + mock_client = mock.Mock() + mock_client.execute_command.return_value = [] + mock_redis.return_value = mock_client + RedisProcessor(host='localhost').run_command('COMMAND') + mock_client.execute_command.assert_called_once_with('COMMAND') + + def test_run_command_blocks_command_getkeysandflags(self): + with self.assertRaises(RedisCommandNotAllowed): + RedisProcessor(host='localhost').run_command('COMMAND GETKEYSANDFLAGS SET foo bar') + + def test_run_command_handles_quoted_argument(self): + with mock.patch('redis.Redis') as mock_redis: + mock_client = mock.Mock() + mock_client.execute_command.return_value = 'bar' + mock_redis.return_value = mock_client + RedisProcessor(host='localhost').run_command('GET "my key"') + mock_client.execute_command.assert_called_once_with('GET', 'my key') + + def test_run_command_resets_client_on_failure(self): + # A broken/blocked connection must not be reused by the next call. + with mock.patch('redis.Redis') as mock_redis: + mock_client = mock.Mock() + mock_client.execute_command.side_effect = TimeoutError("timed out") + mock_redis.return_value = mock_client + processor = RedisProcessor(host='localhost') + with self.assertRaises(TimeoutError): + processor.run_command('DBSIZE') + self.assertIsNone(processor.client)