{"action":"created","comment":{"url":"https://api.github.com/repos/kubernetes/community/pulls/comments/208868541","pull_request_review_id":144765508,"id":208868541,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIwODg2ODU0MQ==","diff_hunk":"@@ -0,0 +1,486 @@\n+---\n+kep-number: 18\n+title: Defaulting and Pruning for Custom Resources\n+status: provisional\n+authors:\n+  - \"@sttts\"\n+owning-sig: sig-api-machinery\n+participating-sigs:\n+  - sig-api-machinery\n+  - sig-architecture\n+reviewers:\n+  - \"@deads2k\"\n+  - \"@lavalamp\"\n+  - \"@liggitt\"\n+  - \"@erictune\"\n+  - \"@mbohlool\"\n+  - \"@apelisse\"\n+approvers:\n+  - \"@deads2k\"\n+  - \"@lavalamp - substituted by @fedebongio while @lavalamp is out\"\n+editor:\n+  name: \"@sttts\"\n+creation-date: 2018-07-31\n+last-updated: 2018-07-31\n+---\n+\n+# Defaulting and Pruning for Custom Resources\n+    \n+## Table of Contents\n+* [Defaulting and Pruning for Custom Resources](#defaulting-and-pruning-for-custom-resources)\n+    * [Table of Contents](#table-of-contents)\n+    * [Overview](#overview)\n+    * [Goals](#goals)\n+    * [Non-Goals](#non-goals)\n+    * [Motivation](#motivation)\n+        * [Pruning](#pruning)\n+        * [Defaulting](#defaulting)\n+        * [Mixing of Schema and Value Validation](#mixing-schema-and-value-validation)\n+    * [Formal Proposal – following pruning option 1]()\n+        * [Types and Formats](#types-and-formats)\n+        * [Polymorphic Fields](#polymorphic-fields)\n+        * [Defaulting](#restrictions-on-defaults)\n+        * [Excluding values from Pruning](#excluding-values-from-pruning)\n+    * [Related topis](#related-topics)\n+        * [Ratcheting (slightly related)](#ratcheting-(slightly-related))\n+    * [References](#references)\n+    * [Alternatives Considered](#alternatives-considered)\n+\n+## Overview\n+\n+Native Golang based resources do not persist JSON fields which are not part of the Golang structs which are backing them in the API server memory. This artifact of the use of typed Golang structs inside of the REST implementation has turned into an API convention. Major parts of the Kubernetes depend on this to ensure consistency of data persisted to etcd and returned from the REST API. \n+\n+Without consistency of data in etcd, objects can suddenly render unaccessible on version upgrade because unexpected data may break decoding. Even if persisted data has correct format and decodes correctly, having it not gone through validation and admission when it was stored can break cluster-wide invariants. For example assume the `privileged: true/false` field is added to a type in Kubernetes version X+1. In version X, there is no security check around this. So every user could set that flag if we didn’t drop unknown fields. When the field is added in X+1, that user suddenly has escalated access (note: on read we do not run admission). This is a serious security risk.\n+\n+CustomResources are persisted as JSON blobs today (with the exception of `ObjectMeta` which is pruned since Kubernetes 1.11), i.e. we do not drop unknown fields, with the described consequences. This proposal is about adding a decoding step named \"pruning\" into the decoder from JSON to `unstructured.Unstructured` inside the apiextensions-apiserver. This pruning step will drop fields not specified in the OpenAPI validation spec, leading to the same persistence semantics as for native types.\n+\n+Algorithmically deeply related to validation and pruning is \"defaulting\", i.e. the application of the OpenAPI \"default\" values. Defaulting is done after decoding for native types, before conversion. This proposal suggests to add defaulting just after pruning to the CustomResource JSON decoder.\n+\n+## Goals\n+\n+* Prune unknown fields from CustomResources silently. Unknown means not to be specified in the OpenAPI validation spec.\n+* Allow to opt-out of pruning via the OpenAPI validation spec for a subtree of the JSON objects.\n+* Apply OpenAPI validation schema defaults to CustomResources.\n+* Have simple semantics for pruning and defaulting.\n+\n+## Non-Goals\n+* Add a strict mode to the REST API which rejects objects with unknown fields.\n+* Propose or decide anything about ratcheting. [We discuss the topic](#ratcheting-(slightly-related)) because it is related, but this needs a follow-up KEP.\n+\n+## Motivation\n+### Pruning\n+Pruning of a JSON value means to remove \"unknown fields\". A field (given as a JSON path) is unknown if it is not specified in the OpenAPI validation schema.\n+\n+A JSON path `<JSON path>.x` is specified in an OpenAPI validation schema if `\"x\" is a key in a corresponding `properties` field in the schema.\n+\n+#### Example 1\n+\n+>Assume the OpenAPI schema\n+>\n+>```json\n+>{\"properties\": {\"a\": {}, \"b\": {\"type\": \"string\"}, \"c\": {\"not\": {}}}}\n+>```\n+>\n+>Then a JSON object `{\"a\":1, \"c\": 3, \"d\": 4}` is pruned to `{\"a\":1, \"c\":3}`.\n+>\n+> Note that the pruned object does not validate.\n+\n+#### Example 2\n+\n+> Assume the OpenAPI schema\n+>\n+>```json\n+>{\"anyOf\": [{\"properties\": {\"a\": {}}}, {\"properties\": {\"b\": {}}}]}\n+>```\n+>\n+>Then a JSON object `{\"a\":1, \"b\": 2, \"c\": 3}` is pruned to `{\"a\":1, \"b\":2}` with the given semantics. Note that also `{\"a\":1}`, `{\"b\":2}` would be natural pruning results if we defined \"specified\" in a different way.\n+\n+#### Example 3\n+\n+> Assume the OpenAPI schema\n+>\n+>```json\n+>{\n+>   \"properties\": {\n+>       \"a\": {}, \n+>       \"b\": {\"type\": \"string\", \"properties\": {\"x\": {}}}\n+>   }, \n+>   \"anyOf\": [\n+>\t    { \"properties\": {\"c\":{}} }, \n+>       { \"properties\": {\"d\":{\"not\":{}}}}\n+>   ]\n+>}\n+>```\n+>\n+> Then `a`, `b`, `b.x`, `c`, `d` are specified, `b.y` and `e` are not specified.\n+\n+**Question:** is it natural semantics to assume `d` to be specified and not to prune it? Note that there is no object with `d` that validates against `\"d\":{not:{}}`. But due to the `anyOf` there are objects which will validate against the complete schema.\n+\n+#### Pruning Options\n+\n+Motivated by the examples, we have different options to define the pruning semantics:\n+1. Use the described semantics of `specified`.\n+2. Only consider `properties` fields in the schema which actually successfully validate a given object (then `d` would be pruned in example 3).\n+3. Only consider `properties` fields outside of `anyOf`, `allOf`, `oneOf`, `not` during pruning.\n+4. Only consider `properties` fields outside of `anyOf`, `allOf`, `oneOf`, `not` during pruning, but enforce that every `properties` key inside `anyOf`, `allOf`, `oneOf`, `not` also appears outside all of those.\n+\n+From these options:\n+1. leads to fields being kept from pruning although they only appear in branches of the OpenAPI validation schema which do not contribute to validation of an object.\n+2. is ambiguous if you have multiple branches of `anyOf` validating. Should we drop the fields of the first or the second branch?\n+3. leads to surprising pruning of fields that the user forgot to specify outside of propositional logic of `anyOf`, `allOf`, `oneOf`, `not`.\n+4. forces the user to re-specify all those properties outside of `anyOf`, `allOf`, `oneOf`, `not` which appear inside of them. The outcome will match that of 1, with the difference that it makes the skeleton explicit.\n+\n+**Here we propose not to follow 2 and 3 due to ambiguity of 2 and the danger of user mistakes of 3. Both 1 and 4 lead to the same pruning behaviour and only differ in whether the user has to re-specify property keys or whether they are automatically derived.**\n+\n+### Restrictions on Defaults\n+\n+The OpenAPI validation schema allows to define defaults via the `default` field. For example:\n+\n+```json\n+{\"properties\": {\"a\": {\"default\": {\"x\": 42}}}}\n+```\n+\n+An empty object `{}` would be defaulted to `{\"a\":{\"x\":42}}`\n+\n+\n+Obviously, the default values should validate. But OpenAPI validation schemata do not enforce that. Also one default in a branch of `anyOf`, `allOf`, `oneOf` might force another validation run onto another branch of these quantors.\n+\n+Even worse: forcing onto another branch might make more defaults applicable. In other words, we would need to run validation with defaulting until it converges. This is a hint that the semantics of defaulting in full OpenAPI schemata are not well defined either.\n+\n+**We propose to allow defaults only outside of `anyOf`, `allOf`, `oneOf`, `not`.**\n+\n+Without that, we would get ambiguous defaulting semantics:\n+\n+#### Example 4:\n+\n+> Assume \n+>```json\n+>{\n+>    \"anyOf\":[\n+>       {\"default\": 1},\n+>       {\"default\": 2}\n+>    ]\n+>}\n+>```\n+>\n+> Which branch should be chosen while applying the defaults.\n+\n+#### Example 5:\n+\n+> Assume\n+>\n+>```json\n+>{\n+>  \"anyOf\":[\n+>      {\"properties\": {\"a\": {\"pattern\": \"1\"}, \"b\": {\"type\": \"string\"}, \"d\": {\"default\": 1}}},\n+>      {\"properties\": {\"a\": {\"pattern\": \"2\"}, \"c\": {\"type\": \"string\"}, \"d\": {\"default\": 2}}}\n+>  ]\n+>}\n+>```\n+>\n+> Take an object `{\"a\": \"1\", \"b\":\"foo\", \"c\":\"bar\"}`. Assuming we follow pruning option 1, it is pruned to `{\"a\": \"1\", \"b\":\"foo\", \"c\":\"bar\"}` and to default to `{\"a\":\"1\", \"b\": \"foo\", \"c\":\"bar\", \"d\": 1}`.\n+>\n+> But what about the object `{\"a\": \"12\", \"b\":\"foo\", \"c\":\"bar\"}`? Both patterns apply. With pruning option 1 we keep `\"b\"` and `\"c\"`. But what about the default? Should we default to `1` or `2`?\n+\n+Clearly, we can make the algorithm deterministic by choosing one, e.g. the first matching branch. This choice is arbitrary, hard to understand, and gets even more confusing with even more nested `allOf`, `anyOf` or even `not`.\n+\n+We can later become less restrictive about the allowed positions of default values in the scheme. I.e. the restriction of only allowing default outside of `allOf`, `anyOf`, `oneOf` and `not` is a starting point with a way forward if necessary.\n+\n+### Mixing of Schema and Value Validation\n+\n+The OpenAPI validation schema mixes the actual structural schema validation (which is usually done by the Golang struct JSON decoding for native types) \n+and the value validation (usually done in the validation step of the API server handler pipeline for native types). \n+\n+For CRDs we cannot distinguish both. This was first noticed by @lavalamp in https://github.com/kubernetes/kubernetes/pull/64907#issuecomment-397015030.\n+\n+#### Example 6\n+\n+> Assume \n+>\n+>```json\n+>{\n+>   \"properties\": {\n+>       \"a\": {\"type\": \"string\", \"pattern\": \"<some-long-regex-for-ips>\", \"format\": \"ip\"},\n+>       \"b\": {\"properties\": {\"x\": {}, \"y\": {}}}\n+>   }\n+>}\n+>```\n+>\n+> The type and properties are usually called schema validation, while regex patterns and formats are about values. The latter would be tested in the validation phase, the former would be checked during decoding for native types in the JSON decoder.\n+\n+**Remark:** the line between type and format is blurry. Format is optional to be processed by tools, and the value is open ended. In Kube we have some formats (like `date`) which correspond to custom JSON unmarshallers in the native types. That format would be verified during decoding, although technically a date is just a string. So we might want to replicate that behaviour at some point.\n+\n+For pruning and defaulting we have to apply the OpenAPI validation schema inside of the decoder step (the left box inside apiextensions-apiserver in the figure above). This is considerably earlier than for native types. This has a number of implications:\n+* We have to apply full OpenAPI value validation (e.g. regular expressions, propositional evaluation) during decoding.\n+* Our generic registry applies validation after defaulting. We need the other way around for CRDs.\n+* Our generic registry expects defaulting to always succeed (no error result type). CRD validation needed by defaulting can fail.\n+\n+To avoid these and to get sane semantics, **we propose to split the CRD validation into two steps:**\n+1. During decoding we validate using a skeleton schema which lacks value validations and propositional logic (`anyOf`, `allOf`, `oneOf`, `not`).\n+2. During defaulting we validate using the skeleton schema and apply the resulting defaulter.\n+During standard generic registry validation phase we validate using the full validation schema.","path":"keps/sig-api-machinery/customresources-pruning-and-defaulting.md","position":221,"original_position":221,"commit_id":"221b840b54ee46cdc133b309c10b134becdc2c97","original_commit_id":"221b840b54ee46cdc133b309c10b134becdc2c97","user":{"login":"sttts","id":730123,"node_id":"MDQ6VXNlcjczMDEyMw==","avatar_url":"https://avatars0.githubusercontent.com/u/730123?v=4","gravatar_id":"","url":"https://api.github.com/users/sttts","html_url":"https://github.com/sttts","followers_url":"https://api.github.com/users/sttts/followers","following_url":"https://api.github.com/users/sttts/following{/other_user}","gists_url":"https://api.github.com/users/sttts/gists{/gist_id}","starred_url":"https://api.github.com/users/sttts/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sttts/subscriptions","organizations_url":"https://api.github.com/users/sttts/orgs","repos_url":"https://api.github.com/users/sttts/repos","events_url":"https://api.github.com/users/sttts/events{/privacy}","received_events_url":"https://api.github.com/users/sttts/received_events","type":"User","site_admin":false},"body":"added a note","created_at":"2018-08-09T09:45:54Z","updated_at":"2018-08-09T09:45:54Z","html_url":"https://github.com/kubernetes/community/pull/2443#discussion_r208868541","pull_request_url":"https://api.github.com/repos/kubernetes/community/pulls/2443","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/kubernetes/community/pulls/comments/208868541"},"html":{"href":"https://github.com/kubernetes/community/pull/2443#discussion_r208868541"},"pull_request":{"href":"https://api.github.com/repos/kubernetes/community/pulls/2443"}},"in_reply_to_id":208196151},"pull_request":{"url":"https://api.github.com/repos/kubernetes/community/pulls/2443","id":205201613,"node_id":"MDExOlB1bGxSZXF1ZXN0MjA1MjAxNjEz","html_url":"https://github.com/kubernetes/community/pull/2443","diff_url":"https://github.com/kubernetes/community/pull/2443.diff","patch_url":"https://github.com/kubernetes/community/pull/2443.patch","issue_url":"https://api.github.com/repos/kubernetes/community/issues/2443","number":2443,"state":"open","locked":false,"title":"KEP: Pruning and Defaulting for CustomResources","user":{"login":"sttts","id":730123,"node_id":"MDQ6VXNlcjczMDEyMw==","avatar_url":"https://avatars0.githubusercontent.com/u/730123?v=4","gravatar_id":"","url":"https://api.github.com/users/sttts","html_url":"https://github.com/sttts","followers_url":"https://api.github.com/users/sttts/followers","following_url":"https://api.github.com/users/sttts/following{/other_user}","gists_url":"https://api.github.com/users/sttts/gists{/gist_id}","starred_url":"https://api.github.com/users/sttts/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sttts/subscriptions","organizations_url":"https://api.github.com/users/sttts/orgs","repos_url":"https://api.github.com/users/sttts/repos","events_url":"https://api.github.com/users/sttts/events{/privacy}","received_events_url":"https://api.github.com/users/sttts/received_events","type":"User","site_admin":false},"body":null,"created_at":"2018-07-31T18:26:09Z","updated_at":"2018-08-09T09:45:54Z","closed_at":null,"merged_at":null,"merge_commit_sha":"54c465fbb29e262ee45f8dda0dad1ad68def3733","assignee":null,"assignees":[],"requested_reviewers":[{"login":"lavalamp","id":647318,"node_id":"MDQ6VXNlcjY0NzMxOA==","avatar_url":"https://avatars0.githubusercontent.com/u/647318?v=4","gravatar_id":"","url":"https://api.github.com/users/lavalamp","html_url":"https://github.com/lavalamp","followers_url":"https://api.github.com/users/lavalamp/followers","following_url":"https://api.github.com/users/lavalamp/following{/other_user}","gists_url":"https://api.github.com/users/lavalamp/gists{/gist_id}","starred_url":"https://api.github.com/users/lavalamp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lavalamp/subscriptions","organizations_url":"https://api.github.com/users/lavalamp/orgs","repos_url":"https://api.github.com/users/lavalamp/repos","events_url":"https://api.github.com/users/lavalamp/events{/privacy}","received_events_url":"https://api.github.com/users/lavalamp/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[{"id":486032367,"node_id":"MDU6TGFiZWw0ODYwMzIzNjc=","url":"https://api.github.com/repos/kubernetes/community/labels/cncf-cla:%20yes","name":"cncf-cla: yes","color":"bfe5bf","default":false},{"id":653942785,"node_id":"MDU6TGFiZWw2NTM5NDI3ODU=","url":"https://api.github.com/repos/kubernetes/community/labels/kind/design","name":"kind/design","color":"c7def8","default":false},{"id":653942821,"node_id":"MDU6TGFiZWw2NTM5NDI4MjE=","url":"https://api.github.com/repos/kubernetes/community/labels/needs-rebase","name":"needs-rebase","color":"BDBDBD","default":false},{"id":653942861,"node_id":"MDU6TGFiZWw2NTM5NDI4NjE=","url":"https://api.github.com/repos/kubernetes/community/labels/sig/api-machinery","name":"sig/api-machinery","color":"d2b48c","default":false},{"id":653942863,"node_id":"MDU6TGFiZWw2NTM5NDI4NjM=","url":"https://api.github.com/repos/kubernetes/community/labels/sig/architecture","name":"sig/architecture","color":"d2b48c","default":false},{"id":653942910,"node_id":"MDU6TGFiZWw2NTM5NDI5MTA=","url":"https://api.github.com/repos/kubernetes/community/labels/size/L","name":"size/L","color":"ee9900","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/kubernetes/community/pulls/2443/commits","review_comments_url":"https://api.github.com/repos/kubernetes/community/pulls/2443/comments","review_comment_url":"https://api.github.com/repos/kubernetes/community/pulls/comments{/number}","comments_url":"https://api.github.com/repos/kubernetes/community/issues/2443/comments","statuses_url":"https://api.github.com/repos/kubernetes/community/statuses/221b840b54ee46cdc133b309c10b134becdc2c97","head":{"label":"sttts:sttts-crd-defaulting-and-pruning","ref":"sttts-crd-defaulting-and-pruning","sha":"221b840b54ee46cdc133b309c10b134becdc2c97","user":{"login":"sttts","id":730123,"node_id":"MDQ6VXNlcjczMDEyMw==","avatar_url":"https://avatars0.githubusercontent.com/u/730123?v=4","gravatar_id":"","url":"https://api.github.com/users/sttts","html_url":"https://github.com/sttts","followers_url":"https://api.github.com/users/sttts/followers","following_url":"https://api.github.com/users/sttts/following{/other_user}","gists_url":"https://api.github.com/users/sttts/gists{/gist_id}","starred_url":"https://api.github.com/users/sttts/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sttts/subscriptions","organizations_url":"https://api.github.com/users/sttts/orgs","repos_url":"https://api.github.com/users/sttts/repos","events_url":"https://api.github.com/users/sttts/events{/privacy}","received_events_url":"https://api.github.com/users/sttts/received_events","type":"User","site_admin":false},"repo":{"id":91081440,"node_id":"MDEwOlJlcG9zaXRvcnk5MTA4MTQ0MA==","name":"community","full_name":"sttts/community","owner":{"login":"sttts","id":730123,"node_id":"MDQ6VXNlcjczMDEyMw==","avatar_url":"https://avatars0.githubusercontent.com/u/730123?v=4","gravatar_id":"","url":"https://api.github.com/users/sttts","html_url":"https://github.com/sttts","followers_url":"https://api.github.com/users/sttts/followers","following_url":"https://api.github.com/users/sttts/following{/other_user}","gists_url":"https://api.github.com/users/sttts/gists{/gist_id}","starred_url":"https://api.github.com/users/sttts/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sttts/subscriptions","organizations_url":"https://api.github.com/users/sttts/orgs","repos_url":"https://api.github.com/users/sttts/repos","events_url":"https://api.github.com/users/sttts/events{/privacy}","received_events_url":"https://api.github.com/users/sttts/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/sttts/community","description":"Kubernetes community content","fork":true,"url":"https://api.github.com/repos/sttts/community","forks_url":"https://api.github.com/repos/sttts/community/forks","keys_url":"https://api.github.com/repos/sttts/community/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sttts/community/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sttts/community/teams","hooks_url":"https://api.github.com/repos/sttts/community/hooks","issue_events_url":"https://api.github.com/repos/sttts/community/issues/events{/number}","events_url":"https://api.github.com/repos/sttts/community/events","assignees_url":"https://api.github.com/repos/sttts/community/assignees{/user}","branches_url":"https://api.github.com/repos/sttts/community/branches{/branch}","tags_url":"https://api.github.com/repos/sttts/community/tags","blobs_url":"https://api.github.com/repos/sttts/community/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sttts/community/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sttts/community/git/refs{/sha}","trees_url":"https://api.github.com/repos/sttts/community/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sttts/community/statuses/{sha}","languages_url":"https://api.github.com/repos/sttts/community/languages","stargazers_url":"https://api.github.com/repos/sttts/community/stargazers","contributors_url":"https://api.github.com/repos/sttts/community/contributors","subscribers_url":"https://api.github.com/repos/sttts/community/subscribers","subscription_url":"https://api.github.com/repos/sttts/community/subscription","commits_url":"https://api.github.com/repos/sttts/community/commits{/sha}","git_commits_url":"https://api.github.com/repos/sttts/community/git/commits{/sha}","comments_url":"https://api.github.com/repos/sttts/community/comments{/number}","issue_comment_url":"https://api.github.com/repos/sttts/community/issues/comments{/number}","contents_url":"https://api.github.com/repos/sttts/community/contents/{+path}","compare_url":"https://api.github.com/repos/sttts/community/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sttts/community/merges","archive_url":"https://api.github.com/repos/sttts/community/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sttts/community/downloads","issues_url":"https://api.github.com/repos/sttts/community/issues{/number}","pulls_url":"https://api.github.com/repos/sttts/community/pulls{/number}","milestones_url":"https://api.github.com/repos/sttts/community/milestones{/number}","notifications_url":"https://api.github.com/repos/sttts/community/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sttts/community/labels{/name}","releases_url":"https://api.github.com/repos/sttts/community/releases{/id}","deployments_url":"https://api.github.com/repos/sttts/community/deployments","created_at":"2017-05-12T10:43:45Z","updated_at":"2017-05-12T10:43:48Z","pushed_at":"2018-08-01T17:57:05Z","git_url":"git://github.com/sttts/community.git","ssh_url":"git@github.com:sttts/community.git","clone_url":"https://github.com/sttts/community.git","svn_url":"https://github.com/sttts/community","homepage":"","size":28459,"stargazers_count":0,"watchers_count":0,"language":"Makefile","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":"0a0780c887577ab8e0e3f86546b3c103ae23f804","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":57939112,"node_id":"MDEwOlJlcG9zaXRvcnk1NzkzOTExMg==","name":"community","full_name":"kubernetes/community","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/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":"2018-08-09T08:20:34Z","pushed_at":"2018-08-09T03:53:40Z","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":30300,"stargazers_count":2301,"watchers_count":2301,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1361,"mirror_url":null,"archived":false,"open_issues_count":229,"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":1361,"open_issues":229,"watchers":2301,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/kubernetes/community/pulls/2443"},"html":{"href":"https://github.com/kubernetes/community/pull/2443"},"issue":{"href":"https://api.github.com/repos/kubernetes/community/issues/2443"},"comments":{"href":"https://api.github.com/repos/kubernetes/community/issues/2443/comments"},"review_comments":{"href":"https://api.github.com/repos/kubernetes/community/pulls/2443/comments"},"review_comment":{"href":"https://api.github.com/repos/kubernetes/community/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/kubernetes/community/pulls/2443/commits"},"statuses":{"href":"https://api.github.com/repos/kubernetes/community/statuses/221b840b54ee46cdc133b309c10b134becdc2c97"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"sttts"}}