Skip to content

Commit

Permalink
Merge pull request #492 from airbnb/ryandeivert-apps-class-factory
Browse files Browse the repository at this point in the history
[apps] using factory-style class decorator for registering StreamAlertApps
  • Loading branch information
ryandeivert authored Nov 20, 2017
2 parents 04ac9c1 + fc48a8e commit 0e45f5a
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 60 deletions.
62 changes: 34 additions & 28 deletions app_integrations/apps/app_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,42 +27,48 @@
from app_integrations.batcher import Batcher
from app_integrations.exceptions import AppIntegrationException, AppIntegrationConfigError

STREAMALERT_APPS = {}

class StreamAlertApp(object):
"""Class to be used as a decorator to register all AppIntegration subclasses"""
_apps = {}

def app(subclass):
"""Class decorator to register all AppIntegration classes.
def __new__(cls, app):
StreamAlertApp._apps[app.type()] = app
return app

This should be applied to any subclass for the AppIntegration as '@app'
@classmethod
def get_app(cls, config, init=True):
"""Return the proper app integration class for this service
Args:
subclass (AppIntegration): The subclass of AppIntegration that should
be stored within the STREAMALERT_APPS mapping
"""
STREAMALERT_APPS[subclass.type()] = subclass
return subclass
Args:
config (AppConfig): Loaded configuration with service, etc
init (bool): Whether or not this class should be instantiated with
the config that has been passed in
Returns:
AppIntegration: Subclass of AppIntegration corresponding to the config
"""
try:
if not init:
return cls._apps[config['type']]

def get_app(config, init=True):
"""Return the proper app integration for this service
return cls._apps[config['type']](config)
except KeyError:
if 'type' not in config:
raise AppIntegrationException('The \'type\' is not defined in the config.')
else:
raise AppIntegrationException('App integration does not exist for type: '
'{}'.format(config['type']))

Args:
config (AppConfig): Loaded configuration with service, etc
@classmethod
def get_all_apps(cls):
"""Return a copy of the cache containing all of the app subclasses
Returns:
AppIntegration: Subclass of AppIntegration
"""
try:
if not init:
return STREAMALERT_APPS[config['type']]

return STREAMALERT_APPS[config['type']](config)
except KeyError:
if 'type' not in config:
raise AppIntegrationException('The \'type\' is not defined in the config.')
else:
raise AppIntegrationException('App integration does not exist for type: '
'{}'.format(config['type']))
Returns:
dict: Cached dictionary of all registered StreamAlertApps where
the key is the app type and the value is the class object
"""
return cls._apps.copy()


def safe_timeout(func):
Expand Down
4 changes: 2 additions & 2 deletions app_integrations/apps/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
from requests.exceptions import ConnectionError

from app_integrations import LOGGER
from app_integrations.apps.app_base import app, AppIntegration, safe_timeout
from app_integrations.apps.app_base import StreamAlertApp, AppIntegration, safe_timeout


@app
@StreamAlertApp
class BoxApp(AppIntegration):
"""BoxApp integration"""
_MAX_CHUNK_SIZE = 500
Expand Down
6 changes: 3 additions & 3 deletions app_integrations/apps/duo.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import requests

from app_integrations import LOGGER
from app_integrations.apps.app_base import app, AppIntegration
from app_integrations.apps.app_base import StreamAlertApp, AppIntegration


class DuoApp(AppIntegration):
Expand Down Expand Up @@ -178,7 +178,7 @@ def _sleep_seconds(self):
return abs((self._poll_count % 2) - 1) * 60


@app
@StreamAlertApp
class DuoAuthApp(DuoApp):
"""Duo authentication log app integration"""

Expand All @@ -198,7 +198,7 @@ def _endpoint(cls):
return cls._DUO_AUTH_LOGS_ENDPOINT


@app
@StreamAlertApp
class DuoAdminApp(DuoApp):
"""Duo administrator log app integration"""

Expand Down
22 changes: 11 additions & 11 deletions app_integrations/apps/gsuite.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from oauth2client.service_account import ServiceAccountCredentials

from app_integrations import LOGGER
from app_integrations.apps.app_base import app, AppIntegration
from app_integrations.apps.app_base import StreamAlertApp, AppIntegration


class GSuiteReportsApp(AppIntegration):
Expand Down Expand Up @@ -174,7 +174,7 @@ def _sleep_seconds(self):
return 0


@app
@StreamAlertApp
class GSuiteAdminReports(GSuiteReportsApp):
"""G Suite Admin Activity Report app integration"""

Expand All @@ -183,7 +183,7 @@ def _type(cls):
return 'admin'


@app
@StreamAlertApp
class GSuiteCalendarReports(GSuiteReportsApp):
"""G Suite Calendar Activity Report app integration"""

Expand All @@ -192,7 +192,7 @@ def _type(cls):
return 'calendar'


@app
@StreamAlertApp
class GSuiteDriveReports(GSuiteReportsApp):
"""G Suite Drive Activity Report app integration"""

Expand All @@ -201,7 +201,7 @@ def _type(cls):
return 'drive'


@app
@StreamAlertApp
class GSuiteGroupsReports(GSuiteReportsApp):
"""G Suite Groups Activity Report app integration"""

Expand All @@ -210,7 +210,7 @@ def _type(cls):
return 'groups'


@app
@StreamAlertApp
class GSuiteGPlusReports(GSuiteReportsApp):
"""G Suite Google Plus Activity Report app integration"""

Expand All @@ -219,7 +219,7 @@ def _type(cls):
return 'gplus'


@app
@StreamAlertApp
class GSuiteLoginReports(GSuiteReportsApp):
"""G Suite Login Activity Report app integration"""

Expand All @@ -228,7 +228,7 @@ def _type(cls):
return 'login'


@app
@StreamAlertApp
class GSuiteMobileReports(GSuiteReportsApp):
"""G Suite Mobile Activity Report app integration"""

Expand All @@ -237,7 +237,7 @@ def _type(cls):
return 'mobile'


@app
@StreamAlertApp
class GSuiteRulesReports(GSuiteReportsApp):
"""G Suite Rules Activity Report app integration"""

Expand All @@ -246,7 +246,7 @@ def _type(cls):
return 'rules'


@app
@StreamAlertApp
class GSuiteSAMLReports(GSuiteReportsApp):
"""G Suite SAML Activity Report app integration"""

Expand All @@ -255,7 +255,7 @@ def _type(cls):
return 'saml'


@app
@StreamAlertApp
class GSuiteTokenReports(GSuiteReportsApp):
"""G Suite Token Activity Report app integration"""

Expand Down
4 changes: 2 additions & 2 deletions app_integrations/apps/onelogin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
import re

from app_integrations import LOGGER
from app_integrations.apps.app_base import app, AppIntegration
from app_integrations.apps.app_base import StreamAlertApp, AppIntegration


@app
@StreamAlertApp
class OneLoginApp(AppIntegration):
"""OneLogin StreamAlert App"""
_ONELOGIN_EVENTS_URL = 'https://api.{}.onelogin.com/api/1/events'
Expand Down
4 changes: 2 additions & 2 deletions app_integrations/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from botocore.exceptions import ClientError

from app_integrations import LOGGER
from app_integrations.apps.app_base import get_app
from app_integrations.apps.app_base import StreamAlertApp
from app_integrations.exceptions import AppIntegrationConfigError, AppIntegrationStateError

AWS_RATE_RE = re.compile(r'^rate\(((1) (minute|hour|day)|'
Expand Down Expand Up @@ -279,7 +279,7 @@ def _determine_last_time(self):

# Request the date format from the app since some services expect different types
# Using init=False will return the class without instantiating it
date_format = get_app(self, init=False).date_formatter()
date_format = StreamAlertApp.get_app(self, init=False).date_formatter()
if date_format:
self.last_timestamp = datetime.utcfromtimestamp(time_delta).strftime(date_format)
else:
Expand Down
4 changes: 2 additions & 2 deletions app_integrations/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
from app_integrations.apps.app_base import get_app
from app_integrations.apps.app_base import StreamAlertApp
from app_integrations.config import AppConfig


Expand All @@ -33,7 +33,7 @@ def handler(event, context):
config = AppConfig.load_config(context, event)

# The config specifies what app this function is supposed to run
app = get_app(config)
app = StreamAlertApp.get_app(config)

# Run the gather operation
app.gather()
Expand Down
2 changes: 2 additions & 0 deletions docs/source/app-development.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ to outline what methods from the base ``AppIntegration`` class must be implement
:name: app_integrations/apps/box.py
# app_integrations/apps/box.py
from app_integrations.apps.app_base import StreamAlertApp, AppIntegration
# @StreamAlertApp
class BoxApp(AppIntegration):
"""Box StreamAlert App"""
Expand Down
8 changes: 6 additions & 2 deletions manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from stream_alert_cli.logger import LOGGER_CLI
from stream_alert_cli.runner import cli_runner
from app_integrations.config import AWS_RATE_RE
from app_integrations.apps.app_base import STREAMALERT_APPS
from app_integrations.apps.app_base import StreamAlertApp


class UniqueSetAction(Action):
Expand Down Expand Up @@ -242,7 +242,11 @@ def _add_app_integration_subparser(subparsers):
app_integration_subparsers = app_integration_parser.add_subparsers()

_add_app_integration_list_subparser(app_integration_subparsers)
_add_app_integration_new_subparser(app_integration_subparsers, set(STREAMALERT_APPS), clusters)
_add_app_integration_new_subparser(
app_integration_subparsers,
sorted(StreamAlertApp.get_all_apps()),
clusters
)
_add_app_integration_update_auth_subparser(app_integration_subparsers, clusters)


Expand Down
4 changes: 2 additions & 2 deletions stream_alert_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import os
import re

from app_integrations.apps.app_base import get_app
from app_integrations.apps.app_base import StreamAlertApp
from stream_alert.shared import metrics
from stream_alert_cli.helpers import continue_prompt
from stream_alert_cli.logger import LOGGER_CLI
Expand Down Expand Up @@ -378,7 +378,7 @@ def add_app_integration(self, app_info):
a new app integration
"""
exists, prompt_for_auth, overwrite = False, True, False
app = get_app(app_info, False)
app = StreamAlertApp.get_app(app_info, False)

# Check to see if there is an existing configuration for this app integration
cluster_config = self.config['clusters'][app_info['cluster']]
Expand Down
4 changes: 2 additions & 2 deletions stream_alert_cli/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
from app_integrations.apps.app_base import get_app
from app_integrations.apps.app_base import StreamAlertApp
from stream_alert.alert_processor.outputs import get_output_dispatcher
from stream_alert_cli.apps import save_app_auth_info
from stream_alert_cli.athena.handler import athena_handler
Expand Down Expand Up @@ -226,7 +226,7 @@ def _app_integration_handler(options):
app_info['function_name'] = '_'.join([app_info.get(value)
for value in func_parts] + ['app'])

app = get_app(app_info, False)
app = StreamAlertApp.get_app(app_info, False)

if not save_app_auth_info(app, app_info, True):
return
Expand Down
Loading

0 comments on commit 0e45f5a

Please sign in to comment.