{"action":"created","comment":{"url":"https://api.github.com/repos/BuoyantIO/linkerd/pulls/comments/58917182","id":58917182,"diff_hunk":"@@ -0,0 +1,308 @@\n+package io.buoyant.k8s\n+\n+import com.twitter.concurrent.AsyncStream\n+import com.twitter.finagle.http.Response\n+import com.twitter.finagle.{Filter, http}\n+import com.twitter.finagle.param.HighResTimer\n+import com.twitter.finagle.service.{Backoff, RetryBudget, RetryFilter, RetryPolicy}\n+import com.twitter.finagle.stats.{DefaultStatsReceiver, StatsReceiver}\n+import com.twitter.io.{Buf, Reader}\n+import com.twitter.util.{Closable, _}\n+import com.twitter.util.TimeConversions._\n+import java.util.concurrent.atomic.AtomicReference\n+\n+/**\n+ * Basic functionality shared by all k8s Resource implementations: they require an HTTP client, and can be closed.\n+ */\n+trait Resource extends Closable {\n+  def client: Client\n+  def close(deadline: Time) = client.close(deadline)\n+}\n+\n+/**\n+ * A Version is a top-level API grouping within the K8s API; examples include \"/api/v1\" and \"/apis/extensions/v1beta1\".\n+ *\n+ * Implementors are likely to want to extend this to provide named convenience methods on top of the `list` method and\n+ * to type-specialize on the resource types supported by that version.\n+ *\n+ * @tparam O The parent type for all [Object]s served by this version.\n+ */\n+private[k8s] trait Version[O <: Object] extends Resource {\n+  /**\n+   * The first portion of the API path. Currently-known groups are \"api\" (for the core k8s v1 API) and \"apis\"\n+   * (all others, including extensions and third-party resources).\n+   */\n+  def group: String\n+\n+  /**\n+   * The second portion of the API path. examples include \"v1\", \"extensions/v1beta1\", \"buoyant.io/v1\".\n+   */\n+  def version: String\n+\n+  def client: Client\n+\n+  def namespace(ns: String) = new NsVersion[O](client, group, version, ns)\n+  def path = s\"/$group/$version\"\n+  def list[T <: O: Manifest, W <: Watch[T]: Manifest, L <: List[T]: Manifest](\n+    backoffs: Stream[Duration] = Backoff.exponentialJittered(1.milliseconds, 5.seconds),\n+    stats: StatsReceiver = DefaultStatsReceiver\n+  )(implicit od: ObjectDescriptor[T, W]) = new ListResource[T, W, L](client, path, backoffs, stats)\n+}\n+\n+/**\n+ * A Version with a namespace applied. This is a Class because we want to provide a default `namespace` implementation\n+ * in [[Version]], although in many cases implementors may wish to extend this to provide convenience methods wrapping\n+ * `list`.\n+ *\n+ * @param client The client used to make requests against this API.\n+ * @param version Full path for this version of the API.\n+ * @param ns The namespace\n+ * @tparam O The parent type for all [Object]s served by this version.\n+ */\n+private[k8s] class NsVersion[O <: Object](val client: Client, group: String, val version: String, ns: String) extends Resource {\n+  def path = s\"/$group/$version/namespaces/$ns\"\n+  def list[T <: O: Manifest, W <: Watch[T]: Manifest, L <: List[T]: Manifest](\n+    backoffs: Stream[Duration] = Backoff.exponentialJittered(1.milliseconds, 5.seconds),\n+    stats: StatsReceiver = DefaultStatsReceiver\n+  )(implicit od: ObjectDescriptor[T, W]) = new NsListResource[T, W, L](client, path, backoffs, stats)\n+}\n+\n+/**\n+ * A third-party version contains ThirdPartyResource-typed objects. See\n+ * https://github.com/kubernetes/kubernetes/blob/master/docs/design/extending-api.md for a description of the\n+ * design model.\n+ *\n+ * Implementors are likely to want to extend this to provide convenience methods on top of the `list` method.\n+ *\n+ * @tparam O The parent type for all [Object]s served by this Version.\n+ */\n+trait ThirdPartyVersion[O <: Object] extends Version[O] {\n+  /**\n+   * domain name, i.e. \"buoyant.io\"\n+   */\n+  def owner: String\n+\n+  /**\n+   * version within owner domain, i.e. \"v1\"\n+   */\n+  def ownerVersion: String\n+\n+  def version: String = ThirdPartyVersion.version(owner, ownerVersion)\n+  def basePath: String = s\"/${ThirdPartyVersion.group}/$version\"\n+  val group = ThirdPartyVersion.group\n+  override def namespace(ns: String) = new NsThirdPartyVersion[O](client, owner, ownerVersion, ns)\n+}\n+\n+object ThirdPartyVersion {\n+  def version(owner: String, ownerVersion: String) = s\"$owner/$ownerVersion\"\n+  val group = \"apis\"\n+}\n+\n+/**\n+ * A namespaced third-party version, i.e. \"/apis/buoyant.io/v1/namespaces/ns/\".\n+ */\n+class NsThirdPartyVersion[O <: Object](client: Client, owner: String, ownerVersion: String, ns: String)\n+  extends NsVersion[O](client, ThirdPartyVersion.group, ThirdPartyVersion.version(owner, ownerVersion), ns)\n+\n+/**\n+ * A trait that encapsulates the ability to Watch a k8s resource.\n+ */\n+private[k8s] trait Watchable[O <: Object, W <: Watch[O]] extends Resource {\n+  implicit protected val watchManifest: Manifest[W]\n+\n+  protected def backoffs: Stream[Duration]\n+  protected def path: String\n+  protected def stats: StatsReceiver\n+\n+  /**\n+   * implementing classes should define this method to retrieve the current state\n+   * of the resource to pass as Modified results to any watchers.\n+   *\n+   * @return a Future containing a sequence of Watches and an optional String representing the current resourceVersion\n+   */\n+  protected def restartWatches(\n+    labelSelector: Option[String] = None,\n+    fieldSelector: Option[String] = None\n+  ): Future[(Seq[W], Option[String])]\n+\n+  protected def infiniteRetryFilter = new RetryFilter[http.Request, http.Response](\n+    RetryPolicy.backoff(backoffs) {\n+      // We will assume 5xx are retryable, everything else is not for now\n+      case (_, Return(rep)) => rep.status.code >= 500 && rep.status.code < 600\n+      case (_, Throw(NonFatal(ex))) =>\n+        log.error(s\"retrying k8s endpoints request on error $ex\")\n+        true\n+    },\n+    HighResTimer.Default,\n+    stats,\n+    RetryBudget.Infinite\n+  )\n+\n+  /**\n+   * Watch this resource for changes, using a chunked HTTP request.\n+   *\n+   * TODO: investigate k8s websockets support as an alternative HTTP approach\n+   */\n+  def watch(\n+    labelSelector: Option[String] = None,\n+    fieldSelector: Option[String] = None,\n+    resourceVersion: Option[String] = None\n+  ): (AsyncStream[W], Closable) = {\n+    val close = new AtomicReference[Closable](Closable.nop)\n+\n+    // Internal method used to recursively retry watches as needed on failures.\n+    def _watch(resourceVersion: Option[String] = None): AsyncStream[W] = {\n+      val req = Api.mkreq(http.Method.Get, path, None,\n+        \"watch\" -> Some(\"true\"),\n+        \"labelSelector\" -> labelSelector,\n+        \"fieldSelector\" -> fieldSelector,\n+        \"resourceVersion\" -> resourceVersion)\n+\n+      def currentVersion(initial: Option[String], watch: AsyncStream[W]): Future[Option[String]] = {\n+        watch.foldLeft(initial) {\n+          (version: Option[String], watch: W) => watch.resourceVersion\n+        }\n+      }\n+\n+      def watchFromResponse(rsp: Response): AsyncStream[W] = rsp.status match {\n+        // NOTE: 5xx-class statuses will be retried by the infiniteRetryFilter above.\n+        case http.Status.Ok =>\n+          close.set(Closable.make { _ =>\n+            log.debug(\"k8s watch closed\")\n+            Future {\n+              rsp.reader.discard()\n+            } handle {\n+              case _: Reader.ReaderDiscarded =>\n+            }\n+          })\n+          val theStream = Json.readStream[W](rsp.reader, Api.BufSize)\n+          theStream ++\n+            AsyncStream.fromFuture(\n+              currentVersion(resourceVersion, theStream) flatMap {\n+                case Some(version) =>\n+                  Future.value(_watch(Some(version)))\n+                case None =>\n+                  // In this case, we want to try loading the initial information instead before watching again.\n+                  restartWatches(labelSelector, fieldSelector).map {\n+                    case (ws, ver) =>\n+                      AsyncStream.fromSeq(ws) ++\n+                        _watch(ver)\n+                  }\n+              }\n+            ).flatten\n+\n+        case status =>\n+          close.set(Closable.nop)\n+          log.debug(\"k8s failed to watch endpoints: %d %s\", status.code, status.reason)\n+          val f = Future.exception(Api.UnexpectedResponse(rsp))\n+          AsyncStream.fromFuture(f)\n+      }\n+\n+      val retryingClient = infiniteRetryFilter andThen client","path":"k8s/src/main/scala/io/buoyant/k8s/resources.scala","position":201,"original_position":201,"commit_id":"bdcf45a009609d392bdc9aeb2919815fe00e5a82","original_commit_id":"bdcf45a009609d392bdc9aeb2919815fe00e5a82","user":{"login":"adleong","id":3979810,"avatar_url":"https://avatars.githubusercontent.com/u/3979810?v=3","gravatar_id":"","url":"https://api.github.com/users/adleong","html_url":"https://github.com/adleong","followers_url":"https://api.github.com/users/adleong/followers","following_url":"https://api.github.com/users/adleong/following{/other_user}","gists_url":"https://api.github.com/users/adleong/gists{/gist_id}","starred_url":"https://api.github.com/users/adleong/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/adleong/subscriptions","organizations_url":"https://api.github.com/users/adleong/orgs","repos_url":"https://api.github.com/users/adleong/repos","events_url":"https://api.github.com/users/adleong/events{/privacy}","received_events_url":"https://api.github.com/users/adleong/received_events","type":"User","site_admin":false},"body":"nit: avoid infix","created_at":"2016-04-07T18:03:22Z","updated_at":"2016-04-07T18:03:22Z","html_url":"https://github.com/BuoyantIO/linkerd/pull/247#discussion_r58917182","pull_request_url":"https://api.github.com/repos/BuoyantIO/linkerd/pulls/247","_links":{"self":{"href":"https://api.github.com/repos/BuoyantIO/linkerd/pulls/comments/58917182"},"html":{"href":"https://github.com/BuoyantIO/linkerd/pull/247#discussion_r58917182"},"pull_request":{"href":"https://api.github.com/repos/BuoyantIO/linkerd/pulls/247"}}},"pull_request":{"url":"https://api.github.com/repos/BuoyantIO/linkerd/pulls/247","id":65575082,"html_url":"https://github.com/BuoyantIO/linkerd/pull/247","diff_url":"https://github.com/BuoyantIO/linkerd/pull/247.diff","patch_url":"https://github.com/BuoyantIO/linkerd/pull/247.patch","issue_url":"https://api.github.com/repos/BuoyantIO/linkerd/issues/247","number":247,"state":"open","locked":false,"title":"Implement namerd storage using the Kubernetes ThirdPartyResource APIs.","user":{"login":"gtcampbell","id":14003771,"avatar_url":"https://avatars.githubusercontent.com/u/14003771?v=3","gravatar_id":"","url":"https://api.github.com/users/gtcampbell","html_url":"https://github.com/gtcampbell","followers_url":"https://api.github.com/users/gtcampbell/followers","following_url":"https://api.github.com/users/gtcampbell/following{/other_user}","gists_url":"https://api.github.com/users/gtcampbell/gists{/gist_id}","starred_url":"https://api.github.com/users/gtcampbell/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gtcampbell/subscriptions","organizations_url":"https://api.github.com/users/gtcampbell/orgs","repos_url":"https://api.github.com/users/gtcampbell/repos","events_url":"https://api.github.com/users/gtcampbell/events{/privacy}","received_events_url":"https://api.github.com/users/gtcampbell/received_events","type":"User","site_admin":false},"body":"Fixes #219. I have split this into two commits, and could split further or split into multiple pull requests if needed. I have performed a decent amount of manual testing on this, but suggestions on proper automated tests would be appreciated!","created_at":"2016-04-07T05:15:42Z","updated_at":"2016-04-07T18:03:22Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7a26cb45337af463c07face9c9d697da7123e330","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/BuoyantIO/linkerd/pulls/247/commits","review_comments_url":"https://api.github.com/repos/BuoyantIO/linkerd/pulls/247/comments","review_comment_url":"https://api.github.com/repos/BuoyantIO/linkerd/pulls/comments{/number}","comments_url":"https://api.github.com/repos/BuoyantIO/linkerd/issues/247/comments","statuses_url":"https://api.github.com/repos/BuoyantIO/linkerd/statuses/bdcf45a009609d392bdc9aeb2919815fe00e5a82","head":{"label":"BuoyantIO:greg/k8s3p+namer","ref":"greg/k8s3p+namer","sha":"bdcf45a009609d392bdc9aeb2919815fe00e5a82","user":{"login":"BuoyantIO","id":10824329,"avatar_url":"https://avatars.githubusercontent.com/u/10824329?v=3","gravatar_id":"","url":"https://api.github.com/users/BuoyantIO","html_url":"https://github.com/BuoyantIO","followers_url":"https://api.github.com/users/BuoyantIO/followers","following_url":"https://api.github.com/users/BuoyantIO/following{/other_user}","gists_url":"https://api.github.com/users/BuoyantIO/gists{/gist_id}","starred_url":"https://api.github.com/users/BuoyantIO/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BuoyantIO/subscriptions","organizations_url":"https://api.github.com/users/BuoyantIO/orgs","repos_url":"https://api.github.com/users/BuoyantIO/repos","events_url":"https://api.github.com/users/BuoyantIO/events{/privacy}","received_events_url":"https://api.github.com/users/BuoyantIO/received_events","type":"Organization","site_admin":false},"repo":{"id":49605596,"name":"linkerd","full_name":"BuoyantIO/linkerd","owner":{"login":"BuoyantIO","id":10824329,"avatar_url":"https://avatars.githubusercontent.com/u/10824329?v=3","gravatar_id":"","url":"https://api.github.com/users/BuoyantIO","html_url":"https://github.com/BuoyantIO","followers_url":"https://api.github.com/users/BuoyantIO/followers","following_url":"https://api.github.com/users/BuoyantIO/following{/other_user}","gists_url":"https://api.github.com/users/BuoyantIO/gists{/gist_id}","starred_url":"https://api.github.com/users/BuoyantIO/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BuoyantIO/subscriptions","organizations_url":"https://api.github.com/users/BuoyantIO/orgs","repos_url":"https://api.github.com/users/BuoyantIO/repos","events_url":"https://api.github.com/users/BuoyantIO/events{/privacy}","received_events_url":"https://api.github.com/users/BuoyantIO/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/BuoyantIO/linkerd","description":"A dynamic linker for microservices.","fork":false,"url":"https://api.github.com/repos/BuoyantIO/linkerd","forks_url":"https://api.github.com/repos/BuoyantIO/linkerd/forks","keys_url":"https://api.github.com/repos/BuoyantIO/linkerd/keys{/key_id}","collaborators_url":"https://api.github.com/repos/BuoyantIO/linkerd/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/BuoyantIO/linkerd/teams","hooks_url":"https://api.github.com/repos/BuoyantIO/linkerd/hooks","issue_events_url":"https://api.github.com/repos/BuoyantIO/linkerd/issues/events{/number}","events_url":"https://api.github.com/repos/BuoyantIO/linkerd/events","assignees_url":"https://api.github.com/repos/BuoyantIO/linkerd/assignees{/user}","branches_url":"https://api.github.com/repos/BuoyantIO/linkerd/branches{/branch}","tags_url":"https://api.github.com/repos/BuoyantIO/linkerd/tags","blobs_url":"https://api.github.com/repos/BuoyantIO/linkerd/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/BuoyantIO/linkerd/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/BuoyantIO/linkerd/git/refs{/sha}","trees_url":"https://api.github.com/repos/BuoyantIO/linkerd/git/trees{/sha}","statuses_url":"https://api.github.com/repos/BuoyantIO/linkerd/statuses/{sha}","languages_url":"https://api.github.com/repos/BuoyantIO/linkerd/languages","stargazers_url":"https://api.github.com/repos/BuoyantIO/linkerd/stargazers","contributors_url":"https://api.github.com/repos/BuoyantIO/linkerd/contributors","subscribers_url":"https://api.github.com/repos/BuoyantIO/linkerd/subscribers","subscription_url":"https://api.github.com/repos/BuoyantIO/linkerd/subscription","commits_url":"https://api.github.com/repos/BuoyantIO/linkerd/commits{/sha}","git_commits_url":"https://api.github.com/repos/BuoyantIO/linkerd/git/commits{/sha}","comments_url":"https://api.github.com/repos/BuoyantIO/linkerd/comments{/number}","issue_comment_url":"https://api.github.com/repos/BuoyantIO/linkerd/issues/comments{/number}","contents_url":"https://api.github.com/repos/BuoyantIO/linkerd/contents/{+path}","compare_url":"https://api.github.com/repos/BuoyantIO/linkerd/compare/{base}...{head}","merges_url":"https://api.github.com/repos/BuoyantIO/linkerd/merges","archive_url":"https://api.github.com/repos/BuoyantIO/linkerd/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/BuoyantIO/linkerd/downloads","issues_url":"https://api.github.com/repos/BuoyantIO/linkerd/issues{/number}","pulls_url":"https://api.github.com/repos/BuoyantIO/linkerd/pulls{/number}","milestones_url":"https://api.github.com/repos/BuoyantIO/linkerd/milestones{/number}","notifications_url":"https://api.github.com/repos/BuoyantIO/linkerd/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/BuoyantIO/linkerd/labels{/name}","releases_url":"https://api.github.com/repos/BuoyantIO/linkerd/releases{/id}","deployments_url":"https://api.github.com/repos/BuoyantIO/linkerd/deployments","created_at":"2016-01-13T22:14:52Z","updated_at":"2016-04-07T01:18:00Z","pushed_at":"2016-04-07T16:13:09Z","git_url":"git://github.com/BuoyantIO/linkerd.git","ssh_url":"git@github.com:BuoyantIO/linkerd.git","clone_url":"https://github.com/BuoyantIO/linkerd.git","svn_url":"https://github.com/BuoyantIO/linkerd","homepage":"https://linkerd.io","size":1101,"stargazers_count":562,"watchers_count":562,"language":"Scala","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":26,"mirror_url":null,"open_issues_count":43,"forks":26,"open_issues":43,"watchers":562,"default_branch":"master"}},"base":{"label":"BuoyantIO:master","ref":"master","sha":"cb4fd1ae97905177f3d739ca1f31285d5a44c2bd","user":{"login":"BuoyantIO","id":10824329,"avatar_url":"https://avatars.githubusercontent.com/u/10824329?v=3","gravatar_id":"","url":"https://api.github.com/users/BuoyantIO","html_url":"https://github.com/BuoyantIO","followers_url":"https://api.github.com/users/BuoyantIO/followers","following_url":"https://api.github.com/users/BuoyantIO/following{/other_user}","gists_url":"https://api.github.com/users/BuoyantIO/gists{/gist_id}","starred_url":"https://api.github.com/users/BuoyantIO/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BuoyantIO/subscriptions","organizations_url":"https://api.github.com/users/BuoyantIO/orgs","repos_url":"https://api.github.com/users/BuoyantIO/repos","events_url":"https://api.github.com/users/BuoyantIO/events{/privacy}","received_events_url":"https://api.github.com/users/BuoyantIO/received_events","type":"Organization","site_admin":false},"repo":{"id":49605596,"name":"linkerd","full_name":"BuoyantIO/linkerd","owner":{"login":"BuoyantIO","id":10824329,"avatar_url":"https://avatars.githubusercontent.com/u/10824329?v=3","gravatar_id":"","url":"https://api.github.com/users/BuoyantIO","html_url":"https://github.com/BuoyantIO","followers_url":"https://api.github.com/users/BuoyantIO/followers","following_url":"https://api.github.com/users/BuoyantIO/following{/other_user}","gists_url":"https://api.github.com/users/BuoyantIO/gists{/gist_id}","starred_url":"https://api.github.com/users/BuoyantIO/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BuoyantIO/subscriptions","organizations_url":"https://api.github.com/users/BuoyantIO/orgs","repos_url":"https://api.github.com/users/BuoyantIO/repos","events_url":"https://api.github.com/users/BuoyantIO/events{/privacy}","received_events_url":"https://api.github.com/users/BuoyantIO/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/BuoyantIO/linkerd","description":"A dynamic linker for microservices.","fork":false,"url":"https://api.github.com/repos/BuoyantIO/linkerd","forks_url":"https://api.github.com/repos/BuoyantIO/linkerd/forks","keys_url":"https://api.github.com/repos/BuoyantIO/linkerd/keys{/key_id}","collaborators_url":"https://api.github.com/repos/BuoyantIO/linkerd/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/BuoyantIO/linkerd/teams","hooks_url":"https://api.github.com/repos/BuoyantIO/linkerd/hooks","issue_events_url":"https://api.github.com/repos/BuoyantIO/linkerd/issues/events{/number}","events_url":"https://api.github.com/repos/BuoyantIO/linkerd/events","assignees_url":"https://api.github.com/repos/BuoyantIO/linkerd/assignees{/user}","branches_url":"https://api.github.com/repos/BuoyantIO/linkerd/branches{/branch}","tags_url":"https://api.github.com/repos/BuoyantIO/linkerd/tags","blobs_url":"https://api.github.com/repos/BuoyantIO/linkerd/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/BuoyantIO/linkerd/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/BuoyantIO/linkerd/git/refs{/sha}","trees_url":"https://api.github.com/repos/BuoyantIO/linkerd/git/trees{/sha}","statuses_url":"https://api.github.com/repos/BuoyantIO/linkerd/statuses/{sha}","languages_url":"https://api.github.com/repos/BuoyantIO/linkerd/languages","stargazers_url":"https://api.github.com/repos/BuoyantIO/linkerd/stargazers","contributors_url":"https://api.github.com/repos/BuoyantIO/linkerd/contributors","subscribers_url":"https://api.github.com/repos/BuoyantIO/linkerd/subscribers","subscription_url":"https://api.github.com/repos/BuoyantIO/linkerd/subscription","commits_url":"https://api.github.com/repos/BuoyantIO/linkerd/commits{/sha}","git_commits_url":"https://api.github.com/repos/BuoyantIO/linkerd/git/commits{/sha}","comments_url":"https://api.github.com/repos/BuoyantIO/linkerd/comments{/number}","issue_comment_url":"https://api.github.com/repos/BuoyantIO/linkerd/issues/comments{/number}","contents_url":"https://api.github.com/repos/BuoyantIO/linkerd/contents/{+path}","compare_url":"https://api.github.com/repos/BuoyantIO/linkerd/compare/{base}...{head}","merges_url":"https://api.github.com/repos/BuoyantIO/linkerd/merges","archive_url":"https://api.github.com/repos/BuoyantIO/linkerd/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/BuoyantIO/linkerd/downloads","issues_url":"https://api.github.com/repos/BuoyantIO/linkerd/issues{/number}","pulls_url":"https://api.github.com/repos/BuoyantIO/linkerd/pulls{/number}","milestones_url":"https://api.github.com/repos/BuoyantIO/linkerd/milestones{/number}","notifications_url":"https://api.github.com/repos/BuoyantIO/linkerd/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/BuoyantIO/linkerd/labels{/name}","releases_url":"https://api.github.com/repos/BuoyantIO/linkerd/releases{/id}","deployments_url":"https://api.github.com/repos/BuoyantIO/linkerd/deployments","created_at":"2016-01-13T22:14:52Z","updated_at":"2016-04-07T01:18:00Z","pushed_at":"2016-04-07T16:13:09Z","git_url":"git://github.com/BuoyantIO/linkerd.git","ssh_url":"git@github.com:BuoyantIO/linkerd.git","clone_url":"https://github.com/BuoyantIO/linkerd.git","svn_url":"https://github.com/BuoyantIO/linkerd","homepage":"https://linkerd.io","size":1101,"stargazers_count":562,"watchers_count":562,"language":"Scala","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":26,"mirror_url":null,"open_issues_count":43,"forks":26,"open_issues":43,"watchers":562,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/BuoyantIO/linkerd/pulls/247"},"html":{"href":"https://github.com/BuoyantIO/linkerd/pull/247"},"issue":{"href":"https://api.github.com/repos/BuoyantIO/linkerd/issues/247"},"comments":{"href":"https://api.github.com/repos/BuoyantIO/linkerd/issues/247/comments"},"review_comments":{"href":"https://api.github.com/repos/BuoyantIO/linkerd/pulls/247/comments"},"review_comment":{"href":"https://api.github.com/repos/BuoyantIO/linkerd/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/BuoyantIO/linkerd/pulls/247/commits"},"statuses":{"href":"https://api.github.com/repos/BuoyantIO/linkerd/statuses/bdcf45a009609d392bdc9aeb2919815fe00e5a82"}}}}