{"action":"created","comment":{"url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/pulls/comments/186417364","pull_request_review_id":117989744,"id":186417364,"diff_hunk":"@@ -1,384 +1,205 @@\n # -*- coding: utf-8 -*-\n # Copyright (c) 2016-present, CloudZero, Inc. All rights reserved.\n # Licensed under the BSD-style license. See LICENSE file in the project root for full license information.\n-import functools\n-import os\n+\n+\"\"\"\n+Enrichment plugin for EC2 Instance resource types.\n+This resource type is explicitly supported by Amazon.\n+\"\"\"\n \n import botocore.exceptions\n \n import reactor.constants\n-from lambda_tools import setup_logging\n-from reactor.aws.lambda_utils import get_fully_resolved_function_name, get_arn_of_lambda_function_cached\n-from reactor.aws.provider import Provider\n-from reactor.common import czrn\n-from reactor.database.models import dynamodb\n-from reactor.database.models.dynamodb.cz_reactor_resources import create_resource_record\n-\n-logger = setup_logging('reactor')\n+import reactor.aws.provider as provider\n+import reactor.features.resources.resource as resource\n+import lambda_tools as tools\n+import reactor.common.czrn as czrn\n \n-_SUPPORTED_RESOURCE_TYPES = ['instance']\n-resources_dbh = dynamodb.CZReactorResources()\n+logger = tools.setup_logging('reactor')\n \n \n def enrich_cooldown():\n-    \"\"\"\n-    This function returns the cooldown period for this plugin. The cooldown period defines the number of seconds\n-    before this plugin can be called again\n-\n-    Returns:\n-        int:\n-    \"\"\"\n     return 30\n \n \n-def _get_all_instances(aws):\n-    return aws.resource.instances.all()\n-\n-\n-def _get_instance_properties(instance):\n-    instance_data = instance.meta.data\n-    interesting_properties = {'az': instance_data['Placement']['AvailabilityZone'],\n-                              'vpc_id': instance_data.get('VpcId')}\n-\n-    return {**instance_data, **interesting_properties}\n-\n-\n-def _get_instance_relationships(instance, region, account_id):\n-    relationships = []\n-    for eni in instance.network_interfaces:\n-        eni_czrn = czrn.create_from_components(service_type='ec2', resource_type='network-interface',\n-                                               cloud_local_id=eni.id,\n-                                               region=region, account_id=account_id)\n-        relationships.append(eni_czrn)\n-\n-    if instance.vpc:\n-        vpc_czrn = czrn.create_from_components(service_type='ec2', resource_type='network-interface',\n-                                               cloud_local_id=instance.vpc.id,\n-                                               region=region, account_id=account_id)\n-        relationships.append(vpc_czrn)\n-    return relationships\n-\n-\n-def _get_instance_tags(instance):\n-    return instance.tags\n-\n-\n-def enrich_resource(event):\n-    \"\"\"\n-    Resource enrichment plugin for EC2 services\n-\n-    Args:\n-        event (dict): dict containing details of the event from AWS in the format\n-                       {'res_id': <RESOURCE ID>,\n-                        'timestamp': <TIMESTAMP>,\n-                        'environment_id': <ENV ID>,\n-                        'cloud_account_id': str,\n-                        'aws_account_details': dict,        # AWS connection information\n-                        'cz_metadata': dict,                # CloudZero metadata\n-                        'service_type': str,\n-                        'resource_type': str,\n-                        'cloud_local_id': str,\n-                        'region': str}\n-\n-    Returns: One or more enriched resource records\n-        list: [\n-                {\n-                    \"res_id\": str,\n-                    \"timestamp\": Decimal,\n-                    \"data\": dict,\n-                    \"resource_state\": str,\n-                    \"disposition\": str,\n-                    \"cz_metadata\": dict\n-                }\n-              ]\n-    \"\"\"\n-    logger.info(f\"Received for enrichment {event['res_id']}\")\n+def enrich_resource(resource_stream_event):\n+    logger.info(f\"Received for enrichment {resource_stream_event['res_id']}\")\n     enriched_resources = []\n     data = {}\n \n-    if event['resource_type'] not in _SUPPORTED_RESOURCE_TYPES:\n-        logger.warning(f\"Received a {event['resource_type']} resource, but no resource processor available\")\n-        return\n-\n     try:\n-        aws_account_details = event['aws_account_details']\n+        aws_account_details = resource_stream_event['aws_account_details']\n     except KeyError:\n-        logger.error(f\"Unable to process {event['res_id']}, missing AWS account details\")\n+        logger.error(f\"Unable to process {resource_stream_event['res_id']}, missing AWS account details\")\n         return\n \n     # Detect if observed resource exists outside of the currently connected account\n     # We are unable at this time to enrich such things\n-    resource_cloud_account_id = czrn.extract_account_id(event['res_id'])\n-    logger.info(f\"Account Check: {resource_cloud_account_id}, {event['cloud_account_id']}\")\n-    if resource_cloud_account_id != event['cloud_account_id']:\n+    resource_cloud_account_id = czrn.extract_account_id(resource_stream_event['res_id'])\n+    logger.info(f\"Account Check: {resource_cloud_account_id}, {resource_stream_event['cloud_account_id']}\")\n+    if resource_cloud_account_id != resource_stream_event['cloud_account_id']:\n         data = {\"message\": \"Observed resource exists outside account\",\n                 \"true_cloud_account_id\": resource_cloud_account_id}\n-        enriched_resources.append(basic_resource_record(event,\n-                                                        reactor.constants.RESOURCE_DISPOSITION_EXTERNAL_ACCOUNT,\n-                                                        data))\n-        logger.warning(f\"Outside account resource observed: {event['res_id']}:{event['timestamp']}\")\n+        enriched_resources.append(resource.create_from_stream_event(resource_stream_event,\n+                                                                    reactor.constants.RESOURCE_DISPOSITION_EXTERNAL_ACCOUNT,\n+                                                                    data))\n+        logger.warning(\n+            f\"Outside account resource observed: {resource_stream_event['res_id']}:{resource_stream_event['timestamp']}\")\n         return enriched_resources\n \n-    ec2_provider = Provider(role_arn=aws_account_details['role_arn'],\n-                            external_id=aws_account_details['external_id'],\n-                            region=event['region'],\n-                            service='ec2')\n+    ec2_provider = provider.Provider(role_arn=aws_account_details['role_arn'],\n+                                     external_id=aws_account_details['external_id'],\n+                                     region=resource_stream_event['region'],\n+                                     service='ec2')\n     try:\n         all_instances = _get_all_instances(ec2_provider)\n \n+        # TODO: refactor this so we can load and cache lookups\n         for instance in all_instances:\n             data['properties'] = _get_instance_properties(instance)\n-            data['relationships'] = _get_instance_relationships(instance, event['region'],\n-                                                                event['cloud_account_id'])\n+            data['relationships'] = _get_instance_relationships(instance, resource_stream_event['region'],\n+                                                                resource_stream_event['cloud_account_id'])\n             data['tags'] = _get_instance_tags(instance)\n-            enriched_resource = basic_resource_record(event, reactor.constants.RESOURCE_DISPOSITION_NEW, data)\n-            enriched_resource['res_id'] = czrn.replace_cloud_local_id(event['res_id'], instance.id)\n-            enriched_resource['resource_name'] = instance.id\n-            enriched_resource['cloud_availability_zone'] = data['properties']['az']\n+\n+            enriched_resource = resource.create_from_stream_event(resource_stream_event,\n+                                                                  reactor.constants.RESOURCE_DISPOSITION_NEW, data)\n+            instance_id = data['properties']['instance_id']\n+            enriched_resource['res_id'] = czrn.replace_cloud_local_id(resource_stream_event['res_id'], instance_id)\n+            enriched_resource['cloud_availability_zone'] = data['properties']['availability_zone']\n             enriched_resource['cloud_vpc_id'] = data['properties']['vpc_id']\n-            enriched_resource['resource_state'] = instance.state['Name']\n+            enriched_resource['resource_state'] = data['properties']['state_name']\n+            enriched_resource['cloud_local_id'] = instance_id\n+            enriched_resource['cloud_global_id'] = None  # Instances don't have ARNs\n+\n+            # For future use in CZ-NRF\n+            enriched_resource['resource_properties'] = None\n+            enriched_resource['resource_relationships'] = None\n+            enriched_resource['resource_tags'] = None\n+            enriched_resource['resource_present'] = None\n+\n+            # TODO: this one is obsolete\n+            enriched_resource['resource_name'] = instance_id\n+\n             enriched_resources.append(enriched_resource)\n \n+    # TODO: this should be handled by the caller\n     except botocore.exceptions.ClientError as error:\n         if \"AccessDenied\" in str(error):\n-            logger.error(f\"Access Denied trying to enrich {event['res_id']}. Was our access removed?\")\n-            enriched_resources.append(basic_resource_record(event,\n-                                                            reactor.constants.RESOURCE_DISPOSITION_NO_ACCESS,\n-                                                            data))\n+            logger.error(f\"Access Denied trying to enrich {resource_stream_event['res_id']}. Was our access removed?\")\n+            enriched_resources.append(resource.create_from_stream_event(resource_stream_event,\n+                                                                        reactor.constants.RESOURCE_DISPOSITION_NO_ACCESS,\n+                                                                        data))\n \n         elif \"RequestLimitExceeded\" in str(error):\n-            logger.warning(f\"Unable to enrich resource {event['res_id']}, API limits exceeded ({error})\")\n-            enriched_resources.append(basic_resource_record(event,\n-                                                            reactor.constants.RESOURCE_DISPOSITION_STALE,\n-                                                            data))\n+            logger.warning(\n+                f\"Unable to enrich resource {resource_stream_event['res_id']}, API limits exceeded ({error})\")\n+            enriched_resources.append(resource.create_from_stream_event(resource_stream_event,\n+                                                                        reactor.constants.RESOURCE_DISPOSITION_STALE,\n+                                                                        data))\n         else:\n             raise\n \n-    logger.info(f\"Enriched {event['res_id']}:{event['timestamp']}\")\n+    logger.info(f\"Enriched {resource_stream_event['res_id']}:{resource_stream_event['timestamp']}\")\n     return enriched_resources\n \n \n-def basic_resource_record(event, disposition, data):\n-    return create_resource_record(\n-        res_id=event['res_id'],\n-        service_type=event['service_type'],\n-        resource_name=event['cloud_local_id'],\n-        resource_type=event['resource_type'],\n-        region=event['region'],\n-        cloud_account_id=event['cloud_account_id'],\n-        environment_id=event['environment_id'],\n-        disposition=disposition,\n-        raw_data=data)\n-\n-\n-# ----------------------------------------------------------------\n-# Everything below this line (both code and comments) until the end of the file will be moved into vpc and eni plugins\n-# in a future ticket for now this is just a holding place\n-\n-# try:\n-#     #    data, resource_state = _process_instance(event, ec2_provider)\n-#     # elif event['resource_type'] == 'vpc':\n-#     #     data, resource_state = _process_vpc(event, ec2_provider)\n-#     #     flow_log_status = cz_metadata.get('flow_log_status')\n-#     #     if not flow_log_status:\n-#     #         logger.info(f\"Attempting to configure VPC Flow logs for {event['cloud_local_id']}\")\n-#     #         flow_log_group_name, subscribe_success = configure_flow_log(ec2_provider, event['cloud_local_id'])\n-#     #         flow_log_status = {\"log_group_name\": flow_log_group_name,\n-#     #                            \"type\": \"local\",\n-#     #                            \"subscribed\": subscribe_success,\n-#     #                            \"timestamp\": datetime.now(\n-#     #                                tz=timezone.utc).isoformat()}\n-#     #         cz_metadata = resources_dbh.update_cz_metadata(event['res_id'], event['timestamp'],\n-#     #                                                        'flow_log_status', flow_log_status)\n-\n-\n-def create_eni_resources_from_ec2_instance(instance, service_type, cloud_account_id, region, environment_id):\n-    eni_resources = []\n-    for eni in instance.network_interfaces:\n-        eni.load()\n-        eni_resources.append(create_resource_record(service_type=service_type, resource_name=eni.id,\n-                                                    resource_type='network-interface', region=region,\n-                                                    cloud_account_id=cloud_account_id,\n-                                                    environment_id=environment_id, resource_state=eni.status,\n-                                                    availability_zone=eni.availability_zone, vpc_id=eni.vpc_id,\n-                                                    raw_data=eni.meta.data))\n-    resources_dbh.write(eni_resources)\n-\n-\n-def create_vpc_resources_from_ec2_instance(instance, service_type, cloud_account_id, region, environment_id):\n-    vpc_resource = create_resource_record(service_type=service_type, resource_name=instance.vpc.id,\n-                                          resource_type='vpc', region=region,\n-                                          cloud_account_id=cloud_account_id, environment_id=environment_id,\n-                                          resource_state=instance.vpc.state,\n-                                          vpc_id=instance.vpc.id)\n-    resources_dbh.write([vpc_resource])\n-\n-\n-def _process_eni(event, aws):\n-    eni = aws.resource.NetworkInterface(event['cloud_local_id'])\n-    eni.load()\n-    resource_state = eni.status\n-    data = eni.meta.data\n-    return data, resource_state\n-\n-\n-def _process_vpc(event, aws):\n-    vpc = aws.resource.Vpc(event['cloud_local_id'])\n-    vpc.load()\n-    resource_state = vpc.state\n-    data = vpc.meta.data\n-    return data, resource_state\n-\n-\n-def configure_flow_log(ec2_provider, vpc_id, kinesis=False):\n-    flow_log_group_name = None\n-    subscribe_success = False\n-\n-    try:\n-        logs_provider = Provider(service='logs', **ec2_provider.aws_connection_details)\n-        flow_log_group_name = create_vpc_flow_log(vpc_id, logs_provider, ec2_provider)\n-\n-        if kinesis:\n-            pass\n-            # Implements a \"Logical Destination\" which is a CloudWatch Logs system for allowing cross account\n-            # delivery of events to a Kinesis stream.\n-            # https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutSubscriptionFilter.html\n-            # TODO Placeholder where one would configure/call/subscribe a flow log group to our delivery stream\n-        else:\n-            # This attempts to subscribe our VPC Flow Log to a reactor ingest lambda however\n-            # this method only works for log groups in the same account as the reactor.\n-            # For a more general approach, you must use the Kinesis method (optional)\n-            subscribe_success = subscribe_lambda_to_flow_log(flow_log_group_name, logs_provider)\n-    except Exception as error:\n-        logger.warning(f\"Could not check or enable flow logs for VPC {vpc_id}, check permissions ({error})\")\n-\n-    return flow_log_group_name, subscribe_success\n-\n-\n-def create_vpc_flow_log(vpc_id, logs_provider, ec2_provider):\n-    log_group_name = get_flow_log_group_name_for_vpc(vpc_id)\n-    logger.debug(f'Creating VPC Flow Logs for {log_group_name}')\n-\n-    try:\n-        logs_provider.client.create_log_group(logGroupName=log_group_name)\n-    except botocore.exceptions.ClientError as error:\n-        logger.debug(f'Log group {log_group_name} already exists, skipping {error}')\n-\n-    logs_provider.client.put_retention_policy(logGroupName=log_group_name, retentionInDays=1)\n-\n-    existing_flow_log = get_flow_log_for_vpc_cached(vpc_id,\n-                                                    role_arn=ec2_provider.role_arn,\n-                                                    external_id=ec2_provider.external_id,\n-                                                    region=ec2_provider.region)\n-    if existing_flow_log:\n-        logger.info(f\"Flow Log for {vpc_id} already exists, no need to create ({existing_flow_log})\")\n-        return existing_flow_log\n-    else:\n-        logger.info(f\"No Flow Log for {vpc_id}, creating\")\n-\n-    try:\n-        response = ec2_provider.client.create_flow_logs(\n-            DeliverLogsPermissionArn=logs_provider.role_arn,\n-            LogGroupName=log_group_name,\n-            ResourceIds=[vpc_id],\n-            ResourceType='VPC',\n-            TrafficType='ALL'\n-        )\n-    except botocore.exceptions.ClientError as error:\n-        logger.exception(error)\n-        if \"UnauthorizedOperation\" in str(error):\n-            logger.warning(f'Reactor role {logs_provider.role_arn} '\n-                           f'does not have permission to create vpc flow logs for {vpc_id} ({error})')\n-            return None\n-        else:\n-            logger.warning(f'Flow logs not created for {vpc_id} ({error})')\n-            return None\n-\n-    if response['Unsuccessful']:\n-        logger.warning(f'Could not create VPC Flow Logs for {vpc_id} ({response})')\n-        return None\n-    else:\n-        logger.info(f'Created VPC Flow Log: {log_group_name}')\n-        return log_group_name\n-\n-\n-def subscribe_lambda_to_flow_log(log_group_name, logs_client):\n-    \"\"\"\n-    THIS FUNCTION DOES NOT WORK ACROSS ACCOUNTS :-(\n+def _get_all_instances(aws):\n+    # TODO: use NextToken and make this a generator.\n \n-    Only Kinesis is supported for cross account cloudwatch log delivery\n+    instance_data = aws.client.describe_instances(MaxResults=1000)['Reservations'][0]['Instances']  # TODO: fix this\n+    logger.debug(instance_data)\n+    return instance_data\n \n-    Args:\n-        log_group_name:\n-        logs_client:\n \n-    Returns:\n+def _get_instance_properties(instance_data):\n+    interesting_keys = {\n+        'AmiLaunchIndex', 'Architecture', 'EbsOptimized', 'InstanceId', 'InstanceType',\n+        'KernelId', 'KeyName', 'LaunchTime', 'VpcId', 'Platform', 'RamdiskId', 'SourceDestCheck',\n+        'VirtualizationType', 'EnaSupport', 'Hypervisor',\n+    }\n \n-    \"\"\"\n+    interesting_properties = {resource.normalize_property_name(k): instance_data.get(k) for k in interesting_keys}\n \n-    if not log_group_name:\n-        return False\n+    other_properties = {","path":"reactor/features/resources/plugins/ec2/instance.py","position":392,"original_position":392,"commit_id":"6b7312dad03ead8e85214af5e4a4e4917333a358","original_commit_id":"6b7312dad03ead8e85214af5e4a4e4917333a358","user":{"login":"killthrush","id":6638896,"avatar_url":"https://avatars0.githubusercontent.com/u/6638896?v=4","gravatar_id":"","url":"https://api.github.com/users/killthrush","html_url":"https://github.com/killthrush","followers_url":"https://api.github.com/users/killthrush/followers","following_url":"https://api.github.com/users/killthrush/following{/other_user}","gists_url":"https://api.github.com/users/killthrush/gists{/gist_id}","starred_url":"https://api.github.com/users/killthrush/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/killthrush/subscriptions","organizations_url":"https://api.github.com/users/killthrush/orgs","repos_url":"https://api.github.com/users/killthrush/repos","events_url":"https://api.github.com/users/killthrush/events{/privacy}","received_events_url":"https://api.github.com/users/killthrush/received_events","type":"User","site_admin":false},"body":"There are ones that can be resolved in a very simple fashion and others that need to be plucked from a more complex data structure.  However, I think I could probably use one of those dict util functions here to make this less complicated - I'll try it.","created_at":"2018-05-07T13:09:29Z","updated_at":"2018-05-07T13:09:29Z","html_url":"https://github.com/Cloudzero/cloudzero-reactor-aws/pull/79#discussion_r186417364","pull_request_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/pulls/79","author_association":"COLLABORATOR","_links":{"self":{"href":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/pulls/comments/186417364"},"html":{"href":"https://github.com/Cloudzero/cloudzero-reactor-aws/pull/79#discussion_r186417364"},"pull_request":{"href":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/pulls/79"}},"in_reply_to_id":186324229},"pull_request":{"url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/pulls/79","id":186082405,"html_url":"https://github.com/Cloudzero/cloudzero-reactor-aws/pull/79","diff_url":"https://github.com/Cloudzero/cloudzero-reactor-aws/pull/79.diff","patch_url":"https://github.com/Cloudzero/cloudzero-reactor-aws/pull/79.patch","issue_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/issues/79","number":79,"state":"open","locked":false,"title":"Feature/reactor 288 - enhance instance plugin and establish test patterns","user":{"login":"killthrush","id":6638896,"avatar_url":"https://avatars0.githubusercontent.com/u/6638896?v=4","gravatar_id":"","url":"https://api.github.com/users/killthrush","html_url":"https://github.com/killthrush","followers_url":"https://api.github.com/users/killthrush/followers","following_url":"https://api.github.com/users/killthrush/following{/other_user}","gists_url":"https://api.github.com/users/killthrush/gists{/gist_id}","starred_url":"https://api.github.com/users/killthrush/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/killthrush/subscriptions","organizations_url":"https://api.github.com/users/killthrush/orgs","repos_url":"https://api.github.com/users/killthrush/repos","events_url":"https://api.github.com/users/killthrush/events{/privacy}","received_events_url":"https://api.github.com/users/killthrush/received_events","type":"User","site_admin":false},"body":"### Description\r\n<!--- Provide a brief description of the PR and issue it addresses. -->\r\nAs a prerequisite for writing a bunch of plugins that support VPC Flow Log event sources, improve the quality of the `ec2.instance` enrichment plugin and add unit tests.  This should set a solid precedent for future work.\r\n\r\nAlso improve the documentation and clean up some cruft that was hanging around from the original CZRNv1 feature.\r\n\r\n- [ ] Existing GitHub Issue? Please provide #: \r\n\r\n### Acceptance checklist\r\n<!--- Here we make sure all criteria for merge has been satisfied. -->\r\n- [ ] Feedback addressed\r\n- [ ] Tests added or modified appropriately\r\n- [ ] All tests passed\r\n\r\n","created_at":"2018-05-04T19:41:09Z","updated_at":"2018-05-07T13:09:29Z","closed_at":null,"merged_at":null,"merge_commit_sha":"0da1e247b5b66f67b187ddf9df68c80f57803b52","assignee":null,"assignees":[],"requested_reviewers":[{"login":"tiyberius","id":1237044,"avatar_url":"https://avatars0.githubusercontent.com/u/1237044?v=4","gravatar_id":"","url":"https://api.github.com/users/tiyberius","html_url":"https://github.com/tiyberius","followers_url":"https://api.github.com/users/tiyberius/followers","following_url":"https://api.github.com/users/tiyberius/following{/other_user}","gists_url":"https://api.github.com/users/tiyberius/gists{/gist_id}","starred_url":"https://api.github.com/users/tiyberius/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tiyberius/subscriptions","organizations_url":"https://api.github.com/users/tiyberius/orgs","repos_url":"https://api.github.com/users/tiyberius/repos","events_url":"https://api.github.com/users/tiyberius/events{/privacy}","received_events_url":"https://api.github.com/users/tiyberius/received_events","type":"User","site_admin":false},{"login":"Tankanow","id":2100107,"avatar_url":"https://avatars0.githubusercontent.com/u/2100107?v=4","gravatar_id":"","url":"https://api.github.com/users/Tankanow","html_url":"https://github.com/Tankanow","followers_url":"https://api.github.com/users/Tankanow/followers","following_url":"https://api.github.com/users/Tankanow/following{/other_user}","gists_url":"https://api.github.com/users/Tankanow/gists{/gist_id}","starred_url":"https://api.github.com/users/Tankanow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Tankanow/subscriptions","organizations_url":"https://api.github.com/users/Tankanow/orgs","repos_url":"https://api.github.com/users/Tankanow/repos","events_url":"https://api.github.com/users/Tankanow/events{/privacy}","received_events_url":"https://api.github.com/users/Tankanow/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/pulls/79/commits","review_comments_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/pulls/79/comments","review_comment_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/issues/79/comments","statuses_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/statuses/6b7312dad03ead8e85214af5e4a4e4917333a358","head":{"label":"Cloudzero:feature/REACTOR-288","ref":"feature/REACTOR-288","sha":"6b7312dad03ead8e85214af5e4a4e4917333a358","user":{"login":"Cloudzero","id":22050846,"avatar_url":"https://avatars0.githubusercontent.com/u/22050846?v=4","gravatar_id":"","url":"https://api.github.com/users/Cloudzero","html_url":"https://github.com/Cloudzero","followers_url":"https://api.github.com/users/Cloudzero/followers","following_url":"https://api.github.com/users/Cloudzero/following{/other_user}","gists_url":"https://api.github.com/users/Cloudzero/gists{/gist_id}","starred_url":"https://api.github.com/users/Cloudzero/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Cloudzero/subscriptions","organizations_url":"https://api.github.com/users/Cloudzero/orgs","repos_url":"https://api.github.com/users/Cloudzero/repos","events_url":"https://api.github.com/users/Cloudzero/events{/privacy}","received_events_url":"https://api.github.com/users/Cloudzero/received_events","type":"Organization","site_admin":false},"repo":{"id":104886482,"name":"cloudzero-reactor-aws","full_name":"Cloudzero/cloudzero-reactor-aws","owner":{"login":"Cloudzero","id":22050846,"avatar_url":"https://avatars0.githubusercontent.com/u/22050846?v=4","gravatar_id":"","url":"https://api.github.com/users/Cloudzero","html_url":"https://github.com/Cloudzero","followers_url":"https://api.github.com/users/Cloudzero/followers","following_url":"https://api.github.com/users/Cloudzero/following{/other_user}","gists_url":"https://api.github.com/users/Cloudzero/gists{/gist_id}","starred_url":"https://api.github.com/users/Cloudzero/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Cloudzero/subscriptions","organizations_url":"https://api.github.com/users/Cloudzero/orgs","repos_url":"https://api.github.com/users/Cloudzero/repos","events_url":"https://api.github.com/users/Cloudzero/events{/privacy}","received_events_url":"https://api.github.com/users/Cloudzero/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Cloudzero/cloudzero-reactor-aws","description":"CloudZero Reactor for AWS","fork":false,"url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws","forks_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/forks","keys_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/teams","hooks_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/hooks","issue_events_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/issues/events{/number}","events_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/events","assignees_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/assignees{/user}","branches_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/branches{/branch}","tags_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/tags","blobs_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/git/refs{/sha}","trees_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/statuses/{sha}","languages_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/languages","stargazers_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/stargazers","contributors_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/contributors","subscribers_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/subscribers","subscription_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/subscription","commits_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/commits{/sha}","git_commits_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/git/commits{/sha}","comments_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/comments{/number}","issue_comment_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/issues/comments{/number}","contents_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/contents/{+path}","compare_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/merges","archive_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/downloads","issues_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/issues{/number}","pulls_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/pulls{/number}","milestones_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/milestones{/number}","notifications_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/labels{/name}","releases_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/releases{/id}","deployments_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/deployments","created_at":"2017-09-26T13:13:15Z","updated_at":"2018-05-02T13:29:09Z","pushed_at":"2018-05-07T12:38:50Z","git_url":"git://github.com/Cloudzero/cloudzero-reactor-aws.git","ssh_url":"git@github.com:Cloudzero/cloudzero-reactor-aws.git","clone_url":"https://github.com/Cloudzero/cloudzero-reactor-aws.git","svn_url":"https://github.com/Cloudzero/cloudzero-reactor-aws","homepage":"https://www.cloudzero.com/","size":26105,"stargazers_count":19,"watchers_count":19,"language":"Python","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":3,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause"},"forks":0,"open_issues":3,"watchers":19,"default_branch":"develop"}},"base":{"label":"Cloudzero:develop","ref":"develop","sha":"82ed6698682ddf892fa53df7e43257e86ae113ab","user":{"login":"Cloudzero","id":22050846,"avatar_url":"https://avatars0.githubusercontent.com/u/22050846?v=4","gravatar_id":"","url":"https://api.github.com/users/Cloudzero","html_url":"https://github.com/Cloudzero","followers_url":"https://api.github.com/users/Cloudzero/followers","following_url":"https://api.github.com/users/Cloudzero/following{/other_user}","gists_url":"https://api.github.com/users/Cloudzero/gists{/gist_id}","starred_url":"https://api.github.com/users/Cloudzero/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Cloudzero/subscriptions","organizations_url":"https://api.github.com/users/Cloudzero/orgs","repos_url":"https://api.github.com/users/Cloudzero/repos","events_url":"https://api.github.com/users/Cloudzero/events{/privacy}","received_events_url":"https://api.github.com/users/Cloudzero/received_events","type":"Organization","site_admin":false},"repo":{"id":104886482,"name":"cloudzero-reactor-aws","full_name":"Cloudzero/cloudzero-reactor-aws","owner":{"login":"Cloudzero","id":22050846,"avatar_url":"https://avatars0.githubusercontent.com/u/22050846?v=4","gravatar_id":"","url":"https://api.github.com/users/Cloudzero","html_url":"https://github.com/Cloudzero","followers_url":"https://api.github.com/users/Cloudzero/followers","following_url":"https://api.github.com/users/Cloudzero/following{/other_user}","gists_url":"https://api.github.com/users/Cloudzero/gists{/gist_id}","starred_url":"https://api.github.com/users/Cloudzero/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Cloudzero/subscriptions","organizations_url":"https://api.github.com/users/Cloudzero/orgs","repos_url":"https://api.github.com/users/Cloudzero/repos","events_url":"https://api.github.com/users/Cloudzero/events{/privacy}","received_events_url":"https://api.github.com/users/Cloudzero/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Cloudzero/cloudzero-reactor-aws","description":"CloudZero Reactor for AWS","fork":false,"url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws","forks_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/forks","keys_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/teams","hooks_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/hooks","issue_events_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/issues/events{/number}","events_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/events","assignees_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/assignees{/user}","branches_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/branches{/branch}","tags_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/tags","blobs_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/git/refs{/sha}","trees_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/statuses/{sha}","languages_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/languages","stargazers_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/stargazers","contributors_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/contributors","subscribers_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/subscribers","subscription_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/subscription","commits_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/commits{/sha}","git_commits_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/git/commits{/sha}","comments_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/comments{/number}","issue_comment_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/issues/comments{/number}","contents_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/contents/{+path}","compare_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/merges","archive_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/downloads","issues_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/issues{/number}","pulls_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/pulls{/number}","milestones_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/milestones{/number}","notifications_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/labels{/name}","releases_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/releases{/id}","deployments_url":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/deployments","created_at":"2017-09-26T13:13:15Z","updated_at":"2018-05-02T13:29:09Z","pushed_at":"2018-05-07T12:38:50Z","git_url":"git://github.com/Cloudzero/cloudzero-reactor-aws.git","ssh_url":"git@github.com:Cloudzero/cloudzero-reactor-aws.git","clone_url":"https://github.com/Cloudzero/cloudzero-reactor-aws.git","svn_url":"https://github.com/Cloudzero/cloudzero-reactor-aws","homepage":"https://www.cloudzero.com/","size":26105,"stargazers_count":19,"watchers_count":19,"language":"Python","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":3,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause"},"forks":0,"open_issues":3,"watchers":19,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/pulls/79"},"html":{"href":"https://github.com/Cloudzero/cloudzero-reactor-aws/pull/79"},"issue":{"href":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/issues/79"},"comments":{"href":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/issues/79/comments"},"review_comments":{"href":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/pulls/79/comments"},"review_comment":{"href":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/pulls/79/commits"},"statuses":{"href":"https://api.github.com/repos/Cloudzero/cloudzero-reactor-aws/statuses/6b7312dad03ead8e85214af5e4a4e4917333a358"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"killthrush"}}