{"action":"created","comment":{"url":"https://api.github.com/repos/google/minions/pulls/comments/218977040","pull_request_review_id":157038487,"id":218977040,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxODk3NzA0MA==","diff_hunk":"@@ -0,0 +1,209 @@\n+//  Copyright 2018 Google LLC\n+//  Licensed under the Apache License, Version 2.0 (the \"License\");\n+//  you may not use this file except in compliance with the License.\n+//  You may obtain a copy of the License at\n+\n+//        https://www.apache.org/licenses/LICENSE-2.0\n+\n+//  Unless required by applicable law or agreed to in writing, software\n+//  distributed under the License is distributed on an \"AS IS\" BASIS,\n+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+//  See the License for the specific language governing permissions and\n+//\tlimitations under the License.\n+\n+// Package docker contains libraries to access and handle docker containers.\n+// This feature will *only* work on Linux OS with appropriate Kernel support.\n+package docker\n+\n+import (\n+\t\"errors\"\n+\t\"fmt\"\n+\t\"io/ioutil\"\n+\t\"log\"\n+\t\"os\"\n+\t\"os/exec\"\n+\t\"path/filepath\"\n+\t\"strings\"\n+)\n+\n+const aufsPath = \"/sbin/mount.aufs\"\n+\n+// Mount mounts the container's filesystem into a mount directory.\n+// Note that this method just executes a bunch of shell command\n+// to build the filesystem. Not much magic here.\n+// All of this is based on https://github.com/google/docker-explorer/blob/master/docker_explorer/lib/storage.py\n+func Mount(mountDir string, dockerDir string, dockerVersion int, containerID string, driver string) error {\n+\tvar err error\n+\tvar commands []*exec.Cmd\n+\n+\t//TODO: figure out the volume and mountpoints for AUFS\n+\n+\trootDir := filepath.Clean(filepath.Join(dockerDir, \"...\"))\n+\tmountID := \"\" // Default with V1\n+\tif dockerVersion == 2 {\n+\t\tcPath := filepath.Join(dockerDir, \"image\", driver, \"layerdb\", \"mounts\", containerID)\n+\t\tmid, err := ioutil.ReadFile(filepath.Join(cPath, \"mount-id\"))\n+\t\tif err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t\tmountID = string(mid)\n+\t}\n+\n+\tswitch driver {\n+\tcase \"aufs\":\n+\t\tcommands, err = aufsMountCommands(mountDir, rootDir, mountID, dockerDir, driver, dockerVersion, nil, nil)\n+\tcase \"overlay\":\n+\t\tcommands, err = overlayFsMountCommands(mountDir, mountID, dockerDir, driver, dockerVersion)\n+\tcase \"overlay2\":\n+\t\tcommands, err = overlayFsMountCommands(mountDir, mountID, dockerDir, driver, dockerVersion)\n+\tdefault:\n+\t\treturn errors.New(\"unknown driver\")\n+\t}\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\n+\tfor _, c := range commands {\n+\t\tfmt.Printf(\"Will now run the following command: %s \", c.Path)\n+\t\tfor _, a := range c.Args {\n+\t\t\tfmt.Printf(\" %s \", a)\n+\t\t}\n+\t\tfmt.Print(\"\\n\")\n+\t\tstdoutStderr, err := c.CombinedOutput()\n+\t\tfmt.Printf(\"%s\\n\", stdoutStderr)\n+\t\tif err != nil {\n+\t\t\tlog.Fatal(err)\n+\t\t}\n+\t}\n+\treturn nil\n+}\n+\n+type storageInfo struct {\n+\tsource      string\n+\tdestination string\n+\tname        string\n+}\n+\n+func aufsMountCommands(mountDir string, rootDir string, mountID string, dockerDir string,\n+\tstorageMethod string, dockerVersion int, volumes map[string]string, mountPoints []storageInfo) ([]*exec.Cmd, error) {\n+\n+\tif _, err := os.Stat(aufsPath); os.IsNotExist(err) {\n+\t\treturn nil, errors.New(\"cannot find /sbin/mount.aufs. Maybe install aufs-tools?\")\n+\t}\n+\tlayersPath := filepath.Join(dockerDir, storageMethod, \"layers\", mountID)\n+\tcontainerID := filepath.Join(dockerDir, storageMethod, \"diff\", mountID)\n+\tif dockerVersion == 1 { // Old-style layers path.\n+\t\tlayersPath = filepath.Join(dockerDir, storageMethod, \"layers\", containerID)\n+\t}\n+\n+\tvar commands []*exec.Cmd\n+\tmountpointPath := filepath.Join(dockerDir, storageMethod, \"diff\", containerID)\n+\t// Mount the base.\n+\topts := fmt.Sprintf(\"-oro,br%s=ro+wh\", mountpointPath)\n+\targs := []string{\"-taufs\", opts, \"none\", mountDir}\n+\tcommands = append(commands, exec.Command(\"mount\", args...))\n+\t// Now mount all layers above.\n+\tfiles, err := ioutil.ReadDir(layersPath)\n+\tif err != nil {\n+\t\treturn nil, fmt.Errorf(\"Error while building layers mount command on layerspath %s: %v\", layersPath, err)\n+\t}\n+\tfor _, f := range files {\n+\t\tpath := filepath.Join(dockerDir, storageMethod, \"diff\", f.Name())\n+\t\topts := fmt.Sprintf(\"-oro,remount,append:%s=ro+wh\", path)\n+\t\targs := []string{\"-taufs\", opts, \"none\", mountDir}\n+\t\tcommands = append(commands, exec.Command(\"mount\", args...))\n+\t}\n+\n+\t// Now mount volumes.\n+\tswitch dockerVersion {\n+\tcase 1:\n+\t\treturn append(commands, mountVolumesDocker1(mountDir, rootDir, volumes)...), nil\n+\tcase 2:\n+\t\treturn append(commands, mountVolumesDocker2(mountDir, mountPoints)...), nil\n+\tdefault:\n+\t\treturn nil, fmt.Errorf(\"Unsupported docker version: %d\", dockerVersion)\n+\t}\n+}\n+\n+func mountVolumesDocker1(mountDir string, rootDir string, volumes map[string]string) []*exec.Cmd {\n+\tcommands := make([]*exec.Cmd, 0)\n+\tfor mountpoint, storage := range volumes {\n+\t\tstorageIhp := strings.TrimLeft(mountpoint, string(os.PathSeparator))\n+\t\tmountpointIhp := strings.TrimLeft(storage, string(os.PathSeparator))\n+\t\tstoragePath := filepath.Join(rootDir, storageIhp)\n+\t\tvolumeMountpoint := filepath.Join(mountDir, mountpointIhp)\n+\t\targs := []string{\"--bind\", \"-oro\", storagePath, volumeMountpoint}\n+\t\tcommands = append(commands, exec.Command(\"mount\", args...))\n+\t}\n+\treturn commands\n+}\n+\n+func mountVolumesDocker2(mountDir string, mountPoints []storageInfo) []*exec.Cmd {\n+\tcommands := make([]*exec.Cmd, 0)\n+\tfor _, s := range mountPoints {\n+\t\tsrcMount := strings.TrimLeft(s.source, string(os.PathSeparator))\n+\t\tdstMount := strings.TrimLeft(s.destination, string(os.PathSeparator))\n+\t\tif srcMount == \"\" {\n+\t\t\tvolumeName := s.name\n+\t\t\tsrcMount = filepath.Join(\"docker\", \"volumes\", volumeName, \"_data\")\n+\t\t}\n+\t\tstoragePath := filepath.Join(mountDir, dstMount)\n+\t\tvolumeMountpoint := filepath.Join(mountDir, dstMount)\n+\t\targs := []string{\"--bind\", \"-oro\", storagePath, volumeMountpoint}\n+\t\tcommands = append(commands, exec.Command(\"mount\", args...))\n+\t}\n+\treturn commands\n+}\n+\n+func overlayFsMountCommands(mountDir string, mountID string, dockerDir string,\n+\tstorageMethod string, dockerVersion int) ([]*exec.Cmd, error) {\n+\tmountIDPath := filepath.Join(dockerDir, storageMethod, mountID)\n+\tconst lowerdirName = \"lower\"\n+\tconst upperdirName = \"diff\"\n+\n+\tlowerDirPath := filepath.Join(mountIDPath, lowerdirName)\n+\tlowerContent, err := ioutil.ReadFile(lowerDirPath)\n+\tif err != nil {\n+\t\treturn nil, fmt.Errorf(\"cannot read lower content pointer: %s\", err)\n+\t}\n+\n+\t// There are differences between overlay and overlay2 here for the lower directory\n+\tif storageMethod == \"overlay\" {\n+\t\tlowerDirPath = filepath.Join(dockerDir, storageMethod, string(lowerContent), \"root\")\n+\t} else {\n+\t\t// For the overlay2 driver, the pointer to the 'lower directory' can be\n+\t\t// made of multiple layers.\n+\t\t// For that argument to be passed to the mount.overlay command, we need to\n+\t\t// reconstruct full paths to all these layers.\n+\t\t// ie: from 'abcd:0123' to '/var/lib/docker/abcd:/var/lib/docker/0123'\n+\t\tvar paths []string\n+\t\tfor _, lc := range strings.Split(string(lowerContent), \":\") {","path":"src/go/goblins/docker/docker.go","position":180,"original_position":180,"commit_id":"9b6d765eb45b3d01245d6bf89692287a269af107","original_commit_id":"9b6d765eb45b3d01245d6bf89692287a269af107","user":{"login":"paradoxengine","id":8309115,"node_id":"MDQ6VXNlcjgzMDkxMTU=","avatar_url":"https://avatars3.githubusercontent.com/u/8309115?v=4","gravatar_id":"","url":"https://api.github.com/users/paradoxengine","html_url":"https://github.com/paradoxengine","followers_url":"https://api.github.com/users/paradoxengine/followers","following_url":"https://api.github.com/users/paradoxengine/following{/other_user}","gists_url":"https://api.github.com/users/paradoxengine/gists{/gist_id}","starred_url":"https://api.github.com/users/paradoxengine/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/paradoxengine/subscriptions","organizations_url":"https://api.github.com/users/paradoxengine/orgs","repos_url":"https://api.github.com/users/paradoxengine/repos","events_url":"https://api.github.com/users/paradoxengine/events{/privacy}","received_events_url":"https://api.github.com/users/paradoxengine/received_events","type":"User","site_admin":false},"body":"Sure","created_at":"2018-09-19T21:54:58Z","updated_at":"2018-09-19T21:54:58Z","html_url":"https://github.com/google/minions/pull/26#discussion_r218977040","pull_request_url":"https://api.github.com/repos/google/minions/pulls/26","author_association":"COLLABORATOR","_links":{"self":{"href":"https://api.github.com/repos/google/minions/pulls/comments/218977040"},"html":{"href":"https://github.com/google/minions/pull/26#discussion_r218977040"},"pull_request":{"href":"https://api.github.com/repos/google/minions/pulls/26"}},"in_reply_to_id":218829328},"pull_request":{"url":"https://api.github.com/repos/google/minions/pulls/26","id":215793291,"node_id":"MDExOlB1bGxSZXF1ZXN0MjE1NzkzMjkx","html_url":"https://github.com/google/minions/pull/26","diff_url":"https://github.com/google/minions/pull/26.diff","patch_url":"https://github.com/google/minions/pull/26.patch","issue_url":"https://api.github.com/repos/google/minions/issues/26","number":26,"state":"open","locked":false,"title":"Add a Goblin that supports Docker","user":{"login":"paradoxengine","id":8309115,"node_id":"MDQ6VXNlcjgzMDkxMTU=","avatar_url":"https://avatars3.githubusercontent.com/u/8309115?v=4","gravatar_id":"","url":"https://api.github.com/users/paradoxengine","html_url":"https://github.com/paradoxengine","followers_url":"https://api.github.com/users/paradoxengine/followers","following_url":"https://api.github.com/users/paradoxengine/following{/other_user}","gists_url":"https://api.github.com/users/paradoxengine/gists{/gist_id}","starred_url":"https://api.github.com/users/paradoxengine/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/paradoxengine/subscriptions","organizations_url":"https://api.github.com/users/paradoxengine/orgs","repos_url":"https://api.github.com/users/paradoxengine/repos","events_url":"https://api.github.com/users/paradoxengine/events{/privacy}","received_events_url":"https://api.github.com/users/paradoxengine/received_events","type":"User","site_admin":false},"body":"Refactor Goblins so that we can better support multiple types of goblins.\r\nAdd support for mounting docker containers and scan them.\r\nFixes #7 ","created_at":"2018-09-15T22:54:03Z","updated_at":"2018-09-19T21:54:58Z","closed_at":null,"merged_at":null,"merge_commit_sha":"63fb3db586308b4e33e4af2af5579f0c4f148f90","assignee":null,"assignees":[],"requested_reviewers":[{"login":"n3vermind","id":4421529,"node_id":"MDQ6VXNlcjQ0MjE1Mjk=","avatar_url":"https://avatars2.githubusercontent.com/u/4421529?v=4","gravatar_id":"","url":"https://api.github.com/users/n3vermind","html_url":"https://github.com/n3vermind","followers_url":"https://api.github.com/users/n3vermind/followers","following_url":"https://api.github.com/users/n3vermind/following{/other_user}","gists_url":"https://api.github.com/users/n3vermind/gists{/gist_id}","starred_url":"https://api.github.com/users/n3vermind/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/n3vermind/subscriptions","organizations_url":"https://api.github.com/users/n3vermind/orgs","repos_url":"https://api.github.com/users/n3vermind/repos","events_url":"https://api.github.com/users/n3vermind/events{/privacy}","received_events_url":"https://api.github.com/users/n3vermind/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":{"url":"https://api.github.com/repos/google/minions/milestones/3","html_url":"https://github.com/google/minions/milestone/3","labels_url":"https://api.github.com/repos/google/minions/milestones/3/labels","id":3639540,"node_id":"MDk6TWlsZXN0b25lMzYzOTU0MA==","number":3,"title":"0.3","description":null,"creator":{"login":"paradoxengine","id":8309115,"node_id":"MDQ6VXNlcjgzMDkxMTU=","avatar_url":"https://avatars3.githubusercontent.com/u/8309115?v=4","gravatar_id":"","url":"https://api.github.com/users/paradoxengine","html_url":"https://github.com/paradoxengine","followers_url":"https://api.github.com/users/paradoxengine/followers","following_url":"https://api.github.com/users/paradoxengine/following{/other_user}","gists_url":"https://api.github.com/users/paradoxengine/gists{/gist_id}","starred_url":"https://api.github.com/users/paradoxengine/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/paradoxengine/subscriptions","organizations_url":"https://api.github.com/users/paradoxengine/orgs","repos_url":"https://api.github.com/users/paradoxengine/repos","events_url":"https://api.github.com/users/paradoxengine/events{/privacy}","received_events_url":"https://api.github.com/users/paradoxengine/received_events","type":"User","site_admin":false},"open_issues":2,"closed_issues":0,"state":"open","created_at":"2018-09-10T14:15:53Z","updated_at":"2018-09-15T22:54:03Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/google/minions/pulls/26/commits","review_comments_url":"https://api.github.com/repos/google/minions/pulls/26/comments","review_comment_url":"https://api.github.com/repos/google/minions/pulls/comments{/number}","comments_url":"https://api.github.com/repos/google/minions/issues/26/comments","statuses_url":"https://api.github.com/repos/google/minions/statuses/9b6d765eb45b3d01245d6bf89692287a269af107","head":{"label":"google:docker_goblin","ref":"docker_goblin","sha":"9b6d765eb45b3d01245d6bf89692287a269af107","user":{"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1342004?v=4","gravatar_id":"","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"repo":{"id":131590596,"node_id":"MDEwOlJlcG9zaXRvcnkxMzE1OTA1OTY=","name":"minions","full_name":"google/minions","private":false,"owner":{"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1342004?v=4","gravatar_id":"","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/google/minions","description":"Distributed filesystem scanner","fork":false,"url":"https://api.github.com/repos/google/minions","forks_url":"https://api.github.com/repos/google/minions/forks","keys_url":"https://api.github.com/repos/google/minions/keys{/key_id}","collaborators_url":"https://api.github.com/repos/google/minions/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/google/minions/teams","hooks_url":"https://api.github.com/repos/google/minions/hooks","issue_events_url":"https://api.github.com/repos/google/minions/issues/events{/number}","events_url":"https://api.github.com/repos/google/minions/events","assignees_url":"https://api.github.com/repos/google/minions/assignees{/user}","branches_url":"https://api.github.com/repos/google/minions/branches{/branch}","tags_url":"https://api.github.com/repos/google/minions/tags","blobs_url":"https://api.github.com/repos/google/minions/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/google/minions/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/google/minions/git/refs{/sha}","trees_url":"https://api.github.com/repos/google/minions/git/trees{/sha}","statuses_url":"https://api.github.com/repos/google/minions/statuses/{sha}","languages_url":"https://api.github.com/repos/google/minions/languages","stargazers_url":"https://api.github.com/repos/google/minions/stargazers","contributors_url":"https://api.github.com/repos/google/minions/contributors","subscribers_url":"https://api.github.com/repos/google/minions/subscribers","subscription_url":"https://api.github.com/repos/google/minions/subscription","commits_url":"https://api.github.com/repos/google/minions/commits{/sha}","git_commits_url":"https://api.github.com/repos/google/minions/git/commits{/sha}","comments_url":"https://api.github.com/repos/google/minions/comments{/number}","issue_comment_url":"https://api.github.com/repos/google/minions/issues/comments{/number}","contents_url":"https://api.github.com/repos/google/minions/contents/{+path}","compare_url":"https://api.github.com/repos/google/minions/compare/{base}...{head}","merges_url":"https://api.github.com/repos/google/minions/merges","archive_url":"https://api.github.com/repos/google/minions/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/google/minions/downloads","issues_url":"https://api.github.com/repos/google/minions/issues{/number}","pulls_url":"https://api.github.com/repos/google/minions/pulls{/number}","milestones_url":"https://api.github.com/repos/google/minions/milestones{/number}","notifications_url":"https://api.github.com/repos/google/minions/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/google/minions/labels{/name}","releases_url":"https://api.github.com/repos/google/minions/releases{/id}","deployments_url":"https://api.github.com/repos/google/minions/deployments","created_at":"2018-04-30T12:02:00Z","updated_at":"2018-09-19T21:32:56Z","pushed_at":"2018-09-19T21:32:54Z","git_url":"git://github.com/google/minions.git","ssh_url":"git@github.com:google/minions.git","clone_url":"https://github.com/google/minions.git","svn_url":"https://github.com/google/minions","homepage":"","size":234,"stargazers_count":51,"watchers_count":51,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":4,"mirror_url":null,"archived":false,"open_issues_count":15,"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":4,"open_issues":15,"watchers":51,"default_branch":"master"}},"base":{"label":"google:master","ref":"master","sha":"b0883439aeae3384f6c8134ea233586b77c23674","user":{"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1342004?v=4","gravatar_id":"","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"repo":{"id":131590596,"node_id":"MDEwOlJlcG9zaXRvcnkxMzE1OTA1OTY=","name":"minions","full_name":"google/minions","private":false,"owner":{"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1342004?v=4","gravatar_id":"","url":"https://api.github.com/users/google","html_url":"https://github.com/google","followers_url":"https://api.github.com/users/google/followers","following_url":"https://api.github.com/users/google/following{/other_user}","gists_url":"https://api.github.com/users/google/gists{/gist_id}","starred_url":"https://api.github.com/users/google/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/google/subscriptions","organizations_url":"https://api.github.com/users/google/orgs","repos_url":"https://api.github.com/users/google/repos","events_url":"https://api.github.com/users/google/events{/privacy}","received_events_url":"https://api.github.com/users/google/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/google/minions","description":"Distributed filesystem scanner","fork":false,"url":"https://api.github.com/repos/google/minions","forks_url":"https://api.github.com/repos/google/minions/forks","keys_url":"https://api.github.com/repos/google/minions/keys{/key_id}","collaborators_url":"https://api.github.com/repos/google/minions/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/google/minions/teams","hooks_url":"https://api.github.com/repos/google/minions/hooks","issue_events_url":"https://api.github.com/repos/google/minions/issues/events{/number}","events_url":"https://api.github.com/repos/google/minions/events","assignees_url":"https://api.github.com/repos/google/minions/assignees{/user}","branches_url":"https://api.github.com/repos/google/minions/branches{/branch}","tags_url":"https://api.github.com/repos/google/minions/tags","blobs_url":"https://api.github.com/repos/google/minions/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/google/minions/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/google/minions/git/refs{/sha}","trees_url":"https://api.github.com/repos/google/minions/git/trees{/sha}","statuses_url":"https://api.github.com/repos/google/minions/statuses/{sha}","languages_url":"https://api.github.com/repos/google/minions/languages","stargazers_url":"https://api.github.com/repos/google/minions/stargazers","contributors_url":"https://api.github.com/repos/google/minions/contributors","subscribers_url":"https://api.github.com/repos/google/minions/subscribers","subscription_url":"https://api.github.com/repos/google/minions/subscription","commits_url":"https://api.github.com/repos/google/minions/commits{/sha}","git_commits_url":"https://api.github.com/repos/google/minions/git/commits{/sha}","comments_url":"https://api.github.com/repos/google/minions/comments{/number}","issue_comment_url":"https://api.github.com/repos/google/minions/issues/comments{/number}","contents_url":"https://api.github.com/repos/google/minions/contents/{+path}","compare_url":"https://api.github.com/repos/google/minions/compare/{base}...{head}","merges_url":"https://api.github.com/repos/google/minions/merges","archive_url":"https://api.github.com/repos/google/minions/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/google/minions/downloads","issues_url":"https://api.github.com/repos/google/minions/issues{/number}","pulls_url":"https://api.github.com/repos/google/minions/pulls{/number}","milestones_url":"https://api.github.com/repos/google/minions/milestones{/number}","notifications_url":"https://api.github.com/repos/google/minions/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/google/minions/labels{/name}","releases_url":"https://api.github.com/repos/google/minions/releases{/id}","deployments_url":"https://api.github.com/repos/google/minions/deployments","created_at":"2018-04-30T12:02:00Z","updated_at":"2018-09-19T21:32:56Z","pushed_at":"2018-09-19T21:32:54Z","git_url":"git://github.com/google/minions.git","ssh_url":"git@github.com:google/minions.git","clone_url":"https://github.com/google/minions.git","svn_url":"https://github.com/google/minions","homepage":"","size":234,"stargazers_count":51,"watchers_count":51,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":4,"mirror_url":null,"archived":false,"open_issues_count":15,"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":4,"open_issues":15,"watchers":51,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/google/minions/pulls/26"},"html":{"href":"https://github.com/google/minions/pull/26"},"issue":{"href":"https://api.github.com/repos/google/minions/issues/26"},"comments":{"href":"https://api.github.com/repos/google/minions/issues/26/comments"},"review_comments":{"href":"https://api.github.com/repos/google/minions/pulls/26/comments"},"review_comment":{"href":"https://api.github.com/repos/google/minions/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/google/minions/pulls/26/commits"},"statuses":{"href":"https://api.github.com/repos/google/minions/statuses/9b6d765eb45b3d01245d6bf89692287a269af107"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"paradoxengine"}}