{"action":"created","comment":{"url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/comments/67186606","id":67186606,"diff_hunk":"@@ -49,201 +59,310 @@ type ObjectScheme interface {\n \truntime.ObjectTyper\n }\n \n-// ObjectReaction returns a ReactionFunc that takes a generic action string of the form\n-// <verb>-<resource> or <verb>-<subresource>-<resource> and attempts to return a runtime\n-// Object or error that matches the requested action. For instance, list-replicationControllers\n-// should attempt to return a list of replication controllers. This method delegates to the\n-// ObjectRetriever interface to satisfy retrieval of lists or retrieval of single items.\n-// TODO: add support for sub resources\n-func ObjectReaction(o ObjectRetriever, mapper meta.RESTMapper) ReactionFunc {\n+// ObjectReaction returns a ReactionFunc that applies core.Action to\n+// the given tracker.\n+func ObjectReaction(tracker ObjectTracker, mapper meta.RESTMapper) ReactionFunc {\n \treturn func(action Action) (bool, runtime.Object, error) {\n-\t\tresource := action.GetResource()\n-\t\tkind, err := mapper.KindFor(resource)\n+\t\tns := action.GetNamespace()\n+\t\tgvr := action.GetResource()\n+\n+\t\tgvk, err := mapper.KindFor(gvr)\n+\t\tif err != nil {\n+\t\t\treturn false, nil, fmt.Errorf(\"error getting kind for resource %q: %s\", gvr, err)\n+\t\t}\n+\n \t\t// This is a temporary fix. Because there is no internal resource, so\n \t\t// the caller has no way to express that it expects to get an internal\n \t\t// kind back. A more proper fix will be directly specify the Kind when\n \t\t// build the action.\n-\t\tkind.Version = resource.Version\n-\t\tif err != nil {\n-\t\t\treturn false, nil, fmt.Errorf(\"unrecognized action %s: %v\", action.GetResource(), err)\n+\t\tgvk.Version = gvr.Version\n+\t\tif len(gvk.Version) == 0 {\n+\t\t\tgvk.Version = runtime.APIVersionInternal\n \t\t}\n \n-\t\t// TODO: have mapper return a Kind for a subresource?\n-\t\tswitch castAction := action.(type) {\n-\t\tcase ListAction:\n-\t\t\tkind.Kind += \"List\"\n-\t\t\tresource, err := o.Kind(kind, \"\")\n-\t\t\treturn true, resource, err\n+\t\t// Here and below we need to switch on implementation types,\n+\t\t// not on interfaces, as some interfaces are identical\n+\t\t// (e.g. UpdateAction and CreateAction), so if we use them,\n+\t\t// updates and creates end up matching the same case branch.\n+\t\tswitch action := action.(type) {\n \n-\t\tcase GetAction:\n-\t\t\tresource, err := o.Kind(kind, castAction.GetName())\n-\t\t\treturn true, resource, err\n+\t\tcase ListActionImpl:\n+\t\t\tobj, err := tracker.List(gvk, ns)\n+\t\t\treturn true, obj, err\n \n-\t\tcase DeleteAction:\n-\t\t\tresource, err := o.Kind(kind, castAction.GetName())\n-\t\t\treturn true, resource, err\n+\t\tcase GetActionImpl:\n+\t\t\tobj, err := tracker.Get(gvk, ns, action.GetName())\n+\t\t\treturn true, obj, err\n \n-\t\tcase CreateAction:\n-\t\t\taccessor, err := meta.Accessor(castAction.GetObject())\n+\t\tcase CreateActionImpl:\n+\t\t\tobjMeta, err := meta.Accessor(action.GetObject())\n \t\t\tif err != nil {\n \t\t\t\treturn true, nil, err\n \t\t\t}\n-\t\t\tresource, err := o.Kind(kind, accessor.GetName())\n-\t\t\treturn true, resource, err\n+\t\t\tif action.GetSubresource() == \"\" {\n+\t\t\t\terr = tracker.Add(action.GetObject())\n+\t\t\t} else {\n+\t\t\t\t// TODO: Currently we're handling subresource creation as an update\n+\t\t\t\t// on the enclosing resource. This works for some subresources but\n+\t\t\t\t// might not be generic enough.\n+\t\t\t\terr = tracker.Update(action.GetObject())\n+\t\t\t}\n+\t\t\tif err != nil {\n+\t\t\t\treturn true, nil, err\n+\t\t\t}\n+\t\t\tobj, err := tracker.Get(gvk, ns, objMeta.GetName())\n+\t\t\treturn true, obj, err\n \n-\t\tcase UpdateAction:\n-\t\t\taccessor, err := meta.Accessor(castAction.GetObject())\n+\t\tcase UpdateActionImpl:\n+\t\t\tobjMeta, err := meta.Accessor(action.GetObject())\n \t\t\tif err != nil {\n \t\t\t\treturn true, nil, err\n \t\t\t}\n-\t\t\tresource, err := o.Kind(kind, accessor.GetName())\n-\t\t\treturn true, resource, err\n+\t\t\terr = tracker.Update(action.GetObject())\n+\t\t\tif err != nil {\n+\t\t\t\treturn true, nil, err\n+\t\t\t}\n+\t\t\tobj, err := tracker.Get(gvk, ns, objMeta.GetName())\n+\t\t\treturn true, obj, err\n+\n+\t\tcase DeleteActionImpl:\n+\t\t\terr := tracker.Delete(gvk, ns, action.GetName())\n+\t\t\tif err != nil {\n+\t\t\t\treturn true, nil, err\n+\t\t\t}\n+\t\t\treturn true, nil, nil\n \n \t\tdefault:\n \t\t\treturn false, nil, fmt.Errorf(\"no reaction implemented for %s\", action)\n \t\t}\n \t}\n }\n \n-// AddObjectsFromPath loads the JSON or YAML file containing Kubernetes API resources\n-// and adds them to the provided ObjectRetriever.\n-func AddObjectsFromPath(path string, o ObjectRetriever, decoder runtime.Decoder) error {\n-\tdata, err := ioutil.ReadFile(path)\n-\tif err != nil {\n-\t\treturn err\n-\t}\n-\tdata, err = yaml.ToJSON(data)\n-\tif err != nil {\n-\t\treturn err\n-\t}\n-\tobj, err := runtime.Decode(decoder, data)\n-\tif err != nil {\n-\t\treturn err\n-\t}\n-\tif err := o.Add(obj); err != nil {\n-\t\treturn err\n-\t}\n-\treturn nil\n-}\n-\n-type objects struct {\n-\ttypes   map[string][]runtime.Object\n-\tlast    map[string]int\n+type tracker struct {\n \tscheme  ObjectScheme\n \tdecoder runtime.Decoder\n+\tlock    sync.RWMutex\n+\tobjects map[unversioned.GroupVersionKind][]runtime.Object\n }\n \n-var _ ObjectRetriever = &objects{}\n-\n-// NewObjects implements the ObjectRetriever interface by introspecting the\n-// objects provided to Add() and returning them when the Kind method is invoked.\n-// If an api.List object is provided to Add(), each child item is added. If an\n-// object is added that is itself a list (PodList, ServiceList) then that is added\n-// to the \"PodList\" kind. If no PodList is added, the retriever will take any loaded\n-// Pods and return them in a list. If an api.Status is added, and the Details.Kind field\n-// is set, that status will be returned instead (as an error if Status != Success, or\n-// as a runtime.Object if Status == Success).  If multiple PodLists are provided, they\n-// will be returned in order by the Kind call, and the last PodList will be reused for\n-// subsequent calls.\n-func NewObjects(scheme ObjectScheme, decoder runtime.Decoder) ObjectRetriever {\n-\treturn objects{\n-\t\ttypes:   make(map[string][]runtime.Object),\n-\t\tlast:    make(map[string]int),\n+var _ ObjectTracker = &tracker{}\n+\n+// NewObjectTracker returns an ObjectTracker that can be used to keep track\n+// of objects for the fake clientset. Mostly useful for unit tests.\n+func NewObjectTracker(scheme ObjectScheme, decoder runtime.Decoder) ObjectTracker {\n+\treturn &tracker{\n \t\tscheme:  scheme,\n \t\tdecoder: decoder,\n+\t\tobjects: make(map[unversioned.GroupVersionKind][]runtime.Object),\n \t}\n }\n \n-func (o objects) Kind(kind unversioned.GroupVersionKind, name string) (runtime.Object, error) {\n-\tif len(kind.Version) == 0 {\n-\t\tkind.Version = runtime.APIVersionInternal\n+func (t *tracker) List(gvk unversioned.GroupVersionKind, ns string) (runtime.Object, error) {\n+\t// Heuristic for list kind: original kind + List suffix. Might\n+\t// not always be true but this tracker has a pretty limited\n+\t// understanding of the actual API model.\n+\tlistGVK := gvk\n+\tlistGVK.Kind = listGVK.Kind + \"List\"\n+\n+\tlist, err := t.scheme.New(listGVK)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tif !meta.IsListType(list) {\n+\t\treturn nil, fmt.Errorf(\"%q is not a list type\", listGVK.Kind)\n \t}\n-\tempty, err := o.scheme.New(kind)\n-\tnilValue := reflect.Zero(reflect.TypeOf(empty)).Interface().(runtime.Object)\n \n-\tarr, ok := o.types[kind.Kind]\n+\tt.lock.RLock()\n+\tdefer t.lock.RUnlock()\n+\n+\tobjs, ok := t.objects[gvk]\n \tif !ok {\n-\t\tif strings.HasSuffix(kind.Kind, \"List\") {\n-\t\t\titemKind := kind.Kind[:len(kind.Kind)-4]\n-\t\t\tarr, ok := o.types[itemKind]\n-\t\t\tif !ok {\n-\t\t\t\treturn empty, nil\n-\t\t\t}\n-\t\t\tout, err := o.scheme.New(kind)\n-\t\t\tif err != nil {\n-\t\t\t\treturn nilValue, err\n-\t\t\t}\n-\t\t\tif err := meta.SetList(out, arr); err != nil {\n-\t\t\t\treturn nilValue, err\n-\t\t\t}\n-\t\t\tif out, err = o.scheme.Copy(out); err != nil {\n-\t\t\t\treturn nilValue, err\n-\t\t\t}\n-\t\t\treturn out, nil\n-\t\t}\n-\t\treturn nilValue, errors.NewNotFound(unversioned.GroupResource{Group: kind.Group, Resource: kind.Kind}, name)\n+\t\treturn list, nil\n \t}\n \n-\tindex := o.last[kind.Kind]\n-\tif index >= len(arr) {\n-\t\tindex = len(arr) - 1\n+\tmatchingObjs, err := filterByNamespaceAndName(objs, ns, \"\")\n+\tif err != nil {\n+\t\treturn nil, err\n \t}\n-\tif index < 0 {\n-\t\treturn nilValue, errors.NewNotFound(unversioned.GroupResource{Group: kind.Group, Resource: kind.Kind}, name)\n+\tif len(matchingObjs) == 0 {\n+\t\treturn list, nil\n \t}\n-\tout, err := o.scheme.Copy(arr[index])\n+\tif err := meta.SetList(list, matchingObjs); err != nil {\n+\t\treturn nil, err\n+\t}\n+\tif list, err = t.scheme.Copy(list); err != nil {\n+\t\treturn nil, err\n+\t}\n+\treturn list, nil\n+}\n+\n+func (t *tracker) Get(gvk unversioned.GroupVersionKind, ns, name string) (runtime.Object, error) {\n+\tobj, err := t.scheme.New(gvk)\n \tif err != nil {\n-\t\treturn nilValue, err\n+\t\treturn nil, err\n \t}\n-\to.last[kind.Kind] = index + 1\n+\terrNotFound := errors.NewNotFound(unversioned.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, name)\n+\n+\tt.lock.RLock()\n+\tdefer t.lock.RUnlock()\n \n-\tif status, ok := out.(*unversioned.Status); ok {\n+\tobjs, ok := t.objects[gvk]\n+\tif !ok {\n+\t\treturn nil, errNotFound\n+\t}\n+\n+\tmatchingObjs, err := filterByNamespaceAndName(objs, ns, name)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\tif len(matchingObjs) == 0 {\n+\t\treturn nil, errNotFound\n+\t}\n+\n+\t// Only one object should match in the tracker if it works\n+\t// correctly, as Add/Update methods enforce kind/namespace/name\n+\t// uniqueness.\n+\tobj, err = t.scheme.Copy(matchingObjs[0])\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tif status, ok := obj.(*unversioned.Status); ok {\n \t\tif status.Details != nil {\n-\t\t\tstatus.Details.Kind = kind.Kind\n+\t\t\tstatus.Details.Kind = gvk.Kind\n \t\t}\n \t\tif status.Status != unversioned.StatusSuccess {\n-\t\t\treturn nilValue, &errors.StatusError{ErrStatus: *status}\n+\t\t\treturn nil, &errors.StatusError{ErrStatus: *status}\n \t\t}\n \t}\n \n-\treturn out, nil\n+\treturn obj, nil\n+}\n+\n+func (t *tracker) Add(obj runtime.Object) error {\n+\treturn t.add(obj, false)\n }\n \n-func (o objects) Add(obj runtime.Object) error {\n-\tgvks, _, err := o.scheme.ObjectKinds(obj)\n+func (t *tracker) Update(obj runtime.Object) error {\n+\treturn t.add(obj, true)\n+}\n+\n+func (t *tracker) add(obj runtime.Object, replaceExisting bool) error {\n+\tif meta.IsListType(obj) {\n+\t\treturn t.addList(obj, replaceExisting)\n+\t}\n+\n+\tgvks, _, err := t.scheme.ObjectKinds(obj)\n \tif err != nil {\n \t\treturn err\n \t}\n-\tkind := gvks[0].Kind\n+\tif len(gvks) == 0 {\n+\t\treturn fmt.Errorf(\"no registered kinds for %v\", obj)\n+\t}\n+\tgvk := gvks[0]\n+\tgr := unversioned.GroupResource{Group: gvk.Group, Resource: gvk.Kind}\n \n-\tswitch {\n-\tcase meta.IsListType(obj):\n-\t\tif kind != \"List\" {\n-\t\t\to.types[kind] = append(o.types[kind], obj)\n-\t\t}\n+\t// To avoid the object from being accidentally modified by caller\n+\t// after it's been added to the tracker, we always store the deep\n+\t// copy.\n+\tobj, err = t.scheme.Copy(obj)\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\n+\tif status, ok := obj.(*unversioned.Status); ok && status.Details != nil {\n+\t\tgvk.Kind = status.Details.Kind\n+\t}\n+\n+\tnewMeta, err := meta.Accessor(obj)\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\n+\tt.lock.Lock()\n+\tdefer t.lock.Unlock()\n \n-\t\tlist, err := meta.ExtractList(obj)\n+\tfor i, existingObj := range t.objects[gvk] {\n+\t\toldMeta, err := meta.Accessor(existingObj)\n \t\tif err != nil {\n \t\t\treturn err\n \t\t}\n-\t\tif errs := runtime.DecodeList(list, o.decoder); len(errs) > 0 {\n-\t\t\treturn errs[0]\n-\t\t}\n-\t\tfor _, obj := range list {\n-\t\t\tif err := o.Add(obj); err != nil {\n-\t\t\t\treturn err\n+\t\tif oldMeta.GetNamespace() == newMeta.GetNamespace() && oldMeta.GetName() == newMeta.GetName() {\n+\t\t\tif replaceExisting {\n+\t\t\t\tt.objects[gvk][i] = obj\n+\t\t\t\treturn nil\n \t\t\t}\n+\t\t\treturn errors.NewAlreadyExists(gr, newMeta.GetName())\n+\t\t}\n+\t}\n+\n+\tif replaceExisting {\n+\t\t// Tried to update but no matching object was found.\n+\t\treturn errors.NewNotFound(gr, newMeta.GetName())\n+\t}\n+\n+\tt.objects[gvk] = append(t.objects[gvk], obj)\n+\n+\treturn nil\n+}\n+\n+func (t *tracker) addList(obj runtime.Object, replaceExisting bool) error {\n+\tlist, err := meta.ExtractList(obj)\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\terrs := runtime.DecodeList(list, t.decoder)\n+\tif len(errs) > 0 {\n+\t\treturn errs[0]\n+\t}\n+\tfor _, obj := range list {\n+\t\terr := t.add(obj, replaceExisting)\n+\t\tif err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t}\n+\treturn nil\n+}\n+\n+func (t *tracker) Delete(gvk unversioned.GroupVersionKind, ns, name string) error {\n+\tt.lock.Lock()\n+\tdefer t.lock.Unlock()\n+\n+\tfor i, existingObj := range t.objects[gvk] {\n+\t\tobjMeta, err := meta.Accessor(existingObj)\n+\t\tif err != nil {\n+\t\t\treturn err\n \t\t}\n-\tdefault:\n-\t\tif status, ok := obj.(*unversioned.Status); ok && status.Details != nil {\n-\t\t\tkind = status.Details.Kind\n+\t\tif objMeta.GetNamespace() == ns && objMeta.GetName() == name {\n+\t\t\tt.objects[gvk] = append(t.objects[gvk][:i], t.objects[gvk][i+1:]...)\n+\t\t\treturn nil\n \t\t}\n-\t\to.types[kind] = append(o.types[kind], obj)\n \t}\n \n \treturn nil\n }\n \n+func filterByNamespaceAndName(objs []runtime.Object, ns, name string) ([]runtime.Object, error) {","path":"pkg/client/testing/core/fixture.go","position":468,"original_position":468,"commit_id":"df548a0fcc5fed9e5ece89f15b408f36f8609ba5","original_commit_id":"df548a0fcc5fed9e5ece89f15b408f36f8609ba5","user":{"login":"deads2k","id":8225098,"avatar_url":"https://avatars.githubusercontent.com/u/8225098?v=3","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},"body":"doc the meaning of empty here.  Empty namespace is one of the worst constructs we have.  Sometimes it is none, sometimes it is all, sometimes it is any, and sometimes it is \"default\".","created_at":"2016-06-15T15:38:50Z","updated_at":"2016-06-15T15:38:50Z","html_url":"https://github.com/kubernetes/kubernetes/pull/26680#discussion_r67186606","pull_request_url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/26680","_links":{"self":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/comments/67186606"},"html":{"href":"https://github.com/kubernetes/kubernetes/pull/26680#discussion_r67186606"},"pull_request":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/26680"}}},"pull_request":{"url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/26680","id":72288377,"html_url":"https://github.com/kubernetes/kubernetes/pull/26680","diff_url":"https://github.com/kubernetes/kubernetes/pull/26680.diff","patch_url":"https://github.com/kubernetes/kubernetes/pull/26680.patch","issue_url":"https://api.github.com/repos/kubernetes/kubernetes/issues/26680","number":26680,"state":"open","locked":false,"title":"Track object modifications in fake clientset","user":{"login":"olegshaldybin","id":8273,"avatar_url":"https://avatars.githubusercontent.com/u/8273?v=3","gravatar_id":"","url":"https://api.github.com/users/olegshaldybin","html_url":"https://github.com/olegshaldybin","followers_url":"https://api.github.com/users/olegshaldybin/followers","following_url":"https://api.github.com/users/olegshaldybin/following{/other_user}","gists_url":"https://api.github.com/users/olegshaldybin/gists{/gist_id}","starred_url":"https://api.github.com/users/olegshaldybin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/olegshaldybin/subscriptions","organizations_url":"https://api.github.com/users/olegshaldybin/orgs","repos_url":"https://api.github.com/users/olegshaldybin/repos","events_url":"https://api.github.com/users/olegshaldybin/events{/privacy}","received_events_url":"https://api.github.com/users/olegshaldybin/received_events","type":"User","site_admin":false},"body":"Fake clientset is used by unit tests extensively but it has some\r\nshortcomings:\r\n\r\n- no filtering on namespace and name: tests that want to test objects in\r\n  multiple namespaces end up getting all objects from this clientset,\r\n  as it doesn't perform any filtering based on name and namespace;\r\n\r\n- updates and deletes don't modify the clientset state, so some tests\r\n  can get unexpected results if they modify/delete objects using the\r\n  clientset;\r\n\r\n- it's possible to insert multiple objects with the same\r\n  kind/name/namespace, this leads to confusing behavior, as retrieval is\r\n  based on the insertion order, but anchors on the last added object as\r\n  long as no more objects are added.\r\n\r\nThis change changes core.ObjectRetriever implementation to track object\r\nadds, updates and deletes.\r\n\r\nSome unit tests were depending on the previous (and somewhat incorrect)\r\nbehavior. These are fixed in the following few commits.","created_at":"2016-06-02T04:39:11Z","updated_at":"2016-06-15T15:38:50Z","closed_at":null,"merged_at":null,"merge_commit_sha":"76f92b8473b887cfbe62aac2ff73093599463816","assignee":{"login":"deads2k","id":8225098,"avatar_url":"https://avatars.githubusercontent.com/u/8225098?v=3","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},"milestone":null,"commits_url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/26680/commits","review_comments_url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/26680/comments","review_comment_url":"https://api.github.com/repos/kubernetes/kubernetes/pulls/comments{/number}","comments_url":"https://api.github.com/repos/kubernetes/kubernetes/issues/26680/comments","statuses_url":"https://api.github.com/repos/kubernetes/kubernetes/statuses/df548a0fcc5fed9e5ece89f15b408f36f8609ba5","head":{"label":"olegshaldybin:fake-clientset-registry","ref":"fake-clientset-registry","sha":"df548a0fcc5fed9e5ece89f15b408f36f8609ba5","user":{"login":"olegshaldybin","id":8273,"avatar_url":"https://avatars.githubusercontent.com/u/8273?v=3","gravatar_id":"","url":"https://api.github.com/users/olegshaldybin","html_url":"https://github.com/olegshaldybin","followers_url":"https://api.github.com/users/olegshaldybin/followers","following_url":"https://api.github.com/users/olegshaldybin/following{/other_user}","gists_url":"https://api.github.com/users/olegshaldybin/gists{/gist_id}","starred_url":"https://api.github.com/users/olegshaldybin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/olegshaldybin/subscriptions","organizations_url":"https://api.github.com/users/olegshaldybin/orgs","repos_url":"https://api.github.com/users/olegshaldybin/repos","events_url":"https://api.github.com/users/olegshaldybin/events{/privacy}","received_events_url":"https://api.github.com/users/olegshaldybin/received_events","type":"User","site_admin":false},"repo":{"id":49236803,"name":"kubernetes","full_name":"olegshaldybin/kubernetes","owner":{"login":"olegshaldybin","id":8273,"avatar_url":"https://avatars.githubusercontent.com/u/8273?v=3","gravatar_id":"","url":"https://api.github.com/users/olegshaldybin","html_url":"https://github.com/olegshaldybin","followers_url":"https://api.github.com/users/olegshaldybin/followers","following_url":"https://api.github.com/users/olegshaldybin/following{/other_user}","gists_url":"https://api.github.com/users/olegshaldybin/gists{/gist_id}","starred_url":"https://api.github.com/users/olegshaldybin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/olegshaldybin/subscriptions","organizations_url":"https://api.github.com/users/olegshaldybin/orgs","repos_url":"https://api.github.com/users/olegshaldybin/repos","events_url":"https://api.github.com/users/olegshaldybin/events{/privacy}","received_events_url":"https://api.github.com/users/olegshaldybin/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/olegshaldybin/kubernetes","description":"Container Cluster Manager from Google","fork":true,"url":"https://api.github.com/repos/olegshaldybin/kubernetes","forks_url":"https://api.github.com/repos/olegshaldybin/kubernetes/forks","keys_url":"https://api.github.com/repos/olegshaldybin/kubernetes/keys{/key_id}","collaborators_url":"https://api.github.com/repos/olegshaldybin/kubernetes/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/olegshaldybin/kubernetes/teams","hooks_url":"https://api.github.com/repos/olegshaldybin/kubernetes/hooks","issue_events_url":"https://api.github.com/repos/olegshaldybin/kubernetes/issues/events{/number}","events_url":"https://api.github.com/repos/olegshaldybin/kubernetes/events","assignees_url":"https://api.github.com/repos/olegshaldybin/kubernetes/assignees{/user}","branches_url":"https://api.github.com/repos/olegshaldybin/kubernetes/branches{/branch}","tags_url":"https://api.github.com/repos/olegshaldybin/kubernetes/tags","blobs_url":"https://api.github.com/repos/olegshaldybin/kubernetes/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/olegshaldybin/kubernetes/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/olegshaldybin/kubernetes/git/refs{/sha}","trees_url":"https://api.github.com/repos/olegshaldybin/kubernetes/git/trees{/sha}","statuses_url":"https://api.github.com/repos/olegshaldybin/kubernetes/statuses/{sha}","languages_url":"https://api.github.com/repos/olegshaldybin/kubernetes/languages","stargazers_url":"https://api.github.com/repos/olegshaldybin/kubernetes/stargazers","contributors_url":"https://api.github.com/repos/olegshaldybin/kubernetes/contributors","subscribers_url":"https://api.github.com/repos/olegshaldybin/kubernetes/subscribers","subscription_url":"https://api.github.com/repos/olegshaldybin/kubernetes/subscription","commits_url":"https://api.github.com/repos/olegshaldybin/kubernetes/commits{/sha}","git_commits_url":"https://api.github.com/repos/olegshaldybin/kubernetes/git/commits{/sha}","comments_url":"https://api.github.com/repos/olegshaldybin/kubernetes/comments{/number}","issue_comment_url":"https://api.github.com/repos/olegshaldybin/kubernetes/issues/comments{/number}","contents_url":"https://api.github.com/repos/olegshaldybin/kubernetes/contents/{+path}","compare_url":"https://api.github.com/repos/olegshaldybin/kubernetes/compare/{base}...{head}","merges_url":"https://api.github.com/repos/olegshaldybin/kubernetes/merges","archive_url":"https://api.github.com/repos/olegshaldybin/kubernetes/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/olegshaldybin/kubernetes/downloads","issues_url":"https://api.github.com/repos/olegshaldybin/kubernetes/issues{/number}","pulls_url":"https://api.github.com/repos/olegshaldybin/kubernetes/pulls{/number}","milestones_url":"https://api.github.com/repos/olegshaldybin/kubernetes/milestones{/number}","notifications_url":"https://api.github.com/repos/olegshaldybin/kubernetes/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/olegshaldybin/kubernetes/labels{/name}","releases_url":"https://api.github.com/repos/olegshaldybin/kubernetes/releases{/id}","deployments_url":"https://api.github.com/repos/olegshaldybin/kubernetes/deployments","created_at":"2016-01-07T23:20:56Z","updated_at":"2016-01-14T00:47:29Z","pushed_at":"2016-06-14T04:11:48Z","git_url":"git://github.com/olegshaldybin/kubernetes.git","ssh_url":"git@github.com:olegshaldybin/kubernetes.git","clone_url":"https://github.com/olegshaldybin/kubernetes.git","svn_url":"https://github.com/olegshaldybin/kubernetes","homepage":"http://kubernetes.io","size":267589,"stargazers_count":0,"watchers_count":0,"language":"Go","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":"ede661b4af20453b6ef818d2a22b3af28500d207","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":20580498,"name":"kubernetes","full_name":"kubernetes/kubernetes","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/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":"2016-06-15T14:35:17Z","pushed_at":"2016-06-15T15:34:46Z","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":478521,"stargazers_count":14914,"watchers_count":14914,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":4594,"mirror_url":null,"open_issues_count":3770,"forks":4594,"open_issues":3770,"watchers":14914,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/26680"},"html":{"href":"https://github.com/kubernetes/kubernetes/pull/26680"},"issue":{"href":"https://api.github.com/repos/kubernetes/kubernetes/issues/26680"},"comments":{"href":"https://api.github.com/repos/kubernetes/kubernetes/issues/26680/comments"},"review_comments":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/26680/comments"},"review_comment":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/kubernetes/kubernetes/pulls/26680/commits"},"statuses":{"href":"https://api.github.com/repos/kubernetes/kubernetes/statuses/df548a0fcc5fed9e5ece89f15b408f36f8609ba5"}}}}
{"actor":{"display_login":"deads2k"}}