{"action":"created","comment":{"url":"https://api.github.com/repos/romana/core/pulls/comments/66379866","id":66379866,"diff_hunk":"@@ -0,0 +1,406 @@\n+// Copyright (c) 2016 Pani Networks\n+// All Rights Reserved.\n+//\n+// Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n+// not use this file except in compliance with the License. You may obtain\n+// 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, WITHOUT\n+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n+// License for the specific language governing permissions and limitations\n+// under the License.\n+\n+package policy\n+\n+import (\n+\t\"fmt\"\n+\t\"github.com/romana/core/common\"\n+\t\"github.com/romana/core/tenant\"\n+\t\"log\"\n+\t\"strconv\"\n+\t\"strings\"\n+)\n+\n+// Policy provides Policy service.\n+type PolicySvc struct {\n+\tclient *common.RestClient\n+\tconfig common.ServiceConfig\n+\tstore  policyStore\n+}\n+\n+const (\n+\tinfoListPath       = \"/info\"\n+\tfindPath           = \"/find\"\n+\tpoliciesPath       = \"/policies\"\n+\tpolicyNameQueryVar = \"policyName\"\n+)\n+\n+func (policy *PolicySvc) Routes() common.Routes {\n+\troutes := common.Routes{\n+\t\tcommon.Route{\n+\t\t\tMethod:          \"POST\",\n+\t\t\tPattern:         policiesPath,\n+\t\t\tHandler:         policy.addPolicy,\n+\t\t\tMakeMessage:     func() interface{} { return &common.Policy{} },\n+\t\t\tUseRequestToken: false,\n+\t\t},\n+\t\tcommon.Route{\n+\t\t\tMethod:          \"DELETE\",\n+\t\t\tPattern:         policiesPath,\n+\t\t\tHandler:         policy.deletePolicyHandler,\n+\t\t\tMakeMessage:     func() interface{} { return &common.Policy{} },\n+\t\t\tUseRequestToken: false,\n+\t\t},\n+\t\tcommon.Route{\n+\t\t\tMethod:          \"DELETE\",\n+\t\t\tPattern:         policiesPath + \"/{policyID}\",\n+\t\t\tHandler:         policy.deletePolicyHandler,\n+\t\t\tMakeMessage:     func() interface{} { return &common.Policy{} },\n+\t\t\tUseRequestToken: false,\n+\t\t},\n+\t\tcommon.Route{\n+\t\t\tMethod:          \"GET\",\n+\t\t\tPattern:         policiesPath,\n+\t\t\tHandler:         policy.listPolicies,\n+\t\t\tMakeMessage:     nil,\n+\t\t\tUseRequestToken: false,\n+\t\t},\n+\t\tcommon.Route{\n+\t\t\tMethod:          \"GET\",\n+\t\t\tPattern:         policiesPath + \"/{policyID}\",\n+\t\t\tHandler:         policy.getPolicy,\n+\t\t\tMakeMessage:     nil,\n+\t\t\tUseRequestToken: false,\n+\t\t},\n+\t\tcommon.Route{\n+\t\t\tMethod:  \"GET\",\n+\t\t\tPattern: findPath + policiesPath + \"/{policyName}\",\n+\t\t\tHandler: policy.findPolicyByName,\n+\t\t},\n+\t}\n+\treturn routes\n+}\n+\n+// augmentEndpoint augments the endpoint provided with appropriate information\n+// by looking it up in the appropriate service.\n+func (policy *PolicySvc) augmentEndpoint(endpoint *common.Endpoint) error {\n+\ttenantSvcUrl, err := policy.client.GetServiceUrl(\"tenant\")\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\n+\t// TODO this will have to be changed once we implement\n+\t// https://paninetworks.kanbanize.com/ctrl_board/3/cards/319/details\n+\tvar tenantIDToUse string\n+\tten := &tenant.Tenant{}\n+\tif endpoint.TenantID != 0 {\n+\t\ttenantIDToUse = strconv.FormatUint(endpoint.TenantID, 10)\n+\t} else if endpoint.TenantExternalID != \"\" {\n+\t\ttenantIDToUse = endpoint.TenantExternalID\n+\t}\n+\tif tenantIDToUse != \"\" {\n+\t\ttenantsUrl := fmt.Sprintf(\"%s/tenants/%s\", tenantSvcUrl, tenantIDToUse)\n+\t\terr = policy.client.Get(tenantsUrl, &ten)\n+\t\tif err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t\tendpoint.TenantNetworkID = &ten.Seq\n+\t\tlog.Printf(\"Net ID from %s: %d\", tenantsUrl, *endpoint.TenantNetworkID)\n+\t}\n+\n+\tvar segmentIDToUse string\n+\tif endpoint.SegmentID != 0 {\n+\t\tsegmentIDToUse = strconv.FormatUint(endpoint.SegmentID, 10)\n+\t} else if endpoint.SegmentExternalID != \"\" {\n+\t\tsegmentIDToUse = endpoint.SegmentExternalID\n+\t}\n+\tif segmentIDToUse != \"\" {\n+\t\ttenantsUrl := fmt.Sprintf(\"%s/tenants/%d/segments/%s\", tenantSvcUrl, ten.ID, segmentIDToUse)\n+\t\tsegment := &tenant.Segment{}\n+\t\terr = policy.client.Get(tenantsUrl, &segment)\n+\t\tif err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t\tendpoint.SegmentNetworkID = &segment.Seq\n+\t\tlog.Printf(\"Net ID from %s: %d\", tenantsUrl, *endpoint.SegmentNetworkID)\n+\t}\n+\n+\treturn nil\n+}\n+\n+// augmentPolicy augments the provided policy with information gotten from\n+// various services.\n+func (policy *PolicySvc) augmentPolicy(policyDoc *common.Policy) error {\n+\t// Get info from topology service\n+\ttopoUrl, err := policy.client.GetServiceUrl(\"topology\")\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\n+\t// Query topology for data center information\n+\t// TODO move this to root\n+\tindex := common.IndexResponse{}\n+\terr = policy.client.Get(topoUrl, &index)\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\n+\tdcURL := index.Links.FindByRel(\"datacenter\")\n+\tdc := common.Datacenter{}\n+\terr = policy.client.Get(dcURL, &dc)\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\tlog.Printf(\"Policy server received datacenter information from topology service: %s\\n\", dc)\n+\tpolicyDoc.Datacenter = dc\n+\n+\tfor i, _ := range policyDoc.Rules {\n+\t\trule := &policyDoc.Rules[i]\n+\t\trule.Protocol = strings.ToUpper(rule.Protocol)\n+\t}\n+\n+\tfor i, _ := range policyDoc.AppliedTo {\n+\t\tendpoint := &policyDoc.AppliedTo[i]\n+\t\terr = policy.augmentEndpoint(endpoint)\n+\t\tif err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t}\n+\n+\tfor i, _ := range policyDoc.Peers {\n+\t\tendpoint := &policyDoc.Peers[i]\n+\t\terr = policy.augmentEndpoint(endpoint)\n+\t\tif err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t}\n+\treturn nil\n+}\n+\n+// distributePolicy distributes policy to all agents.\n+// TODO how should error handling work here really?\n+func (policy *PolicySvc) distributePolicy(policyDoc *common.Policy) error {\n+\thosts, err := policy.client.ListHosts()\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\terrStr := make([]string, 0)\n+\tfor _, host := range hosts {\n+\t\t// TODO make schema configurable\n+\t\turl := fmt.Sprintf(\"http://%s:%d/policies\", host.Ip, host.AgentPort)\n+\t\tlog.Printf(\"Sending policy %s to agent at %s\", policyDoc.Name, url)\n+\t\tresult := make(map[string]interface{})\n+\t\terr = policy.client.Post(url, policyDoc, result)\n+\t\tlog.Printf(\"Agent at %s returned %v\", host.Ip, result)\n+\t\tif err != nil {\n+\t\t\terrStr = append(errStr, fmt.Sprintf(\"Error applying policy %d to host %s: %v. \", policyDoc.ID, host.Ip, err))\n+\t\t}\n+\t}\n+\tif len(errStr) > 0 {\n+\t\treturn common.NewError500(errStr)\n+\t}\n+\treturn nil\n+}\n+\n+func (policy *PolicySvc) getPolicy(input interface{}, ctx common.RestContext) (interface{}, error) {\n+\tidStr := ctx.PathVariables[\"policyID\"]\n+\tid, err := strconv.ParseUint(idStr, 10, 64)\n+\tif err != nil {\n+\t\treturn nil, common.NewError404(\"policy\", idStr)\n+\t}\n+\tpolicyDoc, err := policy.store.getPolicy(id, false)\n+\tlog.Printf(\"Found policy for ID %d: %s (%v)\", id, policyDoc, err)\n+\treturn policyDoc, err\n+}\n+\n+func (policy *PolicySvc) deletePolicyHandler(input interface{}, ctx common.RestContext) (interface{}, error) {\n+\tidStr := strings.TrimSpace(ctx.PathVariables[\"policyID\"])\n+\tif idStr == \"\" {\n+\t\tif input == nil {\n+\t\t\treturn nil, common.NewError400(\"Request must either be to /policies/{policyID} or have a body.\")\n+\t\t}\n+\t\tpolicyDoc := input.(*common.Policy)\n+\t\terr := policyDoc.Validate()\n+\t\tif err != nil {\n+\t\t\treturn nil, err\n+\t\t}\n+\t\tid, err := policy.store.lookupPolicy(policyDoc.ExternalID, policyDoc.Datacenter.Id)\n+\t\tlog.Printf(\"Found %d / %v (%T) from external ID %s\", id, err, err, policyDoc.ExternalID)\n+\t\tif err != nil {\n+\t\t\treturn nil, err\n+\t\t}\n+\t\treturn policy.deletePolicy(id)\n+\t} else {\n+\t\tif input != nil {\n+\t\t\tcommon.NewError400(\"Request must either be to /policies/{policyID} or have a body.\")\n+\t\t}\n+\t\tid, err := strconv.ParseUint(idStr, 10, 64)\n+\t\tif err != nil {\n+\t\t\treturn nil, common.NewError404(\"policy\", idStr)\n+\t\t}\n+\t\treturn policy.deletePolicy(id)\n+\t}\n+}\n+\n+// deletePolicy deletes policy based the following algorithm:\n+//1. Mark the policy as \"deleted\" in the backend store.\n+func (policy *PolicySvc) deletePolicy(id uint64) (interface{}, error) {\n+\t// TODO do we need this to be transactional or not ... case can be made for either.\n+\terr := policy.store.inactivatePolicy(id)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tpolicyDoc, err := policy.store.getPolicy(id, true)\n+\tlog.Printf(\"Found policy for ID %d: %s (%v)\", id, policyDoc, err)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\thosts, err := policy.client.ListHosts()\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\terrStr := make([]string, 0)\n+\tfor _, host := range hosts {\n+\t\t// TODO make schema configurable\n+\t\turl := fmt.Sprintf(\"http://%s:%d/policies\", host.Ip, host.AgentPort)\n+\t\tresult := make(map[string]interface{})\n+\t\terr = policy.client.Delete(url, policyDoc, result)\n+\t\tlog.Printf(\"Agent at %s returned %v\", host.Ip, result)\n+\t\tif err != nil {\n+\t\t\terrStr = append(errStr, fmt.Sprintf(\"Error deleting policy %d (%s) from host %s: %v. \", id, policyDoc.Name, host.Ip, err))\n+\t\t}\n+\t}\n+\tif len(errStr) > 0 {\n+\t\treturn nil, common.NewError500(errStr)\n+\t}\n+\terr = policy.store.deletePolicy(id)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\treturn policyDoc, nil\n+}\n+\n+// deletePolicy deletes policy...","path":"policy/policy.go","position":286,"original_position":286,"commit_id":"ba08d8a79c08814b2373fa38dcbb1c8464e4b9b9","original_commit_id":"ba08d8a79c08814b2373fa38dcbb1c8464e4b9b9","user":{"login":"jbrendel","id":233741,"avatar_url":"https://avatars.githubusercontent.com/u/233741?v=3","gravatar_id":"","url":"https://api.github.com/users/jbrendel","html_url":"https://github.com/jbrendel","followers_url":"https://api.github.com/users/jbrendel/followers","following_url":"https://api.github.com/users/jbrendel/following{/other_user}","gists_url":"https://api.github.com/users/jbrendel/gists{/gist_id}","starred_url":"https://api.github.com/users/jbrendel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jbrendel/subscriptions","organizations_url":"https://api.github.com/users/jbrendel/orgs","repos_url":"https://api.github.com/users/jbrendel/repos","events_url":"https://api.github.com/users/jbrendel/events{/privacy}","received_events_url":"https://api.github.com/users/jbrendel/received_events","type":"User","site_admin":false},"body":"docstring wrong","created_at":"2016-06-09T04:05:40Z","updated_at":"2016-06-09T04:05:40Z","html_url":"https://github.com/romana/core/pull/102#discussion_r66379866","pull_request_url":"https://api.github.com/repos/romana/core/pulls/102","_links":{"self":{"href":"https://api.github.com/repos/romana/core/pulls/comments/66379866"},"html":{"href":"https://github.com/romana/core/pull/102#discussion_r66379866"},"pull_request":{"href":"https://api.github.com/repos/romana/core/pulls/102"}}},"pull_request":{"url":"https://api.github.com/repos/romana/core/pulls/102","id":72833418,"html_url":"https://github.com/romana/core/pull/102","diff_url":"https://github.com/romana/core/pull/102.diff","patch_url":"https://github.com/romana/core/pull/102.patch","issue_url":"https://api.github.com/repos/romana/core/issues/102","number":102,"state":"closed","locked":false,"title":"Policy Service with various support utilities.","user":{"login":"pritesh","id":9474171,"avatar_url":"https://avatars.githubusercontent.com/u/9474171?v=3","gravatar_id":"","url":"https://api.github.com/users/pritesh","html_url":"https://github.com/pritesh","followers_url":"https://api.github.com/users/pritesh/followers","following_url":"https://api.github.com/users/pritesh/following{/other_user}","gists_url":"https://api.github.com/users/pritesh/gists{/gist_id}","starred_url":"https://api.github.com/users/pritesh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pritesh/subscriptions","organizations_url":"https://api.github.com/users/pritesh/orgs","repos_url":"https://api.github.com/users/pritesh/repos","events_url":"https://api.github.com/users/pritesh/events{/privacy}","received_events_url":"https://api.github.com/users/pritesh/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-06-07T08:20:51Z","updated_at":"2016-06-09T04:05:40Z","closed_at":"2016-06-08T21:21:45Z","merged_at":"2016-06-08T21:21:45Z","merge_commit_sha":"073d64e220e5135becf1d011bd690bfc4c2acb9c","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/romana/core/pulls/102/commits","review_comments_url":"https://api.github.com/repos/romana/core/pulls/102/comments","review_comment_url":"https://api.github.com/repos/romana/core/pulls/comments{/number}","comments_url":"https://api.github.com/repos/romana/core/issues/102/comments","statuses_url":"https://api.github.com/repos/romana/core/statuses/ba08d8a79c08814b2373fa38dcbb1c8464e4b9b9","head":{"label":"romana:project/policy-vectors","ref":"project/policy-vectors","sha":"ba08d8a79c08814b2373fa38dcbb1c8464e4b9b9","user":{"login":"romana","id":9849297,"avatar_url":"https://avatars.githubusercontent.com/u/9849297?v=3","gravatar_id":"","url":"https://api.github.com/users/romana","html_url":"https://github.com/romana","followers_url":"https://api.github.com/users/romana/followers","following_url":"https://api.github.com/users/romana/following{/other_user}","gists_url":"https://api.github.com/users/romana/gists{/gist_id}","starred_url":"https://api.github.com/users/romana/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/romana/subscriptions","organizations_url":"https://api.github.com/users/romana/orgs","repos_url":"https://api.github.com/users/romana/repos","events_url":"https://api.github.com/users/romana/events{/privacy}","received_events_url":"https://api.github.com/users/romana/received_events","type":"Organization","site_admin":false},"repo":{"id":43855674,"name":"core","full_name":"romana/core","owner":{"login":"romana","id":9849297,"avatar_url":"https://avatars.githubusercontent.com/u/9849297?v=3","gravatar_id":"","url":"https://api.github.com/users/romana","html_url":"https://github.com/romana","followers_url":"https://api.github.com/users/romana/followers","following_url":"https://api.github.com/users/romana/following{/other_user}","gists_url":"https://api.github.com/users/romana/gists{/gist_id}","starred_url":"https://api.github.com/users/romana/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/romana/subscriptions","organizations_url":"https://api.github.com/users/romana/orgs","repos_url":"https://api.github.com/users/romana/repos","events_url":"https://api.github.com/users/romana/events{/privacy}","received_events_url":"https://api.github.com/users/romana/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/romana/core","description":"Romana core components - Micro services written in Go.","fork":false,"url":"https://api.github.com/repos/romana/core","forks_url":"https://api.github.com/repos/romana/core/forks","keys_url":"https://api.github.com/repos/romana/core/keys{/key_id}","collaborators_url":"https://api.github.com/repos/romana/core/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/romana/core/teams","hooks_url":"https://api.github.com/repos/romana/core/hooks","issue_events_url":"https://api.github.com/repos/romana/core/issues/events{/number}","events_url":"https://api.github.com/repos/romana/core/events","assignees_url":"https://api.github.com/repos/romana/core/assignees{/user}","branches_url":"https://api.github.com/repos/romana/core/branches{/branch}","tags_url":"https://api.github.com/repos/romana/core/tags","blobs_url":"https://api.github.com/repos/romana/core/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/romana/core/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/romana/core/git/refs{/sha}","trees_url":"https://api.github.com/repos/romana/core/git/trees{/sha}","statuses_url":"https://api.github.com/repos/romana/core/statuses/{sha}","languages_url":"https://api.github.com/repos/romana/core/languages","stargazers_url":"https://api.github.com/repos/romana/core/stargazers","contributors_url":"https://api.github.com/repos/romana/core/contributors","subscribers_url":"https://api.github.com/repos/romana/core/subscribers","subscription_url":"https://api.github.com/repos/romana/core/subscription","commits_url":"https://api.github.com/repos/romana/core/commits{/sha}","git_commits_url":"https://api.github.com/repos/romana/core/git/commits{/sha}","comments_url":"https://api.github.com/repos/romana/core/comments{/number}","issue_comment_url":"https://api.github.com/repos/romana/core/issues/comments{/number}","contents_url":"https://api.github.com/repos/romana/core/contents/{+path}","compare_url":"https://api.github.com/repos/romana/core/compare/{base}...{head}","merges_url":"https://api.github.com/repos/romana/core/merges","archive_url":"https://api.github.com/repos/romana/core/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/romana/core/downloads","issues_url":"https://api.github.com/repos/romana/core/issues{/number}","pulls_url":"https://api.github.com/repos/romana/core/pulls{/number}","milestones_url":"https://api.github.com/repos/romana/core/milestones{/number}","notifications_url":"https://api.github.com/repos/romana/core/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/romana/core/labels{/name}","releases_url":"https://api.github.com/repos/romana/core/releases{/id}","deployments_url":"https://api.github.com/repos/romana/core/deployments","created_at":"2015-10-08T01:45:35Z","updated_at":"2016-06-03T17:41:07Z","pushed_at":"2016-06-09T01:12:54Z","git_url":"git://github.com/romana/core.git","ssh_url":"git@github.com:romana/core.git","clone_url":"https://github.com/romana/core.git","svn_url":"https://github.com/romana/core","homepage":"","size":4937,"stargazers_count":12,"watchers_count":12,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":3,"mirror_url":null,"open_issues_count":8,"forks":3,"open_issues":8,"watchers":12,"default_branch":"master"}},"base":{"label":"romana:master","ref":"master","sha":"9ef31ea50ffe7bf0ba8de406acad2bf5e8cb2470","user":{"login":"romana","id":9849297,"avatar_url":"https://avatars.githubusercontent.com/u/9849297?v=3","gravatar_id":"","url":"https://api.github.com/users/romana","html_url":"https://github.com/romana","followers_url":"https://api.github.com/users/romana/followers","following_url":"https://api.github.com/users/romana/following{/other_user}","gists_url":"https://api.github.com/users/romana/gists{/gist_id}","starred_url":"https://api.github.com/users/romana/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/romana/subscriptions","organizations_url":"https://api.github.com/users/romana/orgs","repos_url":"https://api.github.com/users/romana/repos","events_url":"https://api.github.com/users/romana/events{/privacy}","received_events_url":"https://api.github.com/users/romana/received_events","type":"Organization","site_admin":false},"repo":{"id":43855674,"name":"core","full_name":"romana/core","owner":{"login":"romana","id":9849297,"avatar_url":"https://avatars.githubusercontent.com/u/9849297?v=3","gravatar_id":"","url":"https://api.github.com/users/romana","html_url":"https://github.com/romana","followers_url":"https://api.github.com/users/romana/followers","following_url":"https://api.github.com/users/romana/following{/other_user}","gists_url":"https://api.github.com/users/romana/gists{/gist_id}","starred_url":"https://api.github.com/users/romana/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/romana/subscriptions","organizations_url":"https://api.github.com/users/romana/orgs","repos_url":"https://api.github.com/users/romana/repos","events_url":"https://api.github.com/users/romana/events{/privacy}","received_events_url":"https://api.github.com/users/romana/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/romana/core","description":"Romana core components - Micro services written in Go.","fork":false,"url":"https://api.github.com/repos/romana/core","forks_url":"https://api.github.com/repos/romana/core/forks","keys_url":"https://api.github.com/repos/romana/core/keys{/key_id}","collaborators_url":"https://api.github.com/repos/romana/core/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/romana/core/teams","hooks_url":"https://api.github.com/repos/romana/core/hooks","issue_events_url":"https://api.github.com/repos/romana/core/issues/events{/number}","events_url":"https://api.github.com/repos/romana/core/events","assignees_url":"https://api.github.com/repos/romana/core/assignees{/user}","branches_url":"https://api.github.com/repos/romana/core/branches{/branch}","tags_url":"https://api.github.com/repos/romana/core/tags","blobs_url":"https://api.github.com/repos/romana/core/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/romana/core/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/romana/core/git/refs{/sha}","trees_url":"https://api.github.com/repos/romana/core/git/trees{/sha}","statuses_url":"https://api.github.com/repos/romana/core/statuses/{sha}","languages_url":"https://api.github.com/repos/romana/core/languages","stargazers_url":"https://api.github.com/repos/romana/core/stargazers","contributors_url":"https://api.github.com/repos/romana/core/contributors","subscribers_url":"https://api.github.com/repos/romana/core/subscribers","subscription_url":"https://api.github.com/repos/romana/core/subscription","commits_url":"https://api.github.com/repos/romana/core/commits{/sha}","git_commits_url":"https://api.github.com/repos/romana/core/git/commits{/sha}","comments_url":"https://api.github.com/repos/romana/core/comments{/number}","issue_comment_url":"https://api.github.com/repos/romana/core/issues/comments{/number}","contents_url":"https://api.github.com/repos/romana/core/contents/{+path}","compare_url":"https://api.github.com/repos/romana/core/compare/{base}...{head}","merges_url":"https://api.github.com/repos/romana/core/merges","archive_url":"https://api.github.com/repos/romana/core/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/romana/core/downloads","issues_url":"https://api.github.com/repos/romana/core/issues{/number}","pulls_url":"https://api.github.com/repos/romana/core/pulls{/number}","milestones_url":"https://api.github.com/repos/romana/core/milestones{/number}","notifications_url":"https://api.github.com/repos/romana/core/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/romana/core/labels{/name}","releases_url":"https://api.github.com/repos/romana/core/releases{/id}","deployments_url":"https://api.github.com/repos/romana/core/deployments","created_at":"2015-10-08T01:45:35Z","updated_at":"2016-06-03T17:41:07Z","pushed_at":"2016-06-09T01:12:54Z","git_url":"git://github.com/romana/core.git","ssh_url":"git@github.com:romana/core.git","clone_url":"https://github.com/romana/core.git","svn_url":"https://github.com/romana/core","homepage":"","size":4937,"stargazers_count":12,"watchers_count":12,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":3,"mirror_url":null,"open_issues_count":8,"forks":3,"open_issues":8,"watchers":12,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/romana/core/pulls/102"},"html":{"href":"https://github.com/romana/core/pull/102"},"issue":{"href":"https://api.github.com/repos/romana/core/issues/102"},"comments":{"href":"https://api.github.com/repos/romana/core/issues/102/comments"},"review_comments":{"href":"https://api.github.com/repos/romana/core/pulls/102/comments"},"review_comment":{"href":"https://api.github.com/repos/romana/core/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/romana/core/pulls/102/commits"},"statuses":{"href":"https://api.github.com/repos/romana/core/statuses/ba08d8a79c08814b2373fa38dcbb1c8464e4b9b9"}}}}
{"actor":{"display_login":"jbrendel"}}