{"action":"created","comment":{"url":"https://api.github.com/repos/kubernetes/community/pulls/comments/94074586","pull_request_review_id":14598291,"id":94074586,"diff_hunk":"@@ -0,0 +1,507 @@\n+# Extension of Admission Control via Initializers and External Admission Enforcement\n+\n+Admission control is the primary business-logic policy and enforcement subsystem in Kubernetes. It provides synchronous\n+hooks for all API operations and allows an integrator to impose additional controls on the system - rejecting, altering,\n+or reacting to changes to core objects. Today each of these plugins must be compiled into Kubernetes. As Kubernetes grows,\n+the requirement that all policy enforcement beyond coarse grained access control be done through in-tree compilation and\n+distribution becomes unweildy and limits administrators and the growth of the ecosystem.\n+\n+This proposal covers changes to the admission control subsystem that allow extension of admission without recompilation\n+and dynamic adminission control configuration in ways that resemble existing controller behavior.\n+\n+\n+## Background\n+\n+The four core systems in Kubernetes are:\n+\n+1. API servers with persistent storage, providing basic object validation and CRUD operations\n+2. Authentication and authorization layers that identify an actor and constrain the coarse actions that actor can take on API objects\n+3. [Admission controller layers](admission_control.md) that can control and limit the CRUD operations clients perform synchronously.\n+4. Controllers which watch the API and react to changes made by other users asynchronously (scheduler, replication controller, kubelet, kube-proxy, and ingress are all examples of controllers).\n+\n+Admission control supports a wide range of policy and behavior enforcement for administrators.\n+\n+\n+### Types of Admission Control\n+\n+In Kubernetes 1.5 and OpenShift 1.4, the following types of functionality have been implemented through admission\n+(all file references are relative to `plugin/pkg/admission`, or simply identified by name for OpenShift). Many of the\n+Kubernetes admission controllers originated in OpenShift and are listed in both for history.\n+\n+#### Resource Control\n+\n+These admission controllers take resource usage for pods into account to ensure namespaces cannot abuse the cluster\n+by consuming more than their fair share of resources. These perform security or defaulting type roles.\n+\n+##### Kubernetes\n+\n+Name | Code | Description\n+---- | ---- | -----------\n+InitialResources | initialresources/admission.go | Default the resources for a container based on past usage\n+LimitRanger | limitranger/admission.go | Set defaults for container requests and limits, or enforce upper bounds on certain resources (no more than 2GB of memory, default to 512MB)\n+ResourceQuota | resourcequota/admission.go | Calculate and deny number of objects (pods, rc, service load balancers) or total consumed resources (cpu, memory, disk) in a namespace\n+\n+##### OpenShift\n+\n+Name | Code | Description\n+---- | ---- | -----------\n+ClusterResourceOverride | clusterresourceoverride/admission.go | Allows administrators to override the user's container request for CPU or memory as a percentage of their request (the administrator's target overcommit number), or to default a limit based on a request. Allows cluster administrators to control overcommit on a cluster.\n+ClusterResourceQuota | clusterresourcequota/admission.go | Performs quota calculations over a set of namespaces with a shared quota. Can be used in conjunction with resource quota for hard and soft limits.\n+ExternalIPRanger | externalip_admission.go | Prevents users from creating services with externalIPs inside of fixed CIDR ranges, including the pod network, service network, or node network CIDRs to prevent hijacking of connections.\n+ImageLimitRange | admission.go | Performs LimitRanging on images that are pushed into the integrated image registry\n+OriginResourceQuota | resourcequota/admission.go | Performs quota calculations for API resources exposed by OpenShift. Demonstrates how quota would be implemented for API extensions.\n+ProjectRequestLimit | requestlimit/admission.go | A quota on how many namespaces may be created by any individual user. Has a global default and also a per user override.\n+RunOnceDuration | runonceduration/admission.go | Enforces a maximum ActiveDeadlineSeconds value on all RestartNever pods in a namespace. This ensures that users are defaulted to have a deadline if they did not request it (which prevents pathological resource consumption)\n+\n+Quota is typically last in the admission chain, to give all other components a chance to reject or modify the resource.\n+\n+\n+#### Security\n+\n+These controllers defend against specific actions within a resource that might be dangerous that the authorization\n+system cannot enforce.\n+\n+##### Kubernetes\n+\n+Name | Code | Description\n+---- | ---- | -----------\n+AlwaysPullImages | alwayspullimages/admission.go | Forces the Kubelet to pull images to prevent pods from from accessing private images that another user with credentials has already pulled to the node.\n+LimitPodHardAntiAffinityTopology | antiaffinity/admission.go | Defended the cluster against abusive anti-affinity topology rules that might hang the scheduler.\n+DenyEscalatingExec | exec/admission.go | Prevent users from executing into pods that have higher privileges via their service account than allowed by their policy (regular users can't exec into admin pods).\n+DenyExecOnPrivileged | exec/admission.go | Blanket ban exec access to pods with host level security. Superceded by DenyEscalatingExec\n+OwnerReferencesPermissionEnforcement | gc/gc_admission.go | Require that a user who sets a owner reference (which could result in garbage collection) has permission to delete the object, to prevent abuse.\n+ImagePolicyWebhook | imagepolicy/admission.go | Invoke a remote API to determine whether an image is allowed to run on the cluster.\n+PodNodeSelector | podnodeselector/admission.go | Default and limit what node selectors may be used within a namespace by reading a namespace annotation and a global configuration.\n+PodSecurityPolicy | security/podsecuritypolicy/admission.go | Control what security features pods are allowed to run as based on the end user launching the pod or the service account. Sophisticated policy rules.\n+SecurityContextDeny | securitycontext/scdeny/admission.go | Blanket deny setting any security context settings on a pod.\n+\n+##### OpenShift\n+\n+Name | Code | Description\n+---- | ---- | -----------\n+BuildByStrategy | strategyrestrictions/admission.go | Control which types of image builds a user can create by checking for a specific virtual authorization rule (field level authorization), since some build types have security implications.\n+OriginPodNodeEnvironment | nodeenv/admission.go | Predecessor to PodNodeSelector.\n+PodNodeConstraints | podnodeconstraints/admission.go | Prevent users from setting nodeName directly unless they can invoke the `bind` resource on pods (same as a scheduler). This prevents users from attacking nodes by repeatedly creating pods that target a specific node and forcing it to reject those pods. (field level authorization)\n+RestrictedEndpointsAdmission | endpoint_admission.go | In a multitenant network setup where namespaces are isolated like OpenShift SDN, service endpoints must not allow a user to probe other namespaces. If a user edits the endpoints object and sets IPs that fall within the pod network CIDR, the user must have `create` permission on a virtual resource `endpoints/restricted`. The service controller is granted this permission by default.\n+SecurityContextConstraint | admission.go | Predecessor to PodSecurityPolicy.\n+SCCExecRestrictions | scc_exec.go | Predecessor to DenyEscalatingExec.\n+\n+Many other controllers have been proposed, including but not limited to:\n+\n+* Control over what taints and tolerations a user can set on a pod\n+* Control over which labels and annotations can be set or changed\n+* Generic control over which fields certain users may set (field level access control)\n+\n+\n+#### Defaulting / Injection\n+\n+These controllers inject namespace or cluster context into pods and other resources at runtime to decouple\n+application config from runtime config (separate the user's pod settings from environmental controls)\n+\n+##### Kubernetes\n+\n+Name | Code | Description\n+---- | ---- | -----------\n+ServiceAccount | serviceaccount/admission.go | Bind mount the service account token for a pod into the pod at a specific location.\n+PersistentVolumeLabel | persistentvolume/label/admission.go | Lazily bind persistent volume claims to a given zone when a pod is scheduled.\n+DefaultStorageClass | storageclass/default/admission.go | Set a default storage class on any PVC created without a storage class.\n+\n+Many other controllers have been proposed, including but not limited to:\n+\n+* ServiceInjectionPolicy to inject environment, configmaps, and secrets into pods that reference those services\n+* Namespace level environment injection (all pods in this namespace should have env var `ENV=PROD`)\n+* Label selector based resource defaults (all pods with these labels get these default resources)\n+\n+\n+#### Referential Consistency\n+\n+These controllers enforce that certain guarantees of the system related to integrity.\n+\n+##### Kubernetes\n+\n+Name | Code | Description\n+---- | ---- | -----------\n+NamespaceAutoProvision | namespace/autoprovision/admission.go | When users create resources in a namespace that does not exist, ensure the namespace is created so it can be seen with `kubectl get namespaces`\n+NamespaceExists | namespace/exists/admission.go | Require that a namespace object exist prior to a resource being created.\n+NamespaceLifecycle | namespace/lifecycle/admission.go | More powerful and flexible version of NamespaceExists.\n+\n+##### OpenShift\n+\n+Name | Code | Description\n+---- | ---- | -----------\n+JenkinsBootstrapper | jenkinsbootstrapper/admission.go | Spawn a Jenkins instance in any project where a Build is defined that references a Jenkins pipeline. Checks that the creating user has permission to act-as an editor in the project to prevent escalation within a namespace.\n+ImagePolicy | imagepolicy/imagepolicy.go | Performs policy functions like ImagePolicyWebhook, but also is able to mutate the image reference from a tag to a digest (fully qualified spec), look up additional information about the image from the OpenShift Image API and potentially enforce resource consumption or placement decisions based on the image. May also be used to deny images from being used that don't resolve to image metadata that OpenShift tracks.\n+OriginNamespaceLifecycle | lifecycle/admission.go | Controls accepting resources for namespaces.\n+\n+\n+### Patterns\n+\n+In a study of all known admission controllers, the following patterns were seen most often:\n+\n+1. Defaulting on creation\n+2. Synchronous validation on creation\n+3. Synchronous validation on update - side-effect free\n+\n+Other patterns seen less frequently include:\n+\n+1. Defaulting on update\n+2. Resolving / further specifying values on update (ImagePolicy)\n+3. Creating resources in response to user action with the correct permission check (JenkinsBootstrapper)\n+4. Policy decisions based on *who* is doing the action (JenkinsBootstrapper)\n+5. Synchronous validation on update - with side effects (quota)\n+\n+While admission controllers can operate on all verbs, resources, and sub resource types, in practice they\n+mostly deal with create and update on primary resources. Most sub resources are highly privileged operations\n+and so are typically covered by policy. Other controllers like quota tend to be per apiserver and therefore\n+are not required to be extensible.\n+\n+\n+### Building enforcement\n+\n+In order to implement custom admission, an admin, integrator, or distribution of Kubernetes must compile their\n+admission controller(s) into the Kubernetes `kube-apiserver` binary. As Kubernetes is intended to be a\n+modular layered system this means core components must be upgraded to effect policy changes and only a fixed\n+list of plugins can be used. It also prevents experimentation and prototyping of policy, or \"quick fix\"\n+solutions applied on site. As we add additional APIs that are not hosted in the main binary (either as third\n+party resources or API extension servers), these APIs have many of the same security and policy needs that\n+the core resources do, but must compile in their own subsets of admission.\n+\n+Further, distributions of Kubernetes like OpenShift that wish to offer complete solutions (such as OpenShift's\n+multi-tenancy model) have no mechanism for running on top of Kubernetes without recompilation of the core or\n+for extending the core with additional policy. This prevents the formation of an open ecosystem for tools\n+*around* Kubernetes, forcing all changes to policy to go through the Kubernetes codebase review gate (when\n+such review is unnecessary or disruptive to Kubernetes itself).\n+\n+\n+## Design\n+\n+It should be possible to perform holistic policy enforcement in Kubernetes without the recompilation of the\n+core project as plugins that can be added and removed to a stock Kubernetes release. That extension\n+of admission control should leverage similar mechanisms to our existing controller frameworks where possible\n+and otherwise be performant and reliable.\n+\n+\n+### Requirements\n+\n+1.  Easy Initialization\n+    Privileged components should be able to easily participate in the **initialization** of a new object.\n+2.  Synchronous Validation\n+    Synchronous rejection of initialized objects or mutations must be possible outside of the kube-apiserver binary\n+3.  Backwards Compatible\n+    Existing API clients must see no change in behavior to external admission other than increased latency\n+4.  Easy Installation\n+    Administrators should be able to easily write a new admission plugin and deploy it in the cluster\n+5.  Performant\n+    External admission must not significantly regress performance in large and dense clusters\n+6.  Reliable\n+    External admission should be capable of being \"production-grade\" for deployment in an extremely large and dense cluster\n+7.  Internally Consistent\n+    Developing an admission controller should reuse as much infrastructure and tools as possible from building custom controllers so as to reduce the cost of extension.\n+\n+\n+### Specification\n+\n+Based on observation of the actual admission control implementations the majority of mutation\n+occurs as part of creation, and a large chunk of the remaining controllers are for side-effect free\n+validation of creation and updates. Therefore we propose the following changes to Kubernetes:\n+\n+1.  Allow some controllers to act as \"initializers\" - watching the API and mutating the object before it is visible to normal clients.\n+    This would reuse the majority of the infrastructure in place for controllers. Because creation is\n+    one-way, the object can be \"revealed\" to regular clients once a set list of initializers is consumed. These\n+    controllers could run on the cluster as pods.\n+2.  Add a generic **external admission webhook** controller that is non-mutating (thus parallelizable)\n+    This generic webhook API would resemble `admission.Interface` and be given the input object (for create) and the\n+    previous object (for update). After initialization or on any update, these hooks would be invoked in parallel\n+    against the remote servers and any rejection would reject the mutation.\n+3.  Make the registration of both initializers and admission webhooks dynamic via the API (a configmap or cluster scoped resource)\n+    Administrators should be able to dynamically add or remove hooks and initializers on demand to the cluster.\n+    Configuration would be similar to registering new API group versions and include config like \"fail open\" or\n+    \"fail closed\".\n+\n+Some admission controller types would not be possible for these extensions:\n+\n+* Mutating admission webhooks could be a later addition\n+* Admission controllers that need access to the acting user can receive that via the external webhook.\n+* Admission controllers that \"react\" to the acting user can couple the information received via a webhook and then act if they observe mutation succeed (tuple combining resource UID and resource generation).\n+* Quota will continue to be a core plugin per API server, so extension is not critical.\n+\n+\n+\n+#### Implications:\n+\n+1. Initializers and generic admission controllers are highly privileged, so while some separation is valuable they are effectively cluster scoped\n+2. This mechanism would allow dedicated infrastructure to host admission for multiple clusters, and allow some expensive admission to be centralized (like quota which is hard to performantly distribute)\n+3. There is no way to build initializers for updates without a much more complicated model, but we anticipate initializers to work best on creation.\n+4. Ordering will probably be necessary on initializers because defaulting in the wild requires ordering. Non-mutating validation on the other hand can be fully parallel.\n+5. Some admission depends on knowing the identity of the actor - we will likely need to include the **creator** as information to initializers.\n+6. Quota must still run after all validators are invoked. We may need to make quota extensible in the future.\n+\n+\n+#### Initializers\n+\n+An initializer must allow some API clients to perceive creations prior to other apps. For backwards compatibility,\n+uninitialized objects must be invisible to legacy clients. In addition, initializers must be recognized as participating\n+in the initialization process and therefore a list of initializers must be populated onto each new object. Like\n+finalizers, each initializer should perform an update and remove itself from the object.\n+\n+Every API object will have the following field added:\n+\n+```\n+type ObjectMeta struct {\n+    ...\n+    // Initializers is a list of initializers that must run prior to this object being visible to\n+    // normal clients. Initializers are executed in order. This field is nil if initialization is not\n+    // specified, or empty if no initializers should be run. Only highly privileged users\n+    // may modify this field.\n+    Initializers []Initializer `json:\"initializers\"`\n+    ...\n+}\n+\n+type Initializer struct {\n+    // Name is the name of the process that owns this initializer\n+    Name string `json:\"name\"`\n+    // Error is set if the initializer cannot make progress\n+    Status *metav1.Status `json:\"status\"`\n+}\n+```\n+\n+On creation, an admission controller defaults the initializers field (if nil) to a value from the system\n+configuration that may vary by resource type. If initializers is set to empty slice, or has entries,\n+the admission controller will check whether the user has the ability to run the `initialize` verb on\n+the current resource type, and reject the entry with 403 if not.\n+\n+Once created, an object is not visible to clients unless the following conditions are satisfied:\n+\n+1. The initializers field is an empty array.\n+2. The client provides a special option to GET, LIST, or WATCH indicating that the user wishes to see uninitialized objects.\n+\n+Each initializer is a controller or other client agent that watches for new objects with an initializer\n+whose first position matches their assigned name (e.g. `PodAutoSizer`) and then operate on them. These\n+clients would use the `?includeUninitializedObjects=true` query param (working name) and observe all\n+objects.\n+\n+The initializer would perform a normal update on the object to perform their function, and then\n+remove their entry from `initializers` (or adding more entries). If an error occurs during initialization\n+that must terminate initialization, the `Status` field on the initializer should be set instead of removing\n+the initializer entry and then the initializer should delete the object.","path":"contributors/design-proposals/admission_control_extension.md","position":286,"original_position":286,"commit_id":"d70f02c90a1e965372e07cf32705623d63545ca2","original_commit_id":"d70f02c90a1e965372e07cf32705623d63545ca2","user":{"login":"thockin","id":5595220,"avatar_url":"https://avatars.githubusercontent.com/u/5595220?v=3","gravatar_id":"","url":"https://api.github.com/users/thockin","html_url":"https://github.com/thockin","followers_url":"https://api.github.com/users/thockin/followers","following_url":"https://api.github.com/users/thockin/following{/other_user}","gists_url":"https://api.github.com/users/thockin/gists{/gist_id}","starred_url":"https://api.github.com/users/thockin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/thockin/subscriptions","organizations_url":"https://api.github.com/users/thockin/orgs","repos_url":"https://api.github.com/users/thockin/repos","events_url":"https://api.github.com/users/thockin/events{/privacy}","received_events_url":"https://api.github.com/users/thockin/received_events","type":"User","site_admin":false},"body":"Why set status and then immediately delete?  Are we expecting the apiserver to link the deletion to the pending creation and extract the initializer status?  Or will the apiserver install a finalizer so the deletion REST call can complete but the status is not lost until the blocking create ACKs?  What about dropped connections or crashes?  I'd like to see this fleshed out more.","created_at":"2016-12-28T20:32:14Z","updated_at":"2016-12-28T20:56:25Z","html_url":"https://github.com/kubernetes/community/pull/132#discussion_r94074586","pull_request_url":"https://api.github.com/repos/kubernetes/community/pulls/132","_links":{"self":{"href":"https://api.github.com/repos/kubernetes/community/pulls/comments/94074586"},"html":{"href":"https://github.com/kubernetes/community/pull/132#discussion_r94074586"},"pull_request":{"href":"https://api.github.com/repos/kubernetes/community/pulls/132"}}},"pull_request":{"url":"https://api.github.com/repos/kubernetes/community/pulls/132","id":96217825,"html_url":"https://github.com/kubernetes/community/pull/132","diff_url":"https://github.com/kubernetes/community/pull/132.diff","patch_url":"https://github.com/kubernetes/community/pull/132.patch","issue_url":"https://api.github.com/repos/kubernetes/community/issues/132","number":132,"state":"open","locked":false,"title":"Proposal: Extensible Admission Control","user":{"login":"smarterclayton","id":1163175,"avatar_url":"https://avatars.githubusercontent.com/u/1163175?v=3","gravatar_id":"","url":"https://api.github.com/users/smarterclayton","html_url":"https://github.com/smarterclayton","followers_url":"https://api.github.com/users/smarterclayton/followers","following_url":"https://api.github.com/users/smarterclayton/following{/other_user}","gists_url":"https://api.github.com/users/smarterclayton/gists{/gist_id}","starred_url":"https://api.github.com/users/smarterclayton/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/smarterclayton/subscriptions","organizations_url":"https://api.github.com/users/smarterclayton/orgs","repos_url":"https://api.github.com/users/smarterclayton/repos","events_url":"https://api.github.com/users/smarterclayton/events{/privacy}","received_events_url":"https://api.github.com/users/smarterclayton/received_events","type":"User","site_admin":false},"body":"This is a very rough first draft of the proposal for how to make admission control extensible. It covers the reasoning and background at a high level but I'm still pulling together the proposed design (one exists, just not in paper).\n\nBasically, add a new \"initialization\" phase for resources that is opaque to old clients but controllers can opt in to seeing uninitialized objects - the last initializer makes the object visible to everyone else.  Also, add non-mutating generic webhooks for doing remotable checks against a server (like SubjectAccessReview). Make both of those dynamically configurable via some core API object.\n\n@kubernetes/sig-api-machinery @bgrant0607","created_at":"2016-12-02T03:54:13Z","updated_at":"2016-12-28T20:56:25Z","closed_at":null,"merged_at":null,"merge_commit_sha":"99ce3706838623b82f75e8f622e858f75f7b122f","assignee":{"login":"bgrant0607","id":7725777,"avatar_url":"https://avatars.githubusercontent.com/u/7725777?v=3","gravatar_id":"","url":"https://api.github.com/users/bgrant0607","html_url":"https://github.com/bgrant0607","followers_url":"https://api.github.com/users/bgrant0607/followers","following_url":"https://api.github.com/users/bgrant0607/following{/other_user}","gists_url":"https://api.github.com/users/bgrant0607/gists{/gist_id}","starred_url":"https://api.github.com/users/bgrant0607/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bgrant0607/subscriptions","organizations_url":"https://api.github.com/users/bgrant0607/orgs","repos_url":"https://api.github.com/users/bgrant0607/repos","events_url":"https://api.github.com/users/bgrant0607/events{/privacy}","received_events_url":"https://api.github.com/users/bgrant0607/received_events","type":"User","site_admin":false},"assignees":[{"login":"bgrant0607","id":7725777,"avatar_url":"https://avatars.githubusercontent.com/u/7725777?v=3","gravatar_id":"","url":"https://api.github.com/users/bgrant0607","html_url":"https://github.com/bgrant0607","followers_url":"https://api.github.com/users/bgrant0607/followers","following_url":"https://api.github.com/users/bgrant0607/following{/other_user}","gists_url":"https://api.github.com/users/bgrant0607/gists{/gist_id}","starred_url":"https://api.github.com/users/bgrant0607/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bgrant0607/subscriptions","organizations_url":"https://api.github.com/users/bgrant0607/orgs","repos_url":"https://api.github.com/users/bgrant0607/repos","events_url":"https://api.github.com/users/bgrant0607/events{/privacy}","received_events_url":"https://api.github.com/users/bgrant0607/received_events","type":"User","site_admin":false}],"milestone":null,"commits_url":"https://api.github.com/repos/kubernetes/community/pulls/132/commits","review_comments_url":"https://api.github.com/repos/kubernetes/community/pulls/132/comments","review_comment_url":"https://api.github.com/repos/kubernetes/community/pulls/comments{/number}","comments_url":"https://api.github.com/repos/kubernetes/community/issues/132/comments","statuses_url":"https://api.github.com/repos/kubernetes/community/statuses/d70f02c90a1e965372e07cf32705623d63545ca2","head":{"label":"smarterclayton:external_admission","ref":"external_admission","sha":"d70f02c90a1e965372e07cf32705623d63545ca2","user":{"login":"smarterclayton","id":1163175,"avatar_url":"https://avatars.githubusercontent.com/u/1163175?v=3","gravatar_id":"","url":"https://api.github.com/users/smarterclayton","html_url":"https://github.com/smarterclayton","followers_url":"https://api.github.com/users/smarterclayton/followers","following_url":"https://api.github.com/users/smarterclayton/following{/other_user}","gists_url":"https://api.github.com/users/smarterclayton/gists{/gist_id}","starred_url":"https://api.github.com/users/smarterclayton/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/smarterclayton/subscriptions","organizations_url":"https://api.github.com/users/smarterclayton/orgs","repos_url":"https://api.github.com/users/smarterclayton/repos","events_url":"https://api.github.com/users/smarterclayton/events{/privacy}","received_events_url":"https://api.github.com/users/smarterclayton/received_events","type":"User","site_admin":false},"repo":{"id":75316472,"name":"community","full_name":"smarterclayton/community","owner":{"login":"smarterclayton","id":1163175,"avatar_url":"https://avatars.githubusercontent.com/u/1163175?v=3","gravatar_id":"","url":"https://api.github.com/users/smarterclayton","html_url":"https://github.com/smarterclayton","followers_url":"https://api.github.com/users/smarterclayton/followers","following_url":"https://api.github.com/users/smarterclayton/following{/other_user}","gists_url":"https://api.github.com/users/smarterclayton/gists{/gist_id}","starred_url":"https://api.github.com/users/smarterclayton/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/smarterclayton/subscriptions","organizations_url":"https://api.github.com/users/smarterclayton/orgs","repos_url":"https://api.github.com/users/smarterclayton/repos","events_url":"https://api.github.com/users/smarterclayton/events{/privacy}","received_events_url":"https://api.github.com/users/smarterclayton/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/smarterclayton/community","description":"Kubernetes community content","fork":true,"url":"https://api.github.com/repos/smarterclayton/community","forks_url":"https://api.github.com/repos/smarterclayton/community/forks","keys_url":"https://api.github.com/repos/smarterclayton/community/keys{/key_id}","collaborators_url":"https://api.github.com/repos/smarterclayton/community/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/smarterclayton/community/teams","hooks_url":"https://api.github.com/repos/smarterclayton/community/hooks","issue_events_url":"https://api.github.com/repos/smarterclayton/community/issues/events{/number}","events_url":"https://api.github.com/repos/smarterclayton/community/events","assignees_url":"https://api.github.com/repos/smarterclayton/community/assignees{/user}","branches_url":"https://api.github.com/repos/smarterclayton/community/branches{/branch}","tags_url":"https://api.github.com/repos/smarterclayton/community/tags","blobs_url":"https://api.github.com/repos/smarterclayton/community/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/smarterclayton/community/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/smarterclayton/community/git/refs{/sha}","trees_url":"https://api.github.com/repos/smarterclayton/community/git/trees{/sha}","statuses_url":"https://api.github.com/repos/smarterclayton/community/statuses/{sha}","languages_url":"https://api.github.com/repos/smarterclayton/community/languages","stargazers_url":"https://api.github.com/repos/smarterclayton/community/stargazers","contributors_url":"https://api.github.com/repos/smarterclayton/community/contributors","subscribers_url":"https://api.github.com/repos/smarterclayton/community/subscribers","subscription_url":"https://api.github.com/repos/smarterclayton/community/subscription","commits_url":"https://api.github.com/repos/smarterclayton/community/commits{/sha}","git_commits_url":"https://api.github.com/repos/smarterclayton/community/git/commits{/sha}","comments_url":"https://api.github.com/repos/smarterclayton/community/comments{/number}","issue_comment_url":"https://api.github.com/repos/smarterclayton/community/issues/comments{/number}","contents_url":"https://api.github.com/repos/smarterclayton/community/contents/{+path}","compare_url":"https://api.github.com/repos/smarterclayton/community/compare/{base}...{head}","merges_url":"https://api.github.com/repos/smarterclayton/community/merges","archive_url":"https://api.github.com/repos/smarterclayton/community/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/smarterclayton/community/downloads","issues_url":"https://api.github.com/repos/smarterclayton/community/issues{/number}","pulls_url":"https://api.github.com/repos/smarterclayton/community/pulls{/number}","milestones_url":"https://api.github.com/repos/smarterclayton/community/milestones{/number}","notifications_url":"https://api.github.com/repos/smarterclayton/community/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/smarterclayton/community/labels{/name}","releases_url":"https://api.github.com/repos/smarterclayton/community/releases{/id}","deployments_url":"https://api.github.com/repos/smarterclayton/community/deployments","created_at":"2016-12-01T17:33:49Z","updated_at":"2016-12-01T17:33:51Z","pushed_at":"2016-12-22T04:16:45Z","git_url":"git://github.com/smarterclayton/community.git","ssh_url":"git@github.com:smarterclayton/community.git","clone_url":"https://github.com/smarterclayton/community.git","svn_url":"https://github.com/smarterclayton/community","homepage":"","size":6791,"stargazers_count":0,"watchers_count":0,"language":"Makefile","has_issues":false,"has_downloads":true,"has_wiki":true,"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":"kubernetes:master","ref":"master","sha":"148fff443dcab5f673b5e90c1e461c328e4b0373","user":{"login":"kubernetes","id":13629408,"avatar_url":"https://avatars.githubusercontent.com/u/13629408?v=3","gravatar_id":"","url":"https://api.github.com/users/kubernetes","html_url":"https://github.com/kubernetes","followers_url":"https://api.github.com/users/kubernetes/followers","following_url":"https://api.github.com/users/kubernetes/following{/other_user}","gists_url":"https://api.github.com/users/kubernetes/gists{/gist_id}","starred_url":"https://api.github.com/users/kubernetes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kubernetes/subscriptions","organizations_url":"https://api.github.com/users/kubernetes/orgs","repos_url":"https://api.github.com/users/kubernetes/repos","events_url":"https://api.github.com/users/kubernetes/events{/privacy}","received_events_url":"https://api.github.com/users/kubernetes/received_events","type":"Organization","site_admin":false},"repo":{"id":57939112,"name":"community","full_name":"kubernetes/community","owner":{"login":"kubernetes","id":13629408,"avatar_url":"https://avatars.githubusercontent.com/u/13629408?v=3","gravatar_id":"","url":"https://api.github.com/users/kubernetes","html_url":"https://github.com/kubernetes","followers_url":"https://api.github.com/users/kubernetes/followers","following_url":"https://api.github.com/users/kubernetes/following{/other_user}","gists_url":"https://api.github.com/users/kubernetes/gists{/gist_id}","starred_url":"https://api.github.com/users/kubernetes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kubernetes/subscriptions","organizations_url":"https://api.github.com/users/kubernetes/orgs","repos_url":"https://api.github.com/users/kubernetes/repos","events_url":"https://api.github.com/users/kubernetes/events{/privacy}","received_events_url":"https://api.github.com/users/kubernetes/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/kubernetes/community","description":"Kubernetes community content","fork":false,"url":"https://api.github.com/repos/kubernetes/community","forks_url":"https://api.github.com/repos/kubernetes/community/forks","keys_url":"https://api.github.com/repos/kubernetes/community/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kubernetes/community/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kubernetes/community/teams","hooks_url":"https://api.github.com/repos/kubernetes/community/hooks","issue_events_url":"https://api.github.com/repos/kubernetes/community/issues/events{/number}","events_url":"https://api.github.com/repos/kubernetes/community/events","assignees_url":"https://api.github.com/repos/kubernetes/community/assignees{/user}","branches_url":"https://api.github.com/repos/kubernetes/community/branches{/branch}","tags_url":"https://api.github.com/repos/kubernetes/community/tags","blobs_url":"https://api.github.com/repos/kubernetes/community/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kubernetes/community/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kubernetes/community/git/refs{/sha}","trees_url":"https://api.github.com/repos/kubernetes/community/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kubernetes/community/statuses/{sha}","languages_url":"https://api.github.com/repos/kubernetes/community/languages","stargazers_url":"https://api.github.com/repos/kubernetes/community/stargazers","contributors_url":"https://api.github.com/repos/kubernetes/community/contributors","subscribers_url":"https://api.github.com/repos/kubernetes/community/subscribers","subscription_url":"https://api.github.com/repos/kubernetes/community/subscription","commits_url":"https://api.github.com/repos/kubernetes/community/commits{/sha}","git_commits_url":"https://api.github.com/repos/kubernetes/community/git/commits{/sha}","comments_url":"https://api.github.com/repos/kubernetes/community/comments{/number}","issue_comment_url":"https://api.github.com/repos/kubernetes/community/issues/comments{/number}","contents_url":"https://api.github.com/repos/kubernetes/community/contents/{+path}","compare_url":"https://api.github.com/repos/kubernetes/community/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kubernetes/community/merges","archive_url":"https://api.github.com/repos/kubernetes/community/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kubernetes/community/downloads","issues_url":"https://api.github.com/repos/kubernetes/community/issues{/number}","pulls_url":"https://api.github.com/repos/kubernetes/community/pulls{/number}","milestones_url":"https://api.github.com/repos/kubernetes/community/milestones{/number}","notifications_url":"https://api.github.com/repos/kubernetes/community/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kubernetes/community/labels{/name}","releases_url":"https://api.github.com/repos/kubernetes/community/releases{/id}","deployments_url":"https://api.github.com/repos/kubernetes/community/deployments","created_at":"2016-05-03T03:36:05Z","updated_at":"2016-12-27T21:28:04Z","pushed_at":"2016-12-28T15:47:26Z","git_url":"git://github.com/kubernetes/community.git","ssh_url":"git@github.com:kubernetes/community.git","clone_url":"https://github.com/kubernetes/community.git","svn_url":"https://github.com/kubernetes/community","homepage":"","size":6896,"stargazers_count":115,"watchers_count":115,"language":"Makefile","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":130,"mirror_url":null,"open_issues_count":59,"forks":130,"open_issues":59,"watchers":115,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/kubernetes/community/pulls/132"},"html":{"href":"https://github.com/kubernetes/community/pull/132"},"issue":{"href":"https://api.github.com/repos/kubernetes/community/issues/132"},"comments":{"href":"https://api.github.com/repos/kubernetes/community/issues/132/comments"},"review_comments":{"href":"https://api.github.com/repos/kubernetes/community/pulls/132/comments"},"review_comment":{"href":"https://api.github.com/repos/kubernetes/community/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/kubernetes/community/pulls/132/commits"},"statuses":{"href":"https://api.github.com/repos/kubernetes/community/statuses/d70f02c90a1e965372e07cf32705623d63545ca2"}}}}
{"actor":{"display_login":"thockin"}}