{"action":"created","comment":{"url":"https://api.github.com/repos/deis/deis/pulls/comments/37246242","id":37246242,"diff_hunk":"@@ -0,0 +1,350 @@\n+package docker\n+\n+import (\n+\t\"archive/tar\"\n+\t\"bytes\"\n+\t\"fmt\"\n+\t\"io\"\n+\t\"os\"\n+\t\"os/exec\"\n+\t\"path\"\n+\t\"path/filepath\"\n+\t\"strings\"\n+\t\"sync\"\n+\t\"time\"\n+\n+\t\"github.com/Masterminds/cookoo\"\n+\t\"github.com/Masterminds/cookoo/log\"\n+\t\"github.com/Masterminds/cookoo/safely\"\n+\t\"github.com/deis/deis/builder/etcd\"\n+\tdocli \"github.com/fsouza/go-dockerclient\"\n+)\n+\n+// Path to the Docker unix socket.\n+var DockSock = \"/var/run/docker.sock\"\n+\n+// Cleanup removes any existing Docker artifacts.\n+//\n+// Returns true if the file exists (and was deleted), or false if no file\n+// was deleted.\n+func Cleanup(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {\n+\n+\t// If info is returned, then the file is there. If we get an error, we're\n+\t// pretty much not going to be able to remove the file (which probably\n+\t// doesn't exist).\n+\tif _, err := os.Stat(DockSock); err == nil {\n+\t\tlog.Infof(c, \"Removing leftover docker socket %s: %v\", DockSock)\n+\t\treturn true, os.Remove(DockSock)\n+\t}\n+\treturn false, nil\n+}\n+\n+// CreateClient creates a new Docker client.\n+//\n+// Params:\n+// \t- url (string): The URI to the Docker daemon. This defaults to the UNIX\n+// \t\tsocket /var/run/docker.sock.\n+//\n+// Returns:\n+// \t- *docker.Client\n+//\n+func CreateClient(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {\n+\tpath := p.Get(\"url\", \"unix:///var/run/docker.sock\").(string)\n+\n+\treturn docli.NewClient(path)\n+}\n+\n+// Start starts a Docker daemon.\n+//\n+// This assumes the presence of the docker client on the host. It does not use\n+// the API.\n+func Start(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {\n+\tdargs := []string{\n+\t\t\"-d\",\n+\t\t\"--bip=172.19.42.1/16\",\n+\t\t\"--insecure-registry\",\n+\t\t\"10.0.0.0/8\",\n+\t\t\"--insecure-registry\",\n+\t\t\"172.16.0.0/12\",\n+\t\t\"--insecure-registry\",\n+\t\t\"192.168.0.0/16\",\n+\t\t\"--insecure-registry\",\n+\t\t\"100.64.0.0/10\",\n+\t\t//\"--storage-driver=overlay\", // Add this dynamically.\n+\t}\n+\n+\t// There is probably a better way to do this. I'm not sure whether the\n+\t// original intent of the mkdir was to accomplish something, or just check\n+\t// whether overlay is supported.\n+\tif err := os.MkdirAll(\"/\", 0700); err == nil {\n+\n+\t\tcmd := exec.Command(\"findmnt\", \"--noheadings\", \"--output\", \"FSTYPE\", \"--target\", \"/\")\n+\n+\t\tif out, err := cmd.Output(); err == nil && strings.TrimSpace(string(out)) == \"overlay\" {\n+\t\t\tdargs = append(dargs, \"--storage-driver=overlay\")\n+\t\t} else {\n+\t\t\tlog.Infof(c, \"File system type: '%s' (%v)\", out, err)\n+\t\t}\n+\t}\n+\n+\t//# spawn a docker daemon to run builds\n+\t//docker -d --bip=172.19.42.1/16 $DRIVER_OVERRIDE --insecure-registry 10.0.0.0/8 --insecure-registry 172.16.0.0/12\n+\t// --insecure-registry 192.168.0.0/16 --insecure-registry 100.64.0.0/10 &\n+\t//DOCKER_PID=$!\n+\n+\tlog.Infof(c, \"Starting docker with %s\", strings.Join(dargs, \" \"))\n+\tcmd := exec.Command(\"docker\", dargs...)\n+\tif err := cmd.Start(); err != nil {\n+\t\tlog.Errf(c, \"Failed to start Docker. %s\", err)\n+\t\treturn -1, err\n+\t}\n+\t// Get the PID and return it.\n+\treturn cmd.Process.Pid, nil\n+}\n+\n+// WaitForStart delays until Docker appears to be up and running.\n+//\n+// Params:\n+// \t- client (*docker.Client): Docker client.\n+// \t- timeout (time.Duration): Time after which to give up.\n+//\n+// Returns:\n+// \t- boolean true if the server is up.\n+func WaitForStart(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {\n+\tok, missing := p.RequiresValue(\"client\")\n+\tif !ok {\n+\t\treturn nil, &cookoo.FatalError{\"Missing required fields: \" + strings.Join(missing, \", \")}\n+\t}\n+\tcli := p.Get(\"client\", nil).(*docli.Client)\n+\ttimeout := p.Get(\"timeout\", 30*time.Second).(time.Duration)\n+\n+\tkeepon := true\n+\ttimer := time.AfterFunc(timeout, func() {\n+\t\tkeepon = false\n+\t})\n+\n+\tfor keepon == true {\n+\t\tif err := cli.Ping(); err == nil {\n+\t\t\ttimer.Stop()\n+\t\t\tlog.Infof(c, \"Docker is running.\")\n+\t\t\treturn true, nil\n+\t\t}\n+\t\ttime.Sleep(time.Second)\n+\t}\n+\treturn false, fmt.Errorf(\"Docker timed out after waiting %s for server.\", timeout)\n+}\n+\n+// BuildImg describes a build image.\n+type BuildImg struct {\n+\tPath, Tag string\n+}\n+\n+// ParallelBuild runs multiple docker builds at the same time.\n+//\n+// Params:\n+//\t-images ([]BuildImg): Images to build\n+// \t-alwaysFetch (bool): Default false. If set to true, this will always fetch\n+// \t\tthe Docker image even if it already exists in the registry.\n+//\n+// Returns:\n+//\n+// \t- Waiter: A *sync.WaitGroup that is waiting for the docker downloads to finish.\n+//\n+// Context:\n+//\n+// This puts 'ParallelBuild.failN\" (int) into the context to indicate how many failures\n+// occurred during fetches.\n+func ParallelBuild(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {\n+\timages := p.Get(\"images\", []BuildImg{}).([]BuildImg)\n+\t//client := p.Get(\"client\", nil).(*docli.Client)\n+\n+\tvar wg sync.WaitGroup\n+\tvar m sync.Mutex\n+\tvar fails int\n+\n+\tfor _, img := range images {\n+\t\timg := img\n+\t\twg.Add(1)\n+\t\tsafely.GoDo(c, func() {\n+\t\t\tlog.Infof(c, \"Starting build for %s (tag: %s)\", img.Path, img.Tag)\n+\t\t\t_, err := buildImg(c, img.Path, img.Tag)\n+\t\t\t//err := build(c, img.Path, img.Tag, client)\n+\t\t\tif err != nil {\n+\t\t\t\tlog.Errf(c, \"Failed to build docker image: %s\", err)\n+\t\t\t\tm.Lock()\n+\t\t\t\tfails++\n+\t\t\t\tm.Unlock()\n+\t\t\t}\n+\t\t\twg.Done()\n+\t\t})\n+\n+\t}\n+\n+\t// Number of failures.\n+\tc.Put(\"ParallelBuild.failN\", fails)\n+\n+\t//wg.Wait()\n+\t//log.Infof(c, \"Docker images are built.\")\n+\treturn &wg, nil\n+}\n+\n+// Waiter describes a thing that can wait.\n+//\n+// It does not bring you food. I should know. I tried.\n+type Waiter interface {\n+\tWait()\n+}\n+\n+// Wait waits for a sync.WaitGroup to finish.\n+//\n+// Params:\n+// \t- wg (Waiter): The thing to wait for.\n+// \t- msg (string): The message to print when done. If this is empty, nothing is sent.\n+// \t- waiting (string): String to tell what we're waiting for. If empty, nothing is displayed.\n+// \t- failures (int): The number of failures that occurred while waiting.\n+//\n+// Returns:\n+//  Nothing.\n+func Wait(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {\n+\tok, missing := p.RequiresValue(\"wg\")\n+\tif !ok {\n+\t\treturn nil, &cookoo.FatalError{\"Missing required fields: \" + strings.Join(missing, \", \")}\n+\t}\n+\twg := p.Get(\"wg\", nil).(Waiter)\n+\tmsg := p.Get(\"msg\", \"\").(string)\n+\tfails := p.Get(\"failures\", 0).(int)\n+\twaitmsg := p.Get(\"waiting\", \"\").(string)\n+\n+\tif len(waitmsg) > 0 {\n+\t\tlog.Info(c, waitmsg)\n+\t}\n+\n+\twg.Wait()\n+\tif len(msg) > 0 {\n+\t\tlog.Info(c, msg)\n+\t}\n+\n+\tif fails > 0 {\n+\t\treturn nil, fmt.Errorf(\"There were %d failures while waiting.\", fails)\n+\t}\n+\treturn nil, nil\n+}\n+\n+// BuildImage builds a docker image.\n+//\n+// Essentially, this executes:\n+// \tdocker build -t TAG PATH\n+//\n+// Params:\n+// \t- path (string): The path to the image. REQUIRED\n+// \t- tag (string): The tag to build.\n+func BuildImage(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {\n+\tpath := p.Get(\"path\", \"\").(string)\n+\ttag := p.Get(\"tag\", \"\").(string)\n+\n+\tlog.Infof(c, \"Building docker image %s (tag: %s)\", path, tag)\n+\n+\treturn buildImg(c, path, tag)\n+}\n+\n+func buildImg(c cookoo.Context, path, tag string) ([]byte, error) {\n+\tdargs := []string{\"build\"}\n+\tif len(tag) > 0 {\n+\t\tdargs = append(dargs, \"-t\", tag)\n+\t}\n+\n+\tdargs = append(dargs, path)\n+\n+\tout, err := exec.Command(\"docker\", dargs...).CombinedOutput()\n+\tif len(out) > 0 {\n+\t\tlog.Infof(c, \"Docker: %s\", out)\n+\t}\n+\treturn out, err\n+}\n+\n+// Push pushes an image to the registry.\n+//\n+// This finds the appropriate registry by looking it up in etcd.\n+//\n+// Params:\n+// - client (etcd.Getter): Client to do etcd lookups.\n+// - tag (string): Tag to push.\n+//\n+// Returns:\n+//\n+func Push(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {\n+\t// docker tag deis/slugrunner:lastest HOST:PORT/deis/slugrunner:latest\n+\t// docker push HOST:PORT/deis/slugrunner:latest\n+\tclient := p.Get(\"client\", nil).(etcd.Getter)\n+\n+\thost, err := client.Get(\"/deis/registry/host\", false, false)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tport, err := client.Get(\"/deis/registry/port\", false, false)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tregistry := host.Node.Value + \":\" + port.Node.Value\n+\ttag := p.Get(\"tag\", \"\").(string)\n+\n+\tlog.Infof(c, \"Pushing %s to %s. This may take some time.\", tag, registry)\n+\trem := path.Join(registry, tag)\n+\n+\tout, err := exec.Command(\"docker\", \"tag\", \"-f\", tag, rem).CombinedOutput()\n+\tif err != nil {\n+\t\tlog.Warnf(c, \"Failed to tag %s on host %s: %s (%s)\", tag, rem, err, out)\n+\t\t//return nil, err","path":"builder/docker/docker.go","position":298,"original_position":298,"commit_id":"2bf06d75d307d516d120a981009445422c907ba0","original_commit_id":"2bf06d75d307d516d120a981009445422c907ba0","user":{"login":"technosophos","id":89193,"avatar_url":"https://avatars.githubusercontent.com/u/89193?v=3","gravatar_id":"","url":"https://api.github.com/users/technosophos","html_url":"https://github.com/technosophos","followers_url":"https://api.github.com/users/technosophos/followers","following_url":"https://api.github.com/users/technosophos/following{/other_user}","gists_url":"https://api.github.com/users/technosophos/gists{/gist_id}","starred_url":"https://api.github.com/users/technosophos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/technosophos/subscriptions","organizations_url":"https://api.github.com/users/technosophos/orgs","repos_url":"https://api.github.com/users/technosophos/repos","events_url":"https://api.github.com/users/technosophos/events{/privacy}","received_events_url":"https://api.github.com/users/technosophos/received_events","type":"User","site_admin":false},"body":"The reason that was commented was that it turns out that docker will return 1 if the label already exists, and the rebuild does not change anything. That happens on occasion, and is not a fatal error. I'll remove the commented code.","created_at":"2015-08-17T22:46:36Z","updated_at":"2015-08-17T22:46:36Z","html_url":"https://github.com/deis/deis/pull/4273#discussion_r37246242","pull_request_url":"https://api.github.com/repos/deis/deis/pulls/4273","_links":{"self":{"href":"https://api.github.com/repos/deis/deis/pulls/comments/37246242"},"html":{"href":"https://github.com/deis/deis/pull/4273#discussion_r37246242"},"pull_request":{"href":"https://api.github.com/repos/deis/deis/pulls/4273"}}},"pull_request":{"url":"https://api.github.com/repos/deis/deis/pulls/4273","id":42416818,"html_url":"https://github.com/deis/deis/pull/4273","diff_url":"https://github.com/deis/deis/pull/4273.diff","patch_url":"https://github.com/deis/deis/pull/4273.patch","issue_url":"https://api.github.com/repos/deis/deis/issues/4273","number":4273,"state":"open","locked":false,"title":"feat(builder): reimplement the builder in Go","user":{"login":"technosophos","id":89193,"avatar_url":"https://avatars.githubusercontent.com/u/89193?v=3","gravatar_id":"","url":"https://api.github.com/users/technosophos","html_url":"https://github.com/technosophos","followers_url":"https://api.github.com/users/technosophos/followers","following_url":"https://api.github.com/users/technosophos/following{/other_user}","gists_url":"https://api.github.com/users/technosophos/gists{/gist_id}","starred_url":"https://api.github.com/users/technosophos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/technosophos/subscriptions","organizations_url":"https://api.github.com/users/technosophos/orgs","repos_url":"https://api.github.com/users/technosophos/repos","events_url":"https://api.github.com/users/technosophos/events{/privacy}","received_events_url":"https://api.github.com/users/technosophos/received_events","type":"User","site_admin":false},"body":"This is part of issue #3836, where the main builder scripts are\r\nrewritten as a Go executable.\r\n\r\nThis feature adds an SSH server implementation to Go, which means we\r\nhave removed one of the major runtime interops on Builder.\r\n\r\nThis changes builder's internal port, and should not break other\r\nthings, but makes Flannel configuration easier.","created_at":"2015-08-14T01:26:06Z","updated_at":"2015-08-17T22:46:36Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f54ef74b36efb8458f8689493943c9607e47e86c","assignee":{"login":"technosophos","id":89193,"avatar_url":"https://avatars.githubusercontent.com/u/89193?v=3","gravatar_id":"","url":"https://api.github.com/users/technosophos","html_url":"https://github.com/technosophos","followers_url":"https://api.github.com/users/technosophos/followers","following_url":"https://api.github.com/users/technosophos/following{/other_user}","gists_url":"https://api.github.com/users/technosophos/gists{/gist_id}","starred_url":"https://api.github.com/users/technosophos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/technosophos/subscriptions","organizations_url":"https://api.github.com/users/technosophos/orgs","repos_url":"https://api.github.com/users/technosophos/repos","events_url":"https://api.github.com/users/technosophos/events{/privacy}","received_events_url":"https://api.github.com/users/technosophos/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/deis/deis/pulls/4273/commits","review_comments_url":"https://api.github.com/repos/deis/deis/pulls/4273/comments","review_comment_url":"https://api.github.com/repos/deis/deis/pulls/comments{/number}","comments_url":"https://api.github.com/repos/deis/deis/issues/4273/comments","statuses_url":"https://api.github.com/repos/deis/deis/statuses/2bf06d75d307d516d120a981009445422c907ba0","head":{"label":"technosophos:feature/go-builder","ref":"feature/go-builder","sha":"2bf06d75d307d516d120a981009445422c907ba0","user":{"login":"technosophos","id":89193,"avatar_url":"https://avatars.githubusercontent.com/u/89193?v=3","gravatar_id":"","url":"https://api.github.com/users/technosophos","html_url":"https://github.com/technosophos","followers_url":"https://api.github.com/users/technosophos/followers","following_url":"https://api.github.com/users/technosophos/following{/other_user}","gists_url":"https://api.github.com/users/technosophos/gists{/gist_id}","starred_url":"https://api.github.com/users/technosophos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/technosophos/subscriptions","organizations_url":"https://api.github.com/users/technosophos/orgs","repos_url":"https://api.github.com/users/technosophos/repos","events_url":"https://api.github.com/users/technosophos/events{/privacy}","received_events_url":"https://api.github.com/users/technosophos/received_events","type":"User","site_admin":false},"repo":{"id":37152356,"name":"deis","full_name":"technosophos/deis","owner":{"login":"technosophos","id":89193,"avatar_url":"https://avatars.githubusercontent.com/u/89193?v=3","gravatar_id":"","url":"https://api.github.com/users/technosophos","html_url":"https://github.com/technosophos","followers_url":"https://api.github.com/users/technosophos/followers","following_url":"https://api.github.com/users/technosophos/following{/other_user}","gists_url":"https://api.github.com/users/technosophos/gists{/gist_id}","starred_url":"https://api.github.com/users/technosophos/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/technosophos/subscriptions","organizations_url":"https://api.github.com/users/technosophos/orgs","repos_url":"https://api.github.com/users/technosophos/repos","events_url":"https://api.github.com/users/technosophos/events{/privacy}","received_events_url":"https://api.github.com/users/technosophos/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/technosophos/deis","description":"Your PaaS. Your Rules. ","fork":true,"url":"https://api.github.com/repos/technosophos/deis","forks_url":"https://api.github.com/repos/technosophos/deis/forks","keys_url":"https://api.github.com/repos/technosophos/deis/keys{/key_id}","collaborators_url":"https://api.github.com/repos/technosophos/deis/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/technosophos/deis/teams","hooks_url":"https://api.github.com/repos/technosophos/deis/hooks","issue_events_url":"https://api.github.com/repos/technosophos/deis/issues/events{/number}","events_url":"https://api.github.com/repos/technosophos/deis/events","assignees_url":"https://api.github.com/repos/technosophos/deis/assignees{/user}","branches_url":"https://api.github.com/repos/technosophos/deis/branches{/branch}","tags_url":"https://api.github.com/repos/technosophos/deis/tags","blobs_url":"https://api.github.com/repos/technosophos/deis/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/technosophos/deis/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/technosophos/deis/git/refs{/sha}","trees_url":"https://api.github.com/repos/technosophos/deis/git/trees{/sha}","statuses_url":"https://api.github.com/repos/technosophos/deis/statuses/{sha}","languages_url":"https://api.github.com/repos/technosophos/deis/languages","stargazers_url":"https://api.github.com/repos/technosophos/deis/stargazers","contributors_url":"https://api.github.com/repos/technosophos/deis/contributors","subscribers_url":"https://api.github.com/repos/technosophos/deis/subscribers","subscription_url":"https://api.github.com/repos/technosophos/deis/subscription","commits_url":"https://api.github.com/repos/technosophos/deis/commits{/sha}","git_commits_url":"https://api.github.com/repos/technosophos/deis/git/commits{/sha}","comments_url":"https://api.github.com/repos/technosophos/deis/comments{/number}","issue_comment_url":"https://api.github.com/repos/technosophos/deis/issues/comments{/number}","contents_url":"https://api.github.com/repos/technosophos/deis/contents/{+path}","compare_url":"https://api.github.com/repos/technosophos/deis/compare/{base}...{head}","merges_url":"https://api.github.com/repos/technosophos/deis/merges","archive_url":"https://api.github.com/repos/technosophos/deis/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/technosophos/deis/downloads","issues_url":"https://api.github.com/repos/technosophos/deis/issues{/number}","pulls_url":"https://api.github.com/repos/technosophos/deis/pulls{/number}","milestones_url":"https://api.github.com/repos/technosophos/deis/milestones{/number}","notifications_url":"https://api.github.com/repos/technosophos/deis/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/technosophos/deis/labels{/name}","releases_url":"https://api.github.com/repos/technosophos/deis/releases{/id}","created_at":"2015-06-09T19:05:33Z","updated_at":"2015-06-09T19:05:34Z","pushed_at":"2015-08-17T17:50:38Z","git_url":"git://github.com/technosophos/deis.git","ssh_url":"git@github.com:technosophos/deis.git","clone_url":"https://github.com/technosophos/deis.git","svn_url":"https://github.com/technosophos/deis","homepage":"http://deis.io","size":18742,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"deis:master","ref":"master","sha":"b90c2fbb5e4cf9a93c649b7285c85eb301a639c8","user":{"login":"deis","id":6117407,"avatar_url":"https://avatars.githubusercontent.com/u/6117407?v=3","gravatar_id":"","url":"https://api.github.com/users/deis","html_url":"https://github.com/deis","followers_url":"https://api.github.com/users/deis/followers","following_url":"https://api.github.com/users/deis/following{/other_user}","gists_url":"https://api.github.com/users/deis/gists{/gist_id}","starred_url":"https://api.github.com/users/deis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/deis/subscriptions","organizations_url":"https://api.github.com/users/deis/orgs","repos_url":"https://api.github.com/users/deis/repos","events_url":"https://api.github.com/users/deis/events{/privacy}","received_events_url":"https://api.github.com/users/deis/received_events","type":"Organization","site_admin":false},"repo":{"id":11589713,"name":"deis","full_name":"deis/deis","owner":{"login":"deis","id":6117407,"avatar_url":"https://avatars.githubusercontent.com/u/6117407?v=3","gravatar_id":"","url":"https://api.github.com/users/deis","html_url":"https://github.com/deis","followers_url":"https://api.github.com/users/deis/followers","following_url":"https://api.github.com/users/deis/following{/other_user}","gists_url":"https://api.github.com/users/deis/gists{/gist_id}","starred_url":"https://api.github.com/users/deis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/deis/subscriptions","organizations_url":"https://api.github.com/users/deis/orgs","repos_url":"https://api.github.com/users/deis/repos","events_url":"https://api.github.com/users/deis/events{/privacy}","received_events_url":"https://api.github.com/users/deis/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/deis/deis","description":"Your PaaS. Your Rules. ","fork":false,"url":"https://api.github.com/repos/deis/deis","forks_url":"https://api.github.com/repos/deis/deis/forks","keys_url":"https://api.github.com/repos/deis/deis/keys{/key_id}","collaborators_url":"https://api.github.com/repos/deis/deis/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/deis/deis/teams","hooks_url":"https://api.github.com/repos/deis/deis/hooks","issue_events_url":"https://api.github.com/repos/deis/deis/issues/events{/number}","events_url":"https://api.github.com/repos/deis/deis/events","assignees_url":"https://api.github.com/repos/deis/deis/assignees{/user}","branches_url":"https://api.github.com/repos/deis/deis/branches{/branch}","tags_url":"https://api.github.com/repos/deis/deis/tags","blobs_url":"https://api.github.com/repos/deis/deis/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/deis/deis/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/deis/deis/git/refs{/sha}","trees_url":"https://api.github.com/repos/deis/deis/git/trees{/sha}","statuses_url":"https://api.github.com/repos/deis/deis/statuses/{sha}","languages_url":"https://api.github.com/repos/deis/deis/languages","stargazers_url":"https://api.github.com/repos/deis/deis/stargazers","contributors_url":"https://api.github.com/repos/deis/deis/contributors","subscribers_url":"https://api.github.com/repos/deis/deis/subscribers","subscription_url":"https://api.github.com/repos/deis/deis/subscription","commits_url":"https://api.github.com/repos/deis/deis/commits{/sha}","git_commits_url":"https://api.github.com/repos/deis/deis/git/commits{/sha}","comments_url":"https://api.github.com/repos/deis/deis/comments{/number}","issue_comment_url":"https://api.github.com/repos/deis/deis/issues/comments{/number}","contents_url":"https://api.github.com/repos/deis/deis/contents/{+path}","compare_url":"https://api.github.com/repos/deis/deis/compare/{base}...{head}","merges_url":"https://api.github.com/repos/deis/deis/merges","archive_url":"https://api.github.com/repos/deis/deis/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/deis/deis/downloads","issues_url":"https://api.github.com/repos/deis/deis/issues{/number}","pulls_url":"https://api.github.com/repos/deis/deis/pulls{/number}","milestones_url":"https://api.github.com/repos/deis/deis/milestones{/number}","notifications_url":"https://api.github.com/repos/deis/deis/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/deis/deis/labels{/name}","releases_url":"https://api.github.com/repos/deis/deis/releases{/id}","created_at":"2013-07-22T19:02:40Z","updated_at":"2015-08-17T22:15:42Z","pushed_at":"2015-08-17T22:41:26Z","git_url":"git://github.com/deis/deis.git","ssh_url":"git@github.com:deis/deis.git","clone_url":"https://github.com/deis/deis.git","svn_url":"https://github.com/deis/deis","homepage":"http://deis.io","size":43203,"stargazers_count":4610,"watchers_count":4610,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":713,"mirror_url":null,"open_issues_count":245,"forks":713,"open_issues":245,"watchers":4610,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/deis/deis/pulls/4273"},"html":{"href":"https://github.com/deis/deis/pull/4273"},"issue":{"href":"https://api.github.com/repos/deis/deis/issues/4273"},"comments":{"href":"https://api.github.com/repos/deis/deis/issues/4273/comments"},"review_comments":{"href":"https://api.github.com/repos/deis/deis/pulls/4273/comments"},"review_comment":{"href":"https://api.github.com/repos/deis/deis/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/deis/deis/pulls/4273/commits"},"statuses":{"href":"https://api.github.com/repos/deis/deis/statuses/2bf06d75d307d516d120a981009445422c907ba0"}}}}