{"action":"created","comment":{"url":"https://api.github.com/repos/nikhita/community/pulls/comments/121430471","pull_request_review_id":43464563,"id":121430471,"diff_hunk":"@@ -0,0 +1,464 @@\n+# Validation for CustomResources\n+\n+Authors: @nikhita, @sttts, some ideas integrated from @xiao.zhou’s proposal<sup id=\"f1\">[1](#footnote1)</sup>\n+\n+\n+## Table of Contents\n+\n+1. [Overview](#overview)\n+2. [Background](#background)\n+    1. [Goals](#goals)\n+    2. [Non-Goals](#non-goals)\n+3. [Proposed Extension of CustomResourceDefinition](#extension)\n+    1. [API Types](#api-types)\n+    2. [JSON-Schema Examples](#examples)\n+4. [Validation Behavior](#validation-behavior)\n+    1. [Metadata](#metadata)\n+    2. [Server-Side Validation](#server-side)\n+    3. [Client-Side Validation](#client-side)\n+    4. [Comparison between server-side and client-side Validation](#comparison)\n+    5. [Existing Instances and changing the Schema](#schema-change)\n+    6. [Outlook to Status Sub-Resources](#sub-resources)\n+    7. [Outlook Admission Webhook](#webhooks)\n+5. [Implementation Plan](#implementation)\n+6. [Appendix](#appendix)\n+    1. [Expressiveness of JSON-Schema](#expressiveness)\n+    2. [JSON-Schema Validation Runtime Complexity](#complexity)\n+    3. [Alternatives](#alternatives)\n+        1. [Direct Embedding of the Schema into the Spec](#direct-embed)\n+        2. [External CustomResourceSchema Type](#external-type)\n+\n+## <a name=\"overview\"></a>Overview\n+\n+This document proposes the design and describes a way to add JSON-Schema based validation for Custom Resources.\n+\n+## <a name=\"background\"></a>Background\n+\n+ThirdPartyResource (TPR) is deprecated and CustomResourceDefinition (CRD) is the successor which solves the fundamental issues of TPRs to form a stable base for further features.\n+Currently we do not provide validation for CustomResources (CR), i.e. the CR payload is free-form JSON. However, one of the most requested [[1](https://github.com/kubernetes/features/issues/95#issuecomment-296416969)][[2](https://github.com/kubernetes/features/issues/95#issuecomment-298791881)] features is validation and this proposal seeks to add it.\n+\n+## <a name=\"goals\"></a>Goals\n+\n+1. To provide validation for CustomResources using a declarative specification language for JSON data.\n+2. To keep open the door to add other validation mechanisms later.<sup id=\"f2\">[2](#footnote2)</sup>\n+\n+3. To allow server-side validation.\n+4. To integrate into the existing client-side validation of kubectl.\n+\n+## <a name=\"non-goals\"></a>Non-Goals\n+\n+1. The JSON-Schema specs can be used for creating OpenAPI documentation for CRs. The format is compatible but we won’t propose an implementation for that.\n+2. A turing-complete specification language is not proposed. Instead a declarative way is proposed to express 90% of validations.\n+3. For now, CRD only allows 1 version at a time. Supporting multiple versions of CRD and/or conversion of CRD is not within the scope of this proposal.\n+\n+## <a name=\"extension\"></a>Proposed Extension of CustomResourceDefinition\n+\n+We propose to add a field “validation” to the spec of a CustomResourceDefinition. As a first validation format we propose to use [JSON-Schema](http://json-schema.org/) under `CRD.Spec.Validation.JSONSchema`. JSON-Schema is a standardized declarative specification language. Different keywords may be utilized to put constraints on the data. Thus it provides ways to make assertions about what a valid document must look like. It is already used in Swagger/OpenAPI specs in Kubernetes and hence such a CRD specification integrates cleanly into the existing infrastructure of the API server which serves these specifications,\n+* into kubectl which is able to verify YAML and JSON objects against the returned specification.\n+* With the https://github.com/go-openapi/validate library, we have a powerful JSON-Schema validator which can be used client and server-side.\n+\n+## <a name=\"api-types\"></a>API Types\n+\n+The schema is referenced in [`CustomResourceDefinitionSpec`](https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go#L22). `Validation` is of the type `CustomResourceValidation`. `JSONSchema` is described in `CustomResourceValidation` to make the validation generic. This way we can add other validations in the future as well.\n+\n+The schema types are derived from OpenAPI types. Reference http://json-schema.org is also used by OpenAPI. We propose this as there are implementations available in Go and with OpenAPI, we will also be able to serve OpenAPI specs for CustomResourceDefintions.\n+\n+```go\n+// CustomResourceSpec describes how a user wants their resource to appear\n+type CustomResourceDefinitionSpec struct {\n+    Group string `json:\"group\" protobuf:\"bytes,1,opt,name=group\"`\n+    Version string `json:\"version\" protobuf:\"bytes,2,opt,name=version\"`\n+    Names CustomResourceDefinitionNames `json:\"names\" protobuf:\"bytes,3,opt,name=names\"`\n+    Scope ResourceScope `json:\"scope\" protobuf:\"bytes,8,opt,name=scope,casttype=ResourceScope\"`\n+    // Validation describes the validation methods for CustomResources\n+    Validation CustomResourceValidation `json:\"validation,omitempty\"`\n+}\n+\n+// CustomResourceValidation is a list of validation methods for CustomResources\n+type CustomResourceValidation struct {\n+    // JSONSchema is the JSON Schema to be validated against.\n+    // Can add other validation methods later if needed.\n+    JSONSchema *JSONSchemaProps\n+}\n+\n+// JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).\n+type JSONSchemaProps struct {\n+\tID                   string                     `json:\"id,omitempty\"`\n+\tSchema               SchemaURL                  `json:\"$schema,omitempty\"`\n+\tRef                  JSONSchemaRef              `json:\"$ref,omitempty\"`\n+\tDescription          string                     `json:\"description,omitempty\"`\n+\tType                 StringOrArray              `json:\"type,omitempty\"`\n+\tFormat               string                     `json:\"format,omitempty\"`\n+\tTitle                string                     `json:\"title,omitempty\"`\n+\tDefault              interface{}                `json:\"default,omitempty\"`\n+\tMaximum              *float64                   `json:\"maximum,omitempty\"`\n+\tExclusiveMaximum     bool                       `json:\"exclusiveMaximum,omitempty\"`\n+\tMinimum              *float64                   `json:\"minimum,omitempty\"`\n+\tExclusiveMinimum     bool                       `json:\"exclusiveMinimum,omitempty\"`\n+\tMaxLength            *int64                     `json:\"maxLength,omitempty\"`\n+\tMinLength            *int64                     `json:\"minLength,omitempty\"`\n+\tPattern              string                     `json:\"pattern,omitempty\"`\n+\tMaxItems             *int64                     `json:\"maxItems,omitempty\"`\n+\tMinItems             *int64                     `json:\"minItems,omitempty\"`\n+\t// disable uniqueItems for now because it can cause the validation runtime\n+\t// complexity to become quadratic.\n+\t// UniqueItems       bool                       `json:\"uniqueItems,omitempty\"`\n+\tMultipleOf           *float64                   `json:\"multipleOf,omitempty\"`\n+\tEnum                 []interface{}              `json:\"enum,omitempty\"`\n+\tMaxProperties        *int64                     `json:\"maxProperties,omitempty\"`\n+\tMinProperties        *int64                     `json:\"minProperties,omitempty\"`\n+\tRequired             []string                   `json:\"required,omitempty\"`\n+\tItems                *JSONSchemaPropsOrArray    `json:\"items,omitempty\"`\n+\tAllOf                []JSONSchemaProps          `json:\"allOf,omitempty\"`\n+\tOneOf                []JSONSchemaProps          `json:\"oneOf,omitempty\"`\n+\tAnyOf                []JSONSchemaProps          `json:\"anyOf,omitempty\"`\n+\tNot                  *JSONSchemaProps           `json:\"not,omitempty\"`\n+\tProperties           map[string]JSONSchemaProps `json:\"properties,omitempty\"`\n+\tAdditionalProperties *JSONSchemaPropsOrBool     `json:\"additionalProperties,omitempty\"`\n+\tPatternProperties    map[string]JSONSchemaProps `json:\"patternProperties,omitempty\"`\n+\tDependencies         Dependencies               `json:\"dependencies,omitempty\"`\n+\tAdditionalItems      *JSONSchemaPropsOrBool     `json:\"additionalItems,omitempty\"`\n+\tDefinitions          Definitions                `json:\"definitions,omitempty\"`\n+}\n+\n+// JSONSchemaRef represents a JSON reference that is potentially resolved.\n+type JSONSchemaRef struct {\n+\treferencePointer Pointer\n+\tHasFullURL       bool\n+\tHasURLPathOnly   bool\n+\tHasFragmentOnly  bool\n+\tHasFileScheme    bool\n+\tHasFullFilePath  bool\n+}\n+\n+// Pointer is the JSON pointer representation.\n+type Pointer struct {\n+\treferenceTokens []string\n+}\n+\n+// SchemaURL represents a schema url. Defaults to JSON Schema Specification Draft 4.\n+type SchemaURL string\n+\n+const (\n+\t// JSONSchemaDraft4URL is the url for JSON Schema Specification Draft 4.\n+\tJSONSchemaDraft4URL SchemaURL = \"http://json-schema.org/draft-04/schema#\"\n+)\n+\n+// StringOrArray represents a value that can either be a string or an array of strings.\n+// Mainly here for serialization purposes.\n+type StringOrArray []string\n+\n+// JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps\n+// or an array of JSONSchemaProps. Mainly here for serialization purposes.\n+type JSONSchemaPropsOrArray struct {\n+\tSchema      *JSONSchemaProps\n+\tJSONSchemas []JSONSchemaProps\n+}\n+\n+// JSONSchemaPropsOrBool represents JSONSchemaProps or a boolean value.\n+// Defaults to true for the boolean property.\n+type JSONSchemaPropsOrBool struct {\n+\tAllows bool\n+\tSchema *JSONSchemaProps\n+}\n+\n+// Dependencies represent a dependencies property.\n+type Dependencies map[string]JSONSchemaPropsOrStringArray\n+\n+// JSONSchemaPropsOrStringArray represents a JSONSchemaProps or a string array.\n+type JSONSchemaPropsOrStringArray struct {\n+\tSchema   *JSONSchemaProps\n+\tProperty []string\n+}\n+\n+// Definitions contains the models explicitly defined in this spec.\n+type Definitions map[string]JSONSchemaProps\n+```\n+\n+Note: A reflective test to check for drift between the types and the conversion object for runtime usage will be added.\n+\n+## <a name=\"examples\"></a>JSON-Schema Examples\n+\n+The following example illustrates how a schema can be used in `CustomResourceDefinition`. It shows various restrictions that can be achieved for validation using JSON-Schema.\n+\n+```json\n+{\n+    \"apiVersion\": \"apiextensions.k8s.io/v1beta1\",\n+    \"kind\": \"CustomResourceDefinition\",\n+    \"metadata\": {\n+        \"name\": \"noxus.mygroup.example.com\"\n+    },\n+    \"spec\": {\n+        \"group\": \"mygroup.example.com\",\n+        \"version\": \"v1alpha1\",\n+        \"scope\": \"Namespaced\",\n+        \"names\": {\n+            \"plural\": \"noxus\",\n+            \"singular\": \"noxu\",\n+            \"kind\": \"Noxu\",\n+            \"listKind\": \"NoxuList\"\n+        },\n+        \"validation\": {\n+            \"jsonSchema\": {\n+                \"$schema\": \"http://json-schema.org/draft-04/schema#\",\n+                \"type\": \"object\",\n+                \"description\": \"Noxu is a kind of Custom Resource which has only fields that are specified\",\n+                \"required\": [\n+                    \"alpha\",\n+                    \"beta\",\n+                    \"gamma\",\n+                    \"delta\",\n+                    \"omega\",\n+                    \"sigma\"\n+                ],\n+                \"properties\": {\n+                    \"alpha\": {\n+                        \"description\": \"Alpha is an alphanumeric string with underscores which defaults to foo_123\",\n+                        \"type\": \"string\",\n+                        \"pattern\": \"^[a-zA-Z0-9_]*$\",\n+                        \"default\": \"foo_123\"\n+                    },\n+                    \"beta\": {\n+                        \"description\": \"We need at least 10 betas. If not specified, it defaults to 10.\",\n+                        \"type\": \"number\",\n+                        \"minimum\": 10,\n+                        \"default\": 10\n+                    },\n+                    \"gamma\": {\n+                        \"description\": \"Gamma is restricted to foo, bar and baz\",\n+                        \"type\": \"string\",\n+                        \"enum\": [\n+                            \"foo\",\n+                            \"bar\",\n+                            \"baz\"\n+                        ]\n+                    },\n+                    \"delta\": {\n+                        \"description\": \"Delta is a string with a maximum length of 5 or a number with a minimum value of 0\",\n+                        \"anyOf\": [\n+                            {\n+                                \"type\": \"string\",\n+                                \"maxLength\": 5\n+                            },\n+                            {\n+                                \"type\": \"number\",\n+                                \"minimum\": 0\n+                            }\n+                        ]\n+                    },\n+                    \"epsilon\": {\n+                        \"description\": \"Epsilon is either of type one zeta or two zeta\",\n+                        \"allOf\": [\n+                            {\n+                                \"$ref\": \"#/definitions/zeta\"\n+                            },\n+                            {\n+                                \"properties\": {\n+                                    \"type\": {\n+                                        \"enum\": [\n+                                            \"one\",\n+                                            \"two\"\n+                                        ]\n+                                    }\n+                                },\n+                                \"required\": [\n+                                    \"type\"\n+                                ],\n+                                \"additionalProperties\": false\n+                            }\n+                        ]\n+                    },\n+                    \"additionalProperties\": false,\n+                    \"definitions\": {\n+                        \"zeta\": {\n+                            \"description\": \"Every zeta needs to have foo, bar and baz\",\n+                            \"type\": \"object\",\n+                            \"properties\": {\n+                                \"foo\": {\n+                                    \"type\": \"string\"\n+                                },\n+                                \"bar\": {\n+                                    \"type\": \"number\"\n+                                },\n+                                \"baz\": {\n+                                    \"type\": \"boolean\"\n+                                }\n+                            },\n+                            \"required\": [\n+                                \"foo\",\n+                                \"bar\",\n+                                \"baz\"\n+                            ],\n+                            \"additionalProperties\": false\n+                        }\n+                    }\n+                }\n+            }\n+        }\n+    }\n+}\n+```\n+\n+## <a name=\"behavior\"></a>Validation Behavior\n+\n+The schema will be described in the `CustomResourceDefinitionSpec`. The validation will be carried out using the [go-openapi validation library](https://github.com/go-openapi/validate).\n+ \n+While creating/updating the CR, the metadata is first validated. To validate the CR against the spec in the CRD, we _must_ have server-side validation and we _can_ have client-side validation.\n+\n+### <a name=\"metadata\"></a>Metadata\n+\n+ObjectMeta and TypeMeta are implicitly specified. They do not have to be added to the JSON-Schema of a CRD. The validation already happens today as part of the apiextensions-apiserver REST handlers.\n+\n+### <a name=\"server-side\"></a>Server-Side Validation\n+\n+The server-side validation is carried out after sending the request to the apiextensions-apiserver, i.e. inside the CREATE and UPDATE handlers for CRs. We do a schema pass there using the https://github.com/go-openapi/validate validator with the provided schema in the corresponding CRD. Validation errors are returned to the caller as for native resources.\n+\n+JSON-Schema also allows us to reject additional fields that are not defined in the schema and only allow the fields that are specified. This can be achieved by using `\"additionalProperties\": false` in the schema.\n+\n+### <a name=\"client-side\"></a>Client-Side Validation\n+\n+The client-side validation is carried out before sending the request to the api-server, or even completely offline. This can be achieved while creating resources through the client i.e. kubectl using the --validate option. If the api-server serves the JSON-Schema in the swagger spec, the existing kubectl code will already be able to also validate CRs. This will be achieved as a follow-up.\n+\n+### <a name=\"comparison\"></a>Comparison between server-side and client-side Validation\n+\n+The table below shows the cases when server-side and client-side validation methods are applicable.\n+\n+| Case                                               | Server-Side  | Client-Side   |\n+|:--------------------------------------------------:|:------------:|:-------------:|\n+| Kubectl create/edit/replace with validity feedback | &#x2713;     | &#x2717;      | \n+| Custom controller creates/updates CRs              | &#x2713;     | &#x2717;      |\n+| CRs are created by an untrusted party              | &#x2713;     | &#x2717;      |\n+| Not making validation for CRs a special case       | &#x2713;     | &#x2717;      |\n+\n+The above table is an evidence that we need server-side validation as well, next to the client-side validation we easily get, nearly for free, by serving Swagger/OpenAPI specs in apiextension-apiserver. This is especially true in situations when CRs are used by components that are out of the control of the admin. Example: A user can create a database CR for a Database-As-A-Service. In this case, only server-side validation can give confidence that the CRs are well formed.\n+\n+### <a name=\"schema-change\"></a>Existing Instances and changing the Schema\n+\n+If the schema is made stricter later, the existing CustomResources might no longer comply with the spec. This will make them unchangeable and essentially read-only. To avoid this, it is the responsibility of the user to make sure that any changes made to the schema are such that the existing CustomResources remain validated. \n+ \n+Note: This is the same behavior that we require for native resources. Validation cannot be made stricter in later Kubernetes versions without breaking compatibility.\n+\n+### <a name=\"sub-resources\"></a>Outlook to Status Sub-Resources\n+\n+As another most-wanted feature, a Status sub-resource might be proposed and implemented for CRDs. The JSON-Schema proposed here might as well cover the Status field of a CR. For now this is not handled or validated in a particular way.\n+ \n+When the Status sub-resource exists some day, the /status endpoint will receive a full CR object. We can then validate the whole object against the status.\n+Hence, this proposal is compatible with a later sub-resource extension.\n+\n+### <a name=\"webhooks\"></a>Outlook Admission Webhook\n+\n+Apiextensions-apiserver uses the normal REST endpoint implementation and only customizes the registry and the codecs. The admission plugins are inherited from the kube-apiserver (when running inside of it via apiserver delegation) and therefore they are supposed to apply to CRs as well. The webhook admission prototyped at https://github.com/kubernetes/kubernetes/pull/46316 is expected to work with CRs out of the box. Hence, for more advanced validation the webhook admission is an option as well (when it is merged). Clearly, providing such a webhook is more complex and therefore cannot replace a built-in JSON-Schema validation and its use-cases.\n+\n+## <a name=\"implementation\"></a>Implementation Plan\n+\n+The implementation is planned in the following steps:\n+Add the proposed types to the v1beta1<sup id=\"f3\">[3](#footnote3)</sup> version of the CRD type.","path":"contributors/design-proposals/customresources-validation.md","position":355,"original_position":355,"commit_id":"a1e0eb354ebead13e90e6db4d1b9abbcca1dbadf","original_commit_id":"a1e0eb354ebead13e90e6db4d1b9abbcca1dbadf","user":{"login":"sttts","id":730123,"avatar_url":"https://avatars3.githubusercontent.com/u/730123?v=3","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":"enumeration is missing.","created_at":"2017-06-12T15:00:59Z","updated_at":"2017-06-12T15:00:59Z","html_url":"https://github.com/nikhita/community/pull/1#discussion_r121430471","pull_request_url":"https://api.github.com/repos/nikhita/community/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/nikhita/community/pulls/comments/121430471"},"html":{"href":"https://github.com/nikhita/community/pull/1#discussion_r121430471"},"pull_request":{"href":"https://api.github.com/repos/nikhita/community/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/nikhita/community/pulls/1","id":125150938,"html_url":"https://github.com/nikhita/community/pull/1","diff_url":"https://github.com/nikhita/community/pull/1.diff","patch_url":"https://github.com/nikhita/community/pull/1.patch","issue_url":"https://api.github.com/repos/nikhita/community/issues/1","number":1,"state":"open","locked":false,"title":"Add proposal for customresources validation","user":{"login":"nikhita","id":16105680,"avatar_url":"https://avatars2.githubusercontent.com/u/16105680?v=3","gravatar_id":"","url":"https://api.github.com/users/nikhita","html_url":"https://github.com/nikhita","followers_url":"https://api.github.com/users/nikhita/followers","following_url":"https://api.github.com/users/nikhita/following{/other_user}","gists_url":"https://api.github.com/users/nikhita/gists{/gist_id}","starred_url":"https://api.github.com/users/nikhita/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nikhita/subscriptions","organizations_url":"https://api.github.com/users/nikhita/orgs","repos_url":"https://api.github.com/users/nikhita/repos","events_url":"https://api.github.com/users/nikhita/events{/privacy}","received_events_url":"https://api.github.com/users/nikhita/received_events","type":"User","site_admin":false},"body":"","created_at":"2017-06-12T14:33:42Z","updated_at":"2017-06-12T15:00:59Z","closed_at":null,"merged_at":null,"merge_commit_sha":"56067abb8ea3976a1d0379699a8bd9723ac3eece","assignee":null,"assignees":[],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/nikhita/community/pulls/1/commits","review_comments_url":"https://api.github.com/repos/nikhita/community/pulls/1/comments","review_comment_url":"https://api.github.com/repos/nikhita/community/pulls/comments{/number}","comments_url":"https://api.github.com/repos/nikhita/community/issues/1/comments","statuses_url":"https://api.github.com/repos/nikhita/community/statuses/a1e0eb354ebead13e90e6db4d1b9abbcca1dbadf","head":{"label":"nikhita:customresources-validation","ref":"customresources-validation","sha":"a1e0eb354ebead13e90e6db4d1b9abbcca1dbadf","user":{"login":"nikhita","id":16105680,"avatar_url":"https://avatars2.githubusercontent.com/u/16105680?v=3","gravatar_id":"","url":"https://api.github.com/users/nikhita","html_url":"https://github.com/nikhita","followers_url":"https://api.github.com/users/nikhita/followers","following_url":"https://api.github.com/users/nikhita/following{/other_user}","gists_url":"https://api.github.com/users/nikhita/gists{/gist_id}","starred_url":"https://api.github.com/users/nikhita/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nikhita/subscriptions","organizations_url":"https://api.github.com/users/nikhita/orgs","repos_url":"https://api.github.com/users/nikhita/repos","events_url":"https://api.github.com/users/nikhita/events{/privacy}","received_events_url":"https://api.github.com/users/nikhita/received_events","type":"User","site_admin":false},"repo":{"id":88247177,"name":"community","full_name":"nikhita/community","owner":{"login":"nikhita","id":16105680,"avatar_url":"https://avatars2.githubusercontent.com/u/16105680?v=3","gravatar_id":"","url":"https://api.github.com/users/nikhita","html_url":"https://github.com/nikhita","followers_url":"https://api.github.com/users/nikhita/followers","following_url":"https://api.github.com/users/nikhita/following{/other_user}","gists_url":"https://api.github.com/users/nikhita/gists{/gist_id}","starred_url":"https://api.github.com/users/nikhita/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nikhita/subscriptions","organizations_url":"https://api.github.com/users/nikhita/orgs","repos_url":"https://api.github.com/users/nikhita/repos","events_url":"https://api.github.com/users/nikhita/events{/privacy}","received_events_url":"https://api.github.com/users/nikhita/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/nikhita/community","description":"Kubernetes community content","fork":true,"url":"https://api.github.com/repos/nikhita/community","forks_url":"https://api.github.com/repos/nikhita/community/forks","keys_url":"https://api.github.com/repos/nikhita/community/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nikhita/community/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nikhita/community/teams","hooks_url":"https://api.github.com/repos/nikhita/community/hooks","issue_events_url":"https://api.github.com/repos/nikhita/community/issues/events{/number}","events_url":"https://api.github.com/repos/nikhita/community/events","assignees_url":"https://api.github.com/repos/nikhita/community/assignees{/user}","branches_url":"https://api.github.com/repos/nikhita/community/branches{/branch}","tags_url":"https://api.github.com/repos/nikhita/community/tags","blobs_url":"https://api.github.com/repos/nikhita/community/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nikhita/community/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nikhita/community/git/refs{/sha}","trees_url":"https://api.github.com/repos/nikhita/community/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nikhita/community/statuses/{sha}","languages_url":"https://api.github.com/repos/nikhita/community/languages","stargazers_url":"https://api.github.com/repos/nikhita/community/stargazers","contributors_url":"https://api.github.com/repos/nikhita/community/contributors","subscribers_url":"https://api.github.com/repos/nikhita/community/subscribers","subscription_url":"https://api.github.com/repos/nikhita/community/subscription","commits_url":"https://api.github.com/repos/nikhita/community/commits{/sha}","git_commits_url":"https://api.github.com/repos/nikhita/community/git/commits{/sha}","comments_url":"https://api.github.com/repos/nikhita/community/comments{/number}","issue_comment_url":"https://api.github.com/repos/nikhita/community/issues/comments{/number}","contents_url":"https://api.github.com/repos/nikhita/community/contents/{+path}","compare_url":"https://api.github.com/repos/nikhita/community/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nikhita/community/merges","archive_url":"https://api.github.com/repos/nikhita/community/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nikhita/community/downloads","issues_url":"https://api.github.com/repos/nikhita/community/issues{/number}","pulls_url":"https://api.github.com/repos/nikhita/community/pulls{/number}","milestones_url":"https://api.github.com/repos/nikhita/community/milestones{/number}","notifications_url":"https://api.github.com/repos/nikhita/community/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nikhita/community/labels{/name}","releases_url":"https://api.github.com/repos/nikhita/community/releases{/id}","deployments_url":"https://api.github.com/repos/nikhita/community/deployments","created_at":"2017-04-14T08:04:39Z","updated_at":"2017-06-12T14:33:26Z","pushed_at":"2017-06-12T14:33:42Z","git_url":"git://github.com/nikhita/community.git","ssh_url":"git@github.com:nikhita/community.git","clone_url":"https://github.com/nikhita/community.git","svn_url":"https://github.com/nikhita/community","homepage":"","size":7869,"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,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"nikhita:master","ref":"master","sha":"630c2f78b86a327f989684b4a7bd9ee06bb431dc","user":{"login":"nikhita","id":16105680,"avatar_url":"https://avatars2.githubusercontent.com/u/16105680?v=3","gravatar_id":"","url":"https://api.github.com/users/nikhita","html_url":"https://github.com/nikhita","followers_url":"https://api.github.com/users/nikhita/followers","following_url":"https://api.github.com/users/nikhita/following{/other_user}","gists_url":"https://api.github.com/users/nikhita/gists{/gist_id}","starred_url":"https://api.github.com/users/nikhita/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nikhita/subscriptions","organizations_url":"https://api.github.com/users/nikhita/orgs","repos_url":"https://api.github.com/users/nikhita/repos","events_url":"https://api.github.com/users/nikhita/events{/privacy}","received_events_url":"https://api.github.com/users/nikhita/received_events","type":"User","site_admin":false},"repo":{"id":88247177,"name":"community","full_name":"nikhita/community","owner":{"login":"nikhita","id":16105680,"avatar_url":"https://avatars2.githubusercontent.com/u/16105680?v=3","gravatar_id":"","url":"https://api.github.com/users/nikhita","html_url":"https://github.com/nikhita","followers_url":"https://api.github.com/users/nikhita/followers","following_url":"https://api.github.com/users/nikhita/following{/other_user}","gists_url":"https://api.github.com/users/nikhita/gists{/gist_id}","starred_url":"https://api.github.com/users/nikhita/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nikhita/subscriptions","organizations_url":"https://api.github.com/users/nikhita/orgs","repos_url":"https://api.github.com/users/nikhita/repos","events_url":"https://api.github.com/users/nikhita/events{/privacy}","received_events_url":"https://api.github.com/users/nikhita/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/nikhita/community","description":"Kubernetes community content","fork":true,"url":"https://api.github.com/repos/nikhita/community","forks_url":"https://api.github.com/repos/nikhita/community/forks","keys_url":"https://api.github.com/repos/nikhita/community/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nikhita/community/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nikhita/community/teams","hooks_url":"https://api.github.com/repos/nikhita/community/hooks","issue_events_url":"https://api.github.com/repos/nikhita/community/issues/events{/number}","events_url":"https://api.github.com/repos/nikhita/community/events","assignees_url":"https://api.github.com/repos/nikhita/community/assignees{/user}","branches_url":"https://api.github.com/repos/nikhita/community/branches{/branch}","tags_url":"https://api.github.com/repos/nikhita/community/tags","blobs_url":"https://api.github.com/repos/nikhita/community/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nikhita/community/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nikhita/community/git/refs{/sha}","trees_url":"https://api.github.com/repos/nikhita/community/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nikhita/community/statuses/{sha}","languages_url":"https://api.github.com/repos/nikhita/community/languages","stargazers_url":"https://api.github.com/repos/nikhita/community/stargazers","contributors_url":"https://api.github.com/repos/nikhita/community/contributors","subscribers_url":"https://api.github.com/repos/nikhita/community/subscribers","subscription_url":"https://api.github.com/repos/nikhita/community/subscription","commits_url":"https://api.github.com/repos/nikhita/community/commits{/sha}","git_commits_url":"https://api.github.com/repos/nikhita/community/git/commits{/sha}","comments_url":"https://api.github.com/repos/nikhita/community/comments{/number}","issue_comment_url":"https://api.github.com/repos/nikhita/community/issues/comments{/number}","contents_url":"https://api.github.com/repos/nikhita/community/contents/{+path}","compare_url":"https://api.github.com/repos/nikhita/community/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nikhita/community/merges","archive_url":"https://api.github.com/repos/nikhita/community/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nikhita/community/downloads","issues_url":"https://api.github.com/repos/nikhita/community/issues{/number}","pulls_url":"https://api.github.com/repos/nikhita/community/pulls{/number}","milestones_url":"https://api.github.com/repos/nikhita/community/milestones{/number}","notifications_url":"https://api.github.com/repos/nikhita/community/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nikhita/community/labels{/name}","releases_url":"https://api.github.com/repos/nikhita/community/releases{/id}","deployments_url":"https://api.github.com/repos/nikhita/community/deployments","created_at":"2017-04-14T08:04:39Z","updated_at":"2017-06-12T14:33:26Z","pushed_at":"2017-06-12T14:33:42Z","git_url":"git://github.com/nikhita/community.git","ssh_url":"git@github.com:nikhita/community.git","clone_url":"https://github.com/nikhita/community.git","svn_url":"https://github.com/nikhita/community","homepage":"","size":7869,"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,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/nikhita/community/pulls/1"},"html":{"href":"https://github.com/nikhita/community/pull/1"},"issue":{"href":"https://api.github.com/repos/nikhita/community/issues/1"},"comments":{"href":"https://api.github.com/repos/nikhita/community/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/nikhita/community/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/nikhita/community/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/nikhita/community/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/nikhita/community/statuses/a1e0eb354ebead13e90e6db4d1b9abbcca1dbadf"}}}}
{"actor":{"display_login":"sttts"}}