{"action":"created","comment":{"url":"https://api.github.com/repos/docker/docker/pulls/comments/35181088","id":35181088,"diff_hunk":"@@ -0,0 +1,437 @@\n+package client\n+\n+import (\n+\t\"bufio\"\n+\t\"encoding/hex\"\n+\t\"encoding/json\"\n+\t\"errors\"\n+\t\"fmt\"\n+\t\"io\"\n+\t\"net\"\n+\t\"net/http\"\n+\t\"net/url\"\n+\t\"os\"\n+\t\"path/filepath\"\n+\t\"regexp\"\n+\t\"strconv\"\n+\t\"strings\"\n+\t\"time\"\n+\n+\t\"github.com/Sirupsen/logrus\"\n+\t\"github.com/docker/distribution/digest\"\n+\t\"github.com/docker/distribution/registry/client/auth\"\n+\t\"github.com/docker/distribution/registry/client/transport\"\n+\t\"github.com/docker/docker/cliconfig\"\n+\t\"github.com/docker/docker/pkg/ansiescape\"\n+\t\"github.com/docker/docker/pkg/ioutils\"\n+\tflag \"github.com/docker/docker/pkg/mflag\"\n+\t\"github.com/docker/docker/pkg/tlsconfig\"\n+\t\"github.com/docker/docker/registry\"\n+\t\"github.com/docker/notary/client\"\n+\t\"github.com/docker/notary/pkg/passphrase\"\n+\t\"github.com/endophage/gotuf/data\"\n+)\n+\n+var untrusted bool\n+\n+func addTrustedFlags(fs *flag.FlagSet, verify bool) {\n+\tvar trusted bool\n+\tif e := os.Getenv(\"DOCKER_TRUST\"); e != \"\" {\n+\t\tvar err error\n+\t\ttrusted, err = strconv.ParseBool(e)\n+\t\tif err != nil {\n+\t\t\t// treat any other value as true\n+\t\t\ttrusted = true\n+\t\t}\n+\t}\n+\tmessage := \"Skip image signing\"\n+\tif verify {\n+\t\tmessage = \"Skip image verification\"\n+\t}\n+\tfs.BoolVar(&untrusted, []string{\"-untrusted\"}, !trusted, message)\n+}\n+\n+func isTrusted() bool {\n+\t// TODO(dmcgowan): false if name is hex image id\n+\t// TODO(dmcgowan): whitelist specific names to be untrusted\n+\t// TODO(dmcgowan): canonicalize name before checking whitelist\n+\treturn !untrusted\n+}\n+\n+var targetRegexp = regexp.MustCompile(`([\\S]+): digest: ([\\S]+) size: ([\\d]+)`)\n+\n+type target struct {\n+\treference registry.Reference\n+\tdigest    digest.Digest\n+\tsize      int64\n+}\n+\n+func (cli *DockerCli) trustDirectory() string {\n+\treturn filepath.Join(cliconfig.ConfigDir(), \"trust\")\n+}\n+\n+// certificateDirectory returns the directory containing\n+// TLS certificates for the given server. An error is\n+// returned if there was an error parsing the server string.\n+func (cli *DockerCli) certificateDirectory(server string) (string, error) {\n+\tu, err := url.Parse(server)\n+\tif err != nil {\n+\t\treturn \"\", err\n+\t}\n+\n+\treturn filepath.Join(cliconfig.ConfigDir(), \"tls\", u.Host), nil\n+}\n+\n+func trustServer(index *registry.IndexInfo) string {\n+\tif s := os.Getenv(\"DOCKER_TRUST_SERVER\"); s != \"\" {\n+\t\tif !strings.HasPrefix(s, \"http\") {\n+\t\t\treturn \"https://\" + s\n+\t\t}\n+\t\treturn s\n+\t}\n+\tif index.Official {\n+\t\treturn registry.NOTARYSERVER\n+\t}\n+\treturn \"https://\" + index.Name\n+}\n+\n+type simpleCredentialStore struct {\n+\tauth cliconfig.AuthConfig\n+}\n+\n+func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {\n+\treturn scs.auth.Username, scs.auth.Password\n+}\n+\n+func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig cliconfig.AuthConfig) (*client.NotaryRepository, error) {\n+\tserver := trustServer(repoInfo.Index)\n+\tvar cfg = tlsconfig.ClientDefault\n+\tcfg.InsecureSkipVerify = !repoInfo.Index.Secure\n+\n+\t// Get certificate base directory\n+\tcertDir, err := cli.certificateDirectory(server)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tlogrus.Debugf(\"reading certificate directory: %s\", certDir)\n+\n+\tif err := registry.ReadCertsDirectory(&cfg, certDir); err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tbase := &http.Transport{\n+\t\tProxy: http.ProxyFromEnvironment,\n+\t\tDial: (&net.Dialer{\n+\t\t\tTimeout:   30 * time.Second,\n+\t\t\tKeepAlive: 30 * time.Second,\n+\t\t\tDualStack: true,\n+\t\t}).Dial,\n+\t\tTLSHandshakeTimeout: 10 * time.Second,\n+\t\tTLSClientConfig:     &cfg,\n+\t\tDisableKeepAlives:   true,\n+\t}\n+\n+\t// Skip configuration headers since request is not going to Docker daemon\n+\tmodifiers := registry.DockerHeaders(http.Header{})\n+\tauthTransport := transport.NewTransport(base, modifiers...)\n+\tpingClient := &http.Client{\n+\t\tTransport: authTransport,\n+\t\tTimeout:   5 * time.Second,\n+\t}\n+\tendpointStr := server + \"/v2/\"\n+\treq, err := http.NewRequest(\"GET\", endpointStr, nil)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tresp, err := pingClient.Do(req)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tdefer resp.Body.Close()\n+\n+\tchallengeManager := auth.NewSimpleChallengeManager()\n+\tif err := challengeManager.AddResponse(resp); err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tcreds := simpleCredentialStore{auth: authConfig}\n+\ttokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.CanonicalName, \"push\", \"pull\")\n+\tbasicHandler := auth.NewBasicHandler(creds)\n+\tmodifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))\n+\ttr := transport.NewTransport(base, modifiers...)\n+\n+\treturn client.NewNotaryRepository(cli.trustDirectory(), repoInfo.CanonicalName, server, tr, cli.getPassphraseRetriever())\n+}\n+\n+func convertTarget(t client.Target) (target, error) {\n+\th, ok := t.Hashes[\"sha256\"]\n+\tif !ok {\n+\t\treturn target{}, errors.New(\"no valid hash, expecting sha256\")\n+\t}\n+\treturn target{\n+\t\treference: registry.ParseReference(t.Name),\n+\t\tdigest:    digest.NewDigestFromHex(\"sha256\", hex.EncodeToString(h)),\n+\t\tsize:      t.Length,\n+\t}, nil\n+}\n+\n+func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {\n+\tbaseRetriever := passphrase.PromptRetriever()\n+\trootEnv := os.Getenv(\"DOCKER_TRUST_ROOT_PASSPHRASE\")\n+\ttargetEnv := os.Getenv(\"DOCKER_TRUST_TARGET_PASSPHRASE\")\n+\tsnapshotEnv := os.Getenv(\"DOCKER_TRUST_SNAPSHOT_PASSPHRASE\")\n+\n+\treturn func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {\n+\t\tif alias == \"root\" && len(rootEnv) >= 8 {\n+\t\t\treturn rootEnv, false, nil\n+\t\t}\n+\t\tif alias == \"targets\" && len(targetEnv) >= 8 {\n+\t\t\treturn targetEnv, false, nil\n+\t\t}\n+\t\tif alias == \"snapshot\" && len(snapshotEnv) >= 8 {\n+\t\t\treturn snapshotEnv, false, nil\n+\t\t}\n+\t\treturn baseRetriever(keyName, alias, createNew, numAttempts)\n+\t}\n+}\n+\n+func (cli *DockerCli) trustedReference(repo string, ref registry.Reference) (registry.Reference, error) {\n+\trepoInfo, err := registry.ParseRepositoryInfo(repo)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\t// Resolve the Auth config relevant for this server\n+\tauthConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index)\n+\n+\tnotaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)\n+\tif err != nil {\n+\t\tfmt.Fprintf(cli.out, \"Error establishing connection to trust repository: %s\\n\", err)\n+\t\treturn nil, err\n+\t}\n+\n+\tt, err := notaryRepo.GetTargetByName(ref.String())\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tr, err := convertTarget(*t)\n+\tif err != nil {\n+\t\treturn nil, err\n+\n+\t}\n+\n+\treturn registry.DigestReference(r.digest), nil\n+}\n+\n+func (cli *DockerCli) tagTrusted(repoInfo *registry.RepositoryInfo, trustedRef, ref registry.Reference) error {\n+\tfullName := trustedRef.ImageName(repoInfo.LocalName)\n+\tfmt.Fprintf(cli.out, \"Tagging %s as %s\\n\", fullName, ref.ImageName(repoInfo.LocalName))\n+\ttv := url.Values{}\n+\ttv.Set(\"repo\", repoInfo.LocalName)\n+\ttv.Set(\"tag\", ref.String())\n+\ttv.Set(\"force\", \"1\")\n+\n+\tif _, _, err := readBody(cli.call(\"POST\", \"/images/\"+fullName+\"/tag?\"+tv.Encode(), nil, nil)); err != nil {\n+\t\treturn err\n+\t}\n+\n+\treturn nil\n+}\n+\n+func notaryError(err error) error {\n+\tif _, ok := err.(*json.SyntaxError); ok {\n+\t\tlogrus.Debugf(\"Notary syntax error: %s\", err)\n+\t\treturn errors.New(\"no trust data available for repository\")\n+\t}\n+\n+\treturn err\n+}\n+\n+func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig cliconfig.AuthConfig) error {\n+\tvar (\n+\t\tv    = url.Values{}\n+\t\trefs = []target{}\n+\t)\n+\n+\tnotaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)\n+\tif err != nil {\n+\t\tfmt.Fprintf(cli.out, \"Error establishing connection to trust repository: %s\\n\", err)\n+\t\treturn err\n+\t}\n+\n+\tif ref.String() == \"\" {\n+\t\t// List all targets\n+\t\ttargets, err := notaryRepo.ListTargets()\n+\t\tif err != nil {\n+\t\t\treturn notaryError(err)\n+\t\t}\n+\t\tfor _, tgt := range targets {\n+\t\t\tt, err := convertTarget(*tgt)\n+\t\t\tif err != nil {\n+\t\t\t\tfmt.Fprintf(cli.out, \"Skipping target for %q\\n\", repoInfo.LocalName)\n+\t\t\t\tcontinue\n+\t\t\t}\n+\t\t\trefs = append(refs, t)\n+\t\t}\n+\t} else {\n+\t\tt, err := notaryRepo.GetTargetByName(ref.String())\n+\t\tif err != nil {\n+\t\t\treturn notaryError(err)\n+\t\t}\n+\t\tr, err := convertTarget(*t)\n+\t\tif err != nil {\n+\t\t\treturn err\n+\n+\t\t}\n+\t\trefs = append(refs, r)\n+\t}\n+\n+\tv.Set(\"fromImage\", repoInfo.LocalName)\n+\tfor i, r := range refs {\n+\t\tdisplayTag := r.reference.String()\n+\t\tif displayTag != \"\" {\n+\t\t\tdisplayTag = \":\" + displayTag\n+\t\t}\n+\t\tfmt.Fprintf(cli.out, \"Pull (%d of %d): %s%s@%s\\n\", i+1, len(refs), repoInfo.LocalName, displayTag, r.digest)\n+\t\tv.Set(\"tag\", r.digest.String())\n+\n+\t\t_, _, err = cli.clientRequestAttemptLogin(\"POST\", \"/images/create?\"+v.Encode(), nil, cli.out, repoInfo.Index, \"pull\")\n+\t\tif err != nil {\n+\t\t\treturn err\n+\t\t}\n+\n+\t\t// If reference is not trusted, tag by trusted reference\n+\t\tif !r.reference.HasDigest() {\n+\t\t\tif err := cli.tagTrusted(repoInfo, registry.DigestReference(r.digest), r.reference); err != nil {\n+\t\t\t\treturn err\n+\n+\t\t\t}\n+\t\t}\n+\t}\n+\treturn nil\n+}\n+\n+func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) {\n+\t// Create multiWriter\n+\tr, w := io.Pipe()\n+\t// Capture output\n+\tout := io.MultiWriter(in, w)\n+\t// output chan\n+\ttargetChan := make(chan []target)\n+\n+\tgo func() {\n+\t\ttargets := []target{}\n+\t\tscanner := bufio.NewScanner(r)\n+\t\tscanner.Split(ansiescape.ScanANSILines)\n+\t\t// New splitter\n+\t\tfor scanner.Scan() {\n+\t\t\tline := scanner.Bytes()\n+\t\t\tif matches := targetRegexp.FindSubmatch(line); len(matches) == 4 {\n+\t\t\t\tdgst, err := digest.ParseDigest(string(matches[2]))\n+\t\t\t\tif err != nil {\n+\t\t\t\t\tfmt.Fprintf(in, \"Bad digest value %q, ignoring\\n\", string(matches[2]))","path":"api/client/trust.go","position":332,"original_position":332,"commit_id":"0a8c1bfdd0c8f41d4f4725fcc2b8bad4bf892482","original_commit_id":"0a8c1bfdd0c8f41d4f4725fcc2b8bad4bf892482","user":{"login":"jfrazelle","id":1445228,"avatar_url":"https://avatars.githubusercontent.com/u/1445228?v=3","gravatar_id":"","url":"https://api.github.com/users/jfrazelle","html_url":"https://github.com/jfrazelle","followers_url":"https://api.github.com/users/jfrazelle/followers","following_url":"https://api.github.com/users/jfrazelle/following{/other_user}","gists_url":"https://api.github.com/users/jfrazelle/gists{/gist_id}","starred_url":"https://api.github.com/users/jfrazelle/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jfrazelle/subscriptions","organizations_url":"https://api.github.com/users/jfrazelle/orgs","repos_url":"https://api.github.com/users/jfrazelle/repos","events_url":"https://api.github.com/users/jfrazelle/events{/privacy}","received_events_url":"https://api.github.com/users/jfrazelle/received_events","type":"User","site_admin":false},"body":"why are these being ignored, could use a comment or something","created_at":"2015-07-22T04:45:44Z","updated_at":"2015-07-22T04:45:44Z","html_url":"https://github.com/docker/docker/pull/14546#discussion_r35181088","pull_request_url":"https://api.github.com/repos/docker/docker/pulls/14546","_links":{"self":{"href":"https://api.github.com/repos/docker/docker/pulls/comments/35181088"},"html":{"href":"https://github.com/docker/docker/pull/14546#discussion_r35181088"},"pull_request":{"href":"https://api.github.com/repos/docker/docker/pulls/14546"}}},"pull_request":{"url":"https://api.github.com/repos/docker/docker/pulls/14546","id":39735735,"html_url":"https://github.com/docker/docker/pull/14546","diff_url":"https://github.com/docker/docker/pull/14546.diff","patch_url":"https://github.com/docker/docker/pull/14546.patch","issue_url":"https://api.github.com/repos/docker/docker/issues/14546","number":14546,"state":"open","locked":false,"title":"Notary integration","user":{"login":"dmcgowan","id":169601,"avatar_url":"https://avatars.githubusercontent.com/u/169601?v=3","gravatar_id":"","url":"https://api.github.com/users/dmcgowan","html_url":"https://github.com/dmcgowan","followers_url":"https://api.github.com/users/dmcgowan/followers","following_url":"https://api.github.com/users/dmcgowan/following{/other_user}","gists_url":"https://api.github.com/users/dmcgowan/gists{/gist_id}","starred_url":"https://api.github.com/users/dmcgowan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dmcgowan/subscriptions","organizations_url":"https://api.github.com/users/dmcgowan/orgs","repos_url":"https://api.github.com/users/dmcgowan/repos","events_url":"https://api.github.com/users/dmcgowan/events{/privacy}","received_events_url":"https://api.github.com/users/dmcgowan/received_events","type":"User","site_admin":false},"body":"# Summary\r\nAdd support for trusted image distribution through the use of notary. Notary logic is done client side to allow cli users full configuration of trust and limiting access to private keys to user's machine. Trusted cli actions which involve pulling will always do a pull by digest to the daemon. Since the daemon already supports pull by digest, no changes to the daemon are needed to support the use of notary. All signatures will be done directly from the client on push. For more details on the design, see the [design document](https://docs.google.com/document/d/1Xn9kuhICOFj_P37ZWng8LeR_32NAj4Ytm42XAVWuTHw/edit#).\r\n\r\n# UX Changes\r\n## Flags\r\n - `--untrusted` - Explicitly run a pull or push action as untrusted. Defaults to true in 1.7. This can be used to override the `DOCKER_NOTARY` environment variable.\r\n\r\n## Trusted Mode\r\n### Pull Changes\r\n\r\n#### Pull by digest\r\nThe initial pull line will now always include the digest of the image being pulled.\r\n\r\n#### Sequential pull all\r\nWhen pulling multiple tags, the client will now sequentially call a pull by digest on every tag.\r\n\r\n#### Full sample pull output\r\n```\r\nExpanded name to docker.io/dmcgowan/myapp\r\nPull (1 of 1): dmcgowan/myapp:latest@sha256:8d62273b15d0dd7726e5dd7e27b5a1c8bff2106557caccfa5e2f1ba6956556c8\r\nsha256:8d62273b15d0dd7726e5dd7e27b5a1c8bff2106557caccfa5e2f1ba6956556c8: Pulling from dmcgowan/myapp\r\na8219747be10: Already exists \r\n91c95931e552: Already exists \r\nDigest: sha256:8d62273b15d0dd7726e5dd7e27b5a1c8bff2106557caccfa5e2f1ba6956556c8\r\nStatus: Downloaded newer image for dmcgowan/myapp@sha256:8d62273b15d0dd7726e5dd7e27b5a1c8bff2106557caccfa5e2f1ba6956556c8\r\nTagging dmcgowan/myapp@sha256:8d62273b15d0dd7726e5dd7e27b5a1c8bff2106557caccfa5e2f1ba6956556c8 as dmcgowan/myapp:latest\r\n```\r\n\r\n### Push Changes\r\n#### Status Line\r\nPush status line which displays digest now shows tag, digest, and size. This was used for client to be able to parse from the output what the pushed manifests were. These output lines represent the changes which will be made to the notary repository.\r\n\r\nold\r\n```\r\nDigest: sha256:f2ee4bbffcce22f295f8c9a987dcc55f0987b339bf8fc0277e52560563e5c118\r\n```\r\nnew\r\n```\r\nlatest: digest: sha256:f2ee4bbffcce22f295f8c9a987dcc55f0987b339bf8fc0277e52560563e5c118 size: 10115\r\n```\r\n\r\n#### Passphrase prompt on first push of repository\r\nWhen a repository is pushed for the first time the cli will be prompted for a passphrase. The passphrase will not be echoed back to the user. If metadata is near expiration time, the passphrase may be prompted again to re-sign the metadata. Subsequent pushes should not require entering a passphrase. If entering in a passphrase is not desirable, then the `notary` tool may be used directly to initialize a repository.\r\n\r\n#### Full sample push output\r\n```\r\nThe push refers to a repository [dmcgowan/myapp] (len: 1)\r\n91c95931e552: Image already exists \r\na8219747be10: Image already exists \r\nlatest: digest: sha256:f2ee4bbffcce22f295f8c9a987dcc55f0987b339bf8fc0277e52560563e5c118 size: 10115\r\nSigning and pushing TUF metadata\r\nExpanded name to docker.io/dmcgowan/myapp\r\nLoading trusted collection.\r\nCreating a new private key\r\nPassphrase:\r\nSaving changes to Trusted Collection.\r\nFinished initializing \"docker.io/dmcgowan/myapp\"\r\nLoading trusted collection.\r\n```\r\n\r\n## Environment Variables\r\n - `DOCKER_NOTARY` - boolean whether to use Notary in Docker\r\n - `NOTARY_SERVER` - Notary server to use for all repositories\r\n\r\n# Remaining items for code review\r\n - [x] Authentication integration with distribution client (need to solve problem with FQDN support)\r\n - [x] Load Certificates from ~/.docker/tls/<hostname>\r\n","created_at":"2015-07-11T00:22:01Z","updated_at":"2015-07-22T04:45:44Z","closed_at":null,"merged_at":null,"merge_commit_sha":"07dabf3887c7aeac8930d3edcf316ed9f33dcf3f","assignee":null,"milestone":{"url":"https://api.github.com/repos/docker/docker/milestones/39","html_url":"https://github.com/docker/docker/milestones/1.8.0","labels_url":"https://api.github.com/repos/docker/docker/milestones/39/labels","id":1151952,"number":39,"title":"1.8.0","description":"","creator":{"login":"thaJeztah","id":1804568,"avatar_url":"https://avatars.githubusercontent.com/u/1804568?v=3","gravatar_id":"","url":"https://api.github.com/users/thaJeztah","html_url":"https://github.com/thaJeztah","followers_url":"https://api.github.com/users/thaJeztah/followers","following_url":"https://api.github.com/users/thaJeztah/following{/other_user}","gists_url":"https://api.github.com/users/thaJeztah/gists{/gist_id}","starred_url":"https://api.github.com/users/thaJeztah/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/thaJeztah/subscriptions","organizations_url":"https://api.github.com/users/thaJeztah/orgs","repos_url":"https://api.github.com/users/thaJeztah/repos","events_url":"https://api.github.com/users/thaJeztah/events{/privacy}","received_events_url":"https://api.github.com/users/thaJeztah/received_events","type":"User","site_admin":false},"open_issues":9,"closed_issues":32,"state":"open","created_at":"2015-06-05T19:27:06Z","updated_at":"2015-07-22T03:58:52Z","due_on":"2015-08-09T22:00:00Z","closed_at":null},"commits_url":"https://api.github.com/repos/docker/docker/pulls/14546/commits","review_comments_url":"https://api.github.com/repos/docker/docker/pulls/14546/comments","review_comment_url":"https://api.github.com/repos/docker/docker/pulls/comments{/number}","comments_url":"https://api.github.com/repos/docker/docker/issues/14546/comments","statuses_url":"https://api.github.com/repos/docker/docker/statuses/0a8c1bfdd0c8f41d4f4725fcc2b8bad4bf892482","head":{"label":"dmcgowan:trusted-notary-integration","ref":"trusted-notary-integration","sha":"0a8c1bfdd0c8f41d4f4725fcc2b8bad4bf892482","user":{"login":"dmcgowan","id":169601,"avatar_url":"https://avatars.githubusercontent.com/u/169601?v=3","gravatar_id":"","url":"https://api.github.com/users/dmcgowan","html_url":"https://github.com/dmcgowan","followers_url":"https://api.github.com/users/dmcgowan/followers","following_url":"https://api.github.com/users/dmcgowan/following{/other_user}","gists_url":"https://api.github.com/users/dmcgowan/gists{/gist_id}","starred_url":"https://api.github.com/users/dmcgowan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dmcgowan/subscriptions","organizations_url":"https://api.github.com/users/dmcgowan/orgs","repos_url":"https://api.github.com/users/dmcgowan/repos","events_url":"https://api.github.com/users/dmcgowan/events{/privacy}","received_events_url":"https://api.github.com/users/dmcgowan/received_events","type":"User","site_admin":false},"repo":{"id":22739098,"name":"docker","full_name":"dmcgowan/docker","owner":{"login":"dmcgowan","id":169601,"avatar_url":"https://avatars.githubusercontent.com/u/169601?v=3","gravatar_id":"","url":"https://api.github.com/users/dmcgowan","html_url":"https://github.com/dmcgowan","followers_url":"https://api.github.com/users/dmcgowan/followers","following_url":"https://api.github.com/users/dmcgowan/following{/other_user}","gists_url":"https://api.github.com/users/dmcgowan/gists{/gist_id}","starred_url":"https://api.github.com/users/dmcgowan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dmcgowan/subscriptions","organizations_url":"https://api.github.com/users/dmcgowan/orgs","repos_url":"https://api.github.com/users/dmcgowan/repos","events_url":"https://api.github.com/users/dmcgowan/events{/privacy}","received_events_url":"https://api.github.com/users/dmcgowan/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/dmcgowan/docker","description":"Docker - the open-source application container engine","fork":true,"url":"https://api.github.com/repos/dmcgowan/docker","forks_url":"https://api.github.com/repos/dmcgowan/docker/forks","keys_url":"https://api.github.com/repos/dmcgowan/docker/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dmcgowan/docker/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dmcgowan/docker/teams","hooks_url":"https://api.github.com/repos/dmcgowan/docker/hooks","issue_events_url":"https://api.github.com/repos/dmcgowan/docker/issues/events{/number}","events_url":"https://api.github.com/repos/dmcgowan/docker/events","assignees_url":"https://api.github.com/repos/dmcgowan/docker/assignees{/user}","branches_url":"https://api.github.com/repos/dmcgowan/docker/branches{/branch}","tags_url":"https://api.github.com/repos/dmcgowan/docker/tags","blobs_url":"https://api.github.com/repos/dmcgowan/docker/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dmcgowan/docker/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dmcgowan/docker/git/refs{/sha}","trees_url":"https://api.github.com/repos/dmcgowan/docker/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dmcgowan/docker/statuses/{sha}","languages_url":"https://api.github.com/repos/dmcgowan/docker/languages","stargazers_url":"https://api.github.com/repos/dmcgowan/docker/stargazers","contributors_url":"https://api.github.com/repos/dmcgowan/docker/contributors","subscribers_url":"https://api.github.com/repos/dmcgowan/docker/subscribers","subscription_url":"https://api.github.com/repos/dmcgowan/docker/subscription","commits_url":"https://api.github.com/repos/dmcgowan/docker/commits{/sha}","git_commits_url":"https://api.github.com/repos/dmcgowan/docker/git/commits{/sha}","comments_url":"https://api.github.com/repos/dmcgowan/docker/comments{/number}","issue_comment_url":"https://api.github.com/repos/dmcgowan/docker/issues/comments{/number}","contents_url":"https://api.github.com/repos/dmcgowan/docker/contents/{+path}","compare_url":"https://api.github.com/repos/dmcgowan/docker/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dmcgowan/docker/merges","archive_url":"https://api.github.com/repos/dmcgowan/docker/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dmcgowan/docker/downloads","issues_url":"https://api.github.com/repos/dmcgowan/docker/issues{/number}","pulls_url":"https://api.github.com/repos/dmcgowan/docker/pulls{/number}","milestones_url":"https://api.github.com/repos/dmcgowan/docker/milestones{/number}","notifications_url":"https://api.github.com/repos/dmcgowan/docker/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dmcgowan/docker/labels{/name}","releases_url":"https://api.github.com/repos/dmcgowan/docker/releases{/id}","created_at":"2014-08-07T22:58:18Z","updated_at":"2015-03-16T21:29:55Z","pushed_at":"2015-07-22T03:43:05Z","git_url":"git://github.com/dmcgowan/docker.git","ssh_url":"git@github.com:dmcgowan/docker.git","clone_url":"https://github.com/dmcgowan/docker.git","svn_url":"https://github.com/dmcgowan/docker","homepage":"http://www.docker.com","size":66172,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":7,"forks":1,"open_issues":7,"watchers":0,"default_branch":"master"}},"base":{"label":"docker:master","ref":"master","sha":"879f440a99264de555255a58521ea4c55499dd4e","user":{"login":"docker","id":5429470,"avatar_url":"https://avatars.githubusercontent.com/u/5429470?v=3","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":7691631,"name":"docker","full_name":"docker/docker","owner":{"login":"docker","id":5429470,"avatar_url":"https://avatars.githubusercontent.com/u/5429470?v=3","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/docker","description":"Docker - the open-source application container engine","fork":false,"url":"https://api.github.com/repos/docker/docker","forks_url":"https://api.github.com/repos/docker/docker/forks","keys_url":"https://api.github.com/repos/docker/docker/keys{/key_id}","collaborators_url":"https://api.github.com/repos/docker/docker/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/docker/docker/teams","hooks_url":"https://api.github.com/repos/docker/docker/hooks","issue_events_url":"https://api.github.com/repos/docker/docker/issues/events{/number}","events_url":"https://api.github.com/repos/docker/docker/events","assignees_url":"https://api.github.com/repos/docker/docker/assignees{/user}","branches_url":"https://api.github.com/repos/docker/docker/branches{/branch}","tags_url":"https://api.github.com/repos/docker/docker/tags","blobs_url":"https://api.github.com/repos/docker/docker/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/docker/docker/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/docker/docker/git/refs{/sha}","trees_url":"https://api.github.com/repos/docker/docker/git/trees{/sha}","statuses_url":"https://api.github.com/repos/docker/docker/statuses/{sha}","languages_url":"https://api.github.com/repos/docker/docker/languages","stargazers_url":"https://api.github.com/repos/docker/docker/stargazers","contributors_url":"https://api.github.com/repos/docker/docker/contributors","subscribers_url":"https://api.github.com/repos/docker/docker/subscribers","subscription_url":"https://api.github.com/repos/docker/docker/subscription","commits_url":"https://api.github.com/repos/docker/docker/commits{/sha}","git_commits_url":"https://api.github.com/repos/docker/docker/git/commits{/sha}","comments_url":"https://api.github.com/repos/docker/docker/comments{/number}","issue_comment_url":"https://api.github.com/repos/docker/docker/issues/comments{/number}","contents_url":"https://api.github.com/repos/docker/docker/contents/{+path}","compare_url":"https://api.github.com/repos/docker/docker/compare/{base}...{head}","merges_url":"https://api.github.com/repos/docker/docker/merges","archive_url":"https://api.github.com/repos/docker/docker/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/docker/docker/downloads","issues_url":"https://api.github.com/repos/docker/docker/issues{/number}","pulls_url":"https://api.github.com/repos/docker/docker/pulls{/number}","milestones_url":"https://api.github.com/repos/docker/docker/milestones{/number}","notifications_url":"https://api.github.com/repos/docker/docker/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/docker/docker/labels{/name}","releases_url":"https://api.github.com/repos/docker/docker/releases{/id}","created_at":"2013-01-18T18:10:57Z","updated_at":"2015-07-22T03:59:55Z","pushed_at":"2015-07-22T04:37:47Z","git_url":"git://github.com/docker/docker.git","ssh_url":"git@github.com:docker/docker.git","clone_url":"https://github.com/docker/docker.git","svn_url":"https://github.com/docker/docker","homepage":"http://www.docker.com","size":228690,"stargazers_count":23439,"watchers_count":23439,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":5843,"mirror_url":null,"open_issues_count":1059,"forks":5843,"open_issues":1059,"watchers":23439,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/docker/docker/pulls/14546"},"html":{"href":"https://github.com/docker/docker/pull/14546"},"issue":{"href":"https://api.github.com/repos/docker/docker/issues/14546"},"comments":{"href":"https://api.github.com/repos/docker/docker/issues/14546/comments"},"review_comments":{"href":"https://api.github.com/repos/docker/docker/pulls/14546/comments"},"review_comment":{"href":"https://api.github.com/repos/docker/docker/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/docker/docker/pulls/14546/commits"},"statuses":{"href":"https://api.github.com/repos/docker/docker/statuses/0a8c1bfdd0c8f41d4f4725fcc2b8bad4bf892482"}}}}