{"action":"created","comment":{"url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/comments/213762985","pull_request_review_id":150666727,"id":213762985,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxMzc2Mjk4NQ==","diff_hunk":"@@ -0,0 +1,446 @@\n+/*\n+Copyright 2018 The Kubernetes Authors.\n+\n+Licensed under the Apache License, Version 2.0 (the \"License\");\n+you may not use this file except in compliance with the License.\n+You may obtain a copy of the License at\n+\n+    http://www.apache.org/licenses/LICENSE-2.0\n+\n+Unless required by applicable law or agreed to in writing, software\n+distributed under the License is distributed on an \"AS IS\" BASIS,\n+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+See the License for the specific language governing permissions and\n+limitations under the License.\n+*/\n+\n+package ttlafterfinished\n+\n+import (\n+\t\"fmt\"\n+\t\"sync\"\n+\t\"time\"\n+\n+\t\"github.com/golang/glog\"\n+\n+\tbatch \"k8s.io/api/batch/v1\"\n+\t\"k8s.io/api/core/v1\"\n+\t\"k8s.io/apimachinery/pkg/api/errors\"\n+\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n+\t\"k8s.io/apimachinery/pkg/util/clock\"\n+\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n+\t\"k8s.io/apimachinery/pkg/util/wait\"\n+\tbatchinformers \"k8s.io/client-go/informers/batch/v1\"\n+\tclientset \"k8s.io/client-go/kubernetes\"\n+\tv1core \"k8s.io/client-go/kubernetes/typed/core/v1\"\n+\tbatchlisters \"k8s.io/client-go/listers/batch/v1\"\n+\t\"k8s.io/client-go/tools/cache\"\n+\t\"k8s.io/client-go/tools/record\"\n+\t\"k8s.io/client-go/util/workqueue\"\n+\t\"k8s.io/kubernetes/pkg/controller\"\n+\tjobutil \"k8s.io/kubernetes/pkg/controller/job\"\n+\t\"k8s.io/kubernetes/pkg/kubectl/scheme\"\n+\t\"k8s.io/kubernetes/pkg/util/metrics\"\n+)\n+\n+// Controller watches for changes of Jobs API objects. Triggered\n+// by Job creation and updates, it enqueues Jobs that have finished and passed\n+// the Jobs' `.spec.ttlSecondsAfterFinished` time period to the `jobsToCleanup`\n+// queue. The Controller has workers who consume the\n+// `jobsToCleanup` queue and send requests to the API server to delete the Jobs\n+// accordingly.\n+// This is implemented outside of Job controller for separation of concerns, and\n+// because it will be extended to handle other finishable resource types.\n+type Controller struct {\n+\tclient   clientset.Interface\n+\trecorder record.EventRecorder\n+\n+\t// jLister can list/get Jobs from the shared informer's store\n+\tjLister batchlisters.JobLister\n+\n+\t// jStoreSynced returns true if the Job store has been synced at least once.\n+\t// Added as a member to the struct to allow injection for testing.\n+\tjListerSynced cache.InformerSynced\n+\n+\t// Jobs that the controller will attempt to delete.\n+\tjobsToCleanup workqueue.RateLimitingInterface\n+\n+\t// Jobs to be added to job cleanup workqueue later after it's ready for clean up.\n+\t// These jobs are stored in a thread-safe heap, and will be popped to job cleanup\n+\t// workqueue after their TTLs expire.\n+\t// Note: we cannot rely on workqueue.AddAfter, because it uses a buffered channel of\n+\t// limited size (1k), and the workqueue will be blocked after the channel is full.\n+\tjobsDelayCleanupQueueMutex sync.RWMutex\n+\tjobsDelayCleanupQueue      *cache.Heap\n+\n+\t// The next time we should check the jobsDelayCleanupQueue.\n+\tnextCheckAtMutex sync.RWMutex\n+\tnextCheckAt      *time.Time\n+\n+\t// The clock for tracking time\n+\tclock clock.Clock\n+}\n+\n+// New creates an instance of Controller\n+func New(jobInformer batchinformers.JobInformer, client clientset.Interface) *Controller {\n+\teventBroadcaster := record.NewBroadcaster()\n+\teventBroadcaster.StartLogging(glog.Infof)\n+\teventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: client.CoreV1().Events(\"\")})\n+\n+\tif client != nil && client.CoreV1().RESTClient().GetRateLimiter() != nil {\n+\t\tmetrics.RegisterMetricAndTrackRateLimiterUsage(\"ttl_after_finished_controller\", client.CoreV1().RESTClient().GetRateLimiter())\n+\t}\n+\n+\ttc := &Controller{\n+\t\tclient:        client,\n+\t\trecorder:      eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: \"ttl-after-finished-controller\"}),\n+\t\tjobsToCleanup: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), \"ttl_jobs_to_delete\"),\n+\t}\n+\n+\tjobInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{\n+\t\tAddFunc:    tc.addJob,\n+\t\tUpdateFunc: tc.updateJob,\n+\t\tDeleteFunc: tc.deleteJob,\n+\t})\n+\n+\ttc.jLister = jobInformer.Lister()\n+\ttc.jListerSynced = jobInformer.Informer().HasSynced\n+\n+\ttc.jobsDelayCleanupQueue = NewJobsDelayCleanupQueue()\n+\n+\ttc.clock = clock.RealClock{}\n+\n+\treturn tc\n+}\n+\n+// Run starts the workers to clean up Jobs.\n+func (tc *Controller) Run(workers int, stopCh <-chan struct{}) {\n+\tdefer utilruntime.HandleCrash()\n+\tdefer tc.jobsToCleanup.ShutDown()\n+\tdefer tc.jobsDelayCleanupQueue.Close()\n+\n+\tglog.Infof(\"Starting TTL after finished controller\")\n+\tdefer glog.Infof(\"Shutting down TTL after finished controller\")\n+\n+\tif !controller.WaitForCacheSync(\"TTL after finished\", stopCh, tc.jListerSynced) {\n+\t\treturn\n+\t}\n+\n+\tfor i := 0; i < workers; i++ {\n+\t\tgo wait.Until(tc.runJobCleanupWorker, time.Second, stopCh)\n+\t}\n+\tgo wait.JitterUntil(tc.runJobDelayCleanupWorker, 10*time.Second, 0.2, true, stopCh)\n+\n+\t<-stopCh\n+}\n+\n+func (tc *Controller) addJob(obj interface{}) {\n+\tjob := obj.(*batch.Job)\n+\tglog.V(4).Infof(\"Adding job %s/%s\", job.Namespace, job.Name)\n+\n+\t// `addJobToCleanupFn` enqueues the Job when TTL expires\n+\taddJobToCleanupFn := func(j *batch.Job) error {\n+\t\ttc.addJobToCleanup(j)\n+\t\treturn nil\n+\t}\n+\t// Check the Job's TTL and run `addJobToCleanupFn` when the TTL expires\n+\terr := tc.checkTTLAndReact(job, addJobToCleanupFn)\n+\tif err != nil {\n+\t\tglog.V(2).Infof(\"Error cleaning up Job %s/%s: %v\", job.Namespace, job.Name, err)\n+\t}\n+}\n+\n+func (tc *Controller) updateJob(old, cur interface{}) {\n+\tjob := cur.(*batch.Job)\n+\tglog.V(4).Infof(\"Updating job %s/%s\", job.Namespace, job.Name)\n+\n+\t// `addJobToCleanupFn` enqueues the Job when TTL expires\n+\taddJobToCleanupFn := func(j *batch.Job) error {\n+\t\ttc.addJobToCleanup(j)\n+\t\treturn nil\n+\t}\n+\t// Check the Job's TTL and run `addJobToCleanupFn` when the TTL expires\n+\terr := tc.checkTTLAndReact(job, addJobToCleanupFn)\n+\tif err != nil {\n+\t\tglog.V(2).Infof(\"Error cleaning up Job %s/%s: %v\", job.Namespace, job.Name, err)\n+\t}\n+}\n+\n+func (tc *Controller) deleteJob(obj interface{}) {\n+\tjob := obj.(*batch.Job)\n+\tglog.V(4).Infof(\"Deleting job %s/%s\", job.Namespace, job.Name)\n+\n+\t// Remove the deleted Job from the delay cleanup queue\n+\ttc.jobsDelayCleanupQueue.Delete(job)\n+}\n+\n+func (tc *Controller) addJobToCleanup(job *batch.Job) {\n+\tglog.V(4).Infof(\"Add job %s/%s to cleanup\", job.Namespace, job.Name)\n+\tkey, err := controller.KeyFunc(job)\n+\tif err != nil {\n+\t\tutilruntime.HandleError(fmt.Errorf(\"couldn't get key for object %#v: %v\", job, err))\n+\t\treturn\n+\t}\n+\n+\ttc.jobsToCleanup.Add(key)\n+}\n+\n+func (tc *Controller) runJobCleanupWorker() {\n+\tfor tc.jobCleanupWorker() {\n+\t}\n+}\n+\n+func (tc *Controller) jobCleanupWorker() bool {\n+\tkey, quit := tc.jobsToCleanup.Get()\n+\tif quit {\n+\t\treturn false\n+\t}\n+\tdefer tc.jobsToCleanup.Done(key)\n+\n+\terr := tc.cleanupJob(key.(string))\n+\ttc.handleErr(err, key)\n+\n+\treturn true\n+}\n+\n+func (tc *Controller) runJobDelayCleanupWorker() {\n+\tfor tc.handleJobsDelayCleanup() {\n+\t}\n+}\n+\n+func (tc *Controller) handleJobsDelayCleanup() bool {\n+\tif tc.jobsDelayCleanupQueue.IsClosed() {\n+\t\tglog.V(2).Infof(\"The delay cleanup queue for Jobs is closed.\")\n+\t\treturn false\n+\t}\n+\n+\tnowUTC := tc.clock.Now().UTC()\n+\n+\ttc.nextCheckAtMutex.RLock()\n+\tcheckAt := tc.nextCheckAt\n+\ttc.nextCheckAtMutex.RUnlock()\n+\n+\t// No need to check delay cleanup queue for now.\n+\tif checkAt == nil || checkAt.IsZero() || nowUTC.Before(checkAt.UTC()) {\n+\t\treturn true\n+\t}\n+\n+\ttc.jobsDelayCleanupQueueMutex.Lock()\n+\tdefer tc.jobsDelayCleanupQueueMutex.Unlock()\n+\n+\tglog.V(4).Infof(\"Handling Jobs in the delay cleanup queue\")\n+\t// Keep finding expired Jobs from the delay cleanup queue, and send them to clean up queue.\n+\tfor {\n+\t\tobj := tc.jobsDelayCleanupQueue.Peek()\n+\t\tif obj == nil {\n+\t\t\t// Nothing in the queue.\n+\t\t\t// Clear the next check time.\n+\t\t\ttc.clearNextCheckAt()\n+\t\t\treturn true\n+\t\t}\n+\t\tjob, ok := obj.(*batch.Job)\n+\t\tif !ok {\n+\t\t\tglog.V(2).Infof(\"Error peeking obj from the delay cleanup queue, it's not a Job, dropping it: %#v\", obj)\n+\t\t\ttc.popJobsDelayCleanupQueue(job)\n+\t\t\tcontinue\n+\t\t}\n+\t\tnowUTC = tc.clock.Now().UTC()\n+\t\tt, expireAt, err := timeLeft(job, &nowUTC)\n+\t\tif err != nil {\n+\t\t\tif jobutil.IsJobFinished(job) && job.Spec.TTLSecondsAfterFinished == nil {\n+\t\t\t\t// This may happen when the user clear the TTL of a finished Job\n+\t\t\t\tglog.V(4).Infof(\"Detected Job %s/%s without TTL seconds, will not clean it up: %v\", job.Namespace, job.Name, err)\n+\t\t\t} else {\n+\t\t\t\tglog.V(2).Infof(\"Unable to find the time to clean up Job %s/%s, dropping it: %v\", job.Namespace, job.Name, err)\n+\t\t\t}\n+\t\t\ttc.popJobsDelayCleanupQueue(job)\n+\t\t\tcontinue\n+\t\t}\n+\t\t// If TTL expires, add the Job to the cleanup queue;\n+\t\t// otherwise, return because nothing left in the queue expires.\n+\t\tif *t <= 0 {\n+\t\t\ttc.popJobsDelayCleanupQueue(job)\n+\t\t\ttc.addJobToCleanup(job)\n+\t\t} else {\n+\t\t\t// This job is the top of the queue.\n+\t\t\t// Update the next check time to this job's expiration time.\n+\t\t\ttc.setNextCheckTo(expireAt)\n+\t\t\treturn true\n+\t\t}\n+\t}\n+}\n+\n+// popJobsDelayCleanupQueue is supposed to be called by handleJobsDelayCleanup only.\n+func (tc *Controller) popJobsDelayCleanupQueue(top *batch.Job) {\n+\t// Pop from the jobs delay cleanup queue.\n+\tobj, err := tc.jobsDelayCleanupQueue.Pop()\n+\tif err != nil {\n+\t\tglog.V(4).Infof(\"Error: failed to pop from the delay cleanup queue: %v\", err)\n+\t}\n+\tjob, ok := obj.(*batch.Job)\n+\tif !ok {\n+\t\tglog.V(2).Infof(\"Error: popped obj from the delay cleanup queue, but it's not a Job: %#v\", obj)\n+\t}\n+\t// If we popped something other than what we want to pop, we need to add it back.\n+\t// We expect this not to happen.\n+\tif job.Namespace != top.Namespace || job.Name != top.Name {\n+\t\tglog.V(2).Infof(\"Error: expect popped job from the delay cleanup queue to be the same as peeked one; popped: %#v, peeked: %#v\", job, top)\n+\t\ttc.jobsDelayCleanupQueue.Add(job)\n+\t\treturn\n+\t}\n+\tglog.V(4).Infof(\"Popped Job %s/%s from the delay cleanup queue\", job.Namespace, job.Name)\n+}\n+\n+// setNextCheckTo sets the nextCheckAt to the given time t\n+func (tc *Controller) setNextCheckTo(t *time.Time) {\n+\ttc.nextCheckAtMutex.Lock()\n+\tdefer tc.nextCheckAtMutex.Unlock()\n+\n+\tif tc.nextCheckAt == nil {\n+\t\ttc.nextCheckAt = new(time.Time)\n+\t}\n+\ttUTC := t.UTC()\n+\t*tc.nextCheckAt = tUTC\n+\tglog.V(4).Infof(\"Next time to check jobs delay cleanup queue updated to: %v\", *tc.nextCheckAt)\n+}\n+\n+func (tc *Controller) handleErr(err error, key interface{}) {\n+\tif err == nil {\n+\t\ttc.jobsToCleanup.Forget(key)\n+\t\treturn\n+\t}\n+\n+\tutilruntime.HandleError(fmt.Errorf(\"error cleaning up Job %v, will retry: %v\", key, err))\n+\ttc.jobsToCleanup.AddRateLimited(key)\n+}\n+\n+// cleanupJob will check the Job's state and TTL and delete the Job when it\n+// finishes and its TTL after finished has expired. This function is not meant\n+// to be invoked concurrently with the same key.\n+func (tc *Controller) cleanupJob(key string) error {\n+\tnamespace, name, err := cache.SplitMetaNamespaceKey(key)\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\n+\tglog.V(4).Infof(\"Attempt to clean up Job %s\", name)\n+\t// Ignore the Jobs that are already deleted or being deleted\n+\tjob, err := tc.jLister.Jobs(namespace).Get(name)\n+\tif errors.IsNotFound(err) {\n+\t\treturn nil\n+\t}\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\tif job.DeletionTimestamp != nil {\n+\t\treturn nil\n+\t}\n+\n+\t// Before deleting the Job, do a final sanity check just in case TTL is modified.","path":"pkg/controller/ttlafterfinished/ttlafterfinished_controller.go","position":339,"original_position":339,"commit_id":"2f0a34e89a30a7291748cb0903bd5b270831a585","original_commit_id":"2f0a34e89a30a7291748cb0903bd5b270831a585","user":{"login":"tnozicka","id":11869554,"node_id":"MDQ6VXNlcjExODY5NTU0","avatar_url":"https://avatars2.githubusercontent.com/u/11869554?v=4","gravatar_id":"","url":"https://api.github.com/users/tnozicka","html_url":"https://github.com/tnozicka","followers_url":"https://api.github.com/users/tnozicka/followers","following_url":"https://api.github.com/users/tnozicka/following{/other_user}","gists_url":"https://api.github.com/users/tnozicka/gists{/gist_id}","starred_url":"https://api.github.com/users/tnozicka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tnozicka/subscriptions","organizations_url":"https://api.github.com/users/tnozicka/orgs","repos_url":"https://api.github.com/users/tnozicka/repos","events_url":"https://api.github.com/users/tnozicka/events{/privacy}","received_events_url":"https://api.github.com/users/tnozicka/received_events","type":"User","site_admin":false},"body":"@janetkuo I think you are correct.\r\n\r\nIs there a use case to support changing the TTL after you set it? \r\n\r\nAlso I guess for pods the spec is immutable anyways hence the TTL field - do we want to differ with JobSpec?","created_at":"2018-08-29T17:15:25Z","updated_at":"2018-08-29T17:15:25Z","html_url":"https://github.com/kubernetes/kubernetes/pull/66840#discussion_r213762985","pull_request_url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/66840","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/comments/213762985"},"html":{"href":"https://github.com/kubernetes/kubernetes/pull/66840#discussion_r213762985"},"pull_request":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/66840"}},"in_reply_to_id":213699522},"pull_request":{"url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/66840","id":205252725,"node_id":"MDExOlB1bGxSZXF1ZXN0MjA1MjUyNzI1","html_url":"https://github.com/kubernetes/kubernetes/pull/66840","diff_url":"https://github.com/kubernetes/kubernetes/pull/66840.diff","patch_url":"https://github.com/kubernetes/kubernetes/pull/66840.patch","issue_url":"https://api.github.com/repos/kubernetes/kubernetes/issues/66840","number":66840,"state":"open","locked":false,"title":"TTL for cleaning up Jobs after they finish","user":{"login":"janetkuo","id":4876867,"node_id":"MDQ6VXNlcjQ4NzY4Njc=","avatar_url":"https://avatars1.githubusercontent.com/u/4876867?v=4","gravatar_id":"","url":"https://api.github.com/users/janetkuo","html_url":"https://github.com/janetkuo","followers_url":"https://api.github.com/users/janetkuo/followers","following_url":"https://api.github.com/users/janetkuo/following{/other_user}","gists_url":"https://api.github.com/users/janetkuo/gists{/gist_id}","starred_url":"https://api.github.com/users/janetkuo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/janetkuo/subscriptions","organizations_url":"https://api.github.com/users/janetkuo/orgs","repos_url":"https://api.github.com/users/janetkuo/repos","events_url":"https://api.github.com/users/janetkuo/events{/privacy}","received_events_url":"https://api.github.com/users/janetkuo/received_events","type":"User","site_admin":false},"body":"<!--  Thanks for sending a pull request!  Here are some tips for you:\r\n1. If this is your first time, read our contributor guidelines https://git.k8s.io/community/contributors/guide#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide\r\n2. If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews\r\n3. Follow the instructions for writing a release note: https://git.k8s.io/community/contributors/guide/release-notes.md\r\n4. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests\r\n-->\r\n\r\n**What this PR does / why we need it**: https://github.com/kubernetes/features/issues/592\r\n\r\n**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:\r\nFixes #64470 \r\n\r\n**Special notes for your reviewer**: @kubernetes/sig-apps-pr-reviews \r\n\r\n**Release note**:\r\n<!--  Write your release note:\r\n1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string \"action required\".\r\n2. If no release note is required, just write \"NONE\".\r\n-->\r\n```release-note\r\nAdd a TTL machenism to clean up Jobs after they finish.\r\n```\r\n","created_at":"2018-07-31T21:50:15Z","updated_at":"2018-08-29T17:15:25Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bab0e20c6d467cda565260acc9f78c81374870c1","assignee":{"login":"liggitt","id":980082,"node_id":"MDQ6VXNlcjk4MDA4Mg==","avatar_url":"https://avatars0.githubusercontent.com/u/980082?v=4","gravatar_id":"","url":"https://api.github.com/users/liggitt","html_url":"https://github.com/liggitt","followers_url":"https://api.github.com/users/liggitt/followers","following_url":"https://api.github.com/users/liggitt/following{/other_user}","gists_url":"https://api.github.com/users/liggitt/gists{/gist_id}","starred_url":"https://api.github.com/users/liggitt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/liggitt/subscriptions","organizations_url":"https://api.github.com/users/liggitt/orgs","repos_url":"https://api.github.com/users/liggitt/repos","events_url":"https://api.github.com/users/liggitt/events{/privacy}","received_events_url":"https://api.github.com/users/liggitt/received_events","type":"User","site_admin":false},"assignees":[{"login":"cheftako","id":947971,"node_id":"MDQ6VXNlcjk0Nzk3MQ==","avatar_url":"https://avatars3.githubusercontent.com/u/947971?v=4","gravatar_id":"","url":"https://api.github.com/users/cheftako","html_url":"https://github.com/cheftako","followers_url":"https://api.github.com/users/cheftako/followers","following_url":"https://api.github.com/users/cheftako/following{/other_user}","gists_url":"https://api.github.com/users/cheftako/gists{/gist_id}","starred_url":"https://api.github.com/users/cheftako/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cheftako/subscriptions","organizations_url":"https://api.github.com/users/cheftako/orgs","repos_url":"https://api.github.com/users/cheftako/repos","events_url":"https://api.github.com/users/cheftako/events{/privacy}","received_events_url":"https://api.github.com/users/cheftako/received_events","type":"User","site_admin":false},{"login":"liggitt","id":980082,"node_id":"MDQ6VXNlcjk4MDA4Mg==","avatar_url":"https://avatars0.githubusercontent.com/u/980082?v=4","gravatar_id":"","url":"https://api.github.com/users/liggitt","html_url":"https://github.com/liggitt","followers_url":"https://api.github.com/users/liggitt/followers","following_url":"https://api.github.com/users/liggitt/following{/other_user}","gists_url":"https://api.github.com/users/liggitt/gists{/gist_id}","starred_url":"https://api.github.com/users/liggitt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/liggitt/subscriptions","organizations_url":"https://api.github.com/users/liggitt/orgs","repos_url":"https://api.github.com/users/liggitt/repos","events_url":"https://api.github.com/users/liggitt/events{/privacy}","received_events_url":"https://api.github.com/users/liggitt/received_events","type":"User","site_admin":false},{"login":"deads2k","id":8225098,"node_id":"MDQ6VXNlcjgyMjUwOTg=","avatar_url":"https://avatars0.githubusercontent.com/u/8225098?v=4","gravatar_id":"","url":"https://api.github.com/users/deads2k","html_url":"https://github.com/deads2k","followers_url":"https://api.github.com/users/deads2k/followers","following_url":"https://api.github.com/users/deads2k/following{/other_user}","gists_url":"https://api.github.com/users/deads2k/gists{/gist_id}","starred_url":"https://api.github.com/users/deads2k/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/deads2k/subscriptions","organizations_url":"https://api.github.com/users/deads2k/orgs","repos_url":"https://api.github.com/users/deads2k/repos","events_url":"https://api.github.com/users/deads2k/events{/privacy}","received_events_url":"https://api.github.com/users/deads2k/received_events","type":"User","site_admin":false},{"login":"kow3ns","id":11380896,"node_id":"MDQ6VXNlcjExMzgwODk2","avatar_url":"https://avatars3.githubusercontent.com/u/11380896?v=4","gravatar_id":"","url":"https://api.github.com/users/kow3ns","html_url":"https://github.com/kow3ns","followers_url":"https://api.github.com/users/kow3ns/followers","following_url":"https://api.github.com/users/kow3ns/following{/other_user}","gists_url":"https://api.github.com/users/kow3ns/gists{/gist_id}","starred_url":"https://api.github.com/users/kow3ns/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kow3ns/subscriptions","organizations_url":"https://api.github.com/users/kow3ns/orgs","repos_url":"https://api.github.com/users/kow3ns/repos","events_url":"https://api.github.com/users/kow3ns/events{/privacy}","received_events_url":"https://api.github.com/users/kow3ns/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":477397086,"node_id":"MDU6TGFiZWw0NzczOTcwODY=","url":"https://api.github.com/repos/kubernetes/kubernetes/labels/cncf-cla:%20yes","name":"cncf-cla: yes","color":"bfe5bf","default":false},{"id":261005360,"node_id":"MDU6TGFiZWwyNjEwMDUzNjA=","url":"https://api.github.com/repos/kubernetes/kubernetes/labels/kind/api-change","name":"kind/api-change","color":"e11d21","default":false},{"id":240247879,"node_id":"MDU6TGFiZWwyNDAyNDc4Nzk=","url":"https://api.github.com/repos/kubernetes/kubernetes/labels/needs-rebase","name":"needs-rebase","color":"BDBDBD","default":false},{"id":200149833,"node_id":"MDU6TGFiZWwyMDAxNDk4MzM=","url":"https://api.github.com/repos/kubernetes/kubernetes/labels/release-note","name":"release-note","color":"c2e0c6","default":false},{"id":173493835,"node_id":"MDU6TGFiZWwxNzM0OTM4MzU=","url":"https://api.github.com/repos/kubernetes/kubernetes/labels/sig/api-machinery","name":"sig/api-machinery","color":"d2b48c","default":false},{"id":404091735,"node_id":"MDU6TGFiZWw0MDQwOTE3MzU=","url":"https://api.github.com/repos/kubernetes/kubernetes/labels/sig/apps","name":"sig/apps","color":"d2b48c","default":false},{"id":636152047,"node_id":"MDU6TGFiZWw2MzYxNTIwNDc=","url":"https://api.github.com/repos/kubernetes/kubernetes/labels/sig/architecture","name":"sig/architecture","color":"d2b48c","default":false},{"id":483069764,"node_id":"MDU6TGFiZWw0ODMwNjk3NjQ=","url":"https://api.github.com/repos/kubernetes/kubernetes/labels/sig/testing","name":"sig/testing","color":"d2b48c","default":false},{"id":253451093,"node_id":"MDU6TGFiZWwyNTM0NTEwOTM=","url":"https://api.github.com/repos/kubernetes/kubernetes/labels/size/XXL","name":"size/XXL","color":"ee0000","default":false}],"milestone":{"url":"https://api.github.com/repos/kubernetes/kubernetes/milestones/39","html_url":"https://github.com/kubernetes/kubernetes/milestone/39","labels_url":"https://api.github.com/repos/kubernetes/kubernetes/milestones/39/labels","id":2913712,"node_id":"MDk6TWlsZXN0b25lMjkxMzcxMg==","number":39,"title":"v1.12","description":"","creator":{"login":"thockin","id":5595220,"node_id":"MDQ6VXNlcjU1OTUyMjA=","avatar_url":"https://avatars0.githubusercontent.com/u/5595220?v=4","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},"open_issues":92,"closed_issues":64,"state":"open","created_at":"2017-11-15T06:32:51Z","updated_at":"2018-08-29T17:13:05Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/66840/commits","review_comments_url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/66840/comments","review_comment_url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/comments{/number}","comments_url":"https://api.github.com/repos/kubernetes/kubernetes/issues/66840/comments","statuses_url":"https://api.github.com/repos/kubernetes/kubernetes/statuses/2f0a34e89a30a7291748cb0903bd5b270831a585","head":{"label":"janetkuo:job-ttl","ref":"job-ttl","sha":"2f0a34e89a30a7291748cb0903bd5b270831a585","user":{"login":"janetkuo","id":4876867,"node_id":"MDQ6VXNlcjQ4NzY4Njc=","avatar_url":"https://avatars1.githubusercontent.com/u/4876867?v=4","gravatar_id":"","url":"https://api.github.com/users/janetkuo","html_url":"https://github.com/janetkuo","followers_url":"https://api.github.com/users/janetkuo/followers","following_url":"https://api.github.com/users/janetkuo/following{/other_user}","gists_url":"https://api.github.com/users/janetkuo/gists{/gist_id}","starred_url":"https://api.github.com/users/janetkuo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/janetkuo/subscriptions","organizations_url":"https://api.github.com/users/janetkuo/orgs","repos_url":"https://api.github.com/users/janetkuo/repos","events_url":"https://api.github.com/users/janetkuo/events{/privacy}","received_events_url":"https://api.github.com/users/janetkuo/received_events","type":"User","site_admin":false},"repo":{"id":37748937,"node_id":"MDEwOlJlcG9zaXRvcnkzNzc0ODkzNw==","name":"kubernetes","full_name":"janetkuo/kubernetes","owner":{"login":"janetkuo","id":4876867,"node_id":"MDQ6VXNlcjQ4NzY4Njc=","avatar_url":"https://avatars1.githubusercontent.com/u/4876867?v=4","gravatar_id":"","url":"https://api.github.com/users/janetkuo","html_url":"https://github.com/janetkuo","followers_url":"https://api.github.com/users/janetkuo/followers","following_url":"https://api.github.com/users/janetkuo/following{/other_user}","gists_url":"https://api.github.com/users/janetkuo/gists{/gist_id}","starred_url":"https://api.github.com/users/janetkuo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/janetkuo/subscriptions","organizations_url":"https://api.github.com/users/janetkuo/orgs","repos_url":"https://api.github.com/users/janetkuo/repos","events_url":"https://api.github.com/users/janetkuo/events{/privacy}","received_events_url":"https://api.github.com/users/janetkuo/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/janetkuo/kubernetes","description":"Container Cluster Manager from Google","fork":true,"url":"https://api.github.com/repos/janetkuo/kubernetes","forks_url":"https://api.github.com/repos/janetkuo/kubernetes/forks","keys_url":"https://api.github.com/repos/janetkuo/kubernetes/keys{/key_id}","collaborators_url":"https://api.github.com/repos/janetkuo/kubernetes/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/janetkuo/kubernetes/teams","hooks_url":"https://api.github.com/repos/janetkuo/kubernetes/hooks","issue_events_url":"https://api.github.com/repos/janetkuo/kubernetes/issues/events{/number}","events_url":"https://api.github.com/repos/janetkuo/kubernetes/events","assignees_url":"https://api.github.com/repos/janetkuo/kubernetes/assignees{/user}","branches_url":"https://api.github.com/repos/janetkuo/kubernetes/branches{/branch}","tags_url":"https://api.github.com/repos/janetkuo/kubernetes/tags","blobs_url":"https://api.github.com/repos/janetkuo/kubernetes/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/janetkuo/kubernetes/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/janetkuo/kubernetes/git/refs{/sha}","trees_url":"https://api.github.com/repos/janetkuo/kubernetes/git/trees{/sha}","statuses_url":"https://api.github.com/repos/janetkuo/kubernetes/statuses/{sha}","languages_url":"https://api.github.com/repos/janetkuo/kubernetes/languages","stargazers_url":"https://api.github.com/repos/janetkuo/kubernetes/stargazers","contributors_url":"https://api.github.com/repos/janetkuo/kubernetes/contributors","subscribers_url":"https://api.github.com/repos/janetkuo/kubernetes/subscribers","subscription_url":"https://api.github.com/repos/janetkuo/kubernetes/subscription","commits_url":"https://api.github.com/repos/janetkuo/kubernetes/commits{/sha}","git_commits_url":"https://api.github.com/repos/janetkuo/kubernetes/git/commits{/sha}","comments_url":"https://api.github.com/repos/janetkuo/kubernetes/comments{/number}","issue_comment_url":"https://api.github.com/repos/janetkuo/kubernetes/issues/comments{/number}","contents_url":"https://api.github.com/repos/janetkuo/kubernetes/contents/{+path}","compare_url":"https://api.github.com/repos/janetkuo/kubernetes/compare/{base}...{head}","merges_url":"https://api.github.com/repos/janetkuo/kubernetes/merges","archive_url":"https://api.github.com/repos/janetkuo/kubernetes/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/janetkuo/kubernetes/downloads","issues_url":"https://api.github.com/repos/janetkuo/kubernetes/issues{/number}","pulls_url":"https://api.github.com/repos/janetkuo/kubernetes/pulls{/number}","milestones_url":"https://api.github.com/repos/janetkuo/kubernetes/milestones{/number}","notifications_url":"https://api.github.com/repos/janetkuo/kubernetes/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/janetkuo/kubernetes/labels{/name}","releases_url":"https://api.github.com/repos/janetkuo/kubernetes/releases{/id}","deployments_url":"https://api.github.com/repos/janetkuo/kubernetes/deployments","created_at":"2015-06-19T22:29:18Z","updated_at":"2016-02-02T19:26:14Z","pushed_at":"2018-08-27T21:48:56Z","git_url":"git://github.com/janetkuo/kubernetes.git","ssh_url":"git@github.com:janetkuo/kubernetes.git","clone_url":"https://github.com/janetkuo/kubernetes.git","svn_url":"https://github.com/janetkuo/kubernetes","homepage":"http://kubernetes.io","size":576925,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"kubernetes:master","ref":"master","sha":"fdb5707194d56cbbd0da11c5be3a2a5653e714c9","user":{"login":"kubernetes","id":13629408,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNjI5NDA4","avatar_url":"https://avatars2.githubusercontent.com/u/13629408?v=4","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":20580498,"node_id":"MDEwOlJlcG9zaXRvcnkyMDU4MDQ5OA==","name":"kubernetes","full_name":"kubernetes/kubernetes","owner":{"login":"kubernetes","id":13629408,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNjI5NDA4","avatar_url":"https://avatars2.githubusercontent.com/u/13629408?v=4","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/kubernetes","description":"Production-Grade Container Scheduling and Management","fork":false,"url":"https://api.github.com/repos/kubernetes/kubernetes","forks_url":"https://api.github.com/repos/kubernetes/kubernetes/forks","keys_url":"https://api.github.com/repos/kubernetes/kubernetes/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kubernetes/kubernetes/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kubernetes/kubernetes/teams","hooks_url":"https://api.github.com/repos/kubernetes/kubernetes/hooks","issue_events_url":"https://api.github.com/repos/kubernetes/kubernetes/issues/events{/number}","events_url":"https://api.github.com/repos/kubernetes/kubernetes/events","assignees_url":"https://api.github.com/repos/kubernetes/kubernetes/assignees{/user}","branches_url":"https://api.github.com/repos/kubernetes/kubernetes/branches{/branch}","tags_url":"https://api.github.com/repos/kubernetes/kubernetes/tags","blobs_url":"https://api.github.com/repos/kubernetes/kubernetes/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kubernetes/kubernetes/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kubernetes/kubernetes/git/refs{/sha}","trees_url":"https://api.github.com/repos/kubernetes/kubernetes/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kubernetes/kubernetes/statuses/{sha}","languages_url":"https://api.github.com/repos/kubernetes/kubernetes/languages","stargazers_url":"https://api.github.com/repos/kubernetes/kubernetes/stargazers","contributors_url":"https://api.github.com/repos/kubernetes/kubernetes/contributors","subscribers_url":"https://api.github.com/repos/kubernetes/kubernetes/subscribers","subscription_url":"https://api.github.com/repos/kubernetes/kubernetes/subscription","commits_url":"https://api.github.com/repos/kubernetes/kubernetes/commits{/sha}","git_commits_url":"https://api.github.com/repos/kubernetes/kubernetes/git/commits{/sha}","comments_url":"https://api.github.com/repos/kubernetes/kubernetes/comments{/number}","issue_comment_url":"https://api.github.com/repos/kubernetes/kubernetes/issues/comments{/number}","contents_url":"https://api.github.com/repos/kubernetes/kubernetes/contents/{+path}","compare_url":"https://api.github.com/repos/kubernetes/kubernetes/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kubernetes/kubernetes/merges","archive_url":"https://api.github.com/repos/kubernetes/kubernetes/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kubernetes/kubernetes/downloads","issues_url":"https://api.github.com/repos/kubernetes/kubernetes/issues{/number}","pulls_url":"https://api.github.com/repos/kubernetes/kubernetes/pulls{/number}","milestones_url":"https://api.github.com/repos/kubernetes/kubernetes/milestones{/number}","notifications_url":"https://api.github.com/repos/kubernetes/kubernetes/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kubernetes/kubernetes/labels{/name}","releases_url":"https://api.github.com/repos/kubernetes/kubernetes/releases{/id}","deployments_url":"https://api.github.com/repos/kubernetes/kubernetes/deployments","created_at":"2014-06-06T22:56:04Z","updated_at":"2018-08-29T17:14:56Z","pushed_at":"2018-08-29T17:06:27Z","git_url":"git://github.com/kubernetes/kubernetes.git","ssh_url":"git@github.com:kubernetes/kubernetes.git","clone_url":"https://github.com/kubernetes/kubernetes.git","svn_url":"https://github.com/kubernetes/kubernetes","homepage":"http://kubernetes.io","size":804346,"stargazers_count":40723,"watchers_count":40723,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":14150,"mirror_url":null,"archived":false,"open_issues_count":3120,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":14150,"open_issues":3120,"watchers":40723,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/66840"},"html":{"href":"https://github.com/kubernetes/kubernetes/pull/66840"},"issue":{"href":"https://api.github.com/repos/kubernetes/kubernetes/issues/66840"},"comments":{"href":"https://api.github.com/repos/kubernetes/kubernetes/issues/66840/comments"},"review_comments":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/66840/comments"},"review_comment":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/66840/commits"},"statuses":{"href":"https://api.github.com/repos/kubernetes/kubernetes/statuses/2f0a34e89a30a7291748cb0903bd5b270831a585"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"tnozicka"}}