{"action":"created","comment":{"url":"https://api.github.com/repos/freenas/freenas/pulls/comments/233204371","pull_request_review_id":174564273,"id":233204371,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzMzIwNDM3MQ==","diff_hunk":"@@ -0,0 +1,241 @@\n+import os\n+import re\n+\n+\n+from middlewared.schema import accepts, Bool, Dict, Int, List, Str\n+from middlewared.service import private, SystemServiceService, ValidationErrors\n+from middlewared.validators import IpAddress, Port, UUID, validate_attributes\n+\n+\n+class NetDataService(SystemServiceService):\n+\n+    class Config:\n+        service = 'netdata'\n+        service_model = 'netdataglobalsettings'\n+        service_verb = 'restart'\n+\n+    @private\n+    def list_alarms(self):\n+        path = '/usr/local/etc/netdata/health.d/'\n+        alarms = {}\n+        pattern = re.compile('.*alarm: +(.*)\\n')\n+\n+        for file in [f for f in os.listdir(path) if 'sample' not in f]:\n+            with open(path + file, 'r') as f:\n+                for alarm in re.findall(pattern, f.read()):\n+                    alarms[alarm.strip()] = path + file\n+\n+        return alarms\n+\n+    @private\n+    async def validate_attrs(self, data):\n+        verrors = ValidationErrors()\n+\n+        additional_params = data.get('additional_params')\n+        if additional_params:\n+            # Let's be very generic here and introduce very basic validation\n+            # Expected format is as following\n+            # [ipv6.icmpneighbor]\n+            #   history = 86400\n+            #   enabled = yes\n+            #\n+            # While we are here, we will also introduce basic formatting to the file to ensure\n+            # that we can make it as compliable as possible\n+\n+            param_str = ''\n+            for i in additional_params.split('\\n'):\n+                i = i.strip()\n+                if not i:\n+                    continue\n+                if i.startswith('#'):\n+                    # Let's not validate this\n+                    if i.replace('#', '').startswith('['):\n+                        param_str += f'\\n\\n{i}'\n+                    else:\n+                        param_str += f'\\n\\t{i}'\n+\n+                    continue\n+\n+                if i.startswith('[') and not i.endswith(']'):\n+                    verrors.add(\n+                        'netdata_update.additional_params',\n+                        f'Please correct format for {i}. i.e [system.intr]'\n+                    )\n+                elif not i.startswith('[') and '=' not in i:\n+                    verrors.add(\n+                        'netdata_update.additional_params',\n+                        f'Please correct format for {i}. i.e enabled = yes'\n+                    )\n+\n+                if i.startswith('['):\n+                    param_str += f'\\n\\n{i}'\n+                else:\n+                    param_str += f'\\n\\t{i}'\n+\n+            data['additional_params'] = param_str + '\\n'\n+        else:\n+            # Let's load up the default value for additional params\n+            # This is sort of a rollback to default configuration if a blank string is provided for\n+            # additional params - we will default to the defaults of netdata.conf giving users a chance\n+            # to come back to default configuration if they messed something up pretty bad\n+            with open('/usr/local/etc/netdata/netdata.conf.sample', 'r') as file:\n+                try:\n+                    data['additional_params'] = file.read().split('per plugin configuration')[1]\n+                except IndexError:\n+                    self.logger.debug('Failed to set default value for additional params')\n+\n+        bind_to_ips = data.get('bind')\n+        if bind_to_ips:\n+            valid_ips = [ip['address'] for ip in await self.middleware.call('interfaces.ip_in_use')]\n+            valid_ips.extend(['127.0.0.1', '::1', '0.0.0.0', '*'])\n+\n+            for bind_ip in bind_to_ips:\n+                if bind_ip not in valid_ips:\n+                    verrors.add(\n+                        'netdata_update.bind',\n+                        f'Invalid {bind_ip} bind IP'\n+                    )\n+        else:\n+            verrors.add(\n+                'netdata_update.bind',\n+                'This field is required'\n+            )\n+\n+        update_alarms = data.pop('update_alarms', {})\n+        valid_alarms = await self.middleware.run_in_thread(self.list_alarms)\n+        if update_alarms:\n+            for alarm in update_alarms:\n+                if alarm not in valid_alarms:\n+                    verrors.add(\n+                        'netdata_update.alarms',\n+                        f'{alarm} not a valid alarm'\n+                    )\n+\n+            verrors.extend(\n+                validate_attributes([Bool(key) for key in update_alarms], {'attributes': update_alarms})\n+            )\n+\n+        # Validating streaming metrics now\n+        stream_mode = data.get('stream_mode')\n+        if stream_mode == 'SLAVE':\n+            for key in ('api_key', 'destination'):\n+                if not data.get(key):\n+                    verrors.add(\n+                        f'netdata_update.{key}',\n+                        f'{key} is required with stream mode as SLAVE'\n+                    )\n+\n+            destinations = data.get('destination')\n+            if destinations:\n+                ip_addr = IpAddress()\n+                port = Port()\n+                for dest in destinations:\n+                    ip = dest.split(':')[0]\n+                    try:\n+                        ip_addr(ip)\n+                    except ValueError as e:\n+                        verrors.add(\n+                            'netdata_update.destination',\n+                            str(e)\n+                        )\n+                    else:\n+                        if ':' in dest:\n+                            try:\n+                                port(dest.split(':')[1])\n+                            except ValueError as e:\n+                                verrors.add(\n+                                    'netdata_update.destination',\n+                                    f'Not a valid port: {e}'\n+                                )\n+        elif stream_mode == 'MASTER':\n+            for key in ('allow_from', 'api_key'):\n+                if not data.get(key):\n+                    verrors.add(\n+                        f'netdata_update.{key}',\n+                        f'{key} is required with stream mode as MASTER'\n+                    )\n+\n+        verrors.check()\n+\n+        for alarm in valid_alarms:\n+            # Let's add alarms to our db if they aren't already there\n+            if alarm not in data['alarms']:\n+                data['alarms'][alarm] = True\n+\n+        for alarm in update_alarms:\n+            # These are valid alarms\n+            data['alarms'][alarm] = update_alarms[alarm]\n+\n+        return data\n+\n+    @accepts(\n+        Dict(\n+            'netdata_update',\n+            Str('additional_params'),\n+            Dict(\n+                'alarms',\n+                additional_attrs=True\n+            ),\n+            List('allow_from', items=[Str('pattern')]),\n+            Str('api_key', validators=[UUID()]),\n+            List('bind', items=[Str('bind_ip')]),\n+            Int('port', validators=[Port()]),\n+            List('destination', items=[Str('destination')]),\n+            Int('history'),\n+            Int('http_port_listen_backlog'),\n+            Str('stream_mode', enum=['NONE', 'MASTER', 'SLAVE']),\n+            Int('update_every')\n+        )\n+    )\n+    async def do_update(self, data):\n+        \"\"\"\n+        Update Netdata Service Configuration\n+\n+        `alarms` is a dictionary where user specifies a key,value pair with key being alarm name and value is a boolean\n+        value which represents should the alarm be enabled or not. Middlewared supports interacting (changing) alarms\n+        in /usr/local/etc/netdata/health.d/ directory.\n+\n+        `allow_from` is used when netdata service is expected to be used as a master. It defaults to \"['*']\". This field\n+        expects a list of Netdata patterns which Netdata will use to set restrictions on incoming connections from slave\n+        accordingly.\n+\n+        `api_key` is a valid UUID which can be generated in command line by typing uuidgen.\n+\n+        `destination` is used when netdata service is expected to be used as a slave. Destination is a list of potential\n+        destinations to which netdata should stream metrics. We expect the format to be IP:PORT ( port is optional ).\n+        The first working destination is used by Netdata service.\n+\n+        `history` is the number of entries the netdata daemon will by default keep in memory for each chart dimension.\n+        It defaults to 86400.\n+\n+        .. examples(websocket)::\n+\n+          Update Netdata Service Configuration\n+\n+            :::javascript\n+            {\n+                \"id\": \"6841f242-840a-11e6-a437-00e04d680384\",\n+                \"msg\": \"method\",\n+                \"method\": \"netdata.update\",\n+                \"params\": [{\n+                    \"history\": 80000,\n+                    \"alarms\": {\n+                        \"used_swap\": \"true\",\n+                        \"ram_in_swap\": \"true\"","path":"src/middlewared/middlewared/plugins/netdata.py","position":224,"original_position":224,"commit_id":"038bc9dc39531ca624ab8dd8d12f06367cc28652","original_commit_id":"038bc9dc39531ca624ab8dd8d12f06367cc28652","user":{"login":"sonicaj","id":17968138,"node_id":"MDQ6VXNlcjE3OTY4MTM4","avatar_url":"https://avatars2.githubusercontent.com/u/17968138?v=4","gravatar_id":"","url":"https://api.github.com/users/sonicaj","html_url":"https://github.com/sonicaj","followers_url":"https://api.github.com/users/sonicaj/followers","following_url":"https://api.github.com/users/sonicaj/following{/other_user}","gists_url":"https://api.github.com/users/sonicaj/gists{/gist_id}","starred_url":"https://api.github.com/users/sonicaj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sonicaj/subscriptions","organizations_url":"https://api.github.com/users/sonicaj/orgs","repos_url":"https://api.github.com/users/sonicaj/repos","events_url":"https://api.github.com/users/sonicaj/events{/privacy}","received_events_url":"https://api.github.com/users/sonicaj/received_events","type":"User","site_admin":false},"body":"Actually I have to omit the \"\" from true if I am not wrong","created_at":"2018-11-13T20:10:44Z","updated_at":"2018-11-13T20:10:44Z","html_url":"https://github.com/freenas/freenas/pull/2051#discussion_r233204371","pull_request_url":"https://api.github.com/repos/freenas/freenas/pulls/2051","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/freenas/freenas/pulls/comments/233204371"},"html":{"href":"https://github.com/freenas/freenas/pull/2051#discussion_r233204371"},"pull_request":{"href":"https://api.github.com/repos/freenas/freenas/pulls/2051"}},"in_reply_to_id":233203013},"pull_request":{"url":"https://api.github.com/repos/freenas/freenas/pulls/2051","id":229152720,"node_id":"MDExOlB1bGxSZXF1ZXN0MjI5MTUyNzIw","html_url":"https://github.com/freenas/freenas/pull/2051","diff_url":"https://github.com/freenas/freenas/pull/2051.diff","patch_url":"https://github.com/freenas/freenas/pull/2051.patch","issue_url":"https://api.github.com/repos/freenas/freenas/issues/2051","number":2051,"state":"open","locked":false,"title":"tkt-27734: Configurable Metrics for Netdata Service","user":{"login":"sonicaj","id":17968138,"node_id":"MDQ6VXNlcjE3OTY4MTM4","avatar_url":"https://avatars2.githubusercontent.com/u/17968138?v=4","gravatar_id":"","url":"https://api.github.com/users/sonicaj","html_url":"https://github.com/sonicaj","followers_url":"https://api.github.com/users/sonicaj/followers","following_url":"https://api.github.com/users/sonicaj/following{/other_user}","gists_url":"https://api.github.com/users/sonicaj/gists{/gist_id}","starred_url":"https://api.github.com/users/sonicaj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sonicaj/subscriptions","organizations_url":"https://api.github.com/users/sonicaj/orgs","repos_url":"https://api.github.com/users/sonicaj/repos","events_url":"https://api.github.com/users/sonicaj/events{/privacy}","received_events_url":"https://api.github.com/users/sonicaj/received_events","type":"User","site_admin":false},"body":"This PR aims to introduce configurable metrics to the Netdata service.\r\nTicket: #27734","created_at":"2018-11-07T20:06:48Z","updated_at":"2018-11-13T20:10:44Z","closed_at":null,"merged_at":null,"merge_commit_sha":"cf4e55eb7aa108bf57d6525e3ea78b2b10421960","assignee":{"login":"sonicaj","id":17968138,"node_id":"MDQ6VXNlcjE3OTY4MTM4","avatar_url":"https://avatars2.githubusercontent.com/u/17968138?v=4","gravatar_id":"","url":"https://api.github.com/users/sonicaj","html_url":"https://github.com/sonicaj","followers_url":"https://api.github.com/users/sonicaj/followers","following_url":"https://api.github.com/users/sonicaj/following{/other_user}","gists_url":"https://api.github.com/users/sonicaj/gists{/gist_id}","starred_url":"https://api.github.com/users/sonicaj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sonicaj/subscriptions","organizations_url":"https://api.github.com/users/sonicaj/orgs","repos_url":"https://api.github.com/users/sonicaj/repos","events_url":"https://api.github.com/users/sonicaj/events{/privacy}","received_events_url":"https://api.github.com/users/sonicaj/received_events","type":"User","site_admin":false},"assignees":[{"login":"sonicaj","id":17968138,"node_id":"MDQ6VXNlcjE3OTY4MTM4","avatar_url":"https://avatars2.githubusercontent.com/u/17968138?v=4","gravatar_id":"","url":"https://api.github.com/users/sonicaj","html_url":"https://github.com/sonicaj","followers_url":"https://api.github.com/users/sonicaj/followers","following_url":"https://api.github.com/users/sonicaj/following{/other_user}","gists_url":"https://api.github.com/users/sonicaj/gists{/gist_id}","starred_url":"https://api.github.com/users/sonicaj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sonicaj/subscriptions","organizations_url":"https://api.github.com/users/sonicaj/orgs","repos_url":"https://api.github.com/users/sonicaj/repos","events_url":"https://api.github.com/users/sonicaj/events{/privacy}","received_events_url":"https://api.github.com/users/sonicaj/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/freenas/freenas/pulls/2051/commits","review_comments_url":"https://api.github.com/repos/freenas/freenas/pulls/2051/comments","review_comment_url":"https://api.github.com/repos/freenas/freenas/pulls/comments{/number}","comments_url":"https://api.github.com/repos/freenas/freenas/issues/2051/comments","statuses_url":"https://api.github.com/repos/freenas/freenas/statuses/038bc9dc39531ca624ab8dd8d12f06367cc28652","head":{"label":"freenas:ticket-27734","ref":"ticket-27734","sha":"038bc9dc39531ca624ab8dd8d12f06367cc28652","user":{"login":"freenas","id":3792476,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3OTI0NzY=","avatar_url":"https://avatars2.githubusercontent.com/u/3792476?v=4","gravatar_id":"","url":"https://api.github.com/users/freenas","html_url":"https://github.com/freenas","followers_url":"https://api.github.com/users/freenas/followers","following_url":"https://api.github.com/users/freenas/following{/other_user}","gists_url":"https://api.github.com/users/freenas/gists{/gist_id}","starred_url":"https://api.github.com/users/freenas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/freenas/subscriptions","organizations_url":"https://api.github.com/users/freenas/orgs","repos_url":"https://api.github.com/users/freenas/repos","events_url":"https://api.github.com/users/freenas/events{/privacy}","received_events_url":"https://api.github.com/users/freenas/received_events","type":"Organization","site_admin":false},"repo":{"id":8627760,"node_id":"MDEwOlJlcG9zaXRvcnk4NjI3NzYw","name":"freenas","full_name":"freenas/freenas","private":false,"owner":{"login":"freenas","id":3792476,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3OTI0NzY=","avatar_url":"https://avatars2.githubusercontent.com/u/3792476?v=4","gravatar_id":"","url":"https://api.github.com/users/freenas","html_url":"https://github.com/freenas","followers_url":"https://api.github.com/users/freenas/followers","following_url":"https://api.github.com/users/freenas/following{/other_user}","gists_url":"https://api.github.com/users/freenas/gists{/gist_id}","starred_url":"https://api.github.com/users/freenas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/freenas/subscriptions","organizations_url":"https://api.github.com/users/freenas/orgs","repos_url":"https://api.github.com/users/freenas/repos","events_url":"https://api.github.com/users/freenas/events{/privacy}","received_events_url":"https://api.github.com/users/freenas/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/freenas/freenas","description":"FreeNAS Git Repository","fork":false,"url":"https://api.github.com/repos/freenas/freenas","forks_url":"https://api.github.com/repos/freenas/freenas/forks","keys_url":"https://api.github.com/repos/freenas/freenas/keys{/key_id}","collaborators_url":"https://api.github.com/repos/freenas/freenas/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/freenas/freenas/teams","hooks_url":"https://api.github.com/repos/freenas/freenas/hooks","issue_events_url":"https://api.github.com/repos/freenas/freenas/issues/events{/number}","events_url":"https://api.github.com/repos/freenas/freenas/events","assignees_url":"https://api.github.com/repos/freenas/freenas/assignees{/user}","branches_url":"https://api.github.com/repos/freenas/freenas/branches{/branch}","tags_url":"https://api.github.com/repos/freenas/freenas/tags","blobs_url":"https://api.github.com/repos/freenas/freenas/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/freenas/freenas/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/freenas/freenas/git/refs{/sha}","trees_url":"https://api.github.com/repos/freenas/freenas/git/trees{/sha}","statuses_url":"https://api.github.com/repos/freenas/freenas/statuses/{sha}","languages_url":"https://api.github.com/repos/freenas/freenas/languages","stargazers_url":"https://api.github.com/repos/freenas/freenas/stargazers","contributors_url":"https://api.github.com/repos/freenas/freenas/contributors","subscribers_url":"https://api.github.com/repos/freenas/freenas/subscribers","subscription_url":"https://api.github.com/repos/freenas/freenas/subscription","commits_url":"https://api.github.com/repos/freenas/freenas/commits{/sha}","git_commits_url":"https://api.github.com/repos/freenas/freenas/git/commits{/sha}","comments_url":"https://api.github.com/repos/freenas/freenas/comments{/number}","issue_comment_url":"https://api.github.com/repos/freenas/freenas/issues/comments{/number}","contents_url":"https://api.github.com/repos/freenas/freenas/contents/{+path}","compare_url":"https://api.github.com/repos/freenas/freenas/compare/{base}...{head}","merges_url":"https://api.github.com/repos/freenas/freenas/merges","archive_url":"https://api.github.com/repos/freenas/freenas/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/freenas/freenas/downloads","issues_url":"https://api.github.com/repos/freenas/freenas/issues{/number}","pulls_url":"https://api.github.com/repos/freenas/freenas/pulls{/number}","milestones_url":"https://api.github.com/repos/freenas/freenas/milestones{/number}","notifications_url":"https://api.github.com/repos/freenas/freenas/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/freenas/freenas/labels{/name}","releases_url":"https://api.github.com/repos/freenas/freenas/releases{/id}","deployments_url":"https://api.github.com/repos/freenas/freenas/deployments","created_at":"2013-03-07T13:23:20Z","updated_at":"2018-11-13T09:17:14Z","pushed_at":"2018-11-13T19:24:17Z","git_url":"git://github.com/freenas/freenas.git","ssh_url":"git@github.com:freenas/freenas.git","clone_url":"https://github.com/freenas/freenas.git","svn_url":"https://github.com/freenas/freenas","homepage":"http://www.freenas.org","size":531334,"stargazers_count":1071,"watchers_count":1071,"language":"Python","has_issues":false,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":299,"mirror_url":null,"archived":false,"open_issues_count":19,"license":null,"forks":299,"open_issues":19,"watchers":1071,"default_branch":"master"}},"base":{"label":"freenas:master","ref":"master","sha":"756f93ff22b56626e8b6e55df50bcb613a011cc7","user":{"login":"freenas","id":3792476,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3OTI0NzY=","avatar_url":"https://avatars2.githubusercontent.com/u/3792476?v=4","gravatar_id":"","url":"https://api.github.com/users/freenas","html_url":"https://github.com/freenas","followers_url":"https://api.github.com/users/freenas/followers","following_url":"https://api.github.com/users/freenas/following{/other_user}","gists_url":"https://api.github.com/users/freenas/gists{/gist_id}","starred_url":"https://api.github.com/users/freenas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/freenas/subscriptions","organizations_url":"https://api.github.com/users/freenas/orgs","repos_url":"https://api.github.com/users/freenas/repos","events_url":"https://api.github.com/users/freenas/events{/privacy}","received_events_url":"https://api.github.com/users/freenas/received_events","type":"Organization","site_admin":false},"repo":{"id":8627760,"node_id":"MDEwOlJlcG9zaXRvcnk4NjI3NzYw","name":"freenas","full_name":"freenas/freenas","private":false,"owner":{"login":"freenas","id":3792476,"node_id":"MDEyOk9yZ2FuaXphdGlvbjM3OTI0NzY=","avatar_url":"https://avatars2.githubusercontent.com/u/3792476?v=4","gravatar_id":"","url":"https://api.github.com/users/freenas","html_url":"https://github.com/freenas","followers_url":"https://api.github.com/users/freenas/followers","following_url":"https://api.github.com/users/freenas/following{/other_user}","gists_url":"https://api.github.com/users/freenas/gists{/gist_id}","starred_url":"https://api.github.com/users/freenas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/freenas/subscriptions","organizations_url":"https://api.github.com/users/freenas/orgs","repos_url":"https://api.github.com/users/freenas/repos","events_url":"https://api.github.com/users/freenas/events{/privacy}","received_events_url":"https://api.github.com/users/freenas/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/freenas/freenas","description":"FreeNAS Git Repository","fork":false,"url":"https://api.github.com/repos/freenas/freenas","forks_url":"https://api.github.com/repos/freenas/freenas/forks","keys_url":"https://api.github.com/repos/freenas/freenas/keys{/key_id}","collaborators_url":"https://api.github.com/repos/freenas/freenas/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/freenas/freenas/teams","hooks_url":"https://api.github.com/repos/freenas/freenas/hooks","issue_events_url":"https://api.github.com/repos/freenas/freenas/issues/events{/number}","events_url":"https://api.github.com/repos/freenas/freenas/events","assignees_url":"https://api.github.com/repos/freenas/freenas/assignees{/user}","branches_url":"https://api.github.com/repos/freenas/freenas/branches{/branch}","tags_url":"https://api.github.com/repos/freenas/freenas/tags","blobs_url":"https://api.github.com/repos/freenas/freenas/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/freenas/freenas/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/freenas/freenas/git/refs{/sha}","trees_url":"https://api.github.com/repos/freenas/freenas/git/trees{/sha}","statuses_url":"https://api.github.com/repos/freenas/freenas/statuses/{sha}","languages_url":"https://api.github.com/repos/freenas/freenas/languages","stargazers_url":"https://api.github.com/repos/freenas/freenas/stargazers","contributors_url":"https://api.github.com/repos/freenas/freenas/contributors","subscribers_url":"https://api.github.com/repos/freenas/freenas/subscribers","subscription_url":"https://api.github.com/repos/freenas/freenas/subscription","commits_url":"https://api.github.com/repos/freenas/freenas/commits{/sha}","git_commits_url":"https://api.github.com/repos/freenas/freenas/git/commits{/sha}","comments_url":"https://api.github.com/repos/freenas/freenas/comments{/number}","issue_comment_url":"https://api.github.com/repos/freenas/freenas/issues/comments{/number}","contents_url":"https://api.github.com/repos/freenas/freenas/contents/{+path}","compare_url":"https://api.github.com/repos/freenas/freenas/compare/{base}...{head}","merges_url":"https://api.github.com/repos/freenas/freenas/merges","archive_url":"https://api.github.com/repos/freenas/freenas/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/freenas/freenas/downloads","issues_url":"https://api.github.com/repos/freenas/freenas/issues{/number}","pulls_url":"https://api.github.com/repos/freenas/freenas/pulls{/number}","milestones_url":"https://api.github.com/repos/freenas/freenas/milestones{/number}","notifications_url":"https://api.github.com/repos/freenas/freenas/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/freenas/freenas/labels{/name}","releases_url":"https://api.github.com/repos/freenas/freenas/releases{/id}","deployments_url":"https://api.github.com/repos/freenas/freenas/deployments","created_at":"2013-03-07T13:23:20Z","updated_at":"2018-11-13T09:17:14Z","pushed_at":"2018-11-13T19:24:17Z","git_url":"git://github.com/freenas/freenas.git","ssh_url":"git@github.com:freenas/freenas.git","clone_url":"https://github.com/freenas/freenas.git","svn_url":"https://github.com/freenas/freenas","homepage":"http://www.freenas.org","size":531334,"stargazers_count":1071,"watchers_count":1071,"language":"Python","has_issues":false,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":299,"mirror_url":null,"archived":false,"open_issues_count":19,"license":null,"forks":299,"open_issues":19,"watchers":1071,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/freenas/freenas/pulls/2051"},"html":{"href":"https://github.com/freenas/freenas/pull/2051"},"issue":{"href":"https://api.github.com/repos/freenas/freenas/issues/2051"},"comments":{"href":"https://api.github.com/repos/freenas/freenas/issues/2051/comments"},"review_comments":{"href":"https://api.github.com/repos/freenas/freenas/pulls/2051/comments"},"review_comment":{"href":"https://api.github.com/repos/freenas/freenas/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/freenas/freenas/pulls/2051/commits"},"statuses":{"href":"https://api.github.com/repos/freenas/freenas/statuses/038bc9dc39531ca624ab8dd8d12f06367cc28652"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"sonicaj"}}