{"action":"created","comment":{"url":"https://api.github.com/repos/docker/swarmkit/pulls/comments/192262268","pull_request_review_id":125006027,"id":192262268,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE5MjI2MjI2OA==","diff_hunk":"@@ -0,0 +1,869 @@\n+package network\n+\n+import (\n+\t\"fmt\"\n+\n+\t\"github.com/docker/docker/pkg/plugingetter\"\n+\t\"github.com/docker/libnetwork/drvregistry\"\n+\n+\t// the allocator types\n+\t\"github.com/docker/swarmkit/manager/allocator/network/driver\"\n+\t\"github.com/docker/swarmkit/manager/allocator/network/errors\"\n+\t\"github.com/docker/swarmkit/manager/allocator/network/ipam\"\n+\t\"github.com/docker/swarmkit/manager/allocator/network/port\"\n+\n+\t\"github.com/docker/swarmkit/api\"\n+)\n+\n+// Allocator is a the highest level type that should read or write network\n+// resources. Allocator functions as an interface between the higher level\n+// caller, which is concerned with Services and Tasks, to lower-level\n+// components, which are concerned with Endpoints and Attachments.\n+type Allocator interface {\n+\tRestore([]*api.Network, []*api.Service, []*api.Task, []*api.Node) error\n+\n+\tAllocateNetwork(*api.Network) error\n+\tDeallocateNetwork(*api.Network) error\n+\n+\tAllocateService(*api.Service) error\n+\tDeallocateService(*api.Service) error\n+\n+\tAllocateTask(*api.Task) error\n+\tDeallocateTask(*api.Task) error\n+\n+\tAllocateNode(*api.Node, map[string]struct{}) error\n+\tDeallocateNode(*api.Node) error\n+}\n+\n+type allocator struct {\n+\t// in order to figure out if the dependencies of a task are fulfilled, we\n+\t// need to keep track of what we have allocated already. this also allows\n+\t// us to avoid having to pass an endpoint from the service when allocating\n+\t// a task.\n+\tservices map[string]*api.Service\n+\n+\t// some networks are node-local, meaning we do not perform driver or IP\n+\t// address allocation on them. however, we still need to know what they are\n+\t// so that the allocator can handle attachments to these node-local\n+\t// networks correctly. this map keeps a set of all such node-local\n+\t// networks, so we know when allocating attachments to skip over these.\n+\t// This is roughly analogous to the networks map in the IPAM allocator but\n+\t// with a different set of networks\n+\tnodeLocalNetworks map[string]*api.Network\n+\n+\t// also attachments don't need to be kept track of, because nothing depends\n+\t// on them.\n+\n+\tipam   ipam.Allocator\n+\tdriver driver.Allocator\n+\tport   port.Allocator\n+\n+\t// ingressID is the ID of the ingress network. If it is empty, no ingress\n+\t// network exists.\n+\tingressID string\n+}\n+\n+// newAllocatorWithComponents creates a new allocator using the provided\n+// subcomponents. It's use is for testing, so that mocked subcomponents can be\n+// swapped in, and the driver initialization code can be skipped, in testing\n+// environments\n+func newAllocatorWithComponents(ipamAlloc ipam.Allocator, driverAlloc driver.Allocator, portAlloc port.Allocator) *allocator {\n+\treturn &allocator{\n+\t\tservices:          map[string]*api.Service{},\n+\t\tnodeLocalNetworks: map[string]*api.Network{},\n+\t\tipam:              ipamAlloc,\n+\t\tdriver:            driverAlloc,\n+\t\tport:              portAlloc,\n+\t}\n+}\n+\n+// NewAllocator creates and returns a new, ready-to use allocator for all\n+// network resources. Before it can be used, the caller must call Restore with\n+// any existing objects that need to be restored to create the state\n+func NewAllocator(pg plugingetter.PluginGetter) Allocator {\n+\t// NOTE(dperny): the err return value is currently not used in\n+\t// drvregistry.New function. I get that it's very frowned upon to rely on\n+\t// implementation details like that, but it simplifies the allocator enough\n+\t// that i'm willing to just check it and panic if it occurs.\n+\treg, err := drvregistry.New(nil, nil, nil, nil, pg)\n+\tif err != nil {\n+\t\tpanic(fmt.Sprintf(\"drvregistry.New returned an error: %v\", err))\n+\t}\n+\t// while we have access to a real DrvRegistry object, because this is the\n+\t// only place we need it, let's init the drivers. If this fails, it means\n+\t// the whole system is megascrewed\n+\tfor _, init := range initializers {\n+\t\tif err := reg.AddDriver(init.ntype, init.fn, nil); err != nil {\n+\t\t\tpanic(fmt.Sprintf(\"reg.AddDriver returned an error: %v\", err))\n+\t\t}\n+\t}\n+\n+\t// then, initialize the IPAM drivers\n+\tif err := initIPAMDrivers(reg); err != nil {\n+\t\tpanic(fmt.Sprintf(\"initIPAMDrivers returned an error: %v\", err))\n+\t}\n+\treturn &allocator{\n+\t\tservices:          map[string]*api.Service{},\n+\t\tnodeLocalNetworks: map[string]*api.Network{},\n+\t\tport:              port.NewAllocator(),\n+\t\tipam:              ipam.NewAllocator(reg),\n+\t\tdriver:            driver.NewAllocator(reg),\n+\t}\n+}\n+\n+// Restore takes slices of the object types managed by the network allocator\n+// and syncs the local state of the Allocator to match the state of the objects\n+// provided.\n+//\n+// If an error occurs during the restore, the local state may be inconsistent,\n+// and this allocator should be abandoned>\n+func (a *allocator) Restore(networks []*api.Network, services []*api.Service, tasks []*api.Task, nodes []*api.Node) error {\n+\t// there is a problem with restoring nodes: because node deallocation\n+\t// depends on network deallocation, it is possible for a network to be\n+\t// deallocated but the corresponding deallocation of nodes to not occur.\n+\t// this can lead to a situation on restore where a node has attachments\n+\t// belonging to no network.\n+\t//\n+\t// the simple fix, first off, is to ignore attempts to deallocate an\n+\t// attachment which has no corresponding network. this allows the caller to\n+\t// get correct allocator and node state by simply reallocating all nodes\n+\t// after restoring network state.\n+\t//\n+\t// however, Restore will return errors if it attempts to restore an\n+\t// attachment for which the network is not allocated; specifically,\n+\t// ipam's Restore will return ErrBadState and no restore any subsequent\n+\t// (correct) allocations.\n+\t//\n+\t// to avoid this case, we'll keep a set of all network IDs that we've\n+\t// encountered so far. then, when we compile our slice of attachments to\n+\t// restore, we'll skip any node attachments belonging to a network not in\n+\t// this list\n+\t//\n+\t// because networks cannot be updated, we can be sure that any node\n+\t// attachment that exists either belongs to a fully allocated network, or\n+\t// belongs to a deleted network, so we can put even unallocated networks in\n+\t// this map.\n+\t//\n+\t// This issue does not exist with task attachments, because a network\n+\t// cannot be deleted while it is still in use by tasks.\n+\texistingNetworks := make(map[string]struct{}, len(networks))\n+\n+\t// find if we have an ingress network in this list. if so, save its ID. we\n+\t// need it to correctly allocate tasks and services. there should only ever\n+\t// be 1 ingress network\n+\tfor _, nw := range networks {\n+\t\texistingNetworks[nw.ID] = struct{}{}\n+\n+\t\tif isIngress(nw) {\n+\t\t\ta.ingressID = nw.ID","path":"manager/allocator/network/allocator.go","position":158,"original_position":158,"commit_id":"40e36bd57eaf6947424ac107607f4cda824077ae","original_commit_id":"bcc1a1a6f4506b47d639f08e02c54d62275dd0c7","user":{"login":"dperny","id":2367858,"node_id":"MDQ6VXNlcjIzNjc4NTg=","avatar_url":"https://avatars3.githubusercontent.com/u/2367858?v=4","gravatar_id":"","url":"https://api.github.com/users/dperny","html_url":"https://github.com/dperny","followers_url":"https://api.github.com/users/dperny/followers","following_url":"https://api.github.com/users/dperny/following{/other_user}","gists_url":"https://api.github.com/users/dperny/gists{/gist_id}","starred_url":"https://api.github.com/users/dperny/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dperny/subscriptions","organizations_url":"https://api.github.com/users/dperny/orgs","repos_url":"https://api.github.com/users/dperny/repos","events_url":"https://api.github.com/users/dperny/events{/privacy}","received_events_url":"https://api.github.com/users/dperny/received_events","type":"User","site_admin":false},"body":"Maybe. I want to avoid importing the logging package anywhere deeper into the allocator codebase, and so I'd like to bubble up that information somehow instead of logging down here.","created_at":"2018-05-31T23:09:30Z","updated_at":"2018-05-31T23:09:30Z","html_url":"https://github.com/docker/swarmkit/pull/2615#discussion_r192262268","pull_request_url":"https://api.github.com/repos/docker/swarmkit/pulls/2615","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/docker/swarmkit/pulls/comments/192262268"},"html":{"href":"https://github.com/docker/swarmkit/pull/2615#discussion_r192262268"},"pull_request":{"href":"https://api.github.com/repos/docker/swarmkit/pulls/2615"}},"in_reply_to_id":191897658},"pull_request":{"url":"https://api.github.com/repos/docker/swarmkit/pulls/2615","id":184742071,"node_id":"MDExOlB1bGxSZXF1ZXN0MTg0NzQyMDcx","html_url":"https://github.com/docker/swarmkit/pull/2615","diff_url":"https://github.com/docker/swarmkit/pull/2615.diff","patch_url":"https://github.com/docker/swarmkit/pull/2615.patch","issue_url":"https://api.github.com/repos/docker/swarmkit/issues/2615","number":2615,"state":"open","locked":false,"title":"[WIP] Rewrite allocator","user":{"login":"dperny","id":2367858,"node_id":"MDQ6VXNlcjIzNjc4NTg=","avatar_url":"https://avatars3.githubusercontent.com/u/2367858?v=4","gravatar_id":"","url":"https://api.github.com/users/dperny","html_url":"https://github.com/dperny","followers_url":"https://api.github.com/users/dperny/followers","following_url":"https://api.github.com/users/dperny/following{/other_user}","gists_url":"https://api.github.com/users/dperny/gists{/gist_id}","starred_url":"https://api.github.com/users/dperny/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dperny/subscriptions","organizations_url":"https://api.github.com/users/dperny/orgs","repos_url":"https://api.github.com/users/dperny/repos","events_url":"https://api.github.com/users/dperny/events{/privacy}","received_events_url":"https://api.github.com/users/dperny/received_events","type":"User","site_admin":false},"body":"<!--\r\nPlease make sure you've read and understood our contributing guidelines;\r\nhttps://github.com/docker/swarmkit/blob/master/CONTRIBUTING.md\r\n\r\n** Make sure all your commits include a signature generated with `git commit -s` **\r\n\r\nFor additional information on our contributing process, read our contributing\r\nguide https://docs.docker.com/opensource/code/\r\n\r\nIf this is a bug fix, make sure your description includes \"fixes #xxxx\", or\r\n\"closes #xxxx\"\r\n\r\nPlease provide the following information:\r\n-->\r\n\r\n**- What I did**\r\n\r\nAdded all of the lower-level allocator subcomponents, paving the way for the final bits of allocator rewrite.\r\n\r\nIncludes and supersedes #2579 and #2605. Does not integrate the new components, which still requires a rewrite of the top-level Allocator object (which is in progress on a local branch).\r\n\r\nWIP because it lacks tests, but all code is ready for review.\r\n\r\n**- How I did it**\r\n\r\nPainfully.\r\n\r\n**- How to review it**\r\n\r\nThis PR is massive, and will be difficult to adequately review. Do not let this discourage you; the design should be clearly delineated, and the components and loosely coupled. There are many comments. If you do not understand why a particular thing was done a particular way during your review, that's not a sign that you can't review it, that's a sign that it hasn't been commented adequately to explain it.\r\n\r\nThis PR should be review-able by anyone with any level of familiarity with the code. The goal is to create a maintainable, easy-to-understand component, that even new contributors to the project can easily understand. Effort has been made to ensure that nothing in the code assumes itself to be obvious.\r\n\r\nTo make reviewing easier, you should follow these guidelines:\r\n\r\n* Start by reading `doc.go`, which should explain the purpose and use of each package. Then, read the doc comments of the public interfaces. Make sure they make sense to you, and adequately explain the intended behavior of the components. If they do not, request changes.\r\n* Look over the design and method signatures. Do the signatures make sense? Are the methods easy to use? Do the concerns of each component seem well-separated?\r\n* Review the lower level components first. The `port` package has previously been subject to review in #2579, but has received changes. The `ipam` package has been partially reviewed in #2605, but has received substantial changes. The `driver` and top-level `network` package has not reviewed yet.\r\n* Review the Allocator component. Does it fit the sub-components together well? Does it adequately respect the separation of concerns?\r\n* Review the tests. Do they cover the cases adequately?\r\n\r\n**- How to test it**\r\n\r\nThis PR is marked WIP primarily because it currently lacks complete testing. The tests it does have are written with the Ginkgo framework.\r\n\r\n**- Description for the changelog**\r\n<!--\r\nWrite a short (one line) summary that describes the changes in this\r\npull request for inclusion in the changelog:\r\n-->","created_at":"2018-04-27T22:35:18Z","updated_at":"2018-05-31T23:09:30Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a9086759b702e6dbb7c2bbf63b03c75fc5c2669d","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/docker/swarmkit/pulls/2615/commits","review_comments_url":"https://api.github.com/repos/docker/swarmkit/pulls/2615/comments","review_comment_url":"https://api.github.com/repos/docker/swarmkit/pulls/comments{/number}","comments_url":"https://api.github.com/repos/docker/swarmkit/issues/2615/comments","statuses_url":"https://api.github.com/repos/docker/swarmkit/statuses/40e36bd57eaf6947424ac107607f4cda824077ae","head":{"label":"dperny:rewrite-allocator","ref":"rewrite-allocator","sha":"40e36bd57eaf6947424ac107607f4cda824077ae","user":{"login":"dperny","id":2367858,"node_id":"MDQ6VXNlcjIzNjc4NTg=","avatar_url":"https://avatars3.githubusercontent.com/u/2367858?v=4","gravatar_id":"","url":"https://api.github.com/users/dperny","html_url":"https://github.com/dperny","followers_url":"https://api.github.com/users/dperny/followers","following_url":"https://api.github.com/users/dperny/following{/other_user}","gists_url":"https://api.github.com/users/dperny/gists{/gist_id}","starred_url":"https://api.github.com/users/dperny/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dperny/subscriptions","organizations_url":"https://api.github.com/users/dperny/orgs","repos_url":"https://api.github.com/users/dperny/repos","events_url":"https://api.github.com/users/dperny/events{/privacy}","received_events_url":"https://api.github.com/users/dperny/received_events","type":"User","site_admin":false},"repo":{"id":60813040,"node_id":"MDEwOlJlcG9zaXRvcnk2MDgxMzA0MA==","name":"swarmkit-1","full_name":"dperny/swarmkit-1","owner":{"login":"dperny","id":2367858,"node_id":"MDQ6VXNlcjIzNjc4NTg=","avatar_url":"https://avatars3.githubusercontent.com/u/2367858?v=4","gravatar_id":"","url":"https://api.github.com/users/dperny","html_url":"https://github.com/dperny","followers_url":"https://api.github.com/users/dperny/followers","following_url":"https://api.github.com/users/dperny/following{/other_user}","gists_url":"https://api.github.com/users/dperny/gists{/gist_id}","starred_url":"https://api.github.com/users/dperny/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dperny/subscriptions","organizations_url":"https://api.github.com/users/dperny/orgs","repos_url":"https://api.github.com/users/dperny/repos","events_url":"https://api.github.com/users/dperny/events{/privacy}","received_events_url":"https://api.github.com/users/dperny/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/dperny/swarmkit-1","description":"A toolkit for orchestrating distributed systems at any scale. It includes primitives for node discovery, raft-based consensus, task scheduling and more.","fork":true,"url":"https://api.github.com/repos/dperny/swarmkit-1","forks_url":"https://api.github.com/repos/dperny/swarmkit-1/forks","keys_url":"https://api.github.com/repos/dperny/swarmkit-1/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dperny/swarmkit-1/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dperny/swarmkit-1/teams","hooks_url":"https://api.github.com/repos/dperny/swarmkit-1/hooks","issue_events_url":"https://api.github.com/repos/dperny/swarmkit-1/issues/events{/number}","events_url":"https://api.github.com/repos/dperny/swarmkit-1/events","assignees_url":"https://api.github.com/repos/dperny/swarmkit-1/assignees{/user}","branches_url":"https://api.github.com/repos/dperny/swarmkit-1/branches{/branch}","tags_url":"https://api.github.com/repos/dperny/swarmkit-1/tags","blobs_url":"https://api.github.com/repos/dperny/swarmkit-1/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dperny/swarmkit-1/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dperny/swarmkit-1/git/refs{/sha}","trees_url":"https://api.github.com/repos/dperny/swarmkit-1/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dperny/swarmkit-1/statuses/{sha}","languages_url":"https://api.github.com/repos/dperny/swarmkit-1/languages","stargazers_url":"https://api.github.com/repos/dperny/swarmkit-1/stargazers","contributors_url":"https://api.github.com/repos/dperny/swarmkit-1/contributors","subscribers_url":"https://api.github.com/repos/dperny/swarmkit-1/subscribers","subscription_url":"https://api.github.com/repos/dperny/swarmkit-1/subscription","commits_url":"https://api.github.com/repos/dperny/swarmkit-1/commits{/sha}","git_commits_url":"https://api.github.com/repos/dperny/swarmkit-1/git/commits{/sha}","comments_url":"https://api.github.com/repos/dperny/swarmkit-1/comments{/number}","issue_comment_url":"https://api.github.com/repos/dperny/swarmkit-1/issues/comments{/number}","contents_url":"https://api.github.com/repos/dperny/swarmkit-1/contents/{+path}","compare_url":"https://api.github.com/repos/dperny/swarmkit-1/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dperny/swarmkit-1/merges","archive_url":"https://api.github.com/repos/dperny/swarmkit-1/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dperny/swarmkit-1/downloads","issues_url":"https://api.github.com/repos/dperny/swarmkit-1/issues{/number}","pulls_url":"https://api.github.com/repos/dperny/swarmkit-1/pulls{/number}","milestones_url":"https://api.github.com/repos/dperny/swarmkit-1/milestones{/number}","notifications_url":"https://api.github.com/repos/dperny/swarmkit-1/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dperny/swarmkit-1/labels{/name}","releases_url":"https://api.github.com/repos/dperny/swarmkit-1/releases{/id}","deployments_url":"https://api.github.com/repos/dperny/swarmkit-1/deployments","created_at":"2016-06-09T23:18:00Z","updated_at":"2016-06-09T23:18:01Z","pushed_at":"2018-05-31T23:05:24Z","git_url":"git://github.com/dperny/swarmkit-1.git","ssh_url":"git@github.com:dperny/swarmkit-1.git","clone_url":"https://github.com/dperny/swarmkit-1.git","svn_url":"https://github.com/dperny/swarmkit-1","homepage":"","size":20993,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":1,"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":1,"watchers":0,"default_branch":"master"}},"base":{"label":"docker:master","ref":"master","sha":"0fe3f12bdbfee0d47216de0118c569b1042e1588","user":{"login":"docker","id":5429470,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU0Mjk0NzA=","avatar_url":"https://avatars1.githubusercontent.com/u/5429470?v=4","gravatar_id":"","url":"https://api.github.com/users/docker","html_url":"https://github.com/docker","followers_url":"https://api.github.com/users/docker/followers","following_url":"https://api.github.com/users/docker/following{/other_user}","gists_url":"https://api.github.com/users/docker/gists{/gist_id}","starred_url":"https://api.github.com/users/docker/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/docker/subscriptions","organizations_url":"https://api.github.com/users/docker/orgs","repos_url":"https://api.github.com/users/docker/repos","events_url":"https://api.github.com/users/docker/events{/privacy}","received_events_url":"https://api.github.com/users/docker/received_events","type":"Organization","site_admin":false},"repo":{"id":51556828,"node_id":"MDEwOlJlcG9zaXRvcnk1MTU1NjgyOA==","name":"swarmkit","full_name":"docker/swarmkit","owner":{"login":"docker","id":5429470,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU0Mjk0NzA=","avatar_url":"https://avatars1.githubusercontent.com/u/5429470?v=4","gravatar_id":"","url":"https://api.github.com/users/docker","html_url":"https://github.com/docker","followers_url":"https://api.github.com/users/docker/followers","following_url":"https://api.github.com/users/docker/following{/other_user}","gists_url":"https://api.github.com/users/docker/gists{/gist_id}","starred_url":"https://api.github.com/users/docker/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/docker/subscriptions","organizations_url":"https://api.github.com/users/docker/orgs","repos_url":"https://api.github.com/users/docker/repos","events_url":"https://api.github.com/users/docker/events{/privacy}","received_events_url":"https://api.github.com/users/docker/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/docker/swarmkit","description":"A toolkit for orchestrating distributed systems at any scale. It includes primitives for node discovery, raft-based consensus, task scheduling and more.","fork":false,"url":"https://api.github.com/repos/docker/swarmkit","forks_url":"https://api.github.com/repos/docker/swarmkit/forks","keys_url":"https://api.github.com/repos/docker/swarmkit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/docker/swarmkit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/docker/swarmkit/teams","hooks_url":"https://api.github.com/repos/docker/swarmkit/hooks","issue_events_url":"https://api.github.com/repos/docker/swarmkit/issues/events{/number}","events_url":"https://api.github.com/repos/docker/swarmkit/events","assignees_url":"https://api.github.com/repos/docker/swarmkit/assignees{/user}","branches_url":"https://api.github.com/repos/docker/swarmkit/branches{/branch}","tags_url":"https://api.github.com/repos/docker/swarmkit/tags","blobs_url":"https://api.github.com/repos/docker/swarmkit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/docker/swarmkit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/docker/swarmkit/git/refs{/sha}","trees_url":"https://api.github.com/repos/docker/swarmkit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/docker/swarmkit/statuses/{sha}","languages_url":"https://api.github.com/repos/docker/swarmkit/languages","stargazers_url":"https://api.github.com/repos/docker/swarmkit/stargazers","contributors_url":"https://api.github.com/repos/docker/swarmkit/contributors","subscribers_url":"https://api.github.com/repos/docker/swarmkit/subscribers","subscription_url":"https://api.github.com/repos/docker/swarmkit/subscription","commits_url":"https://api.github.com/repos/docker/swarmkit/commits{/sha}","git_commits_url":"https://api.github.com/repos/docker/swarmkit/git/commits{/sha}","comments_url":"https://api.github.com/repos/docker/swarmkit/comments{/number}","issue_comment_url":"https://api.github.com/repos/docker/swarmkit/issues/comments{/number}","contents_url":"https://api.github.com/repos/docker/swarmkit/contents/{+path}","compare_url":"https://api.github.com/repos/docker/swarmkit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/docker/swarmkit/merges","archive_url":"https://api.github.com/repos/docker/swarmkit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/docker/swarmkit/downloads","issues_url":"https://api.github.com/repos/docker/swarmkit/issues{/number}","pulls_url":"https://api.github.com/repos/docker/swarmkit/pulls{/number}","milestones_url":"https://api.github.com/repos/docker/swarmkit/milestones{/number}","notifications_url":"https://api.github.com/repos/docker/swarmkit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/docker/swarmkit/labels{/name}","releases_url":"https://api.github.com/repos/docker/swarmkit/releases{/id}","deployments_url":"https://api.github.com/repos/docker/swarmkit/deployments","created_at":"2016-02-12T00:02:15Z","updated_at":"2018-05-31T20:12:57Z","pushed_at":"2018-05-31T23:05:29Z","git_url":"git://github.com/docker/swarmkit.git","ssh_url":"git@github.com:docker/swarmkit.git","clone_url":"https://github.com/docker/swarmkit.git","svn_url":"https://github.com/docker/swarmkit","homepage":"","size":19724,"stargazers_count":1702,"watchers_count":1702,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":392,"mirror_url":null,"archived":false,"open_issues_count":233,"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":392,"open_issues":233,"watchers":1702,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/docker/swarmkit/pulls/2615"},"html":{"href":"https://github.com/docker/swarmkit/pull/2615"},"issue":{"href":"https://api.github.com/repos/docker/swarmkit/issues/2615"},"comments":{"href":"https://api.github.com/repos/docker/swarmkit/issues/2615/comments"},"review_comments":{"href":"https://api.github.com/repos/docker/swarmkit/pulls/2615/comments"},"review_comment":{"href":"https://api.github.com/repos/docker/swarmkit/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/docker/swarmkit/pulls/2615/commits"},"statuses":{"href":"https://api.github.com/repos/docker/swarmkit/statuses/40e36bd57eaf6947424ac107607f4cda824077ae"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"dperny"}}