{"action":"created","comment":{"url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/pulls/comments/223108381","pull_request_review_id":162133315,"id":223108381,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIyMzEwODM4MQ==","diff_hunk":"@@ -0,0 +1,318 @@\n+# Copyright 2017 The Forseti Security Authors. All rights reserved.\n+#\n+# Licensed under the Apache License, Version 2.0 (the \"License\");\n+# you may not use this file except in compliance with the License.\n+# You may obtain a copy of the License at\n+#\n+#    http://www.apache.org/licenses/LICENSE-2.0\n+#\n+# Unless required by applicable law or agreed to in writing, software\n+# distributed under the License is distributed on an \"AS IS\" BASIS,\n+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+# See the License for the specific language governing permissions and\n+# limitations under the License.\n+\n+\"\"\"Rules engine for Bucket acls.\"\"\"\n+import collections\n+from collections import namedtuple\n+import itertools\n+import threading\n+\n+from google.cloud.forseti.common.gcp_type import resource_util\n+from google.cloud.forseti.common.util import logger\n+from google.cloud.forseti.common.util import relationship\n+from google.cloud.forseti.scanner.audit import base_rules_engine as bre\n+from google.cloud.forseti.scanner.audit import errors as audit_errors\n+\n+\n+LOGGER = logger.get_logger(__name__)\n+\n+VIOLATION_TYPE = 'RETENTION_VIOLATION'\n+# Applyto.\n+_APPLY_TO_BUCKETS = 'bucket'\n+_APPLY_TO_RESOURCES = frozenset([_APPLY_TO_BUCKETS])\n+\n+\n+class RetentionRulesEngine(bre.BaseRulesEngine):\n+    \"\"\"Rules engine for retention.\"\"\"\n+\n+    def __init__(self, rules_file_path, snapshot_timestamp=None):\n+        \"\"\"Initialize.\n+\n+        Args:\n+            rules_file_path (str): file location of rules\n+            snapshot_timestamp (str): snapshot timestamp. Defaults to None.\n+                If set, this will be the snapshot timestamp\n+                used in the engine.\n+        \"\"\"\n+        super(RetentionRulesEngine,\n+              self).__init__(rules_file_path=rules_file_path)\n+        self.rule_book = None\n+\n+    def build_rule_book(self, global_configs=None):\n+        \"\"\"Build RetentionRuleBook from the rules definition file.\n+\n+        Args:\n+            global_configs (dict): Global configurations.\n+        \"\"\"\n+        self.rule_book = RetentionRuleBook(self._load_rule_definitions())\n+\n+    def find_buckets_violations(self, buckets_lifecycle, force_rebuild=False):\n+        \"\"\"Determine whether bucket lifecycle violates rules.\n+\n+        Args:\n+            buckets_lifecycle (retention_bucket.RetentionBucket): Object\n+                containing lifecycle data\n+            force_rebuild (bool): If True, rebuilds the rule book. This will\n+                reload the rules definition file and add the rules to the book.\n+\n+        Returns:\n+             generator: A generator of rule violations.\n+        \"\"\"\n+        if self.rule_book is None or force_rebuild:\n+            self.build_rule_book()\n+\n+        violations = itertools.chain()\n+\n+        resource_rules = self.rule_book.get_resource_rules(_APPLY_TO_BUCKETS)\n+\n+        recent_bucket = resource_util.create_resource(\n+            resource_id=buckets_lifecycle.name,\n+            resource_type=_APPLY_TO_BUCKETS)\n+\n+        resource_ancestors = (relationship.find_ancestors(\n+            recent_bucket, buckets_lifecycle.full_name))\n+        for related_resources in resource_ancestors:\n+            rules = resource_rules[related_resources]\n+            for rule in rules:\n+                violations = itertools.chain(\n+                    violations,\n+                    rule.find_buckets_violations(buckets_lifecycle))\n+\n+        return set(violations)\n+\n+\n+class RetentionRuleBook(bre.BaseRuleBook):\n+    \"\"\"The RuleBook for Retention resources.\"\"\"\n+\n+    def __init__(self, rule_defs=None):\n+        \"\"\"Initialization.\n+\n+        Args:\n+            rule_defs (dict): rule definitons\n+        \"\"\"\n+        super(RetentionRuleBook, self).__init__()\n+        self._rules_sema = threading.BoundedSemaphore(value=1)\n+\n+        self.resource_rules_map = {\n+            applies_to: collections.defaultdict(set)\n+            for applies_to in _APPLY_TO_RESOURCES}\n+        if not rule_defs:\n+            self.rule_defs = {}\n+        else:\n+            self.rule_defs = rule_defs\n+            self.add_rules(rule_defs)\n+\n+    def add_rules(self, rule_defs):\n+        \"\"\"Add rules to the rule book\n+\n+        Args:\n+            rule_defs (dict): rule definitions dictionary\n+        \"\"\"\n+        for (i, rule) in enumerate(rule_defs.get('rules', [])):\n+            self.add_rule(rule, i)\n+\n+    def add_rule(self, rule_def, rule_index):\n+        \"\"\"Add a rule to the rule book.\n+\n+        Args:\n+            rule_def (dict): A dictionary containing rule definition\n+                properties.\n+            rule_index (int): The index of the rule from the rule definitions.\n+                Assigned automatically when the rule book is built.\n+        \"\"\"\n+        self._rules_sema.acquire()\n+        try:\n+            applies_to = rule_def.get('applies_to', None)\n+            if applies_to is None:\n+                raise audit_errors.InvalidRulesSchemaError(\n+                    'Lack of applies_to in rule {}'.format(rule_index))\n+            if type(applies_to).__name__ != 'list':\n+                raise audit_errors.InvalidRulesSchemaError(\n+                    'Miss dash (-) near applies_to in rule {}'.format(\n+                        rule_index))\n+\n+            minimum_retention = rule_def.get('minimum_retention', None)\n+            maximum_retention = rule_def.get('maximum_retention', None)\n+            if minimum_retention is None and maximum_retention is None:\n+                raise audit_errors.InvalidRulesSchemaError(\n+                    'Lack of minimum_retention and \\\n+maximum_retention in rule {}'.format(rule_index))\n+            elif minimum_retention != None and maximum_retention != None:\n+                if minimum_retention > maximum_retention:\n+                    raise audit_errors.InvalidRulesSchemaError(\n+                        'minimum_retention larger than \\\n+maximum_retention in rule {}'.format(rule_index))\n+\n+            applies_to_history = {}\n+            for appto in applies_to:\n+                if appto in applies_to_history:\n+                    raise audit_errors.InvalidRulesSchemaError(\n+                        'Duplicate applies_to in rule {}'.format(rule_index))\n+                applies_to_history[appto] = True\n+\n+                if appto == _APPLY_TO_BUCKETS:\n+                    self.add_bucket_rule(\n+                        rule_def,\n+                        rule_index,\n+                        minimum_retention,\n+                        maximum_retention)\n+                # other appto add here\n+        finally:\n+            self._rules_sema.release()\n+\n+    def add_bucket_rule(self, rule_def, rule_index,\n+                        min_retention, max_retention):\n+        \"\"\"Add a rule to the rule book.\n+\n+        Args:\n+            rule_def (dict): A dictionary containing rule definition\n+                properties.\n+            rule_index (int): The index of the rule from the rule definitions.\n+                Assigned automatically when the rule book is built.\n+            min_retention(int): minimum value of the age in lifecycle\n+            max_retention(int): maximum value of the age in lifecycle\n+        \"\"\"\n+        resource = rule_def.get('resource', None)\n+        if resource is None:\n+            raise audit_errors.InvalidRulesSchemaError(\n+                'Lack of resource in rule {}'.format(rule_index))\n+        if type(resource).__name__ != 'list':\n+            raise audit_errors.InvalidRulesSchemaError(\n+                'Miss dash (-) near resource in rule {}'.format(rule_index))\n+\n+        for res in resource:\n+            resource_type = res.get('type', None)\n+            if resource_type is None:\n+                raise audit_errors.InvalidRulesSchemaError(\n+                    'Lack of type in rule {}'.format(rule_index))\n+\n+            resource_ids = res.get('resource_ids', None)\n+            if resource_ids is None:\n+                raise audit_errors.InvalidRulesSchemaError(\n+                    'Lack of resource_ids in rule {}'.format(rule_index))\n+            if type(resource_ids).__name__ != 'list':\n+                raise audit_errors.InvalidRulesSchemaError(\n+                    'Miss dash (-) near resource_ids in rule {}'.format(\n+                        rule_index))\n+            for rid in resource_ids:\n+                if rid == '*':\n+                    raise audit_errors.InvalidRulesSchemaError(\n+                        'The symbol * is not allowed in rule {}'.format(\n+                            rule_index))\n+\n+                rule = Rule(rule_name=rule_def.get('name', 'no name'),\n+                            rule_index=rule_index,\n+                            min_retention=min_retention,\n+                            max_retention=max_retention)\n+                gcp_resource = resource_util.create_resource(\n+                    resource_id=rid,\n+                    resource_type=resource_type)\n+\n+                self.resource_rules_map[_APPLY_TO_BUCKETS][gcp_resource].add(\n+                    rule)\n+\n+    def get_resource_rules(self, applies_to):\n+        \"\"\"Get all the resource rules for (resource, RuleAppliesTo.*).\n+        Args:\n+            applies_to (str): The name of applies_to resource\n+\n+        Returns:\n+           defaultdict:  A list of ResourceRules.\n+        \"\"\"\n+        return self.resource_rules_map[applies_to]\n+\n+\n+class Rule(object):\n+    \"\"\"Rule properties from the rule definition file.\n+    Also finds violations.\n+    \"\"\"\n+\n+    rttRuleViolation = namedtuple(","path":"google/cloud/forseti/scanner/audit/retention_rules_engine.py","position":241,"original_position":241,"commit_id":"420315b9deadd91f865828e20577293223dd8c1e","original_commit_id":"420315b9deadd91f865828e20577293223dd8c1e","user":{"login":"dufton","id":18620887,"node_id":"MDQ6VXNlcjE4NjIwODg3","avatar_url":"https://avatars1.githubusercontent.com/u/18620887?v=4","gravatar_id":"","url":"https://api.github.com/users/dufton","html_url":"https://github.com/dufton","followers_url":"https://api.github.com/users/dufton/followers","following_url":"https://api.github.com/users/dufton/following{/other_user}","gists_url":"https://api.github.com/users/dufton/gists{/gist_id}","starred_url":"https://api.github.com/users/dufton/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dufton/subscriptions","organizations_url":"https://api.github.com/users/dufton/orgs","repos_url":"https://api.github.com/users/dufton/repos","events_url":"https://api.github.com/users/dufton/events{/privacy}","received_events_url":"https://api.github.com/users/dufton/received_events","type":"User","site_admin":false},"body":"just `RuleViolation`","created_at":"2018-10-05T18:53:48Z","updated_at":"2018-10-05T18:57:03Z","html_url":"https://github.com/GoogleCloudPlatform/forseti-security/pull/2077#discussion_r223108381","pull_request_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/pulls/2077","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/pulls/comments/223108381"},"html":{"href":"https://github.com/GoogleCloudPlatform/forseti-security/pull/2077#discussion_r223108381"},"pull_request":{"href":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/pulls/2077"}}},"pull_request":{"url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/pulls/2077","id":220514163,"node_id":"MDExOlB1bGxSZXF1ZXN0MjIwNTE0MTYz","html_url":"https://github.com/GoogleCloudPlatform/forseti-security/pull/2077","diff_url":"https://github.com/GoogleCloudPlatform/forseti-security/pull/2077.diff","patch_url":"https://github.com/GoogleCloudPlatform/forseti-security/pull/2077.patch","issue_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/issues/2077","number":2077,"state":"open","locked":false,"title":"Bucket retention","user":{"login":"gruihuang","id":42782261,"node_id":"MDQ6VXNlcjQyNzgyMjYx","avatar_url":"https://avatars2.githubusercontent.com/u/42782261?v=4","gravatar_id":"","url":"https://api.github.com/users/gruihuang","html_url":"https://github.com/gruihuang","followers_url":"https://api.github.com/users/gruihuang/followers","following_url":"https://api.github.com/users/gruihuang/following{/other_user}","gists_url":"https://api.github.com/users/gruihuang/gists{/gist_id}","starred_url":"https://api.github.com/users/gruihuang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gruihuang/subscriptions","organizations_url":"https://api.github.com/users/gruihuang/orgs","repos_url":"https://api.github.com/users/gruihuang/repos","events_url":"https://api.github.com/users/gruihuang/events{/privacy}","received_events_url":"https://api.github.com/users/gruihuang/received_events","type":"User","site_admin":false},"body":"New feature \"bucket retention\".\r\n[#2076](https://github.com/GoogleCloudPlatform/forseti-security/issues/2076)\r\n","created_at":"2018-10-04T20:53:12Z","updated_at":"2018-10-05T18:57:03Z","closed_at":null,"merged_at":null,"merge_commit_sha":"9f5c08690080a7241f879bbe7f2a433db69dff3a","assignee":null,"assignees":[],"requested_reviewers":[{"login":"umairidris","id":5559279,"node_id":"MDQ6VXNlcjU1NTkyNzk=","avatar_url":"https://avatars1.githubusercontent.com/u/5559279?v=4","gravatar_id":"","url":"https://api.github.com/users/umairidris","html_url":"https://github.com/umairidris","followers_url":"https://api.github.com/users/umairidris/followers","following_url":"https://api.github.com/users/umairidris/following{/other_user}","gists_url":"https://api.github.com/users/umairidris/gists{/gist_id}","starred_url":"https://api.github.com/users/umairidris/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/umairidris/subscriptions","organizations_url":"https://api.github.com/users/umairidris/orgs","repos_url":"https://api.github.com/users/umairidris/repos","events_url":"https://api.github.com/users/umairidris/events{/privacy}","received_events_url":"https://api.github.com/users/umairidris/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[{"id":660630091,"node_id":"MDU6TGFiZWw2NjA2MzAwOTE=","url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/labels/cla:%20no","name":"cla: no","color":"ededed","default":false},{"id":769538991,"node_id":"MDU6TGFiZWw3Njk1Mzg5OTE=","url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/labels/status:%20in-progress","name":"status: in-progress","color":"1d76db","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/pulls/2077/commits","review_comments_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/pulls/2077/comments","review_comment_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/pulls/comments{/number}","comments_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/issues/2077/comments","statuses_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/statuses/420315b9deadd91f865828e20577293223dd8c1e","head":{"label":"gruihuang:dev","ref":"dev","sha":"420315b9deadd91f865828e20577293223dd8c1e","user":{"login":"gruihuang","id":42782261,"node_id":"MDQ6VXNlcjQyNzgyMjYx","avatar_url":"https://avatars2.githubusercontent.com/u/42782261?v=4","gravatar_id":"","url":"https://api.github.com/users/gruihuang","html_url":"https://github.com/gruihuang","followers_url":"https://api.github.com/users/gruihuang/followers","following_url":"https://api.github.com/users/gruihuang/following{/other_user}","gists_url":"https://api.github.com/users/gruihuang/gists{/gist_id}","starred_url":"https://api.github.com/users/gruihuang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gruihuang/subscriptions","organizations_url":"https://api.github.com/users/gruihuang/orgs","repos_url":"https://api.github.com/users/gruihuang/repos","events_url":"https://api.github.com/users/gruihuang/events{/privacy}","received_events_url":"https://api.github.com/users/gruihuang/received_events","type":"User","site_admin":false},"repo":{"id":150486702,"node_id":"MDEwOlJlcG9zaXRvcnkxNTA0ODY3MDI=","name":"forseti-security","full_name":"gruihuang/forseti-security","private":false,"owner":{"login":"gruihuang","id":42782261,"node_id":"MDQ6VXNlcjQyNzgyMjYx","avatar_url":"https://avatars2.githubusercontent.com/u/42782261?v=4","gravatar_id":"","url":"https://api.github.com/users/gruihuang","html_url":"https://github.com/gruihuang","followers_url":"https://api.github.com/users/gruihuang/followers","following_url":"https://api.github.com/users/gruihuang/following{/other_user}","gists_url":"https://api.github.com/users/gruihuang/gists{/gist_id}","starred_url":"https://api.github.com/users/gruihuang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gruihuang/subscriptions","organizations_url":"https://api.github.com/users/gruihuang/orgs","repos_url":"https://api.github.com/users/gruihuang/repos","events_url":"https://api.github.com/users/gruihuang/events{/privacy}","received_events_url":"https://api.github.com/users/gruihuang/received_events","type":"User","site_admin":false},"html_url":"https://github.com/gruihuang/forseti-security","description":"Forseti Security","fork":true,"url":"https://api.github.com/repos/gruihuang/forseti-security","forks_url":"https://api.github.com/repos/gruihuang/forseti-security/forks","keys_url":"https://api.github.com/repos/gruihuang/forseti-security/keys{/key_id}","collaborators_url":"https://api.github.com/repos/gruihuang/forseti-security/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/gruihuang/forseti-security/teams","hooks_url":"https://api.github.com/repos/gruihuang/forseti-security/hooks","issue_events_url":"https://api.github.com/repos/gruihuang/forseti-security/issues/events{/number}","events_url":"https://api.github.com/repos/gruihuang/forseti-security/events","assignees_url":"https://api.github.com/repos/gruihuang/forseti-security/assignees{/user}","branches_url":"https://api.github.com/repos/gruihuang/forseti-security/branches{/branch}","tags_url":"https://api.github.com/repos/gruihuang/forseti-security/tags","blobs_url":"https://api.github.com/repos/gruihuang/forseti-security/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/gruihuang/forseti-security/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/gruihuang/forseti-security/git/refs{/sha}","trees_url":"https://api.github.com/repos/gruihuang/forseti-security/git/trees{/sha}","statuses_url":"https://api.github.com/repos/gruihuang/forseti-security/statuses/{sha}","languages_url":"https://api.github.com/repos/gruihuang/forseti-security/languages","stargazers_url":"https://api.github.com/repos/gruihuang/forseti-security/stargazers","contributors_url":"https://api.github.com/repos/gruihuang/forseti-security/contributors","subscribers_url":"https://api.github.com/repos/gruihuang/forseti-security/subscribers","subscription_url":"https://api.github.com/repos/gruihuang/forseti-security/subscription","commits_url":"https://api.github.com/repos/gruihuang/forseti-security/commits{/sha}","git_commits_url":"https://api.github.com/repos/gruihuang/forseti-security/git/commits{/sha}","comments_url":"https://api.github.com/repos/gruihuang/forseti-security/comments{/number}","issue_comment_url":"https://api.github.com/repos/gruihuang/forseti-security/issues/comments{/number}","contents_url":"https://api.github.com/repos/gruihuang/forseti-security/contents/{+path}","compare_url":"https://api.github.com/repos/gruihuang/forseti-security/compare/{base}...{head}","merges_url":"https://api.github.com/repos/gruihuang/forseti-security/merges","archive_url":"https://api.github.com/repos/gruihuang/forseti-security/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/gruihuang/forseti-security/downloads","issues_url":"https://api.github.com/repos/gruihuang/forseti-security/issues{/number}","pulls_url":"https://api.github.com/repos/gruihuang/forseti-security/pulls{/number}","milestones_url":"https://api.github.com/repos/gruihuang/forseti-security/milestones{/number}","notifications_url":"https://api.github.com/repos/gruihuang/forseti-security/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/gruihuang/forseti-security/labels{/name}","releases_url":"https://api.github.com/repos/gruihuang/forseti-security/releases{/id}","deployments_url":"https://api.github.com/repos/gruihuang/forseti-security/deployments","created_at":"2018-09-26T20:33:11Z","updated_at":"2018-10-04T20:31:09Z","pushed_at":"2018-10-04T20:31:08Z","git_url":"git://github.com/gruihuang/forseti-security.git","ssh_url":"git@github.com:gruihuang/forseti-security.git","clone_url":"https://github.com/gruihuang/forseti-security.git","svn_url":"https://github.com/gruihuang/forseti-security","homepage":"https://forsetisecurity.org","size":28262,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":false,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"dev"}},"base":{"label":"GoogleCloudPlatform:dev","ref":"dev","sha":"9019130517584ad95f47aebddf052864bae3cfca","user":{"login":"GoogleCloudPlatform","id":2810941,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI4MTA5NDE=","avatar_url":"https://avatars0.githubusercontent.com/u/2810941?v=4","gravatar_id":"","url":"https://api.github.com/users/GoogleCloudPlatform","html_url":"https://github.com/GoogleCloudPlatform","followers_url":"https://api.github.com/users/GoogleCloudPlatform/followers","following_url":"https://api.github.com/users/GoogleCloudPlatform/following{/other_user}","gists_url":"https://api.github.com/users/GoogleCloudPlatform/gists{/gist_id}","starred_url":"https://api.github.com/users/GoogleCloudPlatform/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GoogleCloudPlatform/subscriptions","organizations_url":"https://api.github.com/users/GoogleCloudPlatform/orgs","repos_url":"https://api.github.com/users/GoogleCloudPlatform/repos","events_url":"https://api.github.com/users/GoogleCloudPlatform/events{/privacy}","received_events_url":"https://api.github.com/users/GoogleCloudPlatform/received_events","type":"Organization","site_admin":false},"repo":{"id":82210080,"node_id":"MDEwOlJlcG9zaXRvcnk4MjIxMDA4MA==","name":"forseti-security","full_name":"GoogleCloudPlatform/forseti-security","private":false,"owner":{"login":"GoogleCloudPlatform","id":2810941,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI4MTA5NDE=","avatar_url":"https://avatars0.githubusercontent.com/u/2810941?v=4","gravatar_id":"","url":"https://api.github.com/users/GoogleCloudPlatform","html_url":"https://github.com/GoogleCloudPlatform","followers_url":"https://api.github.com/users/GoogleCloudPlatform/followers","following_url":"https://api.github.com/users/GoogleCloudPlatform/following{/other_user}","gists_url":"https://api.github.com/users/GoogleCloudPlatform/gists{/gist_id}","starred_url":"https://api.github.com/users/GoogleCloudPlatform/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GoogleCloudPlatform/subscriptions","organizations_url":"https://api.github.com/users/GoogleCloudPlatform/orgs","repos_url":"https://api.github.com/users/GoogleCloudPlatform/repos","events_url":"https://api.github.com/users/GoogleCloudPlatform/events{/privacy}","received_events_url":"https://api.github.com/users/GoogleCloudPlatform/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/GoogleCloudPlatform/forseti-security","description":"Forseti Security","fork":false,"url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security","forks_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/forks","keys_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/keys{/key_id}","collaborators_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/teams","hooks_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/hooks","issue_events_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/issues/events{/number}","events_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/events","assignees_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/assignees{/user}","branches_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/branches{/branch}","tags_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/tags","blobs_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/git/refs{/sha}","trees_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/git/trees{/sha}","statuses_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/statuses/{sha}","languages_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/languages","stargazers_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/stargazers","contributors_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/contributors","subscribers_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/subscribers","subscription_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/subscription","commits_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/commits{/sha}","git_commits_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/git/commits{/sha}","comments_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/comments{/number}","issue_comment_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/issues/comments{/number}","contents_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/contents/{+path}","compare_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/compare/{base}...{head}","merges_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/merges","archive_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/downloads","issues_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/issues{/number}","pulls_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/pulls{/number}","milestones_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/milestones{/number}","notifications_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/labels{/name}","releases_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/releases{/id}","deployments_url":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/deployments","created_at":"2017-02-16T18:01:26Z","updated_at":"2018-10-05T01:46:30Z","pushed_at":"2018-10-05T18:14:18Z","git_url":"git://github.com/GoogleCloudPlatform/forseti-security.git","ssh_url":"git@github.com:GoogleCloudPlatform/forseti-security.git","clone_url":"https://github.com/GoogleCloudPlatform/forseti-security.git","svn_url":"https://github.com/GoogleCloudPlatform/forseti-security","homepage":"https://forsetisecurity.org","size":29426,"stargazers_count":375,"watchers_count":375,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":false,"has_wiki":false,"has_pages":false,"forks_count":131,"mirror_url":null,"archived":false,"open_issues_count":255,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":131,"open_issues":255,"watchers":375,"default_branch":"dev"}},"_links":{"self":{"href":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/pulls/2077"},"html":{"href":"https://github.com/GoogleCloudPlatform/forseti-security/pull/2077"},"issue":{"href":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/issues/2077"},"comments":{"href":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/issues/2077/comments"},"review_comments":{"href":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/pulls/2077/comments"},"review_comment":{"href":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/pulls/2077/commits"},"statuses":{"href":"https://api.github.com/repos/GoogleCloudPlatform/forseti-security/statuses/420315b9deadd91f865828e20577293223dd8c1e"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"dufton"}}